#!/usr/bin/env python3 """ Script pour générer un PDF complet de toute la documentation MkDocs. Utilise weasyprint pour convertir le HTML en PDF. """ import os import re import subprocess import tempfile import shutil from pathlib import Path from typing import List, Dict, Any import yaml class SafeLineLoader(yaml.SafeLoader): """Loader YAML qui ignore les tags Python non supportés""" pass # Ignorer les tags Python custom (pour les extensions MkDocs) SafeLineLoader.add_multi_constructor('tag:yaml.org,2002:python/', lambda loader, suffix, node: None) def parse_nav(nav: List, docs_dir: Path, prefix: str = "") -> List[Path]: """Parse la navigation MkDocs et retourne la liste ordonnée des fichiers MD""" files = [] for item in nav: if isinstance(item, str): # Fichier simple ou dossier path = docs_dir / item if path.is_file() and path.suffix == '.md': files.append(path) elif path.is_dir(): # C'est un dossier, récupérer tous les fichiers MD files.extend(sorted(path.rglob('*.md'))) elif item.endswith('/'): # Référence à un dossier (ex: bdd/) dir_path = docs_dir / item.rstrip('/') if dir_path.is_dir(): files.extend(sorted(dir_path.rglob('*.md'))) elif isinstance(item, dict): for title, value in item.items(): if isinstance(value, str): # Fichier avec titre ou dossier path = docs_dir / value if path.is_file() and path.suffix == '.md': files.append(path) elif path.is_dir(): files.extend(sorted(path.rglob('*.md'))) elif value.endswith('/'): dir_path = docs_dir / value.rstrip('/') if dir_path.is_dir(): files.extend(sorted(dir_path.rglob('*.md'))) elif isinstance(value, list): # Sous-section files.extend(parse_nav(value, docs_dir, prefix + " ")) return files def get_all_md_files(docs_dir: Path, mkdocs_config: Dict[str, Any]) -> List[Path]: """Récupère tous les fichiers MD dans l'ordre de navigation""" if 'nav' in mkdocs_config: return parse_nav(mkdocs_config['nav'], docs_dir) else: # Pas de nav, récupérer tous les fichiers MD return sorted(docs_dir.rglob('*.md')) def preprocess_markdown(content: str, file_path: Path, docs_dir: Path) -> str: """Prétraite le Markdown pour le PDF""" # Convertir les liens relatifs def fix_link(match): link = match.group(2) if link.startswith('http'): return match.group(0) # Convertir les liens .md en ancres if link.endswith('.md'): link = link[:-3] return f'[{match.group(1)}](#{link})' content = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', fix_link, content) # Supprimer les admonitions MkDocs et les convertir en blockquotes # !!! type "title" -> blockquote content = re.sub( r'!!! (\w+) "([^"]+)"\n\n((?: .+\n)+)', lambda m: f'> **{m.group(2)}**\n>\n' + '\n'.join(f'> {line[4:]}' for line in m.group(3).split('\n') if line), content ) # ??? type "title" -> blockquote (collapsible) content = re.sub( r'\?\?\??\+? (\w+) "([^"]+)"\n\n((?: .+\n)+)', lambda m: f'> **{m.group(2)}**\n>\n' + '\n'.join(f'> {line[4:]}' for line in m.group(3).split('\n') if line), content ) return content def create_combined_markdown(files: List[Path], docs_dir: Path, output_path: Path) -> None: """Combine tous les fichiers MD en un seul""" combined = [] # Page de titre combined.append("# Documentation RoadWave\n\n") combined.append("---\n\n") combined.append("## Table des matières\n\n") # Générer la table des matières toc_entries = [] for f in files: with open(f, 'r', encoding='utf-8') as file: content = file.read() # Extraire le titre H1 title_match = re.search(r'^# (.+)$', content, re.MULTILINE) if title_match: title = title_match.group(1) anchor = re.sub(r'[^\w\s-]', '', title.lower()).replace(' ', '-') toc_entries.append(f"- [{title}](#{anchor})") combined.append('\n'.join(toc_entries)) combined.append("\n\n---\n\n") combined.append('
\n\n') # Ajouter chaque fichier for f in files: with open(f, 'r', encoding='utf-8') as file: content = file.read() # Prétraiter content = preprocess_markdown(content, f, docs_dir) combined.append(content) combined.append("\n\n") combined.append('
\n\n') with open(output_path, 'w', encoding='utf-8') as f: f.write('\n'.join(combined)) def markdown_to_html(md_path: Path, html_path: Path) -> None: """Convertit Markdown en HTML avec styles""" import markdown from markdown.extensions.tables import TableExtension from markdown.extensions.fenced_code import FencedCodeExtension from markdown.extensions.toc import TocExtension with open(md_path, 'r', encoding='utf-8') as f: md_content = f.read() # Convertir en HTML md = markdown.Markdown(extensions=[ 'tables', 'fenced_code', 'toc', 'attr_list', 'md_in_html' ]) html_content = md.convert(md_content) # Template HTML avec styles html_template = f''' Documentation RoadWave {html_content} ''' with open(html_path, 'w', encoding='utf-8') as f: f.write(html_template) def html_to_pdf(html_path: Path, pdf_path: Path) -> None: """Convertit HTML en PDF avec weasyprint""" from weasyprint import HTML, CSS print(f" Conversion HTML → PDF...") HTML(filename=str(html_path)).write_pdf(str(pdf_path)) def main(): """Point d'entrée principal""" project_root = Path(__file__).parent.parent docs_dir = project_root / 'docs' mkdocs_path = project_root / 'mkdocs.yml' output_dir = project_root / 'output' output_dir.mkdir(exist_ok=True) print("📄 Génération du PDF de la documentation RoadWave...") # Charger la config MkDocs with open(mkdocs_path, 'r', encoding='utf-8') as f: mkdocs_config = yaml.load(f, Loader=SafeLineLoader) # Récupérer tous les fichiers MD print(" Collecte des fichiers Markdown...") md_files = get_all_md_files(docs_dir, mkdocs_config) print(f" → {len(md_files)} fichiers trouvés") # Créer un fichier MD combiné combined_md = output_dir / 'documentation_complete.md' print(" Combinaison des fichiers...") create_combined_markdown(md_files, docs_dir, combined_md) # Convertir en HTML html_path = output_dir / 'documentation_complete.html' print(" Conversion Markdown → HTML...") markdown_to_html(combined_md, html_path) # Convertir en PDF pdf_path = output_dir / 'RoadWave_Documentation.pdf' html_to_pdf(html_path, pdf_path) print(f"\n✅ PDF généré: {pdf_path}") print(f" Taille: {pdf_path.stat().st_size / 1024 / 1024:.2f} MB") if __name__ == '__main__': main()