import re
import sys
import unicodedata
def slugify(title: str, seen: dict) -> str:
"""Reproduit l'algorithme d'ancre de GitHub (approximatif mais correct
pour le français) : minuscules, accents retirés, ponctuation supprimée,
espaces -> tirets, et suffixe -1/-2/... en cas de titres en double
(comme le fait GitHub, sinon plusieurs entrées du sommaire pointeraient
vers la même ancre, celle du premier titre)."""
normalized = unicodedata.normalize('NFKD', title.lower())
without_accents = ''.join(c for c in normalized if not unicodedata.combining(c))
slug = re.sub(r'[^\w\s-]', '', without_accents) # ponctuation retirée
slug = re.sub(r'\s+', '-', slug).strip('-')
if slug in seen:
seen[slug] += 1
return f"{slug}-{seen[slug]}"
seen[slug] = 0
return slug
def generate_markdown_toc(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Rechercher la section de sommaire existante
toc_regex = r'<!-- ATOC -->.*?<!-- ATOC -->'
toc_match = re.search(toc_regex, content, re.DOTALL)
# Si une section de sommaire existe déjà, on l'enlève
if toc_match:
content = content.replace(toc_match.group(), '', 1) # une seule occurrence
# Parcourir le contenu pour identifier les en-têtes, en ignorant les blocs
# de code (``` ... ```) : une ligne commençant par '#' à l'intérieur d'un
# bloc de code (commentaire Python, shebang bash, etc.) n'est pas un titre.
toc_lines = []
seen_slugs = {}
in_code_block = False
for line in content.splitlines():
stripped = line.strip()
if stripped.startswith('```') or stripped.startswith('~~~'):
in_code_block = not in_code_block
continue
if in_code_block:
continue
if line.startswith('#'):
level = len(line) - len(line.lstrip('#'))
title = line[level:].strip().strip('#').strip()
if not title or title.lower() == "sommaire": # Exclure "Sommaire" du ToC
continue
identifier = slugify(title, seen_slugs)
toc_lines.append(f"{' ' * (level - 1)}- [{title}](#{identifier})\n")
# Créer le nouveau sommaire sans l'entrée "Sommaire"
new_toc = "<!-- ATOC -->\n# Sommaire\n\n" + ''.join(toc_lines) + "\n<!-- ATOC -->"
# Ajouter le nouveau sommaire en haut du fichier
updated_content = new_toc + '\n' + content
# Écrire le contenu mis à jour dans le fichier
with open(file_path, 'w', encoding='utf-8') as f:
f.write(updated_content)
print(f"Sommaire mis à jour dans '{file_path}'.")
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage : {sys.argv[0]} <fichier.md>", file=sys.stderr)
sys.exit(1)
try:
generate_markdown_toc(sys.argv[1])
except Exception as e:
# On remonte une erreur explicite avec un code de sortie non-nul :
# avant, l'exception était juste affichée et le script rendait la
# main avec un code 0, donc la CI (set -euo pipefail) ne détectait
# jamais un échec de mise à jour du sommaire.
print(f"Erreur lors de la génération du sommaire : {e}", file=sys.stderr)
sys.exit(1)