Une syntaxe plus récente que le Python du dépôt ne casse qu'au chargement, et aucun outil en place ne la voyait : la cible de black borne ce qu'il ÉCRIT, jamais ce qu'il accepte, et flake8 analyse avec l'interpréteur courant. Le contrôle compile les fichiers indexés avec un vrai interpréteur de la version déclarée, trouvé sans réseau par mise, pyenv ou le PATH, et se prend lui-même quand il y tourne déjà. Sans interpréteur, il DIT qu'il n'a pas vérifié plutôt que de laisser croire le contraire. Le hook relaie, sans bloquer le commit, et écarte les dépôts rapatriés dont ce dépôt ne répond pas. Vérifié : les f-strings PEP 701 de la révision précédente sont signalées, rien ne l'est sur l'arbre courant, et l'absence du fichier de version se dit. --- EN --- A syntax newer than the repository's Python only breaks at load, and no tool in place saw it: black's target bounds what it WRITES, never what it accepts, and flake8 parses with the running interpreter. The check compiles staged files with a real interpreter of the declared version, found without network through mise, pyenv or the PATH, and takes itself when it already runs under it. Without one, it SAYS it did not check rather than implying it did. The hook relays, without blocking the commit, and skips the checked-out repositories this one does not answer for. Checked: the PEP 701 f-strings of the previous revision are reported, nothing is on the current tree, and a missing version file is announced. Assisted-by: Claude Opus 5
117 lines
3.6 KiB
Python
Executable file
117 lines
3.6 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
"""Hook pre-commit : montre les commentaires hors convention des fichiers indexés.
|
|
|
|
Il INFORME et ne refuse jamais — le nettoyage se fait au fur et à mesure, sur
|
|
les fichiers qu'on touche déjà, et non en une passe qui réécrirait le dépôt.
|
|
Un hook qui bloquerait sur du style se ferait désinstaller la même semaine.
|
|
|
|
Il relaie aussi check_python_version.py, sans bloquer davantage : le source
|
|
indexé qui ne parse pas sous le Python de conf/python-erplibre-version.
|
|
|
|
Installation :
|
|
|
|
git config core.hooksPath script/git/hooks
|
|
|
|
Il sort toujours en 0. Pour l'interroger à la main, sur un fichier ou un
|
|
répertoire :
|
|
|
|
python3 script/analyse/check_comment_hygiene.py script/todo/todo.py
|
|
python3 script/analyse/check_python_version.py script/todo/todo.py
|
|
"""
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
RACINE = Path(__file__).resolve().parents[3]
|
|
OUTIL = RACINE / "script" / "analyse" / "check_comment_hygiene.py"
|
|
VERSION = RACINE / "script" / "analyse" / "check_python_version.py"
|
|
|
|
# Au-delà, le rapport cesse d'être une invitation et devient un mur.
|
|
PLAFOND = 12
|
|
|
|
|
|
def commentaires() -> int:
|
|
if not OUTIL.is_file():
|
|
return 0
|
|
|
|
try:
|
|
sortie = subprocess.run(
|
|
[sys.executable, str(OUTIL), "--staged", "--no-color"],
|
|
capture_output=True,
|
|
text=True,
|
|
cwd=str(RACINE),
|
|
timeout=30,
|
|
)
|
|
except (OSError, subprocess.SubprocessError):
|
|
return 0
|
|
|
|
if sortie.returncode not in (0, 1):
|
|
# Jamais bloquant, mais jamais muet non plus : un outil cassé qui se
|
|
# tait se confond avec un dépôt propre.
|
|
print(
|
|
f"\n ⓘ check_comment_hygiene a échoué ({sortie.returncode}) ;"
|
|
" les commentaires n'ont pas été relus\n",
|
|
file=sys.stderr,
|
|
)
|
|
return 0
|
|
|
|
if sortie.returncode != 1 or not sortie.stdout.strip():
|
|
return 0
|
|
|
|
lignes = sortie.stdout.rstrip("\n").split("\n")
|
|
trop = len(lignes) - PLAFOND
|
|
if trop > 0:
|
|
lignes = lignes[:PLAFOND] + [f" … et {trop} lignes de plus"]
|
|
|
|
print(
|
|
"\n ⓘ commentaires à relire dans ce que vous committez\n",
|
|
file=sys.stderr,
|
|
)
|
|
for ligne in lignes:
|
|
print(f" {ligne}", file=sys.stderr)
|
|
print(
|
|
"\n 🔴 identifiant : à retirer. 🟡 récit : à relire — un fait durable"
|
|
"\n reste, l'incident où on l'a vu part."
|
|
"\n La règle : .claude/rules/04-code-conventions.md"
|
|
"\n Le commit n'est PAS bloqué. Corrigez ce que vous touchez.\n",
|
|
file=sys.stderr,
|
|
)
|
|
return 0
|
|
|
|
|
|
def syntaxe() -> None:
|
|
"""Relaie les refus de syntaxe, ou l'avis d'un contrôle absent."""
|
|
if not VERSION.is_file():
|
|
return
|
|
try:
|
|
sortie = subprocess.run(
|
|
[sys.executable, str(VERSION), "--staged"],
|
|
capture_output=True,
|
|
text=True,
|
|
cwd=str(RACINE),
|
|
timeout=30,
|
|
)
|
|
except (OSError, subprocess.SubprocessError):
|
|
return
|
|
lignes = (sortie.stdout + sortie.stderr).strip().split("\n")
|
|
if not lignes[0]:
|
|
return
|
|
trop = len(lignes) - PLAFOND
|
|
if trop > 0:
|
|
lignes = lignes[:PLAFOND] + [f" … et {trop} lignes de plus"]
|
|
print("\n ⓘ syntaxe Python du dépôt\n", file=sys.stderr)
|
|
for ligne in lignes:
|
|
print(f" {ligne}", file=sys.stderr)
|
|
print("\n Le commit n'est PAS bloqué.\n", file=sys.stderr)
|
|
|
|
|
|
def main() -> int:
|
|
commentaires()
|
|
syntaxe()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|