Remove `Arguments supplémentaires' boxes
This commit is contained in:
@@ -31,6 +31,8 @@ if CONFIG_PATH is None:
|
|||||||
else:
|
else:
|
||||||
_configuration = _load_user_config(CONFIG_PATH)
|
_configuration = _load_user_config(CONFIG_PATH)
|
||||||
|
|
||||||
|
# Keep new optional settings compatible with older personal configuration files.
|
||||||
|
ALWAYS_CROP = False
|
||||||
for _name in dir(_configuration):
|
for _name in dir(_configuration):
|
||||||
if not _name.startswith("_"):
|
if not _name.startswith("_"):
|
||||||
globals()[_name] = getattr(_configuration, _name)
|
globals()[_name] = getattr(_configuration, _name)
|
||||||
|
|||||||
+39
-5
@@ -178,6 +178,7 @@ class CopienatorApp(tk.Tk):
|
|||||||
self.api_key_var = tk.StringVar(value=os.environ.get("GEMINI_API_KEY", ""))
|
self.api_key_var = tk.StringVar(value=os.environ.get("GEMINI_API_KEY", ""))
|
||||||
self.proxy_var = tk.StringVar(value=DEFAULT_HTTPS_PROXY)
|
self.proxy_var = tk.StringVar(value=DEFAULT_HTTPS_PROXY)
|
||||||
self.use_proxy_var = tk.BooleanVar(value=False)
|
self.use_proxy_var = tk.BooleanVar(value=False)
|
||||||
|
self.verbose_var = tk.BooleanVar(value=False)
|
||||||
self.copy_var = tk.StringVar()
|
self.copy_var = tk.StringVar()
|
||||||
self.variant_var = tk.StringVar()
|
self.variant_var = tk.StringVar()
|
||||||
self.extra_var = tk.StringVar()
|
self.extra_var = tk.StringVar()
|
||||||
@@ -186,6 +187,7 @@ class CopienatorApp(tk.Tk):
|
|||||||
|
|
||||||
self._build_ui(show_personal_steps)
|
self._build_ui(show_personal_steps)
|
||||||
self.extra_var.trace_add("write", lambda *_args: self._update_command_preview())
|
self.extra_var.trace_add("write", lambda *_args: self._update_command_preview())
|
||||||
|
self.verbose_var.trace_add("write", lambda *_args: self._update_command_preview())
|
||||||
self.after(60, self._poll_runner)
|
self.after(60, self._poll_runner)
|
||||||
|
|
||||||
if initial_evaluation and initial_evaluation.is_dir():
|
if initial_evaluation and initial_evaluation.is_dir():
|
||||||
@@ -237,6 +239,11 @@ class CopienatorApp(tk.Tk):
|
|||||||
).pack(side="left", padx=(0, 6))
|
).pack(side="left", padx=(0, 6))
|
||||||
self.proxy_entry = ttk.Entry(environment, textvariable=self.proxy_var, width=28, state="disabled")
|
self.proxy_entry = ttk.Entry(environment, textvariable=self.proxy_var, width=28, state="disabled")
|
||||||
self.proxy_entry.pack(side="left")
|
self.proxy_entry.pack(side="left")
|
||||||
|
ttk.Checkbutton(
|
||||||
|
environment,
|
||||||
|
text="Afficher les détails en cas d’erreur (--verbose)",
|
||||||
|
variable=self.verbose_var,
|
||||||
|
).pack(side="left", padx=(10, 0))
|
||||||
profile = "standard + personnel" if show_personal_steps else "standard"
|
profile = "standard + personnel" if show_personal_steps else "standard"
|
||||||
ttk.Label(environment, text=f"Profil : {profile}").pack(side="left", padx=(12, 0))
|
ttk.Label(environment, text=f"Profil : {profile}").pack(side="left", padx=(12, 0))
|
||||||
|
|
||||||
@@ -672,12 +679,24 @@ class CopienatorApp(tk.Tk):
|
|||||||
row += 1
|
row += 1
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
if not step.is_manual and step.section != REFAIRE_SECTION:
|
show_extra = bool(step.extra_arguments_help) and (
|
||||||
|
not step.extra_arguments_variants
|
||||||
|
or variant.id in step.extra_arguments_variants
|
||||||
|
)
|
||||||
|
if show_extra and not step.is_manual and step.section != REFAIRE_SECTION:
|
||||||
ttk.Label(self.form, text="Arguments supplémentaires").grid(
|
ttk.Label(self.form, text="Arguments supplémentaires").grid(
|
||||||
row=row, column=0, sticky="w", pady=(10, 4), padx=(0, 8)
|
row=row, column=0, sticky="w", pady=(10, 4), padx=(0, 8)
|
||||||
)
|
)
|
||||||
ttk.Entry(self.form, textvariable=self.extra_var).grid(row=row, column=1, sticky="ew", pady=(10, 4))
|
ttk.Entry(self.form, textvariable=self.extra_var).grid(row=row, column=1, sticky="ew", pady=(10, 4))
|
||||||
row += 1
|
row += 1
|
||||||
|
ttk.Label(
|
||||||
|
self.form,
|
||||||
|
text=step.extra_arguments_help,
|
||||||
|
foreground="#666666",
|
||||||
|
wraplength=540,
|
||||||
|
justify="left",
|
||||||
|
).grid(row=row, column=1, sticky="w")
|
||||||
|
row += 1
|
||||||
|
|
||||||
if self.show_personal_steps and step.id == "statement":
|
if self.show_personal_steps and step.id == "statement":
|
||||||
actions = ttk.LabelFrame(self.form, text="Après génération — facultatif", padding=7)
|
actions = ttk.LabelFrame(self.form, text="Après génération — facultatif", padding=7)
|
||||||
@@ -953,7 +972,11 @@ class CopienatorApp(tk.Tk):
|
|||||||
self.current_step.id,
|
self.current_step.id,
|
||||||
variant=self.variant_var.get() or self.current_step.variants[0].id,
|
variant=self.variant_var.get() or self.current_step.variants[0].id,
|
||||||
values=merged_values,
|
values=merged_values,
|
||||||
extra=self.extra_var.get(),
|
extra=(
|
||||||
|
self.extra_var.get()
|
||||||
|
if self.current_step.extra_arguments_help
|
||||||
|
else ""
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _evaluation_arg(self) -> str:
|
def _evaluation_arg(self) -> str:
|
||||||
@@ -1020,21 +1043,32 @@ class CopienatorApp(tk.Tk):
|
|||||||
choice = self.state_store.step("refaire_selection").get("values", {}).get("layout", "auto")
|
choice = self.state_store.step("refaire_selection").get("values", {}).get("layout", "auto")
|
||||||
layout = resolve_layout(selection, EvaluationWorkspace(self.evaluation).read_labels(), choice)
|
layout = resolve_layout(selection, EvaluationWorkspace(self.evaluation).read_labels(), choice)
|
||||||
variant = replace(variant, program="annotate-grouped" if layout == "grouped" else "annotate-checks")
|
variant = replace(variant, program="annotate-grouped" if layout == "grouped" else "annotate-checks")
|
||||||
return [build_command(self.repository, self.current_step, variant,
|
return [build_command(
|
||||||
{**values, "target": target}, self._evaluation_arg()) for target in targets]
|
self.repository,
|
||||||
|
self.current_step,
|
||||||
|
variant,
|
||||||
|
{**values, "target": target},
|
||||||
|
self._evaluation_arg(),
|
||||||
|
verbose=bool(self.verbose_var.get()),
|
||||||
|
) for target in targets]
|
||||||
|
|
||||||
def _make_command(self) -> list[str]:
|
def _make_command(self) -> list[str]:
|
||||||
if not self.current_step:
|
if not self.current_step:
|
||||||
return []
|
return []
|
||||||
if self.current_step.section == REFAIRE_SECTION:
|
if self.current_step.section == REFAIRE_SECTION:
|
||||||
return self._refaire_commands()[0]
|
return self._refaire_commands()[0]
|
||||||
|
allow_extra = bool(self.current_step.extra_arguments_help) and (
|
||||||
|
not self.current_step.extra_arguments_variants
|
||||||
|
or self._current_variant().id in self.current_step.extra_arguments_variants
|
||||||
|
)
|
||||||
return build_command(
|
return build_command(
|
||||||
self.repository,
|
self.repository,
|
||||||
self.current_step,
|
self.current_step,
|
||||||
self._current_variant(),
|
self._current_variant(),
|
||||||
self._values(),
|
self._values(),
|
||||||
self._evaluation_arg(),
|
self._evaluation_arg(),
|
||||||
self.extra_var.get(),
|
self.extra_var.get() if allow_extra else "",
|
||||||
|
verbose=bool(self.verbose_var.get()),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _update_command_preview(self) -> None:
|
def _update_command_preview(self) -> None:
|
||||||
|
|||||||
+82
-11
@@ -7,6 +7,8 @@ from collections.abc import Iterable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from copienator.configuration import ALWAYS_CROP
|
||||||
|
|
||||||
EVALUATION = "${evaluation}"
|
EVALUATION = "${evaluation}"
|
||||||
|
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ class CommandVariant:
|
|||||||
fixed_args_before_positionals: bool = False
|
fixed_args_before_positionals: bool = False
|
||||||
dangerous: bool = False
|
dangerous: bool = False
|
||||||
danger_warning: str | None = None
|
danger_warning: str | None = None
|
||||||
|
supports_verbose: bool = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -50,6 +53,8 @@ class StepDefinition:
|
|||||||
auto_start_first_visit: bool = False
|
auto_start_first_visit: bool = False
|
||||||
skip_for_live_correction: bool = False
|
skip_for_live_correction: bool = False
|
||||||
skip_without_manual_conflicts: bool = False
|
skip_without_manual_conflicts: bool = False
|
||||||
|
extra_arguments_help: str = ""
|
||||||
|
extra_arguments_variants: tuple[str, ...] = ()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_manual(self) -> bool:
|
def is_manual(self) -> bool:
|
||||||
@@ -68,8 +73,15 @@ def arg_target(help_text: str = "Dossier d’évaluation ou fichier à traiter")
|
|||||||
|
|
||||||
|
|
||||||
def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
||||||
python = lambda ident, label, script, **kwargs: CommandVariant(
|
def python(ident, label, script, **kwargs):
|
||||||
ident, label, script, "python", **kwargs
|
supports_verbose = kwargs.pop("supports_verbose", True)
|
||||||
|
return CommandVariant(
|
||||||
|
ident,
|
||||||
|
label,
|
||||||
|
script,
|
||||||
|
"python",
|
||||||
|
supports_verbose=supports_verbose,
|
||||||
|
**kwargs,
|
||||||
)
|
)
|
||||||
manual = lambda ident, label="Étape manuelle": CommandVariant(
|
manual = lambda ident, label="Étape manuelle": CommandVariant(
|
||||||
ident, label, None, "manual"
|
ident, label, None, "manual"
|
||||||
@@ -111,7 +123,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
"statement_groups", "Prétraitement de l’énoncé", "Regrouper les questions avec Gemini",
|
"statement_groups", "Prétraitement de l’énoncé", "Regrouper les questions avec Gemini",
|
||||||
"Facultatif après la génération : remplace les groupes par exercice par des groupes "
|
"Facultatif après la génération : remplace les groupes par exercice par des groupes "
|
||||||
"proposés par Gemini, en conservant les labels, les énoncés, les solutions et les barèmes.",
|
"proposés par Gemini, en conservant les labels, les énoncés, les solutions et les barèmes.",
|
||||||
(CommandVariant("default", "Groupes Gemini", "statement", fixed_args=("--groups-only",)),),
|
(python("default", "Groupes Gemini", "statement", fixed_args=("--groups-only",)),),
|
||||||
arguments=(arg_target("Dossier de l’évaluation"),),
|
arguments=(arg_target("Dossier de l’évaluation"),),
|
||||||
optional=True, personal=True, requires=("labels", "Text2", "Sol2"),
|
optional=True, personal=True, requires=("labels", "Text2", "Sol2"),
|
||||||
),
|
),
|
||||||
@@ -119,7 +131,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
"statement_persp", "Prétraitement de l’énoncé", "Remplacer les barèmes par Gemini",
|
"statement_persp", "Prétraitement de l’énoncé", "Remplacer les barèmes par Gemini",
|
||||||
"Facultatif : remplace Persp par des barèmes Gemini sur 4 points, pour les groupes actuels. "
|
"Facultatif : remplace Persp par des barèmes Gemini sur 4 points, pour les groupes actuels. "
|
||||||
"Les énoncés et les solutions personnels sont conservés.",
|
"Les énoncés et les solutions personnels sont conservés.",
|
||||||
(CommandVariant("default", "Barèmes Gemini", "statement", fixed_args=("--persp-only",)),),
|
(python("default", "Barèmes Gemini", "statement", fixed_args=("--persp-only",)),),
|
||||||
arguments=(arg_target("Dossier de l’évaluation"),),
|
arguments=(arg_target("Dossier de l’évaluation"),),
|
||||||
optional=True, personal=True, requires=("labels", "label_groups", "Text2", "Sol2"),
|
optional=True, personal=True, requires=("labels", "label_groups", "Text2", "Sol2"),
|
||||||
),
|
),
|
||||||
@@ -145,6 +157,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
"python",
|
"python",
|
||||||
("rotate",),
|
("rotate",),
|
||||||
fixed_args_before_positionals=True,
|
fixed_args_before_positionals=True,
|
||||||
|
supports_verbose=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
arguments=(arg_target("Dossier de l’évaluation"),),
|
arguments=(arg_target("Dossier de l’évaluation"),),
|
||||||
@@ -163,6 +176,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
"python",
|
"python",
|
||||||
("rename",),
|
("rename",),
|
||||||
fixed_args_before_positionals=True,
|
fixed_args_before_positionals=True,
|
||||||
|
supports_verbose=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
arguments=(arg_target("Dossier de l’évaluation"),),
|
arguments=(arg_target("Dossier de l’évaluation"),),
|
||||||
@@ -188,6 +202,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
ArgumentSpec("workers", "Copies traitées en parallèle", "int", "--workers", default=5)),
|
ArgumentSpec("workers", "Copies traitées en parallèle", "int", "--workers", default=5)),
|
||||||
optional=True,
|
optional=True,
|
||||||
requires=("Copies",),
|
requires=("Copies",),
|
||||||
|
auto_start_first_visit=ALWAYS_CROP,
|
||||||
),
|
),
|
||||||
StepDefinition(
|
StepDefinition(
|
||||||
"cutleft",
|
"cutleft",
|
||||||
@@ -215,6 +230,10 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
),
|
),
|
||||||
requires=("labels", "Copies", "Cutleft"),
|
requires=("labels", "Copies", "Cutleft"),
|
||||||
artifacts=("Copies/*.json",),
|
artifacts=("Copies/*.json",),
|
||||||
|
extra_arguments_help=(
|
||||||
|
"PDF de copie ou images Cutleft supplémentaires de cette évaluation, "
|
||||||
|
"séparés par des espaces. Mettez entre guillemets les chemins contenant des espaces."
|
||||||
|
),
|
||||||
),
|
),
|
||||||
StepDefinition(
|
StepDefinition(
|
||||||
"plotting",
|
"plotting",
|
||||||
@@ -257,6 +276,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
),
|
),
|
||||||
optional=True,
|
optional=True,
|
||||||
requires=("Copies/Copie*/*.pdf",),
|
requires=("Copies/Copie*/*.pdf",),
|
||||||
|
auto_start_first_visit=ALWAYS_CROP,
|
||||||
),
|
),
|
||||||
StepDefinition(
|
StepDefinition(
|
||||||
"grouping",
|
"grouping",
|
||||||
@@ -289,12 +309,29 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
),
|
),
|
||||||
arguments=(
|
arguments=(
|
||||||
arg_target("Évaluation ou image Group_X.jpg"),
|
arg_target("Évaluation ou image Group_X.jpg"),
|
||||||
ArgumentSpec("overwrite", "Écraser les corrections existantes", "bool", "--overwrite", variants=("live",)),
|
ArgumentSpec(
|
||||||
ArgumentSpec("limit", "Limite d’appels Pro", "int", "--limit", variants=("live",)),
|
"overwrite",
|
||||||
|
"Écraser les corrections existantes",
|
||||||
|
"bool",
|
||||||
|
"--overwrite",
|
||||||
|
variants=("live", "batch", "hybrid", "refaire"),
|
||||||
|
),
|
||||||
|
ArgumentSpec(
|
||||||
|
"limit",
|
||||||
|
"Limite d’appels Pro",
|
||||||
|
"int",
|
||||||
|
"--limit",
|
||||||
|
variants=("live", "hybrid", "refaire"),
|
||||||
|
),
|
||||||
ArgumentSpec("batch_from", "Premier label envoyé en batch", "text", "--batch-from", variants=("hybrid",)),
|
ArgumentSpec("batch_from", "Premier label envoyé en batch", "text", "--batch-from", variants=("hybrid",)),
|
||||||
),
|
),
|
||||||
requires=("Par label", "Persp", "labels"),
|
requires=("Par label", "Persp", "labels"),
|
||||||
artifacts=("correction.json", "batch_requests_*.jsonl"),
|
artifacts=("correction.json", "batch_requests_*.jsonl"),
|
||||||
|
extra_arguments_help=(
|
||||||
|
"Images Group_X.jpg supplémentaires de cette évaluation, séparées par des espaces. "
|
||||||
|
"Mettez entre guillemets les chemins contenant des espaces."
|
||||||
|
),
|
||||||
|
extra_arguments_variants=("live", "batch", "hybrid", "refaire"),
|
||||||
),
|
),
|
||||||
StepDefinition(
|
StepDefinition(
|
||||||
"submit_batches",
|
"submit_batches",
|
||||||
@@ -313,7 +350,16 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
"Consulter l’état des batchs",
|
"Consulter l’état des batchs",
|
||||||
"Affiche les jobs Gemini en cours. L’identifiant de téléchargement est facultatif.",
|
"Affiche les jobs Gemini en cours. L’identifiant de téléchargement est facultatif.",
|
||||||
(python("default", "État des batchs", "batch-status"),),
|
(python("default", "État des batchs", "batch-status"),),
|
||||||
arguments=(ArgumentSpec("download", "Télécharger le job", "text", "--download"),),
|
arguments=(
|
||||||
|
ArgumentSpec("download", "Télécharger le job", "text", "--download"),
|
||||||
|
ArgumentSpec(
|
||||||
|
"output",
|
||||||
|
"Fichier JSONL de destination",
|
||||||
|
"path",
|
||||||
|
"--output",
|
||||||
|
help="Utilisé avec un identifiant de téléchargement.",
|
||||||
|
),
|
||||||
|
),
|
||||||
optional=True,
|
optional=True,
|
||||||
skip_for_live_correction=True,
|
skip_for_live_correction=True,
|
||||||
),
|
),
|
||||||
@@ -360,7 +406,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
arguments=(
|
arguments=(
|
||||||
arg_target("Dossier de l’évaluation"),
|
arg_target("Dossier de l’évaluation"),
|
||||||
ArgumentSpec("overwrite", "Écraser les sorties", "bool", "--overwrite"),
|
ArgumentSpec("overwrite", "Écraser les sorties", "bool", "--overwrite"),
|
||||||
ArgumentSpec("refaire", "Mode refaire", "bool", "--refaire", variants=("checks",)),
|
ArgumentSpec("refaire", "Mode refaire", "bool", "--refaire", variants=("checks", "grouped")),
|
||||||
),
|
),
|
||||||
requires=("correction.json",),
|
requires=("correction.json",),
|
||||||
artifacts=("Anot", "Bnot", "BGnot"),
|
artifacts=("Anot", "Bnot", "BGnot"),
|
||||||
@@ -424,6 +470,15 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
arg_target("Dossier de l’évaluation"),
|
arg_target("Dossier de l’évaluation"),
|
||||||
ArgumentSpec("update_score", "Réappliquer les score.json", "bool", "--update-score"),
|
ArgumentSpec("update_score", "Réappliquer les score.json", "bool", "--update-score"),
|
||||||
ArgumentSpec("refaire", "Mode refaire", "bool", "--refaire", variants=("grouped",)),
|
ArgumentSpec("refaire", "Mode refaire", "bool", "--refaire", variants=("grouped",)),
|
||||||
|
ArgumentSpec(
|
||||||
|
"annotation_dir",
|
||||||
|
"Passage principal du mode refaire",
|
||||||
|
"choice",
|
||||||
|
"--annotation-dir",
|
||||||
|
default="BGnot",
|
||||||
|
choices=("BGnot", "Bnot", "Anot"),
|
||||||
|
variants=("grouped",),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
StepDefinition(
|
StepDefinition(
|
||||||
@@ -466,7 +521,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
"Étapes personnelles",
|
"Étapes personnelles",
|
||||||
"Mettre à jour le fichier ODS",
|
"Mettre à jour le fichier ODS",
|
||||||
"Transfère les scores, avec la possibilité de n’écrire que leur somme.",
|
"Transfère les scores, avec la possibilité de n’écrire que leur somme.",
|
||||||
(python("default", "Mise à jour ODS", "update-ods"),),
|
(python("default", "Mise à jour ODS", "update-ods", supports_verbose=False),),
|
||||||
arguments=(
|
arguments=(
|
||||||
arg_target("Dossier de l’évaluation"),
|
arg_target("Dossier de l’évaluation"),
|
||||||
ArgumentSpec("sum", "Écrire seulement la somme", "bool", "--sum"),
|
ArgumentSpec("sum", "Écrire seulement la somme", "bool", "--sum"),
|
||||||
@@ -494,7 +549,7 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
"Étapes personnelles",
|
"Étapes personnelles",
|
||||||
"Ajouter le score final",
|
"Ajouter le score final",
|
||||||
"Génère les fichiers de diffusion avec le score final.",
|
"Génère les fichiers de diffusion avec le score final.",
|
||||||
(python("default", "Score final", "add-final-score"),),
|
(python("default", "Score final", "add-final-score", supports_verbose=False),),
|
||||||
arguments=(arg_target("Dossier de l’évaluation"),),
|
arguments=(arg_target("Dossier de l’évaluation"),),
|
||||||
personal=True,
|
personal=True,
|
||||||
),
|
),
|
||||||
@@ -540,6 +595,12 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
|||||||
"Continuer ?"
|
"Continuer ?"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
python(
|
||||||
|
"dry_run",
|
||||||
|
"Prévisualiser sans supprimer",
|
||||||
|
"clean",
|
||||||
|
fixed_args=("--dry-run",),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
arguments=(arg_target("Dossier de l’évaluation"),),
|
arguments=(arg_target("Dossier de l’évaluation"),),
|
||||||
optional=True,
|
optional=True,
|
||||||
@@ -580,7 +641,14 @@ def build_refaire_workflow() -> list[StepDefinition]:
|
|||||||
requirements += ("BRnot",)
|
requirements += ("BRnot",)
|
||||||
steps.append(StepDefinition(
|
steps.append(StepDefinition(
|
||||||
f"refaire_{suffix}", SECTION, title, description,
|
f"refaire_{suffix}", SECTION, title, description,
|
||||||
(CommandVariant("default", title, program, "python" if program else "manual", flags),),
|
(CommandVariant(
|
||||||
|
"default",
|
||||||
|
title,
|
||||||
|
program,
|
||||||
|
"python" if program else "manual",
|
||||||
|
flags,
|
||||||
|
supports_verbose=program is not None,
|
||||||
|
),),
|
||||||
arguments=arguments, optional=optional, requires=requirements,
|
arguments=arguments, optional=optional, requires=requirements,
|
||||||
))
|
))
|
||||||
return steps
|
return steps
|
||||||
@@ -597,6 +665,7 @@ def build_command(
|
|||||||
values: dict[str, object],
|
values: dict[str, object],
|
||||||
evaluation_arg: str,
|
evaluation_arg: str,
|
||||||
extra_arguments: str = "",
|
extra_arguments: str = "",
|
||||||
|
verbose: bool = False,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
if variant.kind == "manual" or not variant.program:
|
if variant.kind == "manual" or not variant.program:
|
||||||
return []
|
return []
|
||||||
@@ -633,6 +702,8 @@ def build_command(
|
|||||||
if not variant.fixed_args_before_positionals:
|
if not variant.fixed_args_before_positionals:
|
||||||
command.extend(variant.fixed_args)
|
command.extend(variant.fixed_args)
|
||||||
command.extend(options)
|
command.extend(options)
|
||||||
|
if verbose and variant.supports_verbose:
|
||||||
|
command.append("--verbose")
|
||||||
if extra_arguments.strip():
|
if extra_arguments.strip():
|
||||||
command.extend(shlex.split(extra_arguments, posix=os.name != "nt"))
|
command.extend(shlex.split(extra_arguments, posix=os.name != "nt"))
|
||||||
return command
|
return command
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ IMPORT_DIR = Path("Import")
|
|||||||
# Les étapes gestion_classe, ODS et publication sont masquées par défaut.
|
# Les étapes gestion_classe, ODS et publication sont masquées par défaut.
|
||||||
SHOW_PERSONAL_STEPS = False
|
SHOW_PERSONAL_STEPS = False
|
||||||
|
|
||||||
|
# Lance automatiquement les deux étapes facultatives de rognage dans le GUI.
|
||||||
|
ALWAYS_CROP = False
|
||||||
|
|
||||||
# Chemins utilisés uniquement par les étapes personnelles.
|
# Chemins utilisés uniquement par les étapes personnelles.
|
||||||
CURRENT_SCORE_ODS_PATH = Path("current_eval.ods")
|
CURRENT_SCORE_ODS_PATH = Path("current_eval.ods")
|
||||||
FINAL_SCORE_ODS_PATH = Path("simple_eval.ods")
|
FINAL_SCORE_ODS_PATH = Path("simple_eval.ods")
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class CropExerciseBottomsCommandTests(unittest.TestCase):
|
|||||||
self.assertEqual(steps[index - 1].id, "splitting")
|
self.assertEqual(steps[index - 1].id, "splitting")
|
||||||
self.assertEqual(steps[index + 1].id, "grouping")
|
self.assertEqual(steps[index + 1].id, "grouping")
|
||||||
self.assertTrue(steps[index].optional)
|
self.assertTrue(steps[index].optional)
|
||||||
self.assertFalse(steps[index].auto_start_first_visit)
|
self.assertTrue(steps[index].auto_start_first_visit)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ class CropMarginsCommandTests(unittest.TestCase):
|
|||||||
self.assertEqual(steps[index-1].id, "page_splitter")
|
self.assertEqual(steps[index-1].id, "page_splitter")
|
||||||
self.assertEqual(steps[index+1].id, "cutleft")
|
self.assertEqual(steps[index+1].id, "cutleft")
|
||||||
self.assertTrue(steps[index].optional)
|
self.assertTrue(steps[index].optional)
|
||||||
self.assertFalse(steps[index].auto_start_first_visit)
|
self.assertTrue(steps[index].auto_start_first_visit)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -27,6 +27,18 @@ class GuiConvenienceTests(unittest.TestCase):
|
|||||||
self.app.destroy()
|
self.app.destroy()
|
||||||
self.temp.cleanup()
|
self.temp.cleanup()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _label_texts(widget):
|
||||||
|
return [
|
||||||
|
text
|
||||||
|
for child in widget.winfo_children()
|
||||||
|
for text in (
|
||||||
|
[str(child.cget("text"))]
|
||||||
|
if isinstance(child, ttk.Label)
|
||||||
|
else GuiConvenienceTests._label_texts(child)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
def test_reload_detects_added_and_removed_files_without_advancing(self):
|
def test_reload_detects_added_and_removed_files_without_advancing(self):
|
||||||
for name in ("enonce.pdf", "enonce.tex", "correction.tex"):
|
for name in ("enonce.pdf", "enonce.tex", "correction.tex"):
|
||||||
(self.evaluation / name).touch()
|
(self.evaluation / name).touch()
|
||||||
@@ -57,6 +69,7 @@ class GuiConvenienceTests(unittest.TestCase):
|
|||||||
copies.mkdir()
|
copies.mkdir()
|
||||||
source = copies/"Copie01.pdf"
|
source = copies/"Copie01.pdf"
|
||||||
source.touch()
|
source.touch()
|
||||||
|
self.app.state_store.update_step("crop_blank_margins", visited=True)
|
||||||
self.app.tree.selection_set("crop_blank_margins")
|
self.app.tree.selection_set("crop_blank_margins")
|
||||||
self.app.update()
|
self.app.update()
|
||||||
self.assertEqual(self.app.current_step.id, "crop_blank_margins")
|
self.assertEqual(self.app.current_step.id, "crop_blank_margins")
|
||||||
@@ -72,6 +85,7 @@ class GuiConvenienceTests(unittest.TestCase):
|
|||||||
answers = self.evaluation / "Copies" / "Copie01"
|
answers = self.evaluation / "Copies" / "Copie01"
|
||||||
answers.mkdir(parents=True)
|
answers.mkdir(parents=True)
|
||||||
(answers / "Ex 1.pdf").touch()
|
(answers / "Ex 1.pdf").touch()
|
||||||
|
self.app.state_store.update_step("crop_exercise_bottoms", visited=True)
|
||||||
self.app.tree.selection_set("crop_exercise_bottoms")
|
self.app.tree.selection_set("crop_exercise_bottoms")
|
||||||
self.app.update()
|
self.app.update()
|
||||||
self.assertEqual(self.app.current_step.id, "crop_exercise_bottoms")
|
self.assertEqual(self.app.current_step.id, "crop_exercise_bottoms")
|
||||||
@@ -135,6 +149,24 @@ class GuiConvenienceTests(unittest.TestCase):
|
|||||||
self.assertIn("Cibler la copie sélectionnée", controls)
|
self.assertIn("Cibler la copie sélectionnée", controls)
|
||||||
self.assertIn("Cibler tout le dossier", controls)
|
self.assertIn("Cibler tout le dossier", controls)
|
||||||
|
|
||||||
|
def test_free_form_arguments_are_shown_only_when_documented(self):
|
||||||
|
self.app.tree.selection_set("labels")
|
||||||
|
self.app.update()
|
||||||
|
labels_text = self._label_texts(self.app.form)
|
||||||
|
self.assertIn("Arguments supplémentaires", labels_text)
|
||||||
|
self.assertTrue(any("Cutleft" in text for text in labels_text))
|
||||||
|
|
||||||
|
self.app.tree.selection_set("plotting")
|
||||||
|
self.app.update()
|
||||||
|
self.assertNotIn("Arguments supplémentaires", self._label_texts(self.app.form))
|
||||||
|
|
||||||
|
def test_verbose_checkbox_updates_supported_commands(self):
|
||||||
|
self.app.tree.selection_set("labels")
|
||||||
|
self.app.update()
|
||||||
|
self.assertNotIn("--verbose", self.app._make_command())
|
||||||
|
self.app.verbose_var.set(True)
|
||||||
|
self.assertIn("--verbose", self.app._make_command())
|
||||||
|
|
||||||
def test_console_selection_survives_output_and_is_read_only(self):
|
def test_console_selection_survives_output_and_is_read_only(self):
|
||||||
self.app._append_console("Première ligne\nDeuxième ligne\n")
|
self.app._append_console("Première ligne\nDeuxième ligne\n")
|
||||||
self.app.console.tag_add("sel", "1.0", "1.end")
|
self.app.console.tag_add("sel", "1.0", "1.end")
|
||||||
|
|||||||
+116
-1
@@ -1674,12 +1674,37 @@ class WorkflowTests(unittest.TestCase):
|
|||||||
if step.skip_without_manual_conflicts
|
if step.skip_without_manual_conflicts
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assertEqual(auto_start, {"splitting", "grouping"})
|
self.assertEqual(
|
||||||
|
auto_start,
|
||||||
|
{"crop_blank_margins", "splitting", "crop_exercise_bottoms", "grouping"},
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
skip_for_live, {"submit_batches", "batch_status", "fetch_batches"}
|
skip_for_live, {"submit_batches", "batch_status", "fetch_batches"}
|
||||||
)
|
)
|
||||||
self.assertEqual(skip_without_conflicts, {"manual_resolution"})
|
self.assertEqual(skip_without_conflicts, {"manual_resolution"})
|
||||||
|
|
||||||
|
def test_crop_auto_start_follows_always_crop_configuration(self) -> None:
|
||||||
|
for enabled in (False, True):
|
||||||
|
with self.subTest(enabled=enabled), patch(
|
||||||
|
"copienator_gui.workflow.ALWAYS_CROP", enabled
|
||||||
|
):
|
||||||
|
steps = {step.id: step for step in build_workflow(False)}
|
||||||
|
self.assertEqual(
|
||||||
|
steps["crop_blank_margins"].auto_start_first_visit, enabled
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
steps["crop_exercise_bottoms"].auto_start_first_visit, enabled
|
||||||
|
)
|
||||||
|
self.assertTrue(steps["crop_blank_margins"].optional)
|
||||||
|
self.assertTrue(steps["crop_exercise_bottoms"].optional)
|
||||||
|
|
||||||
|
def test_default_and_repository_crop_configuration(self) -> None:
|
||||||
|
import config
|
||||||
|
import default_config
|
||||||
|
|
||||||
|
self.assertFalse(default_config.ALWAYS_CROP)
|
||||||
|
self.assertTrue(config.ALWAYS_CROP)
|
||||||
|
|
||||||
def test_review_persp_has_shorter_title(self) -> None:
|
def test_review_persp_has_shorter_title(self) -> None:
|
||||||
self.assertEqual(self.steps["review_persp"].title, "Relire les barèmes")
|
self.assertEqual(self.steps["review_persp"].title, "Relire les barèmes")
|
||||||
|
|
||||||
@@ -1790,6 +1815,82 @@ class WorkflowTests(unittest.TestCase):
|
|||||||
command_arguments(command), [self.evaluation, "--batch-from", "Ex 4"]
|
command_arguments(command), [self.evaluation, "--batch-from", "Ex 4"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_only_multi_target_steps_offer_free_form_arguments(self) -> None:
|
||||||
|
documented = {
|
||||||
|
step.id: step
|
||||||
|
for step in self.steps.values()
|
||||||
|
if step.extra_arguments_help
|
||||||
|
}
|
||||||
|
self.assertEqual(set(documented), {"labels", "correction"})
|
||||||
|
self.assertIn("Cutleft", documented["labels"].extra_arguments_help)
|
||||||
|
self.assertIn("Group_X.jpg", documented["correction"].extra_arguments_help)
|
||||||
|
self.assertEqual(
|
||||||
|
documented["correction"].extra_arguments_variants,
|
||||||
|
("live", "batch", "hybrid", "refaire"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_options_previously_requiring_free_form_arguments_have_controls(self) -> None:
|
||||||
|
status = self.command(
|
||||||
|
"batch_status",
|
||||||
|
"default",
|
||||||
|
{"download": "files/job-1", "output": "/tmp/result.jsonl"},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
command_arguments(status),
|
||||||
|
["--download", "files/job-1", "--output", "/tmp/result.jsonl"],
|
||||||
|
)
|
||||||
|
|
||||||
|
grouped = self.command(
|
||||||
|
"read_annotations",
|
||||||
|
"grouped",
|
||||||
|
{
|
||||||
|
"target": self.evaluation,
|
||||||
|
"refaire": True,
|
||||||
|
"annotation_dir": "Bnot",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
command_arguments(grouped),
|
||||||
|
[self.evaluation, "--refaire", "--annotation-dir", "Bnot"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_verbose_is_added_only_to_commands_that_support_it(self) -> None:
|
||||||
|
labels = self.steps["labels"]
|
||||||
|
labels_command = build_command(
|
||||||
|
REPOSITORY,
|
||||||
|
labels,
|
||||||
|
labels.variants[0],
|
||||||
|
{"target": self.evaluation},
|
||||||
|
self.evaluation,
|
||||||
|
verbose=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(command_arguments(labels_command), [self.evaluation, "--verbose"])
|
||||||
|
|
||||||
|
rotate = self.steps["rotate"]
|
||||||
|
rotate_command = build_command(
|
||||||
|
REPOSITORY,
|
||||||
|
rotate,
|
||||||
|
rotate.variants[0],
|
||||||
|
{"target": self.evaluation},
|
||||||
|
self.evaluation,
|
||||||
|
verbose=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
command_arguments(rotate_command),
|
||||||
|
["rotate", self.evaluation, "--verbose"],
|
||||||
|
)
|
||||||
|
|
||||||
|
update = self.steps["update_ods"]
|
||||||
|
update_command = build_command(
|
||||||
|
REPOSITORY,
|
||||||
|
update,
|
||||||
|
update.variants[0],
|
||||||
|
{"target": self.evaluation},
|
||||||
|
self.evaluation,
|
||||||
|
verbose=True,
|
||||||
|
)
|
||||||
|
self.assertNotIn("--verbose", update_command)
|
||||||
|
|
||||||
def test_annotation_variants_are_exclusive_commands(self) -> None:
|
def test_annotation_variants_are_exclusive_commands(self) -> None:
|
||||||
command = self.command(
|
command = self.command(
|
||||||
"annotation", "grouped", {"target": self.evaluation, "overwrite": True}
|
"annotation", "grouped", {"target": self.evaluation, "overwrite": True}
|
||||||
@@ -1815,6 +1916,20 @@ class WorkflowTests(unittest.TestCase):
|
|||||||
),
|
),
|
||||||
[self.evaluation, "--yes"],
|
[self.evaluation, "--yes"],
|
||||||
)
|
)
|
||||||
|
preview = next(variant for variant in clean.variants if variant.id == "dry_run")
|
||||||
|
self.assertFalse(preview.dangerous)
|
||||||
|
self.assertEqual(
|
||||||
|
command_arguments(
|
||||||
|
build_command(
|
||||||
|
REPOSITORY,
|
||||||
|
clean,
|
||||||
|
preview,
|
||||||
|
{"target": self.evaluation},
|
||||||
|
self.evaluation,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
[self.evaluation, "--dry-run"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CrossPlatformFileTests(unittest.TestCase):
|
class CrossPlatformFileTests(unittest.TestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user