Configuration de l'output final
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from copienator import atomic_write_json, configuration, read_json, utils
|
||||
from copienator.filesystem import staged_directory
|
||||
from copienator.platform import safe_filename
|
||||
|
||||
|
||||
def publish_answer_returns(root: Path, source: Path, destination: Path) -> None:
|
||||
"""Publish individual reviewed answers and per-question information."""
|
||||
scores = read_json(source / "score.json")
|
||||
if not isinstance(scores, dict):
|
||||
raise ValueError(f"Expected a score object in {source}")
|
||||
info_path = source / "info.json"
|
||||
if not info_path.is_file():
|
||||
raise ValueError(f"Missing {info_path}; recompile annotations before giving-names")
|
||||
info = read_json(info_path)
|
||||
if not isinstance(info, dict) or set(info) != set(scores):
|
||||
raise ValueError(f"Invalid question information in {info_path}; recompile annotations")
|
||||
for label, entry in info.items():
|
||||
if (
|
||||
not isinstance(entry, dict)
|
||||
or set(entry) != {"present", "not_empty", "touched", "score"}
|
||||
or any(type(entry[key]) is not bool for key in ("present", "not_empty", "touched"))
|
||||
or (entry["not_empty"] and not entry["present"])
|
||||
or (entry["touched"] and not entry["not_empty"])
|
||||
):
|
||||
raise ValueError(f"Invalid question information in {info_path}; recompile annotations")
|
||||
# Match score.json, including manual score edits awaiting recompilation.
|
||||
entry["score"] = scores[label]
|
||||
|
||||
answers_dir = destination / "answers"
|
||||
if answers_dir.is_symlink():
|
||||
raise ValueError(f"Expected a real answer directory: {answers_dir}")
|
||||
if configuration.RETURN_ANSWERS_ENABLED:
|
||||
labels = [label for label, entry in info.items() if entry["present"] and entry["not_empty"]]
|
||||
|
||||
# Import the rendering backend only when individual images are requested.
|
||||
from copienator.commands.annotating import make_base_image
|
||||
from copienator.commands.reading_annotations import concatenate
|
||||
|
||||
all_labels = utils.read_all_labels(root)
|
||||
with staged_directory(answers_dir) as staging:
|
||||
for index, label in enumerate(sorted(labels, key=utils.natural_key), 1):
|
||||
paths = []
|
||||
if configuration.RETURN_ANSWERS_CONTEXT:
|
||||
paths.extend(utils.pdf_images_of_contexts(root, label, all_labels))
|
||||
if configuration.RETURN_ANSWERS_QUESTION:
|
||||
paths.append(utils.pdf_image_of_enonce(root, label))
|
||||
if configuration.RETURN_ANSWERS_SOLUTION:
|
||||
paths.append(utils.pdf_image_of_solution(root, label))
|
||||
images = []
|
||||
for path in paths:
|
||||
if path:
|
||||
supplement, _, _ = make_base_image(path)
|
||||
if supplement is None:
|
||||
raise ValueError(f"Could not render {path}")
|
||||
images.append(supplement)
|
||||
with Image.open(source / f"{label}.jpg") as answer:
|
||||
images.append(answer.convert("RGB"))
|
||||
image = concatenate(images)
|
||||
# Numbering avoids collisions between sanitized label filenames.
|
||||
image.save(staging / f"{index:03d} - {safe_filename(label)}.jpg")
|
||||
elif answers_dir.exists():
|
||||
# Replace the managed directory with an empty one to remove stale exports.
|
||||
with staged_directory(answers_dir):
|
||||
pass
|
||||
atomic_write_json(destination / "info.json", info)
|
||||
(destination / "touched.json").unlink(missing_ok=True)
|
||||
Reference in New Issue
Block a user