[FIX] ssh config : une seule décision sur ce qu'est un nom de machine
Trois lecteurs de ~/.ssh/config tranchaient la même question autrement : l'un
comparait le mot-clé avec la casse, un autre exigeait un espace là où une
tabulation est légale, un troisième laissait passer le motif nié `!nom`. Un
alias déclaré « host » en minuscules était donc vu par l'un et invisible aux
deux autres, ce qui se lit comme une panne intermittente de la découverte.
La décision passe dans un module qui ne lit aucun fichier. Il rend None sur ce
qui n'est pas une déclaration et une liste — possiblement vide — sur ce qui en
est une : « Host * » ne nomme aucune machine mais reste une déclaration, sinon
ses directives se rattachent au bloc précédent. Vérifié : 19 tests, et le menu
reconnaît toujours le serveur du réseau par sa source SSH.
--- EN ---
Three readers of ~/.ssh/config settled the same question differently: one
compared the keyword case-sensitively, another required a space where a tab is
legal, a third let the negated pattern `!name` through. An alias declared with
a lowercase "host" was therefore seen by one and invisible to the other two,
which reads as intermittent discovery failure.
The decision moves into a module that reads no file. It returns None for what
is not a declaration and a list — possibly empty — for what is: "Host *" names
no machine yet remains a declaration, otherwise its directives attach to the
previous block. Checked: 19 tests, and the menu still recognises the server on
the network through its SSH source.
Assisted-by: Claude Opus 5
2026-09-09 04:39:01 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
"""Ce qu'est un nom de machine dans ~/.ssh/config, décidé en un seul endroit.
|
|
|
|
|
|
|
|
|
|
Trois lecteurs de ce fichier vivent dans ce dépôt — l'énumération des alias,
|
|
|
|
|
le menu de montage qui veut aussi l'adresse, et l'inventaire des entrées de
|
|
|
|
|
VM — et ils divergeaient sur la MÊME question. L'un comparait le mot-clé avec
|
|
|
|
|
la casse, un autre exigeait un espace là où une tabulation est légale, un
|
|
|
|
|
troisième laissait passer le motif nié. Un alias déclaré `host exo` était donc
|
|
|
|
|
vu par l'un et invisible aux autres, ce qui se lit comme une panne
|
|
|
|
|
intermittente de la découverte.
|
|
|
|
|
|
|
|
|
|
Le module ne lit aucun fichier et ne suit pas `Include` : il tranche une
|
|
|
|
|
ligne, rien de plus. Un alias déclaré dans un fichier inclus reste donc
|
|
|
|
|
invisible aux trois lecteurs, alors même que `ssh -G` le résoudrait — la
|
|
|
|
|
source est incomplète sans être fausse.
|
|
|
|
|
"""
|
[REF] format : passer l'outillage et les tests sous ruff
Le formateur de ce dépôt est ruff depuis qu'il remplace black, qui ne connaît
aucune cible au-delà de py313 ; ce passage applique sa norme à l'arbre entier,
d'un coup, pour qu'aucun commit de fond n'ait à porter du style. L'écart tient
presque entièrement aux chaînes coupées à la main que ruff recolle quand elles
tiennent sur une ligne, et aux « with » multiples qu'il regroupe : aucune
valeur ne change, et les clés de traduction non plus.
Vérifié : la suite unitaire reste verte après le passage, et le contrôle de
syntaxe ne signale rien.
--- EN ---
This repository's formatter is ruff since it replaced black, which knows no
target beyond py313; this pass applies its standard to the whole tree at once,
so that no substantive commit has to carry style. The difference is almost
entirely the hand-split strings ruff joins back when they fit on one line, and
the multiple "with" it merges: no value changes, nor do the translation keys.
Checked: the unit suite stays green after the pass, and the syntax check
reports nothing.
Assisted-by: Claude Opus 5
2026-09-24 13:30:31 -04:00
|
|
|
|
[FIX] ssh config : une seule décision sur ce qu'est un nom de machine
Trois lecteurs de ~/.ssh/config tranchaient la même question autrement : l'un
comparait le mot-clé avec la casse, un autre exigeait un espace là où une
tabulation est légale, un troisième laissait passer le motif nié `!nom`. Un
alias déclaré « host » en minuscules était donc vu par l'un et invisible aux
deux autres, ce qui se lit comme une panne intermittente de la découverte.
La décision passe dans un module qui ne lit aucun fichier. Il rend None sur ce
qui n'est pas une déclaration et une liste — possiblement vide — sur ce qui en
est une : « Host * » ne nomme aucune machine mais reste une déclaration, sinon
ses directives se rattachent au bloc précédent. Vérifié : 19 tests, et le menu
reconnaît toujours le serveur du réseau par sa source SSH.
--- EN ---
Three readers of ~/.ssh/config settled the same question differently: one
compared the keyword case-sensitively, another required a space where a tab is
legal, a third let the negated pattern `!name` through. An alias declared with
a lowercase "host" was therefore seen by one and invisible to the other two,
which reads as intermittent discovery failure.
The decision moves into a module that reads no file. It returns None for what
is not a declaration and a list — possibly empty — for what is: "Host *" names
no machine yet remains a declaration, otherwise its directives attach to the
previous block. Checked: 19 tests, and the menu still recognises the server on
the network through its SSH source.
Assisted-by: Claude Opus 5
2026-09-09 04:39:01 -04:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
# Le mot-clé, suivi d'au moins un blanc. ssh_config ne distingue pas la casse
|
|
|
|
|
# de ses mots-clés, et sépare par espace OU tabulation. Le blanc est exigé
|
|
|
|
|
# sans quoi `HostName` serait lu comme une déclaration d'hôte.
|
|
|
|
|
DECLARATION = re.compile(r"^[ \t]*host[ \t]+", re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def declared_names(line) -> list | None:
|
|
|
|
|
"""Les noms de machine d'une ligne « Host », ou None si ce n'en est pas une.
|
|
|
|
|
|
|
|
|
|
Rend None pour toute autre ligne, et une liste — possiblement VIDE —
|
|
|
|
|
quand la ligne est une déclaration. Distinguer les deux compte : `Host *`
|
|
|
|
|
EST une déclaration qui ne nomme aucune machine, et un lecteur qui
|
|
|
|
|
accumule un bloc doit clore le précédent malgré tout. Rendre `[]` dans
|
|
|
|
|
les deux cas rattachait les directives d'un bloc générique au bloc
|
|
|
|
|
précédent.
|
|
|
|
|
|
|
|
|
|
Trois formes ne désignent aucune machine et sortent : les jokers `*` et
|
|
|
|
|
`?`, qui décrivent une règle appliquée à plusieurs hôtes, et le motif NIÉ
|
|
|
|
|
`!nom`, qui RETIRE un nom de l'ensemble que la ligne vient de décrire.
|
|
|
|
|
Retenir un motif nié rend une cible dont le nom commence par `!`, que ssh
|
|
|
|
|
ne résoudra jamais.
|
|
|
|
|
"""
|
|
|
|
|
if not DECLARATION.match(line or ""):
|
|
|
|
|
return None
|
|
|
|
|
return [
|
|
|
|
|
nom
|
|
|
|
|
for nom in line.split()[1:]
|
|
|
|
|
if "*" not in nom and "?" not in nom and not nom.startswith("!")
|
|
|
|
|
]
|