From 891d412aa2f9f9aed087f464393b3e32413c07c9 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Tue, 11 Aug 2026 09:24:59 -0400 Subject: [PATCH] [UPD] tui qemu: pick the VM by number for its state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changing state meant retyping the name, on a single comma-separated line. Names are long and alike -- two of them sometimes differ by a suffix only -- and a typo here decided whether a machine started or shut down. The screen now reuses the numbered list from the delete screen, "all" included, and its parser, which already accepts names: nothing is lost for those who typed them. The warning on an unknown entry is kept, that parser otherwise dropping it without a word. --- FR --- Changer l'état demandait de retaper le nom, sur une seule ligne séparée par des virgules. Les noms sont longs et se ressemblent — deux d'entre eux ne diffèrent parfois que par un suffixe — et une faute de frappe portait ici sur le démarrage ou l'extinction d'une machine. L'écran reprend donc la liste numérotée de la suppression, « all » compris, et son analyseur, qui accepte déjà les noms : rien n'est perdu pour qui les tapait. L'avertissement sur une entrée inconnue est conservé, cet analyseur l'écartant autrement sans un mot. Assisted-by: Claude Opus 5 --- script/todo/todo.py | 46 +++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index 12babca..45f5984 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -2137,20 +2137,34 @@ class TODO: if not names: print(f"\n{t('No VM found.')}") return - print(f"\n{t('Available VMs:')} {', '.join(names)}") - raw = input(t("VMs to change (comma-separated): ")).strip() - targets = [n.strip() for n in raw.split(",") if n.strip()] - if not targets: + # Liste NUMÉROTÉE, comme l'écran de suppression : les noms de VM sont + # longs et se ressemblent, les retaper invite à la faute de frappe sur + # une commande qui change l'état d'une machine. + print(f"\n{t('Available VMs:')}") + for i, n in enumerate(names, 1): + print(f" [{i}] {n}") + print(f" [all] {t('select all')}") + raw = input(t("Selection (numbers, or 'all'): ")).strip() + if not raw: print(t("Nothing selected.")) return - # Résoudre les ID -> noms et valider l'existence. - resolved, unknown = [], [] - known = set(names) - for tgt in targets: - real = self._qemu_domname(tgt) - (resolved if real in known else unknown).append(real) - if unknown: - print(f"{t('Unknown VM(s):')} {', '.join(unknown)}") + if raw.lower() in ("all", "*"): + resolved = list(names) + else: + resolved = self._parse_index_selection(raw.lower(), names) + # Le parseur ignore en silence ce qu'il ne reconnaît pas. Sur une + # sélection qui va démarrer ou éteindre des VM, un numéro hors + # liste doit être dit, pas escamoté. + unknown = [ + tok + for tok in re.split(r"[\s,]+", raw.strip()) + if tok and tok not in names and not self._is_index(tok, names) + ] + if unknown: + print(f"{t('Unknown VM(s):')} {', '.join(unknown)}") + return + if not resolved: + print(t("Nothing selected.")) return # Choix de l'état cible : ouvrir (démarrer) ou fermer (éteindre). print(f"\n{t('Target state:')}") @@ -3812,6 +3826,14 @@ class TODO: pass return 0 + @staticmethod + def _is_index(token, options): + """Vrai si le jeton est un numéro valide dans la liste (1-based).""" + try: + return 1 <= int(token) <= len(options) + except ValueError: + return False + @staticmethod def _parse_index_selection(raw, options): """« 1 3 » ou « 1,3 » -> sous-liste d'options (indices 1-based)."""