[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
"""Lire une réponse, ou prendre le défaut au bout du délai.
|
|
|
|
|
|
|
|
|
|
Le mode auto de la migration ne vaut que si TOUTES les invites le
|
|
|
|
|
connaissent. Or six d'entre elles vivent dans des outils lancés en
|
|
|
|
|
sous-processus — le désinstalleur de thème, le détecteur de SCSS figé, le
|
|
|
|
|
test de fumée. Elles ne partagent ni l'objet du pilote ni sa mémoire ; sans
|
|
|
|
|
rien de commun, elles attendraient indéfiniment une frappe qui ne vient
|
|
|
|
|
pas, et l'automatisation s'arrêterait là sans rien dire.
|
|
|
|
|
|
|
|
|
|
Ce qui traverse un `fork`, c'est l'environnement. Le pilote y pose
|
|
|
|
|
`ERPLIBRE_AUTO_EXECUTE`, chaque outil le lit, et il n'existe qu'une seule
|
|
|
|
|
implémentation du compte à rebours.
|
|
|
|
|
|
|
|
|
|
`select` plutôt qu'un fil ou une alarme : la question est « quelque chose
|
|
|
|
|
est-il LISIBLE maintenant », et il faut pouvoir renoncer. Un fil laisserait
|
|
|
|
|
derrière lui un `input()` toujours bloqué, qui volerait la frappe suivante.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
|
[FIX] migration: the countdown names the answer the prompt offered
« ⏱ → d » named a reply that was not on the menu. The prompt reads
« Enter = delete, after saving them, k = keep » and mentions « d »
nowhere, so the line asking to be trusted with a decision described it in
a vocabulary the reader had never been given.
The decision itself was right — Enter deletes, and that is what happened.
Only its report was written from the inside. It now says Enter, in the
migration's language, with the value in brackets for whoever wants the
precision. What the caller receives is unchanged.
--- FR ---
[FIX] migration : le compte à rebours nomme la réponse que l'invite offrait
« ⏱ → d » nommait une réponse qui n'était pas au menu. L'invite annonce
« Entrée = effacer, après les avoir sauvegardés, k = garder » et ne
mentionne « d » nulle part : la ligne qui demandait qu'on lui confie une
décision la décrivait donc dans un vocabulaire jamais montré.
La décision, elle, était juste — Entrée efface, et c'est ce qui a eu lieu.
Seul son compte rendu était écrit du dedans. Il dit maintenant Entrée,
dans la langue de la migration, la valeur entre parenthèses pour qui veut
la précision. Ce que l'appelant reçoit ne change pas.
Assisted-by: Claude Opus 5
2026-08-19 05:16:48 -04:00
|
|
|
sys.path.append(
|
|
|
|
|
os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
from script.todo.todo_i18n import t
|
|
|
|
|
except Exception: # pragma: no cover - repli si i18n indisponible
|
|
|
|
|
|
|
|
|
|
def t(key: str) -> str:
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
ENV_ENABLED = "ERPLIBRE_AUTO_EXECUTE"
|
|
|
|
|
ENV_DELAY = "ERPLIBRE_AUTO_DELAY"
|
|
|
|
|
DEFAULT_DELAY = 5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def enabled():
|
|
|
|
|
"""Le mode auto est-il actif pour CE processus ?"""
|
|
|
|
|
return os.environ.get(ENV_ENABLED, "") == "1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def delay():
|
|
|
|
|
"""Combien de secondes attendre. Une valeur illisible ne bloque rien."""
|
|
|
|
|
try:
|
|
|
|
|
value = float(os.environ.get(ENV_DELAY, DEFAULT_DELAY))
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
return DEFAULT_DELAY
|
|
|
|
|
# Un délai nul ou négatif rendrait la reprise en main impossible : on
|
|
|
|
|
# ne peut pas interrompre ce qui ne laisse aucune fenêtre.
|
|
|
|
|
return value if value > 0 else DEFAULT_DELAY
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def export(on, seconds=None):
|
|
|
|
|
"""Poser le mode auto dans l'environnement, pour tout ce qu'on lancera.
|
|
|
|
|
|
|
|
|
|
C'est le seul canal qu'un sous-processus partage avec nous. Le retirer
|
|
|
|
|
quand on l'éteint, plutôt que d'y écrire « 0 », évite qu'un reste de
|
|
|
|
|
session précédente décide à notre place.
|
|
|
|
|
"""
|
|
|
|
|
if on:
|
|
|
|
|
os.environ[ENV_ENABLED] = "1"
|
|
|
|
|
os.environ[ENV_DELAY] = str(
|
|
|
|
|
seconds if seconds is not None else DEFAULT_DELAY
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
os.environ.pop(ENV_ENABLED, None)
|
|
|
|
|
os.environ.pop(ENV_DELAY, None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ask(prompt, default="", seconds=None):
|
|
|
|
|
"""Poser la question. Rendre `default` si rien n'arrive à temps.
|
|
|
|
|
|
|
|
|
|
Hors mode auto, c'est un `input()` ordinaire — sauf qu'une réponse vide
|
|
|
|
|
vaut le défaut, comme partout ailleurs en ligne de commande. C'est ce
|
|
|
|
|
qui permet d'écrire l'attendu dans la question : « (Y/n) » doit dire la
|
|
|
|
|
vérité pour la personne qui appuie sur Entrée, pas seulement pour le
|
|
|
|
|
compte à rebours.
|
|
|
|
|
"""
|
|
|
|
|
if not enabled():
|
|
|
|
|
return input(prompt) or default
|
|
|
|
|
import select
|
|
|
|
|
|
[ADD] migration: announce the countdown, and cycle three panel states
Every prompt now carries « ⏱5s » before the question. One that looks like
it waits forever does not invite you to step away, which is the whole
point of auto-run. The same clock reports the answer afterwards: it
announces the wait, then says what it decided.
Enter on the auto-run question already meant no; it now says so, with an
explicit default instead of a deduction. And the flag is cleared before
asking — an ERPLIBRE_AUTO_EXECUTE inherited from the shell would have
answered yes on someone's behalf, and that answer commits every later one.
« p » cycles three states rather than toggling one: a plain toggle freed
four lines, while what takes the room is the left COLUMN — on a server log
every character of width counts. All → no summary → detail only, and back.
The state is named in the sub-title, which stays visible in all three: a
panel that vanishes without a word reads as a broken screen.
--- FR ---
[ADD] migration : annoncer le compte à rebours, et trois états de panneaux
Chaque invite porte désormais « ⏱5s » devant la question. Une invite qui a
l'air d'attendre indéfiniment n'invite pas à s'absenter, et c'est pourtant
tout l'intérêt du mode auto. Le même chronomètre rend compte ensuite.
Entrée sur la question d'auto-exécution voulait déjà dire non ; elle le
DIT maintenant, par un défaut explicite plutôt qu'une déduction. Et le
drapeau est effacé avant de demander : une variable héritée du shell
aurait répondu « oui » à la place de quelqu'un.
« p » enchaîne trois états au lieu d'en basculer un : ce qui prend la
place, c'est la colonne de gauche. Tout → sans résumé → détail seul, et
l'on revient. L'état est nommé dans le sous-titre.
Assisted-by: Claude Opus 5
2026-08-19 06:57:19 -04:00
|
|
|
attente = delay() if seconds is None else seconds
|
|
|
|
|
# DIRE qu'on répondra tout seul, et dans combien de temps. Une invite
|
|
|
|
|
# qui a l'air d'attendre indéfiniment n'invite pas à s'absenter, et
|
|
|
|
|
# c'est pourtant tout l'intérêt du mode auto. Devant l'invite plutôt
|
|
|
|
|
# qu'après : elle se lit avant qu'on décide de partir.
|
|
|
|
|
sys.stdout.write(f"⏱{attente:g}s {prompt}")
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
sys.stdout.flush()
|
|
|
|
|
try:
|
[ADD] migration: announce the countdown, and cycle three panel states
Every prompt now carries « ⏱5s » before the question. One that looks like
it waits forever does not invite you to step away, which is the whole
point of auto-run. The same clock reports the answer afterwards: it
announces the wait, then says what it decided.
Enter on the auto-run question already meant no; it now says so, with an
explicit default instead of a deduction. And the flag is cleared before
asking — an ERPLIBRE_AUTO_EXECUTE inherited from the shell would have
answered yes on someone's behalf, and that answer commits every later one.
« p » cycles three states rather than toggling one: a plain toggle freed
four lines, while what takes the room is the left COLUMN — on a server log
every character of width counts. All → no summary → detail only, and back.
The state is named in the sub-title, which stays visible in all three: a
panel that vanishes without a word reads as a broken screen.
--- FR ---
[ADD] migration : annoncer le compte à rebours, et trois états de panneaux
Chaque invite porte désormais « ⏱5s » devant la question. Une invite qui a
l'air d'attendre indéfiniment n'invite pas à s'absenter, et c'est pourtant
tout l'intérêt du mode auto. Le même chronomètre rend compte ensuite.
Entrée sur la question d'auto-exécution voulait déjà dire non ; elle le
DIT maintenant, par un défaut explicite plutôt qu'une déduction. Et le
drapeau est effacé avant de demander : une variable héritée du shell
aurait répondu « oui » à la place de quelqu'un.
« p » enchaîne trois états au lieu d'en basculer un : ce qui prend la
place, c'est la colonne de gauche. Tout → sans résumé → détail seul, et
l'on revient. L'état est nommé dans le sous-titre.
Assisted-by: Claude Opus 5
2026-08-19 06:57:19 -04:00
|
|
|
ready, _, _ = select.select([sys.stdin], [], [], attente)
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
except (OSError, ValueError):
|
|
|
|
|
# stdin n'est pas sélectionnable : on ne devine pas, on demande.
|
|
|
|
|
return input("") or default
|
|
|
|
|
if ready:
|
|
|
|
|
answer = sys.stdin.readline().rstrip("\n")
|
|
|
|
|
# Une réponse VIDE vaut « prends le défaut » : c'est tout le propos
|
|
|
|
|
# du mode auto. Un stdin fermé — exécution non interactive — est
|
|
|
|
|
# lisible tout de suite et rend justement une ligne vide ; sans
|
|
|
|
|
# ceci, le défaut ne servirait jamais là.
|
|
|
|
|
return answer or default
|
[FIX] migration: the countdown names the answer the prompt offered
« ⏱ → d » named a reply that was not on the menu. The prompt reads
« Enter = delete, after saving them, k = keep » and mentions « d »
nowhere, so the line asking to be trusted with a decision described it in
a vocabulary the reader had never been given.
The decision itself was right — Enter deletes, and that is what happened.
Only its report was written from the inside. It now says Enter, in the
migration's language, with the value in brackets for whoever wants the
precision. What the caller receives is unchanged.
--- FR ---
[FIX] migration : le compte à rebours nomme la réponse que l'invite offrait
« ⏱ → d » nommait une réponse qui n'était pas au menu. L'invite annonce
« Entrée = effacer, après les avoir sauvegardés, k = garder » et ne
mentionne « d » nulle part : la ligne qui demandait qu'on lui confie une
décision la décrivait donc dans un vocabulaire jamais montré.
La décision, elle, était juste — Entrée efface, et c'est ce qui a eu lieu.
Seul son compte rendu était écrit du dedans. Il dit maintenant Entrée,
dans la langue de la migration, la valeur entre parenthèses pour qui veut
la précision. Ce que l'appelant reçoit ne change pas.
Assisted-by: Claude Opus 5
2026-08-19 05:16:48 -04:00
|
|
|
# « Entrée », parce que c'est ce que l'invite a proposé. Elle annonce
|
|
|
|
|
# « Entrée = effacer, k = garder » et ne mentionne nulle part « d » :
|
|
|
|
|
# afficher la valeur brute nommait donc une réponse qui n'était pas au
|
|
|
|
|
# menu, et l'on se demandait ce que l'outil venait de décider. La
|
|
|
|
|
# valeur reste, entre parenthèses, pour qui veut la précision.
|
|
|
|
|
detail = f" ({default})" if default else ""
|
|
|
|
|
print(f" ⏱ → {t('Enter')}{detail}")
|
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
|
|
|
return default
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_ask(default, seconds=None):
|
|
|
|
|
"""Un `ask` à un seul argument, pour les outils qui en injectent un.
|
|
|
|
|
|
|
|
|
|
Ils appellent `ask(question)` sans savoir ce qu'est un défaut : celui-ci
|
|
|
|
|
appartient à l'outil, pas à l'appelant, et se fige donc ici.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def ask_one(prompt=""):
|
|
|
|
|
return ask(prompt, default=default, seconds=seconds)
|
|
|
|
|
|
|
|
|
|
return ask_one
|