From d611f5d3ca53aa877cac8999b757ae837b22cfe4 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 16:48:55 +0200 Subject: [PATCH 01/32] refactor: archiver l'ancien cours de base dans archives/cours_2025/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les modules 01-08 de supports/ et les TP 01-08 de travaux_pratiques/ sont déplacés (git mv, historique conservé) dans archives/cours_2025/. Les modules additionnels Git et Docker restent en place. README, CLAUDE.md et CONTRIBUTING sont alignés. Les scripts PDF du cours de base ne sont pas mis à jour : l'ancien cours n'est plus généré, la nouvelle chaîne PDF fera l'objet d'une étape dédiée. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 16 +++++++--------- CONTRIBUTING.md | 12 +++++++----- README.md | 17 ++++++++++------- .../module_01_decouverte/01_histoire_linux.md | 0 .../module_01_decouverte/02_distributions.md | 0 .../module_01_decouverte/03_installation.md | 0 .../module_01_decouverte/04_premier_terminal.md | 0 .../module_02_navigation/01_arborescence.md | 0 .../module_02_navigation/02_commandes_base.md | 0 .../module_02_navigation/03_chemins.md | 0 .../module_02_navigation/04_types_fichiers.md | 0 .../module_03_manipulation/01_creation_copie.md | 0 .../module_03_manipulation/02_suppression.md | 0 .../module_03_manipulation/03_recherche.md | 0 .../module_03_manipulation/04_archivage.md | 0 .../01_lecture_fichiers.md | 0 .../module_04_consultation/02_editeurs_texte.md | 0 .../03_recherche_contenu.md | 0 .../module_04_consultation/04_comparaison.md | 0 .../module_05_droits/01_utilisateurs_groupes.md | 0 .../module_05_droits/02_permissions_fichiers.md | 0 .../module_05_droits/03_permissions_avancees.md | 0 .../module_05_droits/04_sudo_securite.md | 0 .../05_processus_proprietaires.md | 0 .../module_06_processus/01_gestion_processus.md | 0 .../module_06_processus/02_arriere_plan.md | 0 .../03_surveillance_systeme.md | 0 .../04_historique_variables.md | 0 .../01_configuration_reseau.md | 0 .../module_07_reseaux/02_transferts_fichiers.md | 0 .../module_07_reseaux/03_services_systeme.md | 0 .../module_07_reseaux/04_logs_systeme.md | 0 .../01_redirections_pipes.md | 0 .../module_08_automatisation/02_scripts_bash.md | 0 .../03_taches_programmees.md | 0 .../04_alias_personnalisation.md | 0 .../tp01_decouverte/exercice_principal.md | 0 .../tp01_premiere_connexion.md | 0 .../tp02_configuration_environnement.md | 0 .../tp01_exploration_arborescence.md | 0 .../tp02_navigation/tp02_types_liens.md | 0 .../tp03_manipulation/tp01_gestion_fichiers.md | 0 .../tp02_archivage_compression.md | 0 .../tp04_consultation/tp01_lecture_edition.md | 0 .../tp01_utilisateurs_permissions.md | 0 .../tp06_processus/tp01_gestion_surveillance.md | 0 .../tp07_reseaux/tp01_reseau_services_logs.md | 0 .../tp01_automatisation_complete.md | 0 48 files changed, 24 insertions(+), 21 deletions(-) rename {supports => archives/cours_2025/supports}/module_01_decouverte/01_histoire_linux.md (100%) rename {supports => archives/cours_2025/supports}/module_01_decouverte/02_distributions.md (100%) rename {supports => archives/cours_2025/supports}/module_01_decouverte/03_installation.md (100%) rename {supports => archives/cours_2025/supports}/module_01_decouverte/04_premier_terminal.md (100%) rename {supports => archives/cours_2025/supports}/module_02_navigation/01_arborescence.md (100%) rename {supports => archives/cours_2025/supports}/module_02_navigation/02_commandes_base.md (100%) rename {supports => archives/cours_2025/supports}/module_02_navigation/03_chemins.md (100%) rename {supports => archives/cours_2025/supports}/module_02_navigation/04_types_fichiers.md (100%) rename {supports => archives/cours_2025/supports}/module_03_manipulation/01_creation_copie.md (100%) rename {supports => archives/cours_2025/supports}/module_03_manipulation/02_suppression.md (100%) rename {supports => archives/cours_2025/supports}/module_03_manipulation/03_recherche.md (100%) rename {supports => archives/cours_2025/supports}/module_03_manipulation/04_archivage.md (100%) rename {supports => archives/cours_2025/supports}/module_04_consultation/01_lecture_fichiers.md (100%) rename {supports => archives/cours_2025/supports}/module_04_consultation/02_editeurs_texte.md (100%) rename {supports => archives/cours_2025/supports}/module_04_consultation/03_recherche_contenu.md (100%) rename {supports => archives/cours_2025/supports}/module_04_consultation/04_comparaison.md (100%) rename {supports => archives/cours_2025/supports}/module_05_droits/01_utilisateurs_groupes.md (100%) rename {supports => archives/cours_2025/supports}/module_05_droits/02_permissions_fichiers.md (100%) rename {supports => archives/cours_2025/supports}/module_05_droits/03_permissions_avancees.md (100%) rename {supports => archives/cours_2025/supports}/module_05_droits/04_sudo_securite.md (100%) rename {supports => archives/cours_2025/supports}/module_05_droits/05_processus_proprietaires.md (100%) rename {supports => archives/cours_2025/supports}/module_06_processus/01_gestion_processus.md (100%) rename {supports => archives/cours_2025/supports}/module_06_processus/02_arriere_plan.md (100%) rename {supports => archives/cours_2025/supports}/module_06_processus/03_surveillance_systeme.md (100%) rename {supports => archives/cours_2025/supports}/module_06_processus/04_historique_variables.md (100%) rename {supports => archives/cours_2025/supports}/module_07_reseaux/01_configuration_reseau.md (100%) rename {supports => archives/cours_2025/supports}/module_07_reseaux/02_transferts_fichiers.md (100%) rename {supports => archives/cours_2025/supports}/module_07_reseaux/03_services_systeme.md (100%) rename {supports => archives/cours_2025/supports}/module_07_reseaux/04_logs_systeme.md (100%) rename {supports => archives/cours_2025/supports}/module_08_automatisation/01_redirections_pipes.md (100%) rename {supports => archives/cours_2025/supports}/module_08_automatisation/02_scripts_bash.md (100%) rename {supports => archives/cours_2025/supports}/module_08_automatisation/03_taches_programmees.md (100%) rename {supports => archives/cours_2025/supports}/module_08_automatisation/04_alias_personnalisation.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp01_decouverte/exercice_principal.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp01_installation/tp01_premiere_connexion.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp01_installation/tp02_configuration_environnement.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp02_navigation/tp01_exploration_arborescence.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp02_navigation/tp02_types_liens.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp03_manipulation/tp01_gestion_fichiers.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp03_manipulation/tp02_archivage_compression.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp04_consultation/tp01_lecture_edition.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp05_droits/tp01_utilisateurs_permissions.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp06_processus/tp01_gestion_surveillance.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp07_reseaux/tp01_reseau_services_logs.md (100%) rename {travaux_pratiques => archives/cours_2025/travaux_pratiques}/tp08_automatisation/tp01_automatisation_complete.md (100%) diff --git a/CLAUDE.md b/CLAUDE.md index 9b89afd..f679fee 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -137,10 +137,7 @@ formation_linux/ 03_cron_alias_personnalisation.md annexes/ installation.md (guide d'installation pour les deux environnements) - supports/ (ANCIEN materiau, ne plus modifier, conserve en reference) - module_01_decouverte/ - module_02_navigation/ - [autres modules 03-08...] + supports/ modules_additionnels/ (toujours actif : modules Git et Docker) module_git/ 01_introduction_git.md @@ -152,10 +149,7 @@ formation_linux/ 02_images_conteneurs.md 03_volumes_reseaux.md 04_compose_orchestration.md - travaux_pratiques/ (ANCIEN materiau, ne plus modifier, conserve en reference) - tp01_installation/ - tp02_navigation/ - [autres TP 03-08...] + travaux_pratiques/ tp_additionnels/ (toujours actif : TP Git et Docker) tp_git/ tp01_premiers_pas.md @@ -169,6 +163,10 @@ formation_linux/ tp3_volumes_donnees.md tp4_reseaux_communication.md tp5_compose_orchestration.md + archives/ + cours_2025/ (ANCIEN cours de base, ne plus modifier, conserve en reference) + supports/ (modules 01-08) + travaux_pratiques/ (TP 01-08) evaluations/ quiz/ exercices/ @@ -192,7 +190,7 @@ formation_linux/ specs/ (specifications et plans de refonte) ``` -**Remarque** : `supports/` et `travaux_pratiques/` (hors `tp_additionnels/`) sont l'ancien matériau issu de la version précédente du cours. Ne plus les modifier. Tout nouveau contenu de cours va dans `cours_2026/`. +**Remarque** : `archives/cours_2025/` contient l'ancien matériau du cours de base issu de la version précédente. Ne plus le modifier et ne plus le générer en PDF. Tout nouveau contenu de cours va dans `cours_2026/`. `supports/` et `travaux_pratiques/` ne contiennent plus que les modules additionnels Git et Docker, toujours actifs. ## Template de chapitre diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6de8263..cc80130 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ 1. **Fork** le repository 2. **Cloner** votre fork localement -3. **Modifier** les fichiers Markdown dans `supports/` ou `travaux_pratiques/` +3. **Modifier** les fichiers Markdown dans `cours_2026/` (cours de base) ou dans les modules additionnels (`supports/modules_additionnels/`, `travaux_pratiques/tp_additionnels/`) 4. **Tester** localement (optionnel) : ```bash ./scripts/build_git_module.sh # Test module Git @@ -33,9 +33,11 @@ Dès que vous créez une PR, GitHub Actions va : - Mise à jour des références **Fichiers concernés :** -- `supports/module_*/` (modules de base 01-08) +- `cours_2026/module_*/` (modules de base 01-08, exercices inclus dans chaque chapitre) - `supports/modules_additionnels/module_*/` (modules additionnels) -- `travaux_pratiques/tp*/` (TP de base et additionnels) +- `travaux_pratiques/tp_additionnels/` (TP des modules additionnels) + +`archives/cours_2025/` contient l'ancien cours de base : il est conservé en référence et ne doit plus être modifié. ### 🔧 Scripts et outils - Amélioration des scripts de génération @@ -66,7 +68,7 @@ Ces caractères sont correctement supportés par la configuration LaTeX. ### Test avant contribution ```bash # Vérifier les caractères problématiques -grep -r "🔥\|⚠️\|✅\|→" supports/ travaux_pratiques/ +grep -r "🔥\|⚠️\|✅\|→" cours_2026/ supports/ travaux_pratiques/ # Nettoyer si nécessaire ./scripts/clean_unicode.sh fichier-problematique.md @@ -121,7 +123,7 @@ grep -r "🔥\|⚠️\|✅\|→" supports/ travaux_pratiques/ ➡️ Vérifiez les logs dans Actions. C'est souvent un caractère Unicode problématique. ### "Comment ajouter un nouveau module ?" -➡️ Suivre la structure existante dans `supports/module_*/` et créer les TP correspondants dans `travaux_pratiques/`. +➡️ Pour le cours de base, suivre le template de chapitre de `cours_2026/` (voir CLAUDE.md). Pour un module additionnel, suivre la structure de `supports/modules_additionnels/` et créer les TP dans `travaux_pratiques/tp_additionnels/`. ### "Puis-je modifier les workflows ?" ➡️ Oui ! Mais testez d'abord dans un fork pour éviter de casser la génération pour tout le monde. diff --git a/README.md b/README.md index c8cf117..1718633 100644 --- a/README.md +++ b/README.md @@ -117,18 +117,21 @@ formation_linux/ | +-- annexes/ | +-- installation.md | -+-- supports/ # Ancien matériau (référence) -| +-- module_0X_*/ -| +-- modules_additionnels/ ++-- supports/ +| +-- modules_additionnels/ # Modules additionnels (contenu) | +-- module_git/ | +-- module_docker/ | -+-- travaux_pratiques/ # Ancien matériau (référence) -| +-- tp0X_*/ -| +-- tp_additionnels/ ++-- travaux_pratiques/ +| +-- tp_additionnels/ # Modules additionnels (TP) | +-- tp_git/ | +-- tp_docker/ | ++-- archives/ +| +-- cours_2025/ # Ancien cours de base (référence, figé) +| +-- supports/ # Modules 01-08 +| +-- travaux_pratiques/ # TP 01-08 +| +-- evaluations/ # Quiz et exercices +-- ressources/ # Images, schémas, références +-- scripts/ # Scripts de génération PDF @@ -139,7 +142,7 @@ formation_linux/ La chaîne de génération est en cours de refonte et ne couvre pas encore `cours_2026/`. -Les PDFs actuellement générés par GitHub Actions couvrent l'ancien contenu (`supports/`). +Seuls les PDFs des modules additionnels Git et Docker sont générés pour l'instant ; l'ancien cours archivé n'est plus généré. Pour suivre l'avancement ou déclencher manuellement une génération, consulter l'onglet Actions du dépôt. diff --git a/supports/module_01_decouverte/01_histoire_linux.md b/archives/cours_2025/supports/module_01_decouverte/01_histoire_linux.md similarity index 100% rename from supports/module_01_decouverte/01_histoire_linux.md rename to archives/cours_2025/supports/module_01_decouverte/01_histoire_linux.md diff --git a/supports/module_01_decouverte/02_distributions.md b/archives/cours_2025/supports/module_01_decouverte/02_distributions.md similarity index 100% rename from supports/module_01_decouverte/02_distributions.md rename to archives/cours_2025/supports/module_01_decouverte/02_distributions.md diff --git a/supports/module_01_decouverte/03_installation.md b/archives/cours_2025/supports/module_01_decouverte/03_installation.md similarity index 100% rename from supports/module_01_decouverte/03_installation.md rename to archives/cours_2025/supports/module_01_decouverte/03_installation.md diff --git a/supports/module_01_decouverte/04_premier_terminal.md b/archives/cours_2025/supports/module_01_decouverte/04_premier_terminal.md similarity index 100% rename from supports/module_01_decouverte/04_premier_terminal.md rename to archives/cours_2025/supports/module_01_decouverte/04_premier_terminal.md diff --git a/supports/module_02_navigation/01_arborescence.md b/archives/cours_2025/supports/module_02_navigation/01_arborescence.md similarity index 100% rename from supports/module_02_navigation/01_arborescence.md rename to archives/cours_2025/supports/module_02_navigation/01_arborescence.md diff --git a/supports/module_02_navigation/02_commandes_base.md b/archives/cours_2025/supports/module_02_navigation/02_commandes_base.md similarity index 100% rename from supports/module_02_navigation/02_commandes_base.md rename to archives/cours_2025/supports/module_02_navigation/02_commandes_base.md diff --git a/supports/module_02_navigation/03_chemins.md b/archives/cours_2025/supports/module_02_navigation/03_chemins.md similarity index 100% rename from supports/module_02_navigation/03_chemins.md rename to archives/cours_2025/supports/module_02_navigation/03_chemins.md diff --git a/supports/module_02_navigation/04_types_fichiers.md b/archives/cours_2025/supports/module_02_navigation/04_types_fichiers.md similarity index 100% rename from supports/module_02_navigation/04_types_fichiers.md rename to archives/cours_2025/supports/module_02_navigation/04_types_fichiers.md diff --git a/supports/module_03_manipulation/01_creation_copie.md b/archives/cours_2025/supports/module_03_manipulation/01_creation_copie.md similarity index 100% rename from supports/module_03_manipulation/01_creation_copie.md rename to archives/cours_2025/supports/module_03_manipulation/01_creation_copie.md diff --git a/supports/module_03_manipulation/02_suppression.md b/archives/cours_2025/supports/module_03_manipulation/02_suppression.md similarity index 100% rename from supports/module_03_manipulation/02_suppression.md rename to archives/cours_2025/supports/module_03_manipulation/02_suppression.md diff --git a/supports/module_03_manipulation/03_recherche.md b/archives/cours_2025/supports/module_03_manipulation/03_recherche.md similarity index 100% rename from supports/module_03_manipulation/03_recherche.md rename to archives/cours_2025/supports/module_03_manipulation/03_recherche.md diff --git a/supports/module_03_manipulation/04_archivage.md b/archives/cours_2025/supports/module_03_manipulation/04_archivage.md similarity index 100% rename from supports/module_03_manipulation/04_archivage.md rename to archives/cours_2025/supports/module_03_manipulation/04_archivage.md diff --git a/supports/module_04_consultation/01_lecture_fichiers.md b/archives/cours_2025/supports/module_04_consultation/01_lecture_fichiers.md similarity index 100% rename from supports/module_04_consultation/01_lecture_fichiers.md rename to archives/cours_2025/supports/module_04_consultation/01_lecture_fichiers.md diff --git a/supports/module_04_consultation/02_editeurs_texte.md b/archives/cours_2025/supports/module_04_consultation/02_editeurs_texte.md similarity index 100% rename from supports/module_04_consultation/02_editeurs_texte.md rename to archives/cours_2025/supports/module_04_consultation/02_editeurs_texte.md diff --git a/supports/module_04_consultation/03_recherche_contenu.md b/archives/cours_2025/supports/module_04_consultation/03_recherche_contenu.md similarity index 100% rename from supports/module_04_consultation/03_recherche_contenu.md rename to archives/cours_2025/supports/module_04_consultation/03_recherche_contenu.md diff --git a/supports/module_04_consultation/04_comparaison.md b/archives/cours_2025/supports/module_04_consultation/04_comparaison.md similarity index 100% rename from supports/module_04_consultation/04_comparaison.md rename to archives/cours_2025/supports/module_04_consultation/04_comparaison.md diff --git a/supports/module_05_droits/01_utilisateurs_groupes.md b/archives/cours_2025/supports/module_05_droits/01_utilisateurs_groupes.md similarity index 100% rename from supports/module_05_droits/01_utilisateurs_groupes.md rename to archives/cours_2025/supports/module_05_droits/01_utilisateurs_groupes.md diff --git a/supports/module_05_droits/02_permissions_fichiers.md b/archives/cours_2025/supports/module_05_droits/02_permissions_fichiers.md similarity index 100% rename from supports/module_05_droits/02_permissions_fichiers.md rename to archives/cours_2025/supports/module_05_droits/02_permissions_fichiers.md diff --git a/supports/module_05_droits/03_permissions_avancees.md b/archives/cours_2025/supports/module_05_droits/03_permissions_avancees.md similarity index 100% rename from supports/module_05_droits/03_permissions_avancees.md rename to archives/cours_2025/supports/module_05_droits/03_permissions_avancees.md diff --git a/supports/module_05_droits/04_sudo_securite.md b/archives/cours_2025/supports/module_05_droits/04_sudo_securite.md similarity index 100% rename from supports/module_05_droits/04_sudo_securite.md rename to archives/cours_2025/supports/module_05_droits/04_sudo_securite.md diff --git a/supports/module_05_droits/05_processus_proprietaires.md b/archives/cours_2025/supports/module_05_droits/05_processus_proprietaires.md similarity index 100% rename from supports/module_05_droits/05_processus_proprietaires.md rename to archives/cours_2025/supports/module_05_droits/05_processus_proprietaires.md diff --git a/supports/module_06_processus/01_gestion_processus.md b/archives/cours_2025/supports/module_06_processus/01_gestion_processus.md similarity index 100% rename from supports/module_06_processus/01_gestion_processus.md rename to archives/cours_2025/supports/module_06_processus/01_gestion_processus.md diff --git a/supports/module_06_processus/02_arriere_plan.md b/archives/cours_2025/supports/module_06_processus/02_arriere_plan.md similarity index 100% rename from supports/module_06_processus/02_arriere_plan.md rename to archives/cours_2025/supports/module_06_processus/02_arriere_plan.md diff --git a/supports/module_06_processus/03_surveillance_systeme.md b/archives/cours_2025/supports/module_06_processus/03_surveillance_systeme.md similarity index 100% rename from supports/module_06_processus/03_surveillance_systeme.md rename to archives/cours_2025/supports/module_06_processus/03_surveillance_systeme.md diff --git a/supports/module_06_processus/04_historique_variables.md b/archives/cours_2025/supports/module_06_processus/04_historique_variables.md similarity index 100% rename from supports/module_06_processus/04_historique_variables.md rename to archives/cours_2025/supports/module_06_processus/04_historique_variables.md diff --git a/supports/module_07_reseaux/01_configuration_reseau.md b/archives/cours_2025/supports/module_07_reseaux/01_configuration_reseau.md similarity index 100% rename from supports/module_07_reseaux/01_configuration_reseau.md rename to archives/cours_2025/supports/module_07_reseaux/01_configuration_reseau.md diff --git a/supports/module_07_reseaux/02_transferts_fichiers.md b/archives/cours_2025/supports/module_07_reseaux/02_transferts_fichiers.md similarity index 100% rename from supports/module_07_reseaux/02_transferts_fichiers.md rename to archives/cours_2025/supports/module_07_reseaux/02_transferts_fichiers.md diff --git a/supports/module_07_reseaux/03_services_systeme.md b/archives/cours_2025/supports/module_07_reseaux/03_services_systeme.md similarity index 100% rename from supports/module_07_reseaux/03_services_systeme.md rename to archives/cours_2025/supports/module_07_reseaux/03_services_systeme.md diff --git a/supports/module_07_reseaux/04_logs_systeme.md b/archives/cours_2025/supports/module_07_reseaux/04_logs_systeme.md similarity index 100% rename from supports/module_07_reseaux/04_logs_systeme.md rename to archives/cours_2025/supports/module_07_reseaux/04_logs_systeme.md diff --git a/supports/module_08_automatisation/01_redirections_pipes.md b/archives/cours_2025/supports/module_08_automatisation/01_redirections_pipes.md similarity index 100% rename from supports/module_08_automatisation/01_redirections_pipes.md rename to archives/cours_2025/supports/module_08_automatisation/01_redirections_pipes.md diff --git a/supports/module_08_automatisation/02_scripts_bash.md b/archives/cours_2025/supports/module_08_automatisation/02_scripts_bash.md similarity index 100% rename from supports/module_08_automatisation/02_scripts_bash.md rename to archives/cours_2025/supports/module_08_automatisation/02_scripts_bash.md diff --git a/supports/module_08_automatisation/03_taches_programmees.md b/archives/cours_2025/supports/module_08_automatisation/03_taches_programmees.md similarity index 100% rename from supports/module_08_automatisation/03_taches_programmees.md rename to archives/cours_2025/supports/module_08_automatisation/03_taches_programmees.md diff --git a/supports/module_08_automatisation/04_alias_personnalisation.md b/archives/cours_2025/supports/module_08_automatisation/04_alias_personnalisation.md similarity index 100% rename from supports/module_08_automatisation/04_alias_personnalisation.md rename to archives/cours_2025/supports/module_08_automatisation/04_alias_personnalisation.md diff --git a/travaux_pratiques/tp01_decouverte/exercice_principal.md b/archives/cours_2025/travaux_pratiques/tp01_decouverte/exercice_principal.md similarity index 100% rename from travaux_pratiques/tp01_decouverte/exercice_principal.md rename to archives/cours_2025/travaux_pratiques/tp01_decouverte/exercice_principal.md diff --git a/travaux_pratiques/tp01_installation/tp01_premiere_connexion.md b/archives/cours_2025/travaux_pratiques/tp01_installation/tp01_premiere_connexion.md similarity index 100% rename from travaux_pratiques/tp01_installation/tp01_premiere_connexion.md rename to archives/cours_2025/travaux_pratiques/tp01_installation/tp01_premiere_connexion.md diff --git a/travaux_pratiques/tp01_installation/tp02_configuration_environnement.md b/archives/cours_2025/travaux_pratiques/tp01_installation/tp02_configuration_environnement.md similarity index 100% rename from travaux_pratiques/tp01_installation/tp02_configuration_environnement.md rename to archives/cours_2025/travaux_pratiques/tp01_installation/tp02_configuration_environnement.md diff --git a/travaux_pratiques/tp02_navigation/tp01_exploration_arborescence.md b/archives/cours_2025/travaux_pratiques/tp02_navigation/tp01_exploration_arborescence.md similarity index 100% rename from travaux_pratiques/tp02_navigation/tp01_exploration_arborescence.md rename to archives/cours_2025/travaux_pratiques/tp02_navigation/tp01_exploration_arborescence.md diff --git a/travaux_pratiques/tp02_navigation/tp02_types_liens.md b/archives/cours_2025/travaux_pratiques/tp02_navigation/tp02_types_liens.md similarity index 100% rename from travaux_pratiques/tp02_navigation/tp02_types_liens.md rename to archives/cours_2025/travaux_pratiques/tp02_navigation/tp02_types_liens.md diff --git a/travaux_pratiques/tp03_manipulation/tp01_gestion_fichiers.md b/archives/cours_2025/travaux_pratiques/tp03_manipulation/tp01_gestion_fichiers.md similarity index 100% rename from travaux_pratiques/tp03_manipulation/tp01_gestion_fichiers.md rename to archives/cours_2025/travaux_pratiques/tp03_manipulation/tp01_gestion_fichiers.md diff --git a/travaux_pratiques/tp03_manipulation/tp02_archivage_compression.md b/archives/cours_2025/travaux_pratiques/tp03_manipulation/tp02_archivage_compression.md similarity index 100% rename from travaux_pratiques/tp03_manipulation/tp02_archivage_compression.md rename to archives/cours_2025/travaux_pratiques/tp03_manipulation/tp02_archivage_compression.md diff --git a/travaux_pratiques/tp04_consultation/tp01_lecture_edition.md b/archives/cours_2025/travaux_pratiques/tp04_consultation/tp01_lecture_edition.md similarity index 100% rename from travaux_pratiques/tp04_consultation/tp01_lecture_edition.md rename to archives/cours_2025/travaux_pratiques/tp04_consultation/tp01_lecture_edition.md diff --git a/travaux_pratiques/tp05_droits/tp01_utilisateurs_permissions.md b/archives/cours_2025/travaux_pratiques/tp05_droits/tp01_utilisateurs_permissions.md similarity index 100% rename from travaux_pratiques/tp05_droits/tp01_utilisateurs_permissions.md rename to archives/cours_2025/travaux_pratiques/tp05_droits/tp01_utilisateurs_permissions.md diff --git a/travaux_pratiques/tp06_processus/tp01_gestion_surveillance.md b/archives/cours_2025/travaux_pratiques/tp06_processus/tp01_gestion_surveillance.md similarity index 100% rename from travaux_pratiques/tp06_processus/tp01_gestion_surveillance.md rename to archives/cours_2025/travaux_pratiques/tp06_processus/tp01_gestion_surveillance.md diff --git a/travaux_pratiques/tp07_reseaux/tp01_reseau_services_logs.md b/archives/cours_2025/travaux_pratiques/tp07_reseaux/tp01_reseau_services_logs.md similarity index 100% rename from travaux_pratiques/tp07_reseaux/tp01_reseau_services_logs.md rename to archives/cours_2025/travaux_pratiques/tp07_reseaux/tp01_reseau_services_logs.md diff --git a/travaux_pratiques/tp08_automatisation/tp01_automatisation_complete.md b/archives/cours_2025/travaux_pratiques/tp08_automatisation/tp01_automatisation_complete.md similarity index 100% rename from travaux_pratiques/tp08_automatisation/tp01_automatisation_complete.md rename to archives/cours_2025/travaux_pratiques/tp08_automatisation/tp01_automatisation_complete.md From 997c4e1fc97c7ecc53ff80a20f71ddb0d602bf42 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 16:50:31 +0200 Subject: [PATCH 02/32] =?UTF-8?q?refactor:=20archiver=20les=20=C3=A9valuat?= =?UTF-8?q?ions=20de=20l'ancien=20cours?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit evaluations/ (quiz et évaluations finales) n'est pas aligné sur les 22 chapitres de cours_2026/ : déplacé dans archives/cours_2025/. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 4 +--- README.md | 2 +- .../cours_2025/evaluations}/exercices/evaluation_finale.md | 0 .../cours_2025/evaluations}/exercices/evaluation_pratique.md | 0 .../cours_2025/evaluations}/quiz/quiz_general.md | 0 5 files changed, 2 insertions(+), 4 deletions(-) rename {evaluations => archives/cours_2025/evaluations}/exercices/evaluation_finale.md (100%) rename {evaluations => archives/cours_2025/evaluations}/exercices/evaluation_pratique.md (100%) rename {evaluations => archives/cours_2025/evaluations}/quiz/quiz_general.md (100%) diff --git a/CLAUDE.md b/CLAUDE.md index f679fee..64c42d9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -167,9 +167,7 @@ formation_linux/ cours_2025/ (ANCIEN cours de base, ne plus modifier, conserve en reference) supports/ (modules 01-08) travaux_pratiques/ (TP 01-08) - evaluations/ - quiz/ - exercices/ + evaluations/ (quiz et evaluations finales de l'ancien cours) ressources/ images/ scripts/ diff --git a/README.md b/README.md index 1718633..0f3f37c 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,8 @@ formation_linux/ | +-- cours_2025/ # Ancien cours de base (référence, figé) | +-- supports/ # Modules 01-08 | +-- travaux_pratiques/ # TP 01-08 +| +-- evaluations/ # Quiz et évaluations de l'ancien cours | -+-- evaluations/ # Quiz et exercices +-- ressources/ # Images, schémas, références +-- scripts/ # Scripts de génération PDF ``` diff --git a/evaluations/exercices/evaluation_finale.md b/archives/cours_2025/evaluations/exercices/evaluation_finale.md similarity index 100% rename from evaluations/exercices/evaluation_finale.md rename to archives/cours_2025/evaluations/exercices/evaluation_finale.md diff --git a/evaluations/exercices/evaluation_pratique.md b/archives/cours_2025/evaluations/exercices/evaluation_pratique.md similarity index 100% rename from evaluations/exercices/evaluation_pratique.md rename to archives/cours_2025/evaluations/exercices/evaluation_pratique.md diff --git a/evaluations/quiz/quiz_general.md b/archives/cours_2025/evaluations/quiz/quiz_general.md similarity index 100% rename from evaluations/quiz/quiz_general.md rename to archives/cours_2025/evaluations/quiz/quiz_general.md From 725e85edeab6bbcd268238ad9387ba840245d383 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:04:03 +0200 Subject: [PATCH 03/32] =?UTF-8?q?docs:=20spec=20de=20la=20cha=C3=AEne=20de?= =?UTF-8?q?=20g=C3=A9n=C3=A9ration=20PDF=202026?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../2026-10-01-chaine-pdf-2026-design.md | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md diff --git a/docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md b/docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md new file mode 100644 index 0000000..1f0a288 --- /dev/null +++ b/docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md @@ -0,0 +1,155 @@ +# Chaîne de génération PDF 2026 -- Design + +Date : 2026-10-01 +Statut : validé + +## Contexte + +L'ancienne chaîne PDF (`scripts/`, 3 workflows GitHub Actions, plus de +3 300 lignes) n'a probablement jamais produit un build complet sans erreur. +Causes identifiées : + +- **pdfLaTeX** gère mal l'UTF-8 : accents remplacés par des « x », d'où + `clean_unicode.sh` qui réécrit les sources à coups de `sed`. +- **Environnements divergents** : la CI installe ses paquets via `apt`, le + poste local n'a pas les mêmes outils (pandoc absent). +- **Erreurs avalées** : `|| echo`, `> /dev/null 2>&1`, « versions + simplifiées » de repli. Un build cassé pouvait passer pour un succès. + +Depuis l'archivage de l'ancien cours dans `archives/cours_2025/`, la chaîne +ne couvre plus rien d'utile pour le cours de base. + +## Objectif + +Une chaîne qui **fonctionne**, identique en local et sur GitHub, qui échoue +bruyamment au moindre problème et dont le résultat est vérifié par des tests. + +## Décisions + +1. **Périmètre : 11 PDF.** 8 modules de base, 2 modules additionnels (Git, + Docker), 1 annexe d'installation. Pas de PDF du cours complet. +2. **Mise en page sobre d'abord.** La spécification de mise en page de + CLAUDE.md (cadre de couleur, numérotation romaine, recto-verso) n'est pas + visée par cette version ; elle pourra être ajoutée ensuite, test à + l'appui. +3. **Publication : release sur tag.** Chaque push et PR construit et teste ; + une release GitHub n'est créée que sur un tag `v*`. +4. **Technique : pandoc + LuaLaTeX dans une image Docker figée** (approche A, + préférée à pandoc + Typst et à pandoc + WeasyPrint). +5. **L'ancienne chaîne est supprimée** (`scripts/` en entier, les 3 + workflows). Elle reste dans l'historique Git. + +## Architecture + +``` +pdf/ + Dockerfile FROM pandoc/extra: figée par digest + + poppler-utils, pytest, pypdf, ruff, shellcheck (épinglés) + documents.yaml déclaration des 11 PDF + build.py build Python, arrêt à la première erreur + template.tex template LaTeX unique (LuaLaTeX) + tests/ tests pytest (unitaires + vérification des PDF) + build point d'entrée : docker build + docker run +``` + +- **Point d'entrée unique** : `./pdf/build` construit l'image puis lance, dans + le conteneur, `build.py` et les tests. L'utilisateur du conteneur reprend + l'uid/gid local pour que les fichiers produits ne soient pas à root. + Options : `--no-test` (build seul), `--check` (lint seul). +- **Sortie** : `build/pdf/*.pdf` (déjà ignoré par Git), diagnostic dans + `build/pdf/debug/`. +- **`documents.yaml`** déclare chaque PDF explicitement : nom de fichier, + titre, sous-titre éventuel, liste ordonnée des sources. Aucune déduction à + partir des noms de dossiers. Ajouter un PDF = ajouter une entrée. +- **Les sources ne sont jamais modifiées par le build.** `clean_unicode.sh` + disparaît. Un caractère absent des polices fait échouer le build. + +### Les 11 documents + +| PDF | Sources | +|-----|---------| +| `module_01_decouverte.pdf` ... `module_08_automatisation.pdf` | chapitres de `cours_2026/module_0X_*/`, dans l'ordre | +| `module_additionnel_git.pdf` | `supports/modules_additionnels/module_git/` (4 chapitres) puis `travaux_pratiques/tp_additionnels/tp_git/` | +| `module_additionnel_docker.pdf` | `supports/modules_additionnels/module_docker/` (4 chapitres) puis `travaux_pratiques/tp_additionnels/tp_docker/` | +| `annexe_installation.pdf` | `cours_2026/annexes/installation.md` | + +## Rendu et mise en page + +- **Couverture** : « Formation Linux », titre du document, liste des + chapitres (issue de `documents.yaml`), cartouche licence CC BY-NC-SA 4.0 + avec le logo `ressources/images/licenses/cc-by-nc-sa`. +- **Sommaire** : chapitres (`#`) et sections (`##`) avec numéros de page. +- **Chapitres** : `#` -> chapitre LaTeX (nouvelle page), `##` -> section. + Aucune numérotation automatique : les titres portent déjà + « Chapitre X.Y -- ... ». +- **Pages** : A4, `report`, numéro en bas au centre, titre du chapitre en + en-tête. +- **Typographie** : babel français, Latin Modern pour le texte, DejaVu Sans + Mono pour le code. Blocs de code sur fond gris clair avec retour à la + ligne automatique. Tableaux en `longtable`. +- **Sources des modules additionnels** : les flèches, caractères de boîtes et + emojis encore présents (module Git, TP Git) sont remplacés par des + équivalents ASCII, conformément à CLAUDE.md. C'est la seule modification + de contenu prévue. + +## Gestion des erreurs + +- Chaque étape s'arrête à la première erreur (`set -euo pipefail` côté shell, + exceptions non rattrapées côté Python, code de sortie non nul). +- En cas d'échec, `build.py` affiche le PDF concerné, la commande pandoc et + les 30 dernières lignes pertinentes du log LaTeX. Le `.tex` intermédiaire + et le log sont conservés dans `build/pdf/debug/`. +- Le log LaTeX est analysé : tout `Missing character` fait échouer le build. + +## Tests et qualité + +**Tests unitaires de `build.py`** : validation de `documents.yaml` (source +manquante, nom en double, champ absent), construction de la commande pandoc. + +**Tests des PDF produits** (pypdf, `pdftotext`), pour chacun des 11 PDF : + +- le fichier existe et compte au moins 2 pages ; +- métadonnées renseignées (titre, auteur, langue `fr`) ; +- le texte extrait contient les caractères `é è à ç ù ê « »` présents dans + les sources, et chaque titre `#` des sources, accents intacts ; +- les signets du PDF reprennent les titres `#` des sources, dans l'ordre ; +- le log LaTeX ne contient ni `Missing character` ni `Overfull \hbox` + au-delà d'un seuil (fixé à l'implémentation). + +Les tests vérifient le fond, pas l'esthétique : une relecture visuelle reste +nécessaire à chaque évolution notable du template. + +**Qualité** : `ruff check` et `ruff format --check` pour le Python, +`shellcheck` pour `pdf/build`, lancés par `./pdf/build --check` et en CI. + +## CI + +Un seul workflow `.github/workflows/pdf.yml` : + +- **push et pull request** : build de l'image (cache), `./pdf/build --check`, + `./pdf/build`, artifact avec les 11 PDF ; en cas d'échec, artifact + `build/pdf/debug/`. +- **tag `v*`** : idem, plus une release GitHub avec les 11 PDF attachés. + +La CI exécute les mêmes commandes qu'en local : ce qui passe en local passe +sur GitHub. + +## Reproductibilité + +- Image pandoc figée par digest ; versions pip épinglées. +- `SOURCE_DATE_EPOCH` fixé (date du dernier commit) pour des PDF quasi + identiques d'un build à l'autre. + +## Documentation + +CLAUDE.md (sections PDF, GitHub Actions, structure, commandes de diagnostic), +README et CONTRIBUTING sont mis à jour pour décrire `./pdf/build` et la +release sur tag. Les mentions de `clean_unicode.sh` et des anciens scripts +disparaissent. + +## Hors périmètre + +- PDF du cours complet. +- Mise en page riche de CLAUDE.md (cadre de couleur, numérotation romaine, + en-têtes recto-verso). +- Contenu des volets « bureau Debian 13 » et « homelab Docker ». From 34b17a5daa99ea9e4c3bb429f163a7bf2204188a Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:13:48 +0200 Subject: [PATCH 04/32] =?UTF-8?q?docs:=20plan=20d'impl=C3=A9mentation=20de?= =?UTF-8?q?=20la=20cha=C3=AEne=20PDF=202026?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../plans/2026-10-01-chaine-pdf-2026.md | 1456 +++++++++++++++++ .../2026-10-01-chaine-pdf-2026.md.tasks.json | 91 ++ 2 files changed, 1547 insertions(+) create mode 100644 docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md create mode 100644 docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json diff --git a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md new file mode 100644 index 0000000..e6b19cc --- /dev/null +++ b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md @@ -0,0 +1,1456 @@ +# Chaîne PDF 2026 -- Plan d'implémentation + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task. + +**Goal :** remplacer l'ancienne chaîne PDF par une chaîne unique, identique en +local et en CI, qui produit les 11 PDF de la formation et les vérifie par des +tests. + +**Architecture :** une image Docker figée (`pandoc/extra` Debian + poppler, +shellcheck, outils Python épinglés) exécute `pdf/build.py`. Ce script lit +`pdf/documents.yaml`, génère pour chaque PDF une couverture LaTeX, appelle +pandoc (Markdown -> `.tex`) puis `latexmk -lualatex`. Le script shell +`pdf/build` est le point d'entrée unique ; la CI l'appelle tel quel. + +**Tech stack :** pandoc 3.11, LuaLaTeX (TeX Live 2026), latexmk, Python 3.11, +PyYAML, pytest, pypdf, ruff, shellcheck, GitHub Actions. + +**Spec :** `docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md` + +**Branche :** `chaine-pdf-2026` (déjà créée, contient la spec). + +--- + +## Faits vérifiés pendant la préparation (2026-10-01) + +Ces points ont été testés sur un prototype ; ne pas les re-découvrir. + +- Image : `pandoc/extra:3.11.0.0-debian`, digest + `sha256:fa8ccae75418449568f10f1842706aee1885ab24fba54f5aec08516260fc0166`. + Debian 12, pandoc 3.11, Python 3.11.2, pip 23.0.1 (accepte + `--break-system-packages`), latexmk présent, polices DejaVu installées, + `poppler-utils` 22.12 et `shellcheck` 0.9 disponibles via apt. PyYAML + **absent** (à installer). L'entrypoint est `pandoc`, le workdir `/data`. +- Le prototype du module 2 (pandoc -> LuaLaTeX, `report`, `lang=fr`, + DejaVu Sans Mono) donne un PDF correct : accents, signets, `/Lang fr`, + titre dans les métadonnées. +- Les encadrés « Objectifs » contiennent une liste collée au paragraphe + (`> **Objectifs** : ...` puis `> - ...`). Il faut lire les sources avec + `-f markdown+lists_without_preceding_blankline`, sinon la liste s'affiche + en ligne. +- Chaque fichier source (cours_2026, modules et TP additionnels) contient + **exactement un** titre de niveau 1 au sens de pandoc (les `#` dans les + blocs de code ne comptent pas). On en fait une règle. +- `\tracinglostchars=3` rend fatal un caractère absent de la police + (testé : `Missing character: There is no ✅ (U+2705)`, latexmk sort en + code 12). +- Pandoc applique la typographie : `'` devient `’` dans les titres et les + signets. Les comparaisons de titres passent par pandoc lui-même (writer + `plain`), jamais par une regex sur le Markdown. +- Caractères à remplacer (module Git et TP Git uniquement) : `─ │ ├ └ ╱ ╲`, + `→ ←`, `●`, `✅ ✓ √ ❌`, `🚀 🎉`. +- Versions pip épinglées : `pypdf==6.19.0`, `pytest==9.1.1`, + `ruff==0.16.9`, `PyYAML==6.0.3`. + +## Écarts assumés par rapport à la spec + +- Pas de `template.tex` complet : on garde le template LaTeX par défaut de + pandoc (maintenu et testé par pandoc) et on ajoute `pdf/preambule.tex` + (`-H`) et une couverture générée par `build.py` (`-B`). Moins de code + LaTeX à maintenir. +- `pdf/build` prend des sous-commandes (`pdf`, `test`, `check`, `shell`) + plutôt que des options `--no-test` / `--check` : plus lisible, et la CI + peut afficher chaque étape séparément. +- Pas de cache de l'image en CI : le `docker pull` (~2 Go) prend environ une + minute, un cache de 2 Go n'apporterait rien. À revoir si ça devient gênant. + +## Conventions pour toutes les tâches + +- Toutes les commandes se lancent depuis la racine du dépôt. +- Toute exécution Python, pandoc ou LaTeX passe par l'image (`./pdf/build + ...`), jamais par les outils de l'hôte. +- Messages de commit en français, préfixes `feat:`, `fix:`, `refactor:`, + `docs:`, `test:`, `ci:`, terminés par la ligne + `Co-Authored-By: Claude Opus 5.5 `. + +--- + +### Task 1 : Squelette de l'image et du point d'entrée + +**Files :** +- Create : `pdf/Dockerfile` +- Create : `pdf/requirements.txt` +- Create : `pdf/pyproject.toml` +- Create : `pdf/build` +- Modify : `.gitignore` + +**Step 1 : écrire `pdf/Dockerfile`** + +```dockerfile +# Image de génération des PDF de la formation Linux. +# Version figée par digest : ce qui passe en local passe en CI. +FROM pandoc/extra:3.11.0.0-debian@sha256:fa8ccae75418449568f10f1842706aee1885ab24fba54f5aec08516260fc0166 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends poppler-utils shellcheck \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt /tmp/requirements.txt +RUN pip install --no-cache-dir --break-system-packages -r /tmp/requirements.txt + +ENTRYPOINT [] +WORKDIR /data +``` + +**Step 2 : écrire `pdf/requirements.txt`** + +``` +PyYAML==6.0.3 +pypdf==6.19.0 +pytest==9.1.1 +ruff==0.16.9 +``` + +**Step 3 : écrire `pdf/pyproject.toml`** + +```toml +# Configuration des outils Python de la chaîne PDF (pas un paquet installable). + +[tool.pytest.ini_options] +pythonpath = ["."] +testpaths = ["tests"] +addopts = "-p no:cacheprovider" + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "B", "UP"] +``` + +**Step 4 : écrire `pdf/build`** + +```bash +#!/usr/bin/env bash +# Point d'entrée unique de la génération des PDF, en local comme en CI. +# +# ./pdf/build PDF puis tests +# ./pdf/build pdf [FICHIER.pdf ...] PDF seuls (tous, ou ceux indiqués) +# ./pdf/build test [ARGS ...] tests seuls (arguments passés à pytest) +# ./pdf/build check lint : ruff et shellcheck +# ./pdf/build shell [COMMANDE ...] shell (ou commande) dans l'image +set -euo pipefail + +racine="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +image="formation-linux-pdf" +date_source="$(git -C "$racine" log -1 --format=%ct 2>/dev/null || date +%s)" + +docker build --quiet --tag "$image" "$racine/pdf" > /dev/null + +dans_image() { + docker run --rm \ + --user "$(id -u):$(id -g)" \ + --env HOME=/tmp \ + --env PYTHONDONTWRITEBYTECODE=1 \ + --env RUFF_NO_CACHE=true \ + --env SOURCE_DATE_EPOCH="$date_source" \ + --env FORCE_SOURCE_DATE=1 \ + --volume "$racine:/data" \ + --workdir /data \ + "${options_tty[@]}" \ + "$image" "$@" +} +options_tty=() + +commande="${1:-tout}" +if [ $# -gt 0 ]; then shift; fi + +case "$commande" in + tout) + dans_image python3 pdf/build.py + dans_image python3 -m pytest pdf/tests + ;; + pdf) + dans_image python3 pdf/build.py "$@" + ;; + test) + if [ $# -eq 0 ]; then set -- pdf/tests; fi + dans_image python3 -m pytest "$@" + ;; + check) + dans_image ruff check pdf + dans_image ruff format --check pdf + dans_image shellcheck pdf/build + ;; + shell) + if [ $# -eq 0 ]; then + options_tty=(--interactive --tty) + set -- bash + fi + dans_image "$@" + ;; + *) + sed -n '2,9p' "$0" >&2 + exit 2 + ;; +esac +``` + +Puis : `chmod +x pdf/build`. + +**Step 5 : compléter `.gitignore`** (à la fin du fichier) + +``` +# Python (chaîne PDF) +__pycache__/ +.pytest_cache/ +.ruff_cache/ +``` + +**Step 6 : vérifier** + +Run : `./pdf/build shell pandoc --version | head -1` +Expected : `pandoc 3.11` + +Run : `./pdf/build shell python3 -c "import yaml, pypdf; print('ok')"` +Expected : `ok` + +Run : `./pdf/build check` +Expected : échec de ruff (`pdf/` ne contient encore aucun fichier Python, +ruff peut le signaler ou passer) ; `shellcheck pdf/build` ne doit rien +signaler. Corriger tout avertissement shellcheck avant de continuer. + +Run : `./pdf/build nimporte; echo $?` +Expected : l'aide (lignes 2 à 9) puis `2`. + +**Step 7 : commit** + +```bash +git add pdf/Dockerfile pdf/requirements.txt pdf/pyproject.toml pdf/build .gitignore +git commit -m "feat: image Docker et point d'entrée de la chaîne PDF 2026" +``` + +--- + +### Task 2 : Chargement et validation de `documents.yaml` + +**Files :** +- Create : `pdf/tests/test_documents.py` +- Create : `pdf/build.py` + +**Step 1 : écrire le test qui échoue** + +`pdf/tests/test_documents.py` : + +```python +"""Tests du chargement de documents.yaml.""" + +import re + +import pytest + +import build + +VALIDE = """\ +auteur: Auteur +documents: + - fichier: doc.pdf + titre: Mon titre + sources: [a.md] +""" + + +def ecrire(tmp_path, contenu): + (tmp_path / "a.md").write_text("# Titre\n", encoding="utf-8") + chemin = tmp_path / "documents.yaml" + chemin.write_text(contenu, encoding="utf-8") + return chemin + + +def test_charge_un_catalogue_valide(tmp_path): + catalogue = build.charger_documents(ecrire(tmp_path, VALIDE), tmp_path) + + assert catalogue.auteur == "Auteur" + assert catalogue.documents == [ + build.Document("doc.pdf", "Mon titre", (tmp_path / "a.md",)) + ] + + +def test_lit_le_sous_titre_optionnel(tmp_path): + contenu = VALIDE.replace(" titre: Mon titre\n", " titre: Mon titre\n sous_titre: Sous\n") + + catalogue = build.charger_documents(ecrire(tmp_path, contenu), tmp_path) + + assert catalogue.documents[0].sous_titre == "Sous" + + +@pytest.mark.parametrize( + ("contenu", "message"), + [ + ("auteur: [", "YAML invalide"), + ("documents: []\n", "auteur"), + ("auteur: A\n", "documents"), + ("auteur: A\ndocuments: []\n", "documents"), + (VALIDE.replace(" titre: Mon titre\n", ""), "champ 'titre' manquant"), + (VALIDE.replace("[a.md]", "[absent.md]"), "source introuvable : absent.md"), + (VALIDE.replace("doc.pdf", "doc.txt"), "doit se terminer par .pdf"), + (VALIDE + " couleur: rouge\n", "champ(s) inconnu(s) : couleur"), + (VALIDE + VALIDE.split("documents:\n")[1], "déclaré deux fois"), + ], +) +def test_refuse_un_catalogue_invalide(tmp_path, contenu, message): + with pytest.raises(build.ErreurBuild, match=re.escape(message)): + build.charger_documents(ecrire(tmp_path, contenu), tmp_path) +``` + +**Step 2 : lancer le test pour vérifier qu'il échoue** + +Run : `./pdf/build test pdf/tests/test_documents.py -v` +Expected : FAIL, `ModuleNotFoundError: No module named 'build'`. + +**Step 3 : écrire l'implémentation minimale** + +`pdf/build.py` : + +```python +"""Génération des PDF de la formation Linux. + +Lit pdf/documents.yaml et produit build/pdf/.pdf. S'arrête à la +première erreur : aucun repli silencieux. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +import yaml + +RACINE = Path(__file__).resolve().parent.parent +DOSSIER_PDF = Path(__file__).resolve().parent +CHAMPS_DOCUMENT = {"fichier", "titre", "sous_titre", "sources"} + + +class ErreurBuild(Exception): + """Erreur de génération, avec un message destiné à l'utilisateur.""" + + +@dataclass(frozen=True) +class Document: + fichier: str + titre: str + sources: tuple[Path, ...] + sous_titre: str = "" + + +@dataclass(frozen=True) +class Catalogue: + auteur: str + documents: list[Document] + + +def charger_documents(chemin: Path, racine: Path) -> Catalogue: + """Lit et valide le catalogue des PDF à produire.""" + try: + donnees = yaml.safe_load(chemin.read_text(encoding="utf-8")) + except yaml.YAMLError as erreur: + raise ErreurBuild(f"{chemin} : YAML invalide : {erreur}") from erreur + if not isinstance(donnees, dict): + raise ErreurBuild(f"{chemin} : un dictionnaire est attendu") + + auteur = donnees.get("auteur") + if not isinstance(auteur, str) or not auteur: + raise ErreurBuild(f"{chemin} : champ 'auteur' manquant") + entrees = donnees.get("documents") + if not isinstance(entrees, list) or not entrees: + raise ErreurBuild(f"{chemin} : liste 'documents' manquante ou vide") + + documents: list[Document] = [] + for numero, entree in enumerate(entrees, start=1): + documents.append(_lire_document(entree, f"{chemin}, document {numero}", racine)) + + vus: set[str] = set() + for document in documents: + if document.fichier in vus: + raise ErreurBuild(f"{chemin} : '{document.fichier}' déclaré deux fois") + vus.add(document.fichier) + return Catalogue(auteur, documents) + + +def _lire_document(entree: object, contexte: str, racine: Path) -> Document: + if not isinstance(entree, dict): + raise ErreurBuild(f"{contexte} : un dictionnaire est attendu") + inconnus = sorted(set(entree) - CHAMPS_DOCUMENT) + if inconnus: + raise ErreurBuild(f"{contexte} : champ(s) inconnu(s) : {', '.join(inconnus)}") + for champ in ("fichier", "titre", "sources"): + if not entree.get(champ): + raise ErreurBuild(f"{contexte} : champ '{champ}' manquant") + + fichier = entree["fichier"] + if not fichier.endswith(".pdf") or "/" in fichier: + raise ErreurBuild(f"{contexte} : '{fichier}' doit se terminer par .pdf, sans dossier") + sources = [] + for source in entree["sources"]: + chemin = racine / source + if not chemin.is_file(): + raise ErreurBuild(f"{contexte} : source introuvable : {source}") + sources.append(chemin) + return Document(fichier, entree["titre"], tuple(sources), entree.get("sous_titre", "")) +``` + +**Step 4 : lancer les tests** + +Run : `./pdf/build test pdf/tests/test_documents.py -v` +Expected : 11 tests PASS. + +Run : `./pdf/build check` +Expected : aucun avertissement (sinon `./pdf/build shell ruff format pdf` +puis relancer). + +**Step 5 : commit** + +```bash +git add pdf/build.py pdf/tests/test_documents.py +git commit -m "feat: chargement et validation du catalogue des PDF" +``` + +--- + +### Task 3 : Extraction des titres de chapitre via pandoc + +**Files :** +- Create : `pdf/tests/test_titres.py` +- Modify : `pdf/build.py` + +**Step 1 : écrire le test qui échoue** + +`pdf/tests/test_titres.py` : + +```python +"""Tests de l'extraction des titres de niveau 1 (via pandoc).""" + +import pytest + +import build + + +def source(tmp_path, contenu): + chemin = tmp_path / "chapitre.md" + chemin.write_text(contenu, encoding="utf-8") + return chemin + + +def test_extrait_le_titre_avec_la_typographie_de_pandoc(tmp_path): + chemin = source( + tmp_path, + "# Chapitre 1.1 — L'été `ls`\n\n## Section\n\n```\n# pas un titre\n```\n", + ) + + assert build.titre_chapitre(chemin) == "Chapitre 1.1 — L’été ls" + + +@pytest.mark.parametrize("contenu", ["## Section seule\n", "# Un\n\n# Deux\n"]) +def test_refuse_zero_ou_plusieurs_titres_de_niveau_1(tmp_path, contenu): + with pytest.raises(build.ErreurBuild, match="exactement 1 attendu"): + build.titre_chapitre(source(tmp_path, contenu)) + + +def test_signale_une_commande_en_echec(): + with pytest.raises(build.ErreurBuild, match="échec de : false"): + build.executer(["false"]) +``` + +**Step 2 : lancer le test pour vérifier qu'il échoue** + +Run : `./pdf/build test pdf/tests/test_titres.py -v` +Expected : FAIL, `AttributeError: module 'build' has no attribute 'titre_chapitre'`. + +**Step 3 : implémenter** (ajouts à `pdf/build.py`) + +En tête, compléter les imports : + +```python +import json +import subprocess +``` + +Après `CHAMPS_DOCUMENT` : + +```python +FORMAT_MARKDOWN = "markdown+lists_without_preceding_blankline" +``` + +Après `_lire_document` : + +```python +def executer(commande: list[str], entree: str | None = None, cwd: Path | None = None) -> str: + """Lance une commande, renvoie sa sortie ; lève ErreurBuild en cas d'échec.""" + resultat = subprocess.run( + commande, input=entree, capture_output=True, text=True, cwd=cwd, check=False + ) + if resultat.returncode != 0: + raise ErreurBuild(f"échec de : {' '.join(commande)}\n{resultat.stderr.strip()}") + return resultat.stdout + + +def titres_niveau1(source: Path) -> list[str]: + """Titres de niveau 1 d'un fichier Markdown, tels que pandoc les rendra.""" + ast = json.loads(executer(["pandoc", "-f", FORMAT_MARKDOWN, "-t", "json", str(source)])) + titres = [] + for bloc in ast["blocks"]: + if bloc["t"] == "Header" and bloc["c"][0] == 1: + titre = { + "pandoc-api-version": ast["pandoc-api-version"], + "meta": {}, + "blocks": [{"t": "Plain", "c": bloc["c"][2]}], + } + texte = executer( + ["pandoc", "-f", "json", "-t", "plain", "--wrap=none"], entree=json.dumps(titre) + ) + titres.append(texte.strip()) + return titres + + +def titre_chapitre(source: Path) -> str: + """Titre unique d'un fichier source (règle : exactement un titre de niveau 1).""" + titres = titres_niveau1(source) + if len(titres) != 1: + raise ErreurBuild(f"{source} : {len(titres)} titre(s) de niveau 1, exactement 1 attendu") + return titres[0] +``` + +**Step 4 : lancer les tests** + +Run : `./pdf/build test pdf/tests -v` +Expected : tous PASS. Si le titre attendu diffère (par exemple pour +`` `ls` ``), regarder la sortie réelle de pandoc et ajuster **le test** +uniquement si la sortie de pandoc est la bonne référence (c'est elle qui +finit dans les signets). + +Run : `./pdf/build check` +Expected : propre. + +**Step 5 : commit** + +```bash +git add pdf/build.py pdf/tests/test_titres.py +git commit -m "feat: extraction des titres de chapitre via l'AST pandoc" +``` + +--- + +### Task 4 : Couverture, commande pandoc et analyse du log LaTeX + +**Files :** +- Create : `pdf/tests/test_latex.py` +- Modify : `pdf/build.py` + +**Step 1 : écrire le test qui échoue** + +`pdf/tests/test_latex.py` : + +```python +"""Tests de la partie LaTeX : échappement, couverture, commande, log.""" + +from pathlib import Path + +import build + +DOCUMENT = build.Document("doc.pdf", "Module 2 — Navigation", (Path("/s/a.md"), Path("/s/b.md"))) + + +def test_echappe_les_caracteres_speciaux_latex(): + assert build.echapper_latex(r"a_b & 50% #1 {x} ~ ^ \ $") == ( + r"a\_b \& 50\% \#1 \{x\} \textasciitilde{} \textasciicircum{} " + r"\textbackslash{} \$" + ) + + +def test_couverture_contient_titre_chapitres_et_licence(): + couverture = build.generer_couverture( + DOCUMENT, ["Chapitre 2.1 — Les_chemins", "Chapitre 2.2 — Explorer"], Path("/logo.png") + ) + + assert r"\begin{titlepage}" in couverture + assert "Module 2 — Navigation" in couverture + assert couverture.count(r"\item ") == 2 + assert r"Les\_chemins" in couverture + assert r"\includegraphics[width=3cm]{/logo.png}" in couverture + assert "CC BY-NC-SA 4.0" in couverture + + +def test_couverture_avec_sous_titre(): + document = build.Document("doc.pdf", "Titre", (Path("/s/a.md"),), "Le sous-titre") + + assert "Le sous-titre" in build.generer_couverture(document, ["Chap"], Path("/logo.png")) + + +def test_commande_pandoc(): + commande = build.commande_pandoc(DOCUMENT, "Auteur", Path("/d/couv.tex"), Path("/d/doc.tex")) + + assert commande[:3] == ["pandoc", "-f", build.FORMAT_MARKDOWN] + assert commande[-2:] == ["/s/a.md", "/s/b.md"] + for attendu in ( + "lang=fr", + "documentclass=report", + "title-meta=Module 2 — Navigation", + "author-meta=Auteur", + str(build.DOSSIER_PDF / "preambule.tex"), + "/d/couv.tex", + "/d/doc.tex", + ): + assert attendu in commande + + +def test_erreurs_latex_detecte_caractere_manquant_et_erreur_fatale(): + log = ( + "ligne normale\n" + "Missing character: There is no ✅ (U+2705) in font [lmroman10-regular]\n" + "suite\n" + "./doc.tex:78: ==> Fatal error occurred, no output PDF file produced!\n" + ) + + erreurs = build.erreurs_latex(log) + + assert any("U+2705" in ligne for ligne in erreurs) + assert any("Fatal error" in ligne for ligne in erreurs) + + +def test_erreurs_latex_vide_pour_un_log_propre(): + assert build.erreurs_latex("This is LuaHBTeX\nOutput written on doc.pdf\n") == [] +``` + +**Step 2 : lancer le test pour vérifier qu'il échoue** + +Run : `./pdf/build test pdf/tests/test_latex.py -v` +Expected : FAIL, `AttributeError: module 'build' has no attribute 'echapper_latex'`. + +**Step 3 : implémenter** (ajouts à `pdf/build.py`) + +Import supplémentaire : `import re`. + +Constantes, après `FORMAT_MARKDOWN` : + +```python +LOGO_LICENCE = RACINE / "ressources" / "images" / "licenses" / "cc-by-nc-sa.png" +_ECHAPPEMENTS_LATEX = { + "\\": r"\textbackslash{}", + "&": r"\&", + "%": r"\%", + "$": r"\$", + "#": r"\#", + "_": r"\_", + "{": r"\{", + "}": r"\}", + "~": r"\textasciitilde{}", + "^": r"\textasciicircum{}", +} +``` + +Fonctions, après `titre_chapitre` : + +```python +def echapper_latex(texte: str) -> str: + return "".join(_ECHAPPEMENTS_LATEX.get(caractere, caractere) for caractere in texte) + + +def generer_couverture(document: Document, chapitres: list[str], logo: Path) -> str: + """Page de couverture LaTeX : titre, liste des chapitres, licence.""" + sous_titre = ( + rf"\vspace{{0.5cm}}{{\Large {echapper_latex(document.sous_titre)}\par}}" + if document.sous_titre + else "" + ) + items = "\n".join(rf"\item {echapper_latex(chapitre)}" for chapitre in chapitres) + return rf"""\begin{{titlepage}} +\centering +\vspace*{{3cm}} +{{\Large Formation Linux\par}} +\vspace{{1cm}} +{{\Huge\bfseries {echapper_latex(document.titre)}\par}} +{sous_titre} +\vspace{{2cm}} +\begin{{minipage}}{{0.8\textwidth}} +\begin{{itemize}} +{items} +\end{{itemize}} +\end{{minipage}} +\vfill +\includegraphics[width=3cm]{{{logo}}}\par +\vspace{{0.3cm}} +{{\small Ce document est mis à disposition selon les termes de la licence +Creative Commons Attribution -- Pas d'Utilisation Commerciale -- Partage dans +les Mêmes Conditions 4.0 International (CC BY-NC-SA 4.0).\par}} +\end{{titlepage}} +""" + + +def commande_pandoc(document: Document, auteur: str, couverture: Path, sortie: Path) -> list[str]: + """Commande pandoc Markdown -> LaTeX autonome pour un document.""" + return [ + "pandoc", + "-f", + FORMAT_MARKDOWN, + "--standalone", + "--output", + str(sortie), + "--top-level-division=chapter", + "--toc", + "--toc-depth=2", + "--syntax-highlighting=tango", + "-V", + "documentclass=report", + "-V", + "papersize=a4", + "-V", + "geometry:margin=2.2cm", + "-V", + "lang=fr", + "-V", + "monofont=DejaVu Sans Mono", + "-V", + "monofontoptions=Scale=0.85", + "-V", + f"title-meta={document.titre}", + "-V", + f"author-meta={auteur}", + "--include-in-header", + str(DOSSIER_PDF / "preambule.tex"), + "--include-before-body", + str(couverture), + *[str(source) for source in document.sources], + ] + + +def erreurs_latex(log: str) -> list[str]: + """Lignes d'erreur d'un log LaTeX (avec deux lignes de contexte), 30 au plus.""" + lignes = log.splitlines() + erreurs: list[str] = [] + for index, ligne in enumerate(lignes): + if ( + ligne.startswith("!") + or "Missing character" in ligne + or re.match(r"^\S+\.tex:\d+: ", ligne) + ): + erreurs.extend(lignes[index : index + 3]) + return erreurs[:30] +``` + +**Step 4 : lancer les tests** + +Run : `./pdf/build test pdf/tests -v` puis `./pdf/build check` +Expected : tous PASS, lint propre. + +**Step 5 : commit** + +```bash +git add pdf/build.py pdf/tests/test_latex.py +git commit -m "feat: couverture, commande pandoc et analyse du log LaTeX" +``` + +--- + +### Task 5 : Préambule, compilation et premier PDF réel (module 2) + +**Files :** +- Create : `pdf/preambule.tex` +- Create : `pdf/documents.yaml` (première entrée seulement) +- Modify : `pdf/build.py` (compilation et `main`) + +**Step 1 : écrire `pdf/preambule.tex`** + +```latex +% Préambule commun des PDF de la formation (inclus par pandoc, option -H). + +% Un caractère absent de la police devient une erreur fatale. +\tracinglostchars=3 + +% Blocs de code : retour à la ligne automatique, fond gris pour les blocs +% sans coloration (les blocs colorés ont déjà le fond « Shaded » de pandoc). +\usepackage{xcolor} +\definecolor{fondcode}{HTML}{F4F4F4} +\usepackage{fvextra} +\fvset{breaklines,breakanywhere} +\RecustomVerbatimEnvironment{verbatim}{Verbatim}{bgcolor=fondcode} + +% En-tête : titre du chapitre ; pied : numéro de page. +\usepackage{fancyhdr} +\pagestyle{fancy} +\fancyhf{} +\fancyhead[R]{\small\nouppercase{\leftmark}} +\fancyfoot[C]{\thepage} +\renewcommand{\chaptermark}[1]{\markboth{#1}{}} +\fancypagestyle{plain}{\fancyhf{}\fancyfoot[C]{\thepage}\renewcommand{\headrulewidth}{0pt}} + +\usepackage{graphicx} +``` + +**Step 2 : écrire `pdf/documents.yaml`** (entrée du module 2 seule) + +```yaml +# Catalogue des PDF de la formation : un document par entrée. +# Les chemins des sources sont relatifs à la racine du dépôt. +auteur: Pascal Guinet + +documents: + - fichier: module_02_navigation.pdf + titre: Module 2 — Navigation + sources: + - cours_2026/module_02_navigation/01_arborescence_chemins.md + - cours_2026/module_02_navigation/02_se_deplacer_explorer.md + - cours_2026/module_02_navigation/03_types_fichiers_liens.md +``` + +**Step 3 : implémenter la compilation et `main`** (ajouts à `pdf/build.py`) + +Imports supplémentaires : `import argparse`, `import shutil`, `import sys`. + +Après `erreurs_latex` : + +```python +def compiler(tex: Path) -> Path: + """Compile un .tex avec latexmk/LuaLaTeX ; lève ErreurBuild au moindre problème.""" + resultat = subprocess.run( + [ + "latexmk", + "-lualatex", + "-interaction=nonstopmode", + "-halt-on-error", + "-file-line-error", + tex.name, + ], + cwd=tex.parent, + capture_output=True, + text=True, + check=False, + ) + log = tex.with_suffix(".log") + texte_log = log.read_text(encoding="utf-8", errors="replace") if log.exists() else "" + erreurs = erreurs_latex(texte_log) + if resultat.returncode != 0 or erreurs: + detail = "\n".join(erreurs) or "\n".join(resultat.stdout.splitlines()[-30:]) + raise ErreurBuild(f"échec LaTeX pour {tex.name} (log complet : {log})\n{detail}") + return tex.with_suffix(".pdf") + + +def construire(document: Document, auteur: str, sortie: Path) -> Path: + """Produit sortie/ ; les intermédiaires restent dans sortie/debug/.""" + debug = sortie / "debug" + debug.mkdir(parents=True, exist_ok=True) + nom = Path(document.fichier).stem + chapitres = [titre_chapitre(source) for source in document.sources] + couverture = debug / f"{nom}-couverture.tex" + couverture.write_text(generer_couverture(document, chapitres, LOGO_LICENCE), encoding="utf-8") + tex = debug / f"{nom}.tex" + executer(commande_pandoc(document, auteur, couverture, tex)) + pdf = sortie / document.fichier + shutil.copy2(compiler(tex), pdf) + return pdf + + +def selectionner(documents: list[Document], fichiers: list[str]) -> list[Document]: + if not fichiers: + return documents + connus = {document.fichier: document for document in documents} + inconnus = [fichier for fichier in fichiers if fichier not in connus] + if inconnus: + raise ErreurBuild(f"document(s) inconnu(s) : {', '.join(inconnus)}") + return [connus[fichier] for fichier in fichiers] + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("fichiers", nargs="*", help="PDF à produire (tous par défaut)") + parser.add_argument("--sortie", type=Path, default=RACINE / "build" / "pdf") + args = parser.parse_args(argv) + try: + catalogue = charger_documents(DOSSIER_PDF / "documents.yaml", RACINE) + documents = selectionner(catalogue.documents, args.fichiers) + for document in documents: + print(f"-> {document.fichier}", flush=True) + construire(document, catalogue.auteur, args.sortie) + except ErreurBuild as erreur: + print(f"ERREUR : {erreur}", file=sys.stderr) + return 1 + print(f"{len(documents)} PDF produit(s) dans {args.sortie}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) +``` + +**Step 4 : ajouter un test de `selectionner`** (à la fin de +`pdf/tests/test_documents.py`) + +```python +def test_selectionne_les_documents_demandes(): + a = build.Document("a.pdf", "A", ()) + b = build.Document("b.pdf", "B", ()) + + assert build.selectionner([a, b], []) == [a, b] + assert build.selectionner([a, b], ["b.pdf"]) == [b] + with pytest.raises(build.ErreurBuild, match="inconnu"): + build.selectionner([a, b], ["c.pdf"]) +``` + +Run : `./pdf/build test pdf/tests -v` +Expected : tous PASS. + +**Step 5 : produire le premier PDF** + +Run : `./pdf/build pdf` +Expected : `-> module_02_navigation.pdf` puis `1 PDF produit(s) dans +/data/build/pdf`, code de sortie 0. Fichier `build/pdf/module_02_navigation.pdf`. + +En cas d'échec, lire le message (il cite le log) et +`build/pdf/debug/module_02_navigation.log`. Points probables : +- `bgcolor` refusé par `Verbatim` : vérifier la version de fvextra + (`./pdf/build shell kpsewhich fvextra.sty` puis lire la date du fichier) ; + à défaut, remplacer le fond par `frame=single,framerule=0.2pt`. +- `--syntax-highlighting` refusé : revenir à `--highlight-style=tango`. + +**Step 6 : contrôle visuel** + +```bash +./pdf/build shell pdftoppm -r 60 -png -f 1 -l 6 build/pdf/module_02_navigation.pdf build/pdf/debug/apercu +``` + +Ouvrir `build/pdf/debug/apercu-1.png` à `apercu-6.png` (outil Read) et +vérifier : couverture (titre, 3 chapitres, logo et licence en bas), +sommaire sur sa propre page, chapitre 2.1 sur une nouvelle page sans +numéro parasite devant « Chapitre 2.1 », encadré « Objectifs » avec une +vraie liste à puces, blocs de code sur fond gris, en-tête et numéro de page. +Corriger `preambule.tex` ou `build.py` tant que ce n'est pas le cas. + +**Step 7 : commit** + +```bash +git add pdf/preambule.tex pdf/documents.yaml pdf/build.py pdf/tests/test_documents.py +git commit -m "feat: compilation LuaLaTeX et premier PDF (module 2)" +``` + +--- + +### Task 6 : Catalogue complet et nettoyage des sources Git + +**Files :** +- Modify : `pdf/documents.yaml` +- Modify : `pdf/tests/test_documents.py` +- Modify : sources de `supports/modules_additionnels/module_git/` et + `travaux_pratiques/tp_additionnels/tp_git/` (caractères seulement) + +**Step 1 : écrire le test qui échoue** (à la fin de `pdf/tests/test_documents.py`) + +```python +ATTENDUS = [ + "module_01_decouverte.pdf", + "module_02_navigation.pdf", + "module_03_manipulation.pdf", + "module_04_consultation.pdf", + "module_05_droits.pdf", + "module_06_processus.pdf", + "module_07_reseaux.pdf", + "module_08_automatisation.pdf", + "module_additionnel_git.pdf", + "module_additionnel_docker.pdf", + "annexe_installation.pdf", +] + + +def test_le_catalogue_du_depot_declare_les_11_pdf(): + catalogue = build.charger_documents(build.DOSSIER_PDF / "documents.yaml", build.RACINE) + + assert [document.fichier for document in catalogue.documents] == ATTENDUS + + +def test_chaque_chapitre_de_cours_2026_est_dans_un_pdf(): + catalogue = build.charger_documents(build.DOSSIER_PDF / "documents.yaml", build.RACINE) + declares = {source for document in catalogue.documents for source in document.sources} + + oublies = sorted( + str(chemin.relative_to(build.RACINE)) + for chemin in (build.RACINE / "cours_2026").rglob("*.md") + if chemin not in declares + ) + assert oublies == [] +``` + +Run : `./pdf/build test pdf/tests/test_documents.py -v` +Expected : FAIL sur ces deux tests (un seul document déclaré). + +**Step 2 : compléter `pdf/documents.yaml`** + +Remplacer la liste `documents` par les 11 entrées, dans l'ordre de +`ATTENDUS`. Titres : + +| fichier | titre | +|---------|-------| +| module_01_decouverte.pdf | Module 1 — Découverte | +| module_02_navigation.pdf | Module 2 — Navigation | +| module_03_manipulation.pdf | Module 3 — Manipulation | +| module_04_consultation.pdf | Module 4 — Consultation et édition | +| module_05_droits.pdf | Module 5 — Droits | +| module_06_processus.pdf | Module 6 — Processus et système | +| module_07_reseaux.pdf | Module 7 — Réseau et services | +| module_08_automatisation.pdf | Module 8 — Automatisation | +| module_additionnel_git.pdf | Git : contrôle de version (sous_titre : Module additionnel) | +| module_additionnel_docker.pdf | Docker : conteneurisation (sous_titre : Module additionnel) | +| annexe_installation.pdf | Installer son environnement de travail (sous_titre : Annexe) | + +Sources : les fichiers `.md` de `cours_2026/module_0X_*/` triés par nom ; +pour Git, les 4 chapitres de `supports/modules_additionnels/module_git/` +puis `tp01_premiers_pas.md`, `tp02_branches_fusion.md`, +`tp03_collaboration.md`, `exercices_supplementaires.md` de +`travaux_pratiques/tp_additionnels/tp_git/` ; pour Docker, les 4 chapitres +de `module_docker/` puis `README.md`, `tp1_...` à `tp5_...` de `tp_docker/` ; +pour l'annexe, `cours_2026/annexes/installation.md`. + +Run : `./pdf/build test pdf/tests/test_documents.py -v` +Expected : PASS. + +**Step 3 : constater l'échec attendu sur le module Git** + +Run : `./pdf/build pdf module_additionnel_git.pdf` +Expected : `ERREUR : échec LaTeX ...` avec une ligne `Missing character`. +C'est le filet de sécurité qui fonctionne. + +**Step 4 : remplacer les caractères dans les sources Git** + +```bash +./pdf/build shell python3 - <<'EOF' +from pathlib import Path +REMPLACEMENTS = { + "─": "-", "│": "|", "├": "+", "└": "+", "╱": "/", "╲": "\\", + "→": "->", "←": "<-", "●": "*", + "✅": "[OK]", "✓": "[OK]", "√": "[OK]", "❌": "[X]", + "🚀 ": "", "🎉 ": "", "🚀": "", "🎉": "", +} +for dossier in ("supports/modules_additionnels/module_git", "travaux_pratiques/tp_additionnels/tp_git"): + for chemin in sorted(Path(dossier).glob("*.md")): + texte = chemin.read_text(encoding="utf-8") + nouveau = texte + for avant, apres in REMPLACEMENTS.items(): + nouveau = nouveau.replace(avant, apres) + if nouveau != texte: + chemin.write_text(nouveau, encoding="utf-8") + print("modifié :", chemin) +EOF +``` + +Relire **tout** le diff (`git diff supports travaux_pratiques`). Points de +vigilance : diagrammes de branches Git (l'alignement des colonnes doit +rester lisible : `*` à la place de `●`, `/` et `\` pour les embranchements), +listes à cocher (`[OK]` doit se lire naturellement dans la phrase ; sinon +reformuler à la main). Aucune autre modification de contenu. + +**Step 5 : produire les 11 PDF** + +Run : `./pdf/build pdf` +Expected : 11 lignes `-> ...` puis `11 PDF produit(s)`, code 0. + +Tout autre échec se traite à la source du problème (caractère, Markdown +mal formé), jamais en relâchant une règle. Noter chaque correction de +source dans le message de commit. + +**Step 6 : contrôle visuel rapide** + +Pour `module_additionnel_git.pdf` (diagrammes) et `module_05_droits.pdf` +(le plus long) : `pdftoppm` comme en Task 5, inspecter 6 pages au hasard +dont au moins un tableau « L'essentiel » et un diagramme. + +**Step 7 : commits** (séparer catalogue et contenu) + +```bash +git add pdf/documents.yaml pdf/tests/test_documents.py +git commit -m "feat: catalogue des 11 PDF de la formation" +git add supports/modules_additionnels/module_git travaux_pratiques/tp_additionnels/tp_git +git commit -m "fix: remplacer les caractères Unicode non imprimables du module Git" +``` + +--- + +### Task 7 : Tests des PDF produits + +**Files :** +- Create : `pdf/tests/test_pdfs.py` + +**Step 1 : écrire les tests** + +```python +"""Vérification des PDF produits (lancer ./pdf/build pdf avant).""" + +import re +import subprocess + +import pypdf +import pytest + +import build + +SORTIE = build.RACINE / "build" / "pdf" +CATALOGUE = build.charger_documents(build.DOSSIER_PDF / "documents.yaml", build.RACINE) +CARACTERES_FRANCAIS = set("éèêëàâçùûüîïôœÉÈÊÀÇÔ«»") +SEUIL_DEBORDEMENT_PT = 20.0 + + +@pytest.fixture(scope="module", params=CATALOGUE.documents, ids=lambda d: d.fichier) +def document(request): + return request.param + + +@pytest.fixture(scope="module") +def lecteur(document): + chemin = SORTIE / document.fichier + assert chemin.is_file(), f"{chemin} absent : lancer ./pdf/build pdf" + return pypdf.PdfReader(chemin) + + +@pytest.fixture(scope="module") +def log(document): + chemin = SORTIE / "debug" / f"{document.fichier.removesuffix('.pdf')}.log" + assert chemin.is_file(), f"{chemin} absent : lancer ./pdf/build pdf" + return chemin.read_text(encoding="utf-8", errors="replace") + + +def test_au_moins_deux_pages(lecteur): + assert len(lecteur.pages) >= 2 + + +def test_metadonnees(document, lecteur): + assert lecteur.metadata.title == document.titre + assert lecteur.metadata.author == CATALOGUE.auteur + assert lecteur.trailer["/Root"].get("/Lang") == "fr" + + +def test_signets_de_niveau_1_dans_l_ordre_des_sources(document, lecteur): + signets = [entree.title for entree in lecteur.outline if not isinstance(entree, list)] + + assert signets == [build.titre_chapitre(source) for source in document.sources] + + +def test_caracteres_francais_preserves(document): + texte = subprocess.run( + ["pdftotext", str(SORTIE / document.fichier), "-"], + capture_output=True, + text=True, + check=True, + ).stdout + dans_les_sources = { + caractere + for source in document.sources + for caractere in source.read_text(encoding="utf-8") + if caractere in CARACTERES_FRANCAIS + } + + assert dans_les_sources - set(texte) == set() + + +def test_aucun_caractere_manquant(log): + assert "Missing character" not in log + + +def test_aucun_debordement_notable(log): + debordements = [ + float(largeur) + for largeur in re.findall(r"Overfull \\hbox \((\d+(?:\.\d+)?)pt too wide\)", log) + ] + + assert [d for d in debordements if d > SEUIL_DEBORDEMENT_PT] == [] +``` + +**Step 2 : lancer les tests** + +Run : `./pdf/build test pdf/tests/test_pdfs.py -v` +Expected : 66 tests (11 x 6). Les échecs probables : +- `test_aucun_debordement_notable` : retrouver la ligne fautive dans le log + (`Overfull \hbox ... at lines N--M` du `.tex` de debug), en déduire la + source et corriger la cause (tableau trop large -> colonnes plus courtes ; + URL longue -> la mettre en lien ; ligne de code sans espace -> la couper). + Le seuil ne se remonte que si le défaut est invisible à l'œil sur le + rendu, avec une justification dans le commit. +- `test_signets...` : comparer les deux listes affichées par pytest ; un écart + de typographie se corrige dans `titres_niveau1`, pas dans le test. + +**Step 3 : tout lancer** + +Run : `./pdf/build` +Expected : 11 PDF puis tous les tests PASS, code 0. + +Run : `./pdf/build check` +Expected : propre. + +**Step 4 : commit** + +```bash +git add pdf/tests/test_pdfs.py +git commit -m "test: vérification du contenu des 11 PDF produits" +``` + +--- + +### Task 8 : Workflow GitHub Actions unique + +**Files :** +- Create : `.github/workflows/pdf.yml` + +**Step 1 : vérifier les versions majeures des actions** + +```bash +gh api repos/actions/checkout/releases/latest --jq .tag_name +gh api repos/actions/upload-artifact/releases/latest --jq .tag_name +gh api repos/actions/download-artifact/releases/latest --jq .tag_name +``` + +Utiliser la version majeure renvoyée (ex. `v5.0.0` -> `@v5`) à la place des +`@vN` ci-dessous. + +**Step 2 : écrire `.github/workflows/pdf.yml`** + +```yaml +# Génération et vérification des PDF : mêmes commandes qu'en local. +name: PDF + +on: + push: + branches: [master] + tags: ["v*"] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + pdf: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@vN + + - name: Lint + run: ./pdf/build check + + - name: PDF + run: ./pdf/build pdf + + - name: Tests + run: ./pdf/build test + + - name: Publier les PDF (artifact) + uses: actions/upload-artifact@vN + with: + name: pdf-${{ github.sha }} + path: build/pdf/*.pdf + if-no-files-found: error + + - name: Publier le diagnostic (en cas d'échec) + if: failure() + uses: actions/upload-artifact@vN + with: + name: debug-${{ github.sha }} + path: build/pdf/debug/ + if-no-files-found: ignore + + release: + needs: pdf + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/download-artifact@vN + with: + name: pdf-${{ github.sha }} + path: pdf + + - name: Créer la release + env: + GH_TOKEN: ${{ github.token }} + run: > + gh release create "${{ github.ref_name }}" pdf/*.pdf + --repo "${{ github.repository }}" + --title "Formation Linux ${{ github.ref_name }}" + --generate-notes +``` + +**Step 3 : valider la syntaxe** + +```bash +./pdf/build shell python3 -c "import yaml; yaml.safe_load(open('.github/workflows/pdf.yml')); print('ok')" +``` + +Expected : `ok`. (La validation réelle se fait au premier push, Task 11.) + +**Step 4 : commit** + +```bash +git add .github/workflows/pdf.yml +git commit -m "ci: workflow PDF unique (build, tests, release sur tag)" +``` + +--- + +### Task 9 : Suppression de l'ancienne chaîne + +**Files :** +- Delete : `scripts/` (dossier entier : 8 scripts, `README.md`, `templates/`) +- Delete : `.github/workflows/build-pdfs.yml`, `.github/workflows/test-build.yml`, + `.github/workflows/build-artifacts-only.yml`, `.github/WORKFLOWS.md` + +**Step 1 : vérifier qu'aucun fichier actif n'y fait référence** + +```bash +grep -rn "scripts/\|clean_unicode\|build_pdf\|build_formations\|build_git_module\|build_docker_module\|WORKFLOWS.md" \ + --exclude-dir=.git --exclude-dir=archives --exclude-dir=docs --exclude-dir=scripts --exclude-dir=build . +``` + +Expected : seulement `README.md`, `CLAUDE.md`, `CONTRIBUTING.md` (traités +en Task 10) et les anciens workflows (supprimés ici). + +**Step 2 : supprimer** + +```bash +git rm -r -q scripts .github/workflows/build-pdfs.yml .github/workflows/test-build.yml \ + .github/workflows/build-artifacts-only.yml .github/WORKFLOWS.md +``` + +**Step 3 : vérifier que la nouvelle chaîne tourne toujours** + +Run : `./pdf/build` +Expected : 11 PDF, tests PASS. + +**Step 4 : commit** + +```bash +git commit -m "refactor: supprimer l'ancienne chaîne PDF (scripts et workflows)" +``` + +--- + +### Task 10 : Documentation + +**Files :** +- Modify : `CLAUDE.md` +- Modify : `README.md` +- Modify : `CONTRIBUTING.md` + +**Step 1 : `CLAUDE.md`** + +- Arborescence : remplacer le bloc `scripts/ ...` par : + + ``` + pdf/ (chaîne de génération PDF) + build (point d'entrée unique) + build.py + documents.yaml (catalogue des PDF) + preambule.tex + Dockerfile + tests/ + ``` + +- Remplacer la section « Gestion des caractères français et génération + PDF » par une section courte « Caractères dans les sources » : accents + français toujours préservés ; pas de caractères de boîtes, flèches + Unicode ni emojis (utiliser `+ - |`, `->`, `<-`) ; un caractère non + imprimable fait échouer `./pdf/build` avec `Missing character`, et la + correction se fait dans la source. +- Remplacer « Spécifications de génération PDF » et « Automatisation GitHub + Actions » par une section « Génération PDF » : + - commandes : `./pdf/build` (tout), `./pdf/build pdf [FICHIER.pdf]`, + `./pdf/build test`, `./pdf/build check`, `./pdf/build shell` ; + - tout tourne dans l'image Docker `pdf/Dockerfile` (seul prérequis : + Docker) ; résultats dans `build/pdf/`, diagnostic dans + `build/pdf/debug/` ; + - ajouter un PDF = ajouter une entrée dans `pdf/documents.yaml` ; chaque + source contient exactement un titre `#` ; + - CI : `.github/workflows/pdf.yml` lance les mêmes commandes ; artifact à + chaque push et PR ; release quand on pousse un tag `v*` + (`git tag v2026.1 && git push origin v2026.1`) ; + - la mise en page riche (cadre de couleur, numérotation romaine, + recto-verso) est une évolution possible, pas l'état actuel. +- Supprimer les deux mentions « NOTE : chaîne de génération en cours de + refonte ». + +**Step 2 : `README.md`** + +- Arborescence : `scripts/` -> `pdf/ # Génération des PDF (./pdf/build)`. +- Section « Génération PDF » : remplacer par : PDF disponibles dans les + Releases du dépôt ; pour générer localement, Docker suffit : + `./pdf/build` ; résultats dans `build/pdf/`. + +**Step 3 : `CONTRIBUTING.md`** + +- « Tester localement » : `./pdf/build` (Docker requis) au lieu des scripts + Git/Docker. +- « Tests automatiques » : le workflow `PDF` construit et teste les 11 PDF. +- Section caractères : retirer `clean_unicode.sh` ; indiquer que + `./pdf/build` signale le caractère fautif. +- « Scripts et outils » : fichiers concernés `pdf/`, + `.github/workflows/pdf.yml`. + +**Step 4 : vérifier** + +```bash +grep -rn "scripts/\|clean_unicode\|build_git_module\|build_docker_module\|build-pdfs\|test-build" README.md CLAUDE.md CONTRIBUTING.md +``` + +Expected : aucune ligne. + +**Step 5 : commit** + +```bash +git add CLAUDE.md README.md CONTRIBUTING.md +git commit -m "docs: documenter la chaîne PDF 2026 (./pdf/build, release sur tag)" +``` + +--- + +### Task 11 : Vérification finale et validation en CI + +**Step 1 : build propre depuis zéro** + +```bash +rm -rf build/pdf +docker rmi formation-linux-pdf +./pdf/build check && ./pdf/build +``` + +Expected : image reconstruite, lint propre, 11 PDF, tous les tests PASS. + +**Step 2 : reproductibilité** + +```bash +sha256sum build/pdf/*.pdf > /tmp/claude-sha1 +./pdf/build pdf +sha256sum build/pdf/*.pdf | diff /tmp/claude-sha1 - +``` + +Expected : aucune différence (ou, à défaut, seulement l'identifiant `/ID` +du PDF : le noter dans le rapport, ce n'est pas bloquant). +Utiliser le dossier scratchpad de la session plutôt que `/tmp` si disponible. + +**Step 3 : revue visuelle finale** + +Planche des couvertures et d'une page intérieure de chaque PDF (pdftoppm, +résolution 40) ; vérifier l'homogénéité. + +**Step 4 : validation en CI** -- demander l'accord de l'utilisateur avant de +pousser. + +```bash +git push -u origin chaine-pdf-2026 +gh pr create --title "Chaîne PDF 2026" --body "..." +gh pr checks --watch +``` + +Expected : workflow `PDF` vert ; l'artifact `pdf-` contient 11 PDF. +Télécharger l'artifact et comparer les sommes SHA-256 aux PDF locaux +(cohérence locale/CI). + +**Step 5 : clôture** + +Mettre à jour l'en-tête de ce plan (« STATUT : terminé (date) ») et le +fichier `.tasks.json`, puis proposer la fusion dans `master` et la pose du +premier tag `v2026.1`. diff --git a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json new file mode 100644 index 0000000..9d64bd1 --- /dev/null +++ b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json @@ -0,0 +1,91 @@ +{ + "planPath": "docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md", + "tasks": [ + { + "id": 0, + "subject": "Task 1: Squelette de l'image et du point d'entrée", + "status": "pending" + }, + { + "id": 1, + "subject": "Task 2: Chargement et validation de documents.yaml", + "status": "pending", + "blockedBy": [ + 0 + ] + }, + { + "id": 2, + "subject": "Task 3: Extraction des titres de chapitre via pandoc", + "status": "pending", + "blockedBy": [ + 1 + ] + }, + { + "id": 3, + "subject": "Task 4: Couverture, commande pandoc et analyse du log LaTeX", + "status": "pending", + "blockedBy": [ + 2 + ] + }, + { + "id": 4, + "subject": "Task 5: Préambule, compilation et premier PDF réel (module 2)", + "status": "pending", + "blockedBy": [ + 3 + ] + }, + { + "id": 5, + "subject": "Task 6: Catalogue complet et nettoyage des sources Git", + "status": "pending", + "blockedBy": [ + 4 + ] + }, + { + "id": 6, + "subject": "Task 7: Tests des PDF produits", + "status": "pending", + "blockedBy": [ + 5 + ] + }, + { + "id": 7, + "subject": "Task 8: Workflow GitHub Actions unique", + "status": "pending", + "blockedBy": [ + 6 + ] + }, + { + "id": 8, + "subject": "Task 9: Suppression de l'ancienne chaîne", + "status": "pending", + "blockedBy": [ + 7 + ] + }, + { + "id": 9, + "subject": "Task 10: Documentation", + "status": "pending", + "blockedBy": [ + 8 + ] + }, + { + "id": 10, + "subject": "Task 11: Vérification finale et validation en CI", + "status": "pending", + "blockedBy": [ + 9 + ] + } + ], + "lastUpdated": "2026-10-01T17:13:48+02:00" +} \ No newline at end of file From fdc79b862f28d281b0398f5fb70090d9c5d21a8d Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:16:04 +0200 Subject: [PATCH 05/32] =?UTF-8?q?feat:=20image=20Docker=20et=20point=20d'e?= =?UTF-8?q?ntr=C3=A9e=20de=20la=20cha=C3=AEne=20PDF=202026?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .gitignore | 7 ++++- pdf/Dockerfile | 13 +++++++++ pdf/build | 63 ++++++++++++++++++++++++++++++++++++++++++++ pdf/pyproject.toml | 13 +++++++++ pdf/requirements.txt | 4 +++ 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 pdf/Dockerfile create mode 100755 pdf/build create mode 100644 pdf/pyproject.toml create mode 100644 pdf/requirements.txt diff --git a/.gitignore b/.gitignore index cb098e3..8c78c7f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,9 @@ env/ # Logs *.log -logs/ \ No newline at end of file +logs/ + +# Python (chaîne PDF) +__pycache__/ +.pytest_cache/ +.ruff_cache/ diff --git a/pdf/Dockerfile b/pdf/Dockerfile new file mode 100644 index 0000000..aa6ddb2 --- /dev/null +++ b/pdf/Dockerfile @@ -0,0 +1,13 @@ +# Image de génération des PDF de la formation Linux. +# Version figée par digest : ce qui passe en local passe en CI. +FROM pandoc/extra:3.11.0.0-debian@sha256:fa8ccae75418449568f10f1842706aee1885ab24fba54f5aec08516260fc0166 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends poppler-utils shellcheck \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt /tmp/requirements.txt +RUN pip install --no-cache-dir --break-system-packages -r /tmp/requirements.txt + +ENTRYPOINT [] +WORKDIR /data diff --git a/pdf/build b/pdf/build new file mode 100755 index 0000000..ba4feb9 --- /dev/null +++ b/pdf/build @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Point d'entrée unique de la génération des PDF, en local comme en CI. +# +# ./pdf/build PDF puis tests +# ./pdf/build pdf [FICHIER.pdf ...] PDF seuls (tous, ou ceux indiqués) +# ./pdf/build test [ARGS ...] tests seuls (arguments passés à pytest) +# ./pdf/build check lint : ruff et shellcheck +# ./pdf/build shell [COMMANDE ...] shell (ou commande) dans l'image +set -euo pipefail + +racine="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +image="formation-linux-pdf" +date_source="$(git -C "$racine" log -1 --format=%ct 2>/dev/null || date +%s)" + +docker build --quiet --tag "$image" "$racine/pdf" > /dev/null + +dans_image() { + docker run --rm \ + --user "$(id -u):$(id -g)" \ + --env HOME=/tmp \ + --env PYTHONDONTWRITEBYTECODE=1 \ + --env RUFF_NO_CACHE=true \ + --env SOURCE_DATE_EPOCH="$date_source" \ + --env FORCE_SOURCE_DATE=1 \ + --volume "$racine:/data" \ + --workdir /data \ + "${options_tty[@]}" \ + "$image" "$@" +} +options_tty=() + +commande="${1:-tout}" +if [ $# -gt 0 ]; then shift; fi + +case "$commande" in + tout) + dans_image python3 pdf/build.py + dans_image python3 -m pytest pdf/tests + ;; + pdf) + dans_image python3 pdf/build.py "$@" + ;; + test) + if [ $# -eq 0 ]; then set -- pdf/tests; fi + dans_image python3 -m pytest "$@" + ;; + check) + dans_image ruff check pdf + dans_image ruff format --check pdf + dans_image shellcheck pdf/build + ;; + shell) + if [ $# -eq 0 ]; then + options_tty=(--interactive --tty) + set -- bash + fi + dans_image "$@" + ;; + *) + sed -n '2,8p' "$0" >&2 + exit 2 + ;; +esac diff --git a/pdf/pyproject.toml b/pdf/pyproject.toml new file mode 100644 index 0000000..b33aeb9 --- /dev/null +++ b/pdf/pyproject.toml @@ -0,0 +1,13 @@ +# Configuration des outils Python de la chaîne PDF (pas un paquet installable). + +[tool.pytest.ini_options] +pythonpath = ["."] +testpaths = ["tests"] +addopts = "-p no:cacheprovider" + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "B", "UP"] diff --git a/pdf/requirements.txt b/pdf/requirements.txt new file mode 100644 index 0000000..33817c1 --- /dev/null +++ b/pdf/requirements.txt @@ -0,0 +1,4 @@ +PyYAML==6.0.3 +pypdf==6.19.0 +pytest==9.1.1 +ruff==0.16.9 From 4175feee62f2869db29af7e055767df8e4d62c3b Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:19:23 +0200 Subject: [PATCH 06/32] =?UTF-8?q?fix:=20robustesse=20du=20point=20d'entr?= =?UTF-8?q?=C3=A9e=20pdf/build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- pdf/build | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/pdf/build b/pdf/build index ba4feb9..333bec6 100755 --- a/pdf/build +++ b/pdf/build @@ -8,6 +8,18 @@ # ./pdf/build shell [COMMANDE ...] shell (ou commande) dans l'image set -euo pipefail +commande="${1:-tout}" +if [ $# -gt 0 ]; then shift; fi + +# Commande inconnue : aide, sans construire l'image. +case "$commande" in + tout | pdf | test | check | shell) ;; + *) + sed -n '2,8p' "$0" >&2 + exit 2 + ;; +esac + racine="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" image="formation-linux-pdf" date_source="$(git -C "$racine" log -1 --format=%ct 2>/dev/null || date +%s)" @@ -29,9 +41,6 @@ dans_image() { } options_tty=() -commande="${1:-tout}" -if [ $# -gt 0 ]; then shift; fi - case "$commande" in tout) dans_image python3 pdf/build.py @@ -41,7 +50,7 @@ case "$commande" in dans_image python3 pdf/build.py "$@" ;; test) - if [ $# -eq 0 ]; then set -- pdf/tests; fi + if [ $# -eq 0 ] || [[ "$1" == -* ]]; then set -- pdf/tests "$@"; fi dans_image python3 -m pytest "$@" ;; check) @@ -50,14 +59,9 @@ case "$commande" in dans_image shellcheck pdf/build ;; shell) - if [ $# -eq 0 ]; then - options_tty=(--interactive --tty) - set -- bash - fi + options_tty=(--interactive) + if [ -t 0 ]; then options_tty+=(--tty); fi + if [ $# -eq 0 ]; then set -- bash; fi dans_image "$@" ;; - *) - sed -n '2,8p' "$0" >&2 - exit 2 - ;; esac From a089615d6f8af181729eff30751ac5a195244d20 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:21:20 +0200 Subject: [PATCH 07/32] feat: chargement et validation du catalogue des PDF Co-Authored-By: Claude Opus 5.5 --- pdf/build.py | 84 +++++++++++++++++++++++++++++++++++++ pdf/tests/test_documents.py | 58 +++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 pdf/build.py create mode 100644 pdf/tests/test_documents.py diff --git a/pdf/build.py b/pdf/build.py new file mode 100644 index 0000000..04de970 --- /dev/null +++ b/pdf/build.py @@ -0,0 +1,84 @@ +"""Génération des PDF de la formation Linux. + +Lit pdf/documents.yaml et produit build/pdf/.pdf. S'arrête à la +première erreur : aucun repli silencieux. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +import yaml + +RACINE = Path(__file__).resolve().parent.parent +DOSSIER_PDF = Path(__file__).resolve().parent +CHAMPS_DOCUMENT = {"fichier", "titre", "sous_titre", "sources"} + + +class ErreurBuild(Exception): + """Erreur de génération, avec un message destiné à l'utilisateur.""" + + +@dataclass(frozen=True) +class Document: + fichier: str + titre: str + sources: tuple[Path, ...] + sous_titre: str = "" + + +@dataclass(frozen=True) +class Catalogue: + auteur: str + documents: list[Document] + + +def charger_documents(chemin: Path, racine: Path) -> Catalogue: + """Lit et valide le catalogue des PDF à produire.""" + try: + donnees = yaml.safe_load(chemin.read_text(encoding="utf-8")) + except yaml.YAMLError as erreur: + raise ErreurBuild(f"{chemin} : YAML invalide : {erreur}") from erreur + if not isinstance(donnees, dict): + raise ErreurBuild(f"{chemin} : un dictionnaire est attendu") + + auteur = donnees.get("auteur") + if not isinstance(auteur, str) or not auteur: + raise ErreurBuild(f"{chemin} : champ 'auteur' manquant") + entrees = donnees.get("documents") + if not isinstance(entrees, list) or not entrees: + raise ErreurBuild(f"{chemin} : liste 'documents' manquante ou vide") + + documents: list[Document] = [] + for numero, entree in enumerate(entrees, start=1): + documents.append(_lire_document(entree, f"{chemin}, document {numero}", racine)) + + vus: set[str] = set() + for document in documents: + if document.fichier in vus: + raise ErreurBuild(f"{chemin} : '{document.fichier}' déclaré deux fois") + vus.add(document.fichier) + return Catalogue(auteur, documents) + + +def _lire_document(entree: object, contexte: str, racine: Path) -> Document: + if not isinstance(entree, dict): + raise ErreurBuild(f"{contexte} : un dictionnaire est attendu") + inconnus = sorted(set(entree) - CHAMPS_DOCUMENT) + if inconnus: + raise ErreurBuild(f"{contexte} : champ(s) inconnu(s) : {', '.join(inconnus)}") + for champ in ("fichier", "titre", "sources"): + if not entree.get(champ): + raise ErreurBuild(f"{contexte} : champ '{champ}' manquant") + + fichier = entree["fichier"] + if not fichier.endswith(".pdf") or "/" in fichier: + raise ErreurBuild(f"{contexte} : '{fichier}' doit se terminer par .pdf, sans dossier") + sources = [] + for source in entree["sources"]: + chemin = racine / source + if not chemin.is_file(): + raise ErreurBuild(f"{contexte} : source introuvable : {source}") + sources.append(chemin) + return Document(fichier, entree["titre"], tuple(sources), entree.get("sous_titre", "")) diff --git a/pdf/tests/test_documents.py b/pdf/tests/test_documents.py new file mode 100644 index 0000000..6330f55 --- /dev/null +++ b/pdf/tests/test_documents.py @@ -0,0 +1,58 @@ +"""Tests du chargement de documents.yaml.""" + +import re + +import pytest + +import build + +VALIDE = """\ +auteur: Auteur +documents: + - fichier: doc.pdf + titre: Mon titre + sources: [a.md] +""" + + +def ecrire(tmp_path, contenu): + (tmp_path / "a.md").write_text("# Titre\n", encoding="utf-8") + chemin = tmp_path / "documents.yaml" + chemin.write_text(contenu, encoding="utf-8") + return chemin + + +def test_charge_un_catalogue_valide(tmp_path): + catalogue = build.charger_documents(ecrire(tmp_path, VALIDE), tmp_path) + + assert catalogue.auteur == "Auteur" + assert catalogue.documents == [build.Document("doc.pdf", "Mon titre", (tmp_path / "a.md",))] + + +def test_lit_le_sous_titre_optionnel(tmp_path): + contenu = VALIDE.replace( + " titre: Mon titre\n", " titre: Mon titre\n sous_titre: Sous\n" + ) + + catalogue = build.charger_documents(ecrire(tmp_path, contenu), tmp_path) + + assert catalogue.documents[0].sous_titre == "Sous" + + +@pytest.mark.parametrize( + ("contenu", "message"), + [ + ("auteur: [", "YAML invalide"), + ("documents: []\n", "auteur"), + ("auteur: A\n", "documents"), + ("auteur: A\ndocuments: []\n", "documents"), + (VALIDE.replace(" titre: Mon titre\n", ""), "champ 'titre' manquant"), + (VALIDE.replace("[a.md]", "[absent.md]"), "source introuvable : absent.md"), + (VALIDE.replace("doc.pdf", "doc.txt"), "doit se terminer par .pdf"), + (VALIDE + " couleur: rouge\n", "champ(s) inconnu(s) : couleur"), + (VALIDE + VALIDE.split("documents:\n")[1], "déclaré deux fois"), + ], +) +def test_refuse_un_catalogue_invalide(tmp_path, contenu, message): + with pytest.raises(build.ErreurBuild, match=re.escape(message)): + build.charger_documents(ecrire(tmp_path, contenu), tmp_path) From 0d33a2b485135e3b15d3d018508d821d4e3a0bd8 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:21:44 +0200 Subject: [PATCH 08/32] feat: extraction des titres de chapitre via l'AST pandoc Co-Authored-By: Claude Opus 5.5 --- pdf/build.py | 39 +++++++++++++++++++++++++++++++++++++++ pdf/tests/test_titres.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 pdf/tests/test_titres.py diff --git a/pdf/build.py b/pdf/build.py index 04de970..24553da 100644 --- a/pdf/build.py +++ b/pdf/build.py @@ -6,6 +6,8 @@ from __future__ import annotations +import json +import subprocess from dataclasses import dataclass from pathlib import Path @@ -14,6 +16,7 @@ RACINE = Path(__file__).resolve().parent.parent DOSSIER_PDF = Path(__file__).resolve().parent CHAMPS_DOCUMENT = {"fichier", "titre", "sous_titre", "sources"} +FORMAT_MARKDOWN = "markdown+lists_without_preceding_blankline" class ErreurBuild(Exception): @@ -82,3 +85,39 @@ def _lire_document(entree: object, contexte: str, racine: Path) -> Document: raise ErreurBuild(f"{contexte} : source introuvable : {source}") sources.append(chemin) return Document(fichier, entree["titre"], tuple(sources), entree.get("sous_titre", "")) + + +def executer(commande: list[str], entree: str | None = None, cwd: Path | None = None) -> str: + """Lance une commande, renvoie sa sortie ; lève ErreurBuild en cas d'échec.""" + resultat = subprocess.run( + commande, input=entree, capture_output=True, text=True, cwd=cwd, check=False + ) + if resultat.returncode != 0: + raise ErreurBuild(f"échec de : {' '.join(commande)}\n{resultat.stderr.strip()}") + return resultat.stdout + + +def titres_niveau1(source: Path) -> list[str]: + """Titres de niveau 1 d'un fichier Markdown, tels que pandoc les rendra.""" + ast = json.loads(executer(["pandoc", "-f", FORMAT_MARKDOWN, "-t", "json", str(source)])) + titres = [] + for bloc in ast["blocks"]: + if bloc["t"] == "Header" and bloc["c"][0] == 1: + titre = { + "pandoc-api-version": ast["pandoc-api-version"], + "meta": {}, + "blocks": [{"t": "Plain", "c": bloc["c"][2]}], + } + texte = executer( + ["pandoc", "-f", "json", "-t", "plain", "--wrap=none"], entree=json.dumps(titre) + ) + titres.append(texte.strip()) + return titres + + +def titre_chapitre(source: Path) -> str: + """Titre unique d'un fichier source (règle : exactement un titre de niveau 1).""" + titres = titres_niveau1(source) + if len(titres) != 1: + raise ErreurBuild(f"{source} : {len(titres)} titre(s) de niveau 1, exactement 1 attendu") + return titres[0] diff --git a/pdf/tests/test_titres.py b/pdf/tests/test_titres.py new file mode 100644 index 0000000..4bbd7b2 --- /dev/null +++ b/pdf/tests/test_titres.py @@ -0,0 +1,31 @@ +"""Tests de l'extraction des titres de niveau 1 (via pandoc).""" + +import pytest + +import build + + +def source(tmp_path, contenu): + chemin = tmp_path / "chapitre.md" + chemin.write_text(contenu, encoding="utf-8") + return chemin + + +def test_extrait_le_titre_avec_la_typographie_de_pandoc(tmp_path): + chemin = source( + tmp_path, + "# Chapitre 1.1 — L'été `ls`\n\n## Section\n\n```\n# pas un titre\n```\n", + ) + + assert build.titre_chapitre(chemin) == "Chapitre 1.1 — L’été ls" + + +@pytest.mark.parametrize("contenu", ["## Section seule\n", "# Un\n\n# Deux\n"]) +def test_refuse_zero_ou_plusieurs_titres_de_niveau_1(tmp_path, contenu): + with pytest.raises(build.ErreurBuild, match="exactement 1 attendu"): + build.titre_chapitre(source(tmp_path, contenu)) + + +def test_signale_une_commande_en_echec(): + with pytest.raises(build.ErreurBuild, match="échec de : false"): + build.executer(["false"]) From aba66b6321ddd7a919e56f7afb9f2d45c8f973a3 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:22:21 +0200 Subject: [PATCH 09/32] feat: couverture, commande pandoc et analyse du log LaTeX Co-Authored-By: Claude Opus 5.5 --- pdf/build.py | 100 ++++++++++++++++++++++++++++++++++++++++ pdf/tests/test_latex.py | 68 +++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 pdf/tests/test_latex.py diff --git a/pdf/build.py b/pdf/build.py index 24553da..ca54adf 100644 --- a/pdf/build.py +++ b/pdf/build.py @@ -7,6 +7,7 @@ from __future__ import annotations import json +import re import subprocess from dataclasses import dataclass from pathlib import Path @@ -17,6 +18,19 @@ DOSSIER_PDF = Path(__file__).resolve().parent CHAMPS_DOCUMENT = {"fichier", "titre", "sous_titre", "sources"} FORMAT_MARKDOWN = "markdown+lists_without_preceding_blankline" +LOGO_LICENCE = RACINE / "ressources" / "images" / "licenses" / "cc-by-nc-sa.png" +_ECHAPPEMENTS_LATEX = { + "\\": r"\textbackslash{}", + "&": r"\&", + "%": r"\%", + "$": r"\$", + "#": r"\#", + "_": r"\_", + "{": r"\{", + "}": r"\}", + "~": r"\textasciitilde{}", + "^": r"\textasciicircum{}", +} class ErreurBuild(Exception): @@ -121,3 +135,89 @@ def titre_chapitre(source: Path) -> str: if len(titres) != 1: raise ErreurBuild(f"{source} : {len(titres)} titre(s) de niveau 1, exactement 1 attendu") return titres[0] + + +def echapper_latex(texte: str) -> str: + return "".join(_ECHAPPEMENTS_LATEX.get(caractere, caractere) for caractere in texte) + + +def generer_couverture(document: Document, chapitres: list[str], logo: Path) -> str: + """Page de couverture LaTeX : titre, liste des chapitres, licence.""" + sous_titre = ( + rf"\vspace{{0.5cm}}{{\Large {echapper_latex(document.sous_titre)}\par}}" + if document.sous_titre + else "" + ) + items = "\n".join(rf"\item {echapper_latex(chapitre)}" for chapitre in chapitres) + return rf"""\begin{{titlepage}} +\centering +\vspace*{{3cm}} +{{\Large Formation Linux\par}} +\vspace{{1cm}} +{{\Huge\bfseries {echapper_latex(document.titre)}\par}} +{sous_titre} +\vspace{{2cm}} +\begin{{minipage}}{{0.8\textwidth}} +\begin{{itemize}} +{items} +\end{{itemize}} +\end{{minipage}} +\vfill +\includegraphics[width=3cm]{{{logo}}}\par +\vspace{{0.3cm}} +{{\small Ce document est mis à disposition selon les termes de la licence +Creative Commons Attribution -- Pas d'Utilisation Commerciale -- Partage dans +les Mêmes Conditions 4.0 International (CC BY-NC-SA 4.0).\par}} +\end{{titlepage}} +""" + + +def commande_pandoc(document: Document, auteur: str, couverture: Path, sortie: Path) -> list[str]: + """Commande pandoc Markdown -> LaTeX autonome pour un document.""" + return [ + "pandoc", + "-f", + FORMAT_MARKDOWN, + "--standalone", + "--output", + str(sortie), + "--top-level-division=chapter", + "--toc", + "--toc-depth=2", + "--syntax-highlighting=tango", + "-V", + "documentclass=report", + "-V", + "papersize=a4", + "-V", + "geometry:margin=2.2cm", + "-V", + "lang=fr", + "-V", + "monofont=DejaVu Sans Mono", + "-V", + "monofontoptions=Scale=0.85", + "-V", + f"title-meta={document.titre}", + "-V", + f"author-meta={auteur}", + "--include-in-header", + str(DOSSIER_PDF / "preambule.tex"), + "--include-before-body", + str(couverture), + *[str(source) for source in document.sources], + ] + + +def erreurs_latex(log: str) -> list[str]: + """Lignes d'erreur d'un log LaTeX (avec deux lignes de contexte), 30 au plus.""" + lignes = log.splitlines() + erreurs: list[str] = [] + for index, ligne in enumerate(lignes): + if ( + ligne.startswith("!") + or "Missing character" in ligne + or re.match(r"^\S+\.tex:\d+: ", ligne) + ): + erreurs.extend(lignes[index : index + 3]) + return erreurs[:30] diff --git a/pdf/tests/test_latex.py b/pdf/tests/test_latex.py new file mode 100644 index 0000000..4941cef --- /dev/null +++ b/pdf/tests/test_latex.py @@ -0,0 +1,68 @@ +"""Tests de la partie LaTeX : échappement, couverture, commande, log.""" + +from pathlib import Path + +import build + +DOCUMENT = build.Document("doc.pdf", "Module 2 — Navigation", (Path("/s/a.md"), Path("/s/b.md"))) + + +def test_echappe_les_caracteres_speciaux_latex(): + assert build.echapper_latex(r"a_b & 50% #1 {x} ~ ^ \ $") == ( + r"a\_b \& 50\% \#1 \{x\} \textasciitilde{} \textasciicircum{} " + r"\textbackslash{} \$" + ) + + +def test_couverture_contient_titre_chapitres_et_licence(): + couverture = build.generer_couverture( + DOCUMENT, ["Chapitre 2.1 — Les_chemins", "Chapitre 2.2 — Explorer"], Path("/logo.png") + ) + + assert r"\begin{titlepage}" in couverture + assert "Module 2 — Navigation" in couverture + assert couverture.count(r"\item ") == 2 + assert r"Les\_chemins" in couverture + assert r"\includegraphics[width=3cm]{/logo.png}" in couverture + assert "CC BY-NC-SA 4.0" in couverture + + +def test_couverture_avec_sous_titre(): + document = build.Document("doc.pdf", "Titre", (Path("/s/a.md"),), "Le sous-titre") + + assert "Le sous-titre" in build.generer_couverture(document, ["Chap"], Path("/logo.png")) + + +def test_commande_pandoc(): + commande = build.commande_pandoc(DOCUMENT, "Auteur", Path("/d/couv.tex"), Path("/d/doc.tex")) + + assert commande[:3] == ["pandoc", "-f", build.FORMAT_MARKDOWN] + assert commande[-2:] == ["/s/a.md", "/s/b.md"] + for attendu in ( + "lang=fr", + "documentclass=report", + "title-meta=Module 2 — Navigation", + "author-meta=Auteur", + str(build.DOSSIER_PDF / "preambule.tex"), + "/d/couv.tex", + "/d/doc.tex", + ): + assert attendu in commande + + +def test_erreurs_latex_detecte_caractere_manquant_et_erreur_fatale(): + log = ( + "ligne normale\n" + "Missing character: There is no ✅ (U+2705) in font [lmroman10-regular]\n" + "suite\n" + "./doc.tex:78: ==> Fatal error occurred, no output PDF file produced!\n" + ) + + erreurs = build.erreurs_latex(log) + + assert any("U+2705" in ligne for ligne in erreurs) + assert any("Fatal error" in ligne for ligne in erreurs) + + +def test_erreurs_latex_vide_pour_un_log_propre(): + assert build.erreurs_latex("This is LuaHBTeX\nOutput written on doc.pdf\n") == [] From 8c6d9826ed91836cc0c6bf571d79f96959953660 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:26:44 +0200 Subject: [PATCH 10/32] refactor: renommer pdf/build.py en pdf/generer.py Co-Authored-By: Claude Opus 5.5 --- pdf/build | 4 ++-- pdf/{build.py => generer.py} | 0 pdf/tests/test_documents.py | 12 ++++++------ pdf/tests/test_latex.py | 22 +++++++++++----------- pdf/tests/test_titres.py | 12 ++++++------ 5 files changed, 25 insertions(+), 25 deletions(-) rename pdf/{build.py => generer.py} (100%) diff --git a/pdf/build b/pdf/build index 333bec6..38081a7 100755 --- a/pdf/build +++ b/pdf/build @@ -43,11 +43,11 @@ options_tty=() case "$commande" in tout) - dans_image python3 pdf/build.py + dans_image python3 pdf/generer.py dans_image python3 -m pytest pdf/tests ;; pdf) - dans_image python3 pdf/build.py "$@" + dans_image python3 pdf/generer.py "$@" ;; test) if [ $# -eq 0 ] || [[ "$1" == -* ]]; then set -- pdf/tests "$@"; fi diff --git a/pdf/build.py b/pdf/generer.py similarity index 100% rename from pdf/build.py rename to pdf/generer.py diff --git a/pdf/tests/test_documents.py b/pdf/tests/test_documents.py index 6330f55..aa089e4 100644 --- a/pdf/tests/test_documents.py +++ b/pdf/tests/test_documents.py @@ -4,7 +4,7 @@ import pytest -import build +import generer VALIDE = """\ auteur: Auteur @@ -23,10 +23,10 @@ def ecrire(tmp_path, contenu): def test_charge_un_catalogue_valide(tmp_path): - catalogue = build.charger_documents(ecrire(tmp_path, VALIDE), tmp_path) + catalogue = generer.charger_documents(ecrire(tmp_path, VALIDE), tmp_path) assert catalogue.auteur == "Auteur" - assert catalogue.documents == [build.Document("doc.pdf", "Mon titre", (tmp_path / "a.md",))] + assert catalogue.documents == [generer.Document("doc.pdf", "Mon titre", (tmp_path / "a.md",))] def test_lit_le_sous_titre_optionnel(tmp_path): @@ -34,7 +34,7 @@ def test_lit_le_sous_titre_optionnel(tmp_path): " titre: Mon titre\n", " titre: Mon titre\n sous_titre: Sous\n" ) - catalogue = build.charger_documents(ecrire(tmp_path, contenu), tmp_path) + catalogue = generer.charger_documents(ecrire(tmp_path, contenu), tmp_path) assert catalogue.documents[0].sous_titre == "Sous" @@ -54,5 +54,5 @@ def test_lit_le_sous_titre_optionnel(tmp_path): ], ) def test_refuse_un_catalogue_invalide(tmp_path, contenu, message): - with pytest.raises(build.ErreurBuild, match=re.escape(message)): - build.charger_documents(ecrire(tmp_path, contenu), tmp_path) + with pytest.raises(generer.ErreurBuild, match=re.escape(message)): + generer.charger_documents(ecrire(tmp_path, contenu), tmp_path) diff --git a/pdf/tests/test_latex.py b/pdf/tests/test_latex.py index 4941cef..bd065cd 100644 --- a/pdf/tests/test_latex.py +++ b/pdf/tests/test_latex.py @@ -2,20 +2,20 @@ from pathlib import Path -import build +import generer -DOCUMENT = build.Document("doc.pdf", "Module 2 — Navigation", (Path("/s/a.md"), Path("/s/b.md"))) +DOCUMENT = generer.Document("doc.pdf", "Module 2 — Navigation", (Path("/s/a.md"), Path("/s/b.md"))) def test_echappe_les_caracteres_speciaux_latex(): - assert build.echapper_latex(r"a_b & 50% #1 {x} ~ ^ \ $") == ( + assert generer.echapper_latex(r"a_b & 50% #1 {x} ~ ^ \ $") == ( r"a\_b \& 50\% \#1 \{x\} \textasciitilde{} \textasciicircum{} " r"\textbackslash{} \$" ) def test_couverture_contient_titre_chapitres_et_licence(): - couverture = build.generer_couverture( + couverture = generer.generer_couverture( DOCUMENT, ["Chapitre 2.1 — Les_chemins", "Chapitre 2.2 — Explorer"], Path("/logo.png") ) @@ -28,22 +28,22 @@ def test_couverture_contient_titre_chapitres_et_licence(): def test_couverture_avec_sous_titre(): - document = build.Document("doc.pdf", "Titre", (Path("/s/a.md"),), "Le sous-titre") + document = generer.Document("doc.pdf", "Titre", (Path("/s/a.md"),), "Le sous-titre") - assert "Le sous-titre" in build.generer_couverture(document, ["Chap"], Path("/logo.png")) + assert "Le sous-titre" in generer.generer_couverture(document, ["Chap"], Path("/logo.png")) def test_commande_pandoc(): - commande = build.commande_pandoc(DOCUMENT, "Auteur", Path("/d/couv.tex"), Path("/d/doc.tex")) + commande = generer.commande_pandoc(DOCUMENT, "Auteur", Path("/d/couv.tex"), Path("/d/doc.tex")) - assert commande[:3] == ["pandoc", "-f", build.FORMAT_MARKDOWN] + assert commande[:3] == ["pandoc", "-f", generer.FORMAT_MARKDOWN] assert commande[-2:] == ["/s/a.md", "/s/b.md"] for attendu in ( "lang=fr", "documentclass=report", "title-meta=Module 2 — Navigation", "author-meta=Auteur", - str(build.DOSSIER_PDF / "preambule.tex"), + str(generer.DOSSIER_PDF / "preambule.tex"), "/d/couv.tex", "/d/doc.tex", ): @@ -58,11 +58,11 @@ def test_erreurs_latex_detecte_caractere_manquant_et_erreur_fatale(): "./doc.tex:78: ==> Fatal error occurred, no output PDF file produced!\n" ) - erreurs = build.erreurs_latex(log) + erreurs = generer.erreurs_latex(log) assert any("U+2705" in ligne for ligne in erreurs) assert any("Fatal error" in ligne for ligne in erreurs) def test_erreurs_latex_vide_pour_un_log_propre(): - assert build.erreurs_latex("This is LuaHBTeX\nOutput written on doc.pdf\n") == [] + assert generer.erreurs_latex("This is LuaHBTeX\nOutput written on doc.pdf\n") == [] diff --git a/pdf/tests/test_titres.py b/pdf/tests/test_titres.py index 4bbd7b2..ed76e6a 100644 --- a/pdf/tests/test_titres.py +++ b/pdf/tests/test_titres.py @@ -2,7 +2,7 @@ import pytest -import build +import generer def source(tmp_path, contenu): @@ -17,15 +17,15 @@ def test_extrait_le_titre_avec_la_typographie_de_pandoc(tmp_path): "# Chapitre 1.1 — L'été `ls`\n\n## Section\n\n```\n# pas un titre\n```\n", ) - assert build.titre_chapitre(chemin) == "Chapitre 1.1 — L’été ls" + assert generer.titre_chapitre(chemin) == "Chapitre 1.1 — L’été ls" @pytest.mark.parametrize("contenu", ["## Section seule\n", "# Un\n\n# Deux\n"]) def test_refuse_zero_ou_plusieurs_titres_de_niveau_1(tmp_path, contenu): - with pytest.raises(build.ErreurBuild, match="exactement 1 attendu"): - build.titre_chapitre(source(tmp_path, contenu)) + with pytest.raises(generer.ErreurBuild, match="exactement 1 attendu"): + generer.titre_chapitre(source(tmp_path, contenu)) def test_signale_une_commande_en_echec(): - with pytest.raises(build.ErreurBuild, match="échec de : false"): - build.executer(["false"]) + with pytest.raises(generer.ErreurBuild, match="échec de : false"): + generer.executer(["false"]) From 2992f383dc147abc268df791c5e534cd5700b1bf Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:27:51 +0200 Subject: [PATCH 11/32] fix: corrections de la revue des Tasks 2-4 Co-Authored-By: Claude Opus 5.5 --- pdf/generer.py | 19 ++++++++++-------- pdf/tests/test_documents.py | 3 +++ pdf/tests/test_latex.py | 40 +++++++++++++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/pdf/generer.py b/pdf/generer.py index ca54adf..7ac3250 100644 --- a/pdf/generer.py +++ b/pdf/generer.py @@ -88,6 +88,13 @@ def _lire_document(entree: object, contexte: str, racine: Path) -> Document: for champ in ("fichier", "titre", "sources"): if not entree.get(champ): raise ErreurBuild(f"{contexte} : champ '{champ}' manquant") + for champ in ("fichier", "titre", "sous_titre"): + if not isinstance(entree.get(champ, ""), str): + raise ErreurBuild(f"{contexte} : champ '{champ}' : chaîne attendue") + if not isinstance(entree["sources"], list) or not all( + isinstance(source, str) for source in entree["sources"] + ): + raise ErreurBuild(f"{contexte} : champ 'sources' : liste de chemins attendue") fichier = entree["fichier"] if not fichier.endswith(".pdf") or "/" in fichier: @@ -148,7 +155,7 @@ def generer_couverture(document: Document, chapitres: list[str], logo: Path) -> if document.sous_titre else "" ) - items = "\n".join(rf"\item {echapper_latex(chapitre)}" for chapitre in chapitres) + items = "\n".join(rf"\item{{}} {echapper_latex(chapitre)}" for chapitre in chapitres) return rf"""\begin{{titlepage}} \centering \vspace*{{3cm}} @@ -197,9 +204,9 @@ def commande_pandoc(document: Document, auteur: str, couverture: Path, sortie: P "monofont=DejaVu Sans Mono", "-V", "monofontoptions=Scale=0.85", - "-V", + "-M", f"title-meta={document.titre}", - "-V", + "-M", f"author-meta={auteur}", "--include-in-header", str(DOSSIER_PDF / "preambule.tex"), @@ -214,10 +221,6 @@ def erreurs_latex(log: str) -> list[str]: lignes = log.splitlines() erreurs: list[str] = [] for index, ligne in enumerate(lignes): - if ( - ligne.startswith("!") - or "Missing character" in ligne - or re.match(r"^\S+\.tex:\d+: ", ligne) - ): + if ligne.startswith("!") or "Missing character" in ligne or re.match(r"^\S+:\d+: ", ligne): erreurs.extend(lignes[index : index + 3]) return erreurs[:30] diff --git a/pdf/tests/test_documents.py b/pdf/tests/test_documents.py index aa089e4..e3d3523 100644 --- a/pdf/tests/test_documents.py +++ b/pdf/tests/test_documents.py @@ -51,6 +51,9 @@ def test_lit_le_sous_titre_optionnel(tmp_path): (VALIDE.replace("doc.pdf", "doc.txt"), "doit se terminer par .pdf"), (VALIDE + " couleur: rouge\n", "champ(s) inconnu(s) : couleur"), (VALIDE + VALIDE.split("documents:\n")[1], "déclaré deux fois"), + (VALIDE.replace("doc.pdf", "12"), "champ 'fichier' : chaîne attendue"), + (VALIDE + " sous_titre: 3\n", "champ 'sous_titre' : chaîne attendue"), + (VALIDE.replace("[a.md]", "a.md"), "champ 'sources' : liste de chemins attendue"), ], ) def test_refuse_un_catalogue_invalide(tmp_path, contenu, message): diff --git a/pdf/tests/test_latex.py b/pdf/tests/test_latex.py index bd065cd..703a7e6 100644 --- a/pdf/tests/test_latex.py +++ b/pdf/tests/test_latex.py @@ -21,12 +21,18 @@ def test_couverture_contient_titre_chapitres_et_licence(): assert r"\begin{titlepage}" in couverture assert "Module 2 — Navigation" in couverture - assert couverture.count(r"\item ") == 2 + assert couverture.count(r"\item{} ") == 2 assert r"Les\_chemins" in couverture assert r"\includegraphics[width=3cm]{/logo.png}" in couverture assert "CC BY-NC-SA 4.0" in couverture +def test_couverture_protege_un_chapitre_commencant_par_un_crochet(): + couverture = generer.generer_couverture(DOCUMENT, ["[Annexe] Installation"], Path("/l.png")) + + assert r"\item{} [Annexe] Installation" in couverture + + def test_couverture_avec_sous_titre(): document = generer.Document("doc.pdf", "Titre", (Path("/s/a.md"),), "Le sous-titre") @@ -41,13 +47,30 @@ def test_commande_pandoc(): for attendu in ( "lang=fr", "documentclass=report", - "title-meta=Module 2 — Navigation", - "author-meta=Auteur", str(generer.DOSSIER_PDF / "preambule.tex"), "/d/couv.tex", "/d/doc.tex", ): assert attendu in commande + for meta in ("title-meta=Module 2 — Navigation", "author-meta=Auteur"): + assert commande[commande.index(meta) - 1] == "-M" + + +def test_commande_pandoc_echappe_titre_et_auteur_dans_les_metadonnees(tmp_path): + source = tmp_path / "a.md" + source.write_text("# Chapitre\n", encoding="utf-8") + vide = tmp_path / "vide.tex" + vide.write_text("", encoding="utf-8") + sortie = tmp_path / "doc.tex" + document = generer.Document("doc.pdf", "Git & GitHub 100% a_b", (source,)) + commande = generer.commande_pandoc(document, "A & B", vide, sortie) + commande[commande.index(str(generer.DOSSIER_PDF / "preambule.tex"))] = str(vide) + + generer.executer(commande) + + tex = sortie.read_text(encoding="utf-8") + assert r"pdftitle={Git \& GitHub 100\% a\_b}" in tex + assert r"pdfauthor={A \& B}" in tex def test_erreurs_latex_detecte_caractere_manquant_et_erreur_fatale(): @@ -55,7 +78,7 @@ def test_erreurs_latex_detecte_caractere_manquant_et_erreur_fatale(): "ligne normale\n" "Missing character: There is no ✅ (U+2705) in font [lmroman10-regular]\n" "suite\n" - "./doc.tex:78: ==> Fatal error occurred, no output PDF file produced!\n" + "! ==> Fatal error occurred, no output PDF file produced!\n" ) erreurs = generer.erreurs_latex(log) @@ -64,5 +87,14 @@ def test_erreurs_latex_detecte_caractere_manquant_et_erreur_fatale(): assert any("Fatal error" in ligne for ligne in erreurs) +def test_erreurs_latex_detecte_une_erreur_dans_un_paquet(): + log = ( + "/opt/texlive/texdir/texmf-dist/tex/latex/fontspec/fontspec.sty:123: " + 'Package fontspec Error: The font "Absente" cannot be found.\n' + ) + + assert any("fontspec Error" in ligne for ligne in generer.erreurs_latex(log)) + + def test_erreurs_latex_vide_pour_un_log_propre(): assert generer.erreurs_latex("This is LuaHBTeX\nOutput written on doc.pdf\n") == [] From 8fcb922bbff6cfede4880265436ef2398de08c3d Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:28:20 +0200 Subject: [PATCH 12/32] =?UTF-8?q?docs:=20plan=20align=C3=A9=20sur=20genere?= =?UTF-8?q?r.py=20et=20-M,=20Tasks=201=20=C3=A0=204=20termin=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../plans/2026-10-01-chaine-pdf-2026.md | 116 +++++++++--------- .../2026-10-01-chaine-pdf-2026.md.tasks.json | 10 +- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md index e6b19cc..0b5416d 100644 --- a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md +++ b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md @@ -7,7 +7,7 @@ local et en CI, qui produit les 11 PDF de la formation et les vérifie par des tests. **Architecture :** une image Docker figée (`pandoc/extra` Debian + poppler, -shellcheck, outils Python épinglés) exécute `pdf/build.py`. Ce script lit +shellcheck, outils Python épinglés) exécute `pdf/generer.py`. Ce script lit `pdf/documents.yaml`, génère pour chaque PDF une couverture LaTeX, appelle pandoc (Markdown -> `.tex`) puis `latexmk -lualatex`. Le script shell `pdf/build` est le point d'entrée unique ; la CI l'appelle tel quel. @@ -56,7 +56,7 @@ Ces points ont été testés sur un prototype ; ne pas les re-découvrir. - Pas de `template.tex` complet : on garde le template LaTeX par défaut de pandoc (maintenu et testé par pandoc) et on ajoute `pdf/preambule.tex` - (`-H`) et une couverture générée par `build.py` (`-B`). Moins de code + (`-H`) et une couverture générée par `generer.py` (`-B`). Moins de code LaTeX à maintenir. - `pdf/build` prend des sous-commandes (`pdf`, `test`, `check`, `shell`) plutôt que des options `--no-test` / `--check` : plus lisible, et la CI @@ -168,11 +168,11 @@ if [ $# -gt 0 ]; then shift; fi case "$commande" in tout) - dans_image python3 pdf/build.py + dans_image python3 pdf/generer.py dans_image python3 -m pytest pdf/tests ;; pdf) - dans_image python3 pdf/build.py "$@" + dans_image python3 pdf/generer.py "$@" ;; test) if [ $# -eq 0 ]; then set -- pdf/tests; fi @@ -237,7 +237,7 @@ git commit -m "feat: image Docker et point d'entrée de la chaîne PDF 2026" **Files :** - Create : `pdf/tests/test_documents.py` -- Create : `pdf/build.py` +- Create : `pdf/generer.py` **Step 1 : écrire le test qui échoue** @@ -250,7 +250,7 @@ import re import pytest -import build +import generer VALIDE = """\ auteur: Auteur @@ -269,18 +269,18 @@ def ecrire(tmp_path, contenu): def test_charge_un_catalogue_valide(tmp_path): - catalogue = build.charger_documents(ecrire(tmp_path, VALIDE), tmp_path) + catalogue = generer.charger_documents(ecrire(tmp_path, VALIDE), tmp_path) assert catalogue.auteur == "Auteur" assert catalogue.documents == [ - build.Document("doc.pdf", "Mon titre", (tmp_path / "a.md",)) + generer.Document("doc.pdf", "Mon titre", (tmp_path / "a.md",)) ] def test_lit_le_sous_titre_optionnel(tmp_path): contenu = VALIDE.replace(" titre: Mon titre\n", " titre: Mon titre\n sous_titre: Sous\n") - catalogue = build.charger_documents(ecrire(tmp_path, contenu), tmp_path) + catalogue = generer.charger_documents(ecrire(tmp_path, contenu), tmp_path) assert catalogue.documents[0].sous_titre == "Sous" @@ -300,8 +300,8 @@ def test_lit_le_sous_titre_optionnel(tmp_path): ], ) def test_refuse_un_catalogue_invalide(tmp_path, contenu, message): - with pytest.raises(build.ErreurBuild, match=re.escape(message)): - build.charger_documents(ecrire(tmp_path, contenu), tmp_path) + with pytest.raises(generer.ErreurBuild, match=re.escape(message)): + generer.charger_documents(ecrire(tmp_path, contenu), tmp_path) ``` **Step 2 : lancer le test pour vérifier qu'il échoue** @@ -311,7 +311,7 @@ Expected : FAIL, `ModuleNotFoundError: No module named 'build'`. **Step 3 : écrire l'implémentation minimale** -`pdf/build.py` : +`pdf/generer.py` : ```python """Génération des PDF de la formation Linux. @@ -412,7 +412,7 @@ puis relancer). **Step 5 : commit** ```bash -git add pdf/build.py pdf/tests/test_documents.py +git add pdf/generer.py pdf/tests/test_documents.py git commit -m "feat: chargement et validation du catalogue des PDF" ``` @@ -422,7 +422,7 @@ git commit -m "feat: chargement et validation du catalogue des PDF" **Files :** - Create : `pdf/tests/test_titres.py` -- Modify : `pdf/build.py` +- Modify : `pdf/generer.py` **Step 1 : écrire le test qui échoue** @@ -433,7 +433,7 @@ git commit -m "feat: chargement et validation du catalogue des PDF" import pytest -import build +import generer def source(tmp_path, contenu): @@ -448,18 +448,18 @@ def test_extrait_le_titre_avec_la_typographie_de_pandoc(tmp_path): "# Chapitre 1.1 — L'été `ls`\n\n## Section\n\n```\n# pas un titre\n```\n", ) - assert build.titre_chapitre(chemin) == "Chapitre 1.1 — L’été ls" + assert generer.titre_chapitre(chemin) == "Chapitre 1.1 — L’été ls" @pytest.mark.parametrize("contenu", ["## Section seule\n", "# Un\n\n# Deux\n"]) def test_refuse_zero_ou_plusieurs_titres_de_niveau_1(tmp_path, contenu): - with pytest.raises(build.ErreurBuild, match="exactement 1 attendu"): - build.titre_chapitre(source(tmp_path, contenu)) + with pytest.raises(generer.ErreurBuild, match="exactement 1 attendu"): + generer.titre_chapitre(source(tmp_path, contenu)) def test_signale_une_commande_en_echec(): - with pytest.raises(build.ErreurBuild, match="échec de : false"): - build.executer(["false"]) + with pytest.raises(generer.ErreurBuild, match="échec de : false"): + generer.executer(["false"]) ``` **Step 2 : lancer le test pour vérifier qu'il échoue** @@ -467,7 +467,7 @@ def test_signale_une_commande_en_echec(): Run : `./pdf/build test pdf/tests/test_titres.py -v` Expected : FAIL, `AttributeError: module 'build' has no attribute 'titre_chapitre'`. -**Step 3 : implémenter** (ajouts à `pdf/build.py`) +**Step 3 : implémenter** (ajouts à `pdf/generer.py`) En tête, compléter les imports : @@ -535,7 +535,7 @@ Expected : propre. **Step 5 : commit** ```bash -git add pdf/build.py pdf/tests/test_titres.py +git add pdf/generer.py pdf/tests/test_titres.py git commit -m "feat: extraction des titres de chapitre via l'AST pandoc" ``` @@ -545,7 +545,7 @@ git commit -m "feat: extraction des titres de chapitre via l'AST pandoc" **Files :** - Create : `pdf/tests/test_latex.py` -- Modify : `pdf/build.py` +- Modify : `pdf/generer.py` **Step 1 : écrire le test qui échoue** @@ -556,20 +556,20 @@ git commit -m "feat: extraction des titres de chapitre via l'AST pandoc" from pathlib import Path -import build +import generer -DOCUMENT = build.Document("doc.pdf", "Module 2 — Navigation", (Path("/s/a.md"), Path("/s/b.md"))) +DOCUMENT = generer.Document("doc.pdf", "Module 2 — Navigation", (Path("/s/a.md"), Path("/s/b.md"))) def test_echappe_les_caracteres_speciaux_latex(): - assert build.echapper_latex(r"a_b & 50% #1 {x} ~ ^ \ $") == ( + assert generer.echapper_latex(r"a_b & 50% #1 {x} ~ ^ \ $") == ( r"a\_b \& 50\% \#1 \{x\} \textasciitilde{} \textasciicircum{} " r"\textbackslash{} \$" ) def test_couverture_contient_titre_chapitres_et_licence(): - couverture = build.generer_couverture( + couverture = generer.generer_couverture( DOCUMENT, ["Chapitre 2.1 — Les_chemins", "Chapitre 2.2 — Explorer"], Path("/logo.png") ) @@ -582,22 +582,22 @@ def test_couverture_contient_titre_chapitres_et_licence(): def test_couverture_avec_sous_titre(): - document = build.Document("doc.pdf", "Titre", (Path("/s/a.md"),), "Le sous-titre") + document = generer.Document("doc.pdf", "Titre", (Path("/s/a.md"),), "Le sous-titre") - assert "Le sous-titre" in build.generer_couverture(document, ["Chap"], Path("/logo.png")) + assert "Le sous-titre" in generer.generer_couverture(document, ["Chap"], Path("/logo.png")) def test_commande_pandoc(): - commande = build.commande_pandoc(DOCUMENT, "Auteur", Path("/d/couv.tex"), Path("/d/doc.tex")) + commande = generer.commande_pandoc(DOCUMENT, "Auteur", Path("/d/couv.tex"), Path("/d/doc.tex")) - assert commande[:3] == ["pandoc", "-f", build.FORMAT_MARKDOWN] + assert commande[:3] == ["pandoc", "-f", generer.FORMAT_MARKDOWN] assert commande[-2:] == ["/s/a.md", "/s/b.md"] for attendu in ( "lang=fr", "documentclass=report", "title-meta=Module 2 — Navigation", - "author-meta=Auteur", - str(build.DOSSIER_PDF / "preambule.tex"), + "author-meta=Auteur", # passés par -M (échappés par pandoc) + str(generer.DOSSIER_PDF / "preambule.tex"), "/d/couv.tex", "/d/doc.tex", ): @@ -612,14 +612,14 @@ def test_erreurs_latex_detecte_caractere_manquant_et_erreur_fatale(): "./doc.tex:78: ==> Fatal error occurred, no output PDF file produced!\n" ) - erreurs = build.erreurs_latex(log) + erreurs = generer.erreurs_latex(log) assert any("U+2705" in ligne for ligne in erreurs) assert any("Fatal error" in ligne for ligne in erreurs) def test_erreurs_latex_vide_pour_un_log_propre(): - assert build.erreurs_latex("This is LuaHBTeX\nOutput written on doc.pdf\n") == [] + assert generer.erreurs_latex("This is LuaHBTeX\nOutput written on doc.pdf\n") == [] ``` **Step 2 : lancer le test pour vérifier qu'il échoue** @@ -627,7 +627,7 @@ def test_erreurs_latex_vide_pour_un_log_propre(): Run : `./pdf/build test pdf/tests/test_latex.py -v` Expected : FAIL, `AttributeError: module 'build' has no attribute 'echapper_latex'`. -**Step 3 : implémenter** (ajouts à `pdf/build.py`) +**Step 3 : implémenter** (ajouts à `pdf/generer.py`) Import supplémentaire : `import re`. @@ -712,9 +712,9 @@ def commande_pandoc(document: Document, auteur: str, couverture: Path, sortie: P "monofont=DejaVu Sans Mono", "-V", "monofontoptions=Scale=0.85", - "-V", + "-M", f"title-meta={document.titre}", - "-V", + "-M", f"author-meta={auteur}", "--include-in-header", str(DOSSIER_PDF / "preambule.tex"), @@ -746,7 +746,7 @@ Expected : tous PASS, lint propre. **Step 5 : commit** ```bash -git add pdf/build.py pdf/tests/test_latex.py +git add pdf/generer.py pdf/tests/test_latex.py git commit -m "feat: couverture, commande pandoc et analyse du log LaTeX" ``` @@ -757,7 +757,7 @@ git commit -m "feat: couverture, commande pandoc et analyse du log LaTeX" **Files :** - Create : `pdf/preambule.tex` - Create : `pdf/documents.yaml` (première entrée seulement) -- Modify : `pdf/build.py` (compilation et `main`) +- Modify : `pdf/generer.py` (compilation et `main`) **Step 1 : écrire `pdf/preambule.tex`** @@ -803,7 +803,7 @@ documents: - cours_2026/module_02_navigation/03_types_fichiers_liens.md ``` -**Step 3 : implémenter la compilation et `main`** (ajouts à `pdf/build.py`) +**Step 3 : implémenter la compilation et `main`** (ajouts à `pdf/generer.py`) Imports supplémentaires : `import argparse`, `import shutil`, `import sys`. @@ -887,13 +887,13 @@ if __name__ == "__main__": ```python def test_selectionne_les_documents_demandes(): - a = build.Document("a.pdf", "A", ()) - b = build.Document("b.pdf", "B", ()) + a = generer.Document("a.pdf", "A", ()) + b = generer.Document("b.pdf", "B", ()) - assert build.selectionner([a, b], []) == [a, b] - assert build.selectionner([a, b], ["b.pdf"]) == [b] - with pytest.raises(build.ErreurBuild, match="inconnu"): - build.selectionner([a, b], ["c.pdf"]) + assert generer.selectionner([a, b], []) == [a, b] + assert generer.selectionner([a, b], ["b.pdf"]) == [b] + with pytest.raises(generer.ErreurBuild, match="inconnu"): + generer.selectionner([a, b], ["c.pdf"]) ``` Run : `./pdf/build test pdf/tests -v` @@ -923,12 +923,12 @@ vérifier : couverture (titre, 3 chapitres, logo et licence en bas), sommaire sur sa propre page, chapitre 2.1 sur une nouvelle page sans numéro parasite devant « Chapitre 2.1 », encadré « Objectifs » avec une vraie liste à puces, blocs de code sur fond gris, en-tête et numéro de page. -Corriger `preambule.tex` ou `build.py` tant que ce n'est pas le cas. +Corriger `preambule.tex` ou `generer.py` tant que ce n'est pas le cas. **Step 7 : commit** ```bash -git add pdf/preambule.tex pdf/documents.yaml pdf/build.py pdf/tests/test_documents.py +git add pdf/preambule.tex pdf/documents.yaml pdf/generer.py pdf/tests/test_documents.py git commit -m "feat: compilation LuaLaTeX et premier PDF (module 2)" ``` @@ -961,18 +961,18 @@ ATTENDUS = [ def test_le_catalogue_du_depot_declare_les_11_pdf(): - catalogue = build.charger_documents(build.DOSSIER_PDF / "documents.yaml", build.RACINE) + catalogue = generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) assert [document.fichier for document in catalogue.documents] == ATTENDUS def test_chaque_chapitre_de_cours_2026_est_dans_un_pdf(): - catalogue = build.charger_documents(build.DOSSIER_PDF / "documents.yaml", build.RACINE) + catalogue = generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) declares = {source for document in catalogue.documents for source in document.sources} oublies = sorted( - str(chemin.relative_to(build.RACINE)) - for chemin in (build.RACINE / "cours_2026").rglob("*.md") + str(chemin.relative_to(generer.RACINE)) + for chemin in (generer.RACINE / "cours_2026").rglob("*.md") if chemin not in declares ) assert oublies == [] @@ -1088,10 +1088,10 @@ import subprocess import pypdf import pytest -import build +import generer -SORTIE = build.RACINE / "build" / "pdf" -CATALOGUE = build.charger_documents(build.DOSSIER_PDF / "documents.yaml", build.RACINE) +SORTIE = generer.RACINE / "build" / "pdf" +CATALOGUE = generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) CARACTERES_FRANCAIS = set("éèêëàâçùûüîïôœÉÈÊÀÇÔ«»") SEUIL_DEBORDEMENT_PT = 20.0 @@ -1128,7 +1128,7 @@ def test_metadonnees(document, lecteur): def test_signets_de_niveau_1_dans_l_ordre_des_sources(document, lecteur): signets = [entree.title for entree in lecteur.outline if not isinstance(entree, list)] - assert signets == [build.titre_chapitre(source) for source in document.sources] + assert signets == [generer.titre_chapitre(source) for source in document.sources] def test_caracteres_francais_preserves(document): @@ -1343,7 +1343,7 @@ git commit -m "refactor: supprimer l'ancienne chaîne PDF (scripts et workflows) ``` pdf/ (chaîne de génération PDF) build (point d'entrée unique) - build.py + generer.py documents.yaml (catalogue des PDF) preambule.tex Dockerfile diff --git a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json index 9d64bd1..f06e3d0 100644 --- a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json +++ b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json @@ -4,12 +4,12 @@ { "id": 0, "subject": "Task 1: Squelette de l'image et du point d'entrée", - "status": "pending" + "status": "completed" }, { "id": 1, "subject": "Task 2: Chargement et validation de documents.yaml", - "status": "pending", + "status": "completed", "blockedBy": [ 0 ] @@ -17,7 +17,7 @@ { "id": 2, "subject": "Task 3: Extraction des titres de chapitre via pandoc", - "status": "pending", + "status": "completed", "blockedBy": [ 1 ] @@ -25,7 +25,7 @@ { "id": 3, "subject": "Task 4: Couverture, commande pandoc et analyse du log LaTeX", - "status": "pending", + "status": "completed", "blockedBy": [ 2 ] @@ -87,5 +87,5 @@ ] } ], - "lastUpdated": "2026-10-01T17:13:48+02:00" + "lastUpdated": "2026-10-01T17:28:20+02:00" } \ No newline at end of file From 4682466b2f41349898e7780cda2ef05df6ba4e58 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:31:15 +0200 Subject: [PATCH 13/32] feat: compilation LuaLaTeX et premier PDF (module 2) Co-Authored-By: Claude Opus 5.5 --- pdf/documents.yaml | 11 ++++++ pdf/generer.py | 75 +++++++++++++++++++++++++++++++++++++ pdf/preambule.tex | 23 ++++++++++++ pdf/tests/test_documents.py | 10 +++++ 4 files changed, 119 insertions(+) create mode 100644 pdf/documents.yaml create mode 100644 pdf/preambule.tex diff --git a/pdf/documents.yaml b/pdf/documents.yaml new file mode 100644 index 0000000..0bad219 --- /dev/null +++ b/pdf/documents.yaml @@ -0,0 +1,11 @@ +# Catalogue des PDF de la formation : un document par entrée. +# Les chemins des sources sont relatifs à la racine du dépôt. +auteur: Pascal Guinet + +documents: + - fichier: module_02_navigation.pdf + titre: Module 2 — Navigation + sources: + - cours_2026/module_02_navigation/01_arborescence_chemins.md + - cours_2026/module_02_navigation/02_se_deplacer_explorer.md + - cours_2026/module_02_navigation/03_types_fichiers_liens.md diff --git a/pdf/generer.py b/pdf/generer.py index 7ac3250..505513a 100644 --- a/pdf/generer.py +++ b/pdf/generer.py @@ -6,9 +6,12 @@ from __future__ import annotations +import argparse import json import re +import shutil import subprocess +import sys from dataclasses import dataclass from pathlib import Path @@ -224,3 +227,75 @@ def erreurs_latex(log: str) -> list[str]: if ligne.startswith("!") or "Missing character" in ligne or re.match(r"^\S+:\d+: ", ligne): erreurs.extend(lignes[index : index + 3]) return erreurs[:30] + + +def compiler(tex: Path) -> Path: + """Compile un .tex avec latexmk/LuaLaTeX ; lève ErreurBuild au moindre problème.""" + resultat = subprocess.run( + [ + "latexmk", + "-lualatex", + "-interaction=nonstopmode", + "-halt-on-error", + "-file-line-error", + tex.name, + ], + cwd=tex.parent, + capture_output=True, + text=True, + check=False, + ) + log = tex.with_suffix(".log") + texte_log = log.read_text(encoding="utf-8", errors="replace") if log.exists() else "" + erreurs = erreurs_latex(texte_log) + if resultat.returncode != 0 or erreurs: + detail = "\n".join(erreurs) or "\n".join(resultat.stdout.splitlines()[-30:]) + raise ErreurBuild(f"échec LaTeX pour {tex.name} (log complet : {log})\n{detail}") + return tex.with_suffix(".pdf") + + +def construire(document: Document, auteur: str, sortie: Path) -> Path: + """Produit sortie/ ; les intermédiaires restent dans sortie/debug/.""" + debug = sortie / "debug" + debug.mkdir(parents=True, exist_ok=True) + nom = Path(document.fichier).stem + chapitres = [titre_chapitre(source) for source in document.sources] + couverture = debug / f"{nom}-couverture.tex" + couverture.write_text(generer_couverture(document, chapitres, LOGO_LICENCE), encoding="utf-8") + tex = debug / f"{nom}.tex" + executer(commande_pandoc(document, auteur, couverture, tex)) + pdf = sortie / document.fichier + shutil.copy2(compiler(tex), pdf) + return pdf + + +def selectionner(documents: list[Document], fichiers: list[str]) -> list[Document]: + if not fichiers: + return documents + connus = {document.fichier: document for document in documents} + inconnus = [fichier for fichier in fichiers if fichier not in connus] + if inconnus: + raise ErreurBuild(f"document(s) inconnu(s) : {', '.join(inconnus)}") + return [connus[fichier] for fichier in fichiers] + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("fichiers", nargs="*", help="PDF à produire (tous par défaut)") + parser.add_argument("--sortie", type=Path, default=RACINE / "build" / "pdf") + args = parser.parse_args(argv) + try: + catalogue = charger_documents(DOSSIER_PDF / "documents.yaml", RACINE) + documents = selectionner(catalogue.documents, args.fichiers) + for document in documents: + print(f"-> {document.fichier}", flush=True) + construire(document, catalogue.auteur, args.sortie) + except ErreurBuild as erreur: + print(f"ERREUR : {erreur}", file=sys.stderr) + return 1 + print(f"{len(documents)} PDF produit(s) dans {args.sortie}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/pdf/preambule.tex b/pdf/preambule.tex new file mode 100644 index 0000000..fb2fd92 --- /dev/null +++ b/pdf/preambule.tex @@ -0,0 +1,23 @@ +% Préambule commun des PDF de la formation (inclus par pandoc, option -H). + +% Un caractère absent de la police devient une erreur fatale. +\tracinglostchars=3 + +% Blocs de code : retour à la ligne automatique, fond gris pour les blocs +% sans coloration (les blocs colorés ont déjà le fond « Shaded » de pandoc). +\usepackage{xcolor} +\definecolor{fondcode}{HTML}{F4F4F4} +\usepackage{fvextra} +\fvset{breaklines,breakanywhere} +\RecustomVerbatimEnvironment{verbatim}{Verbatim}{bgcolor=fondcode} + +% En-tête : titre du chapitre ; pied : numéro de page. +\usepackage{fancyhdr} +\pagestyle{fancy} +\fancyhf{} +\fancyhead[R]{\small\nouppercase{\leftmark}} +\fancyfoot[C]{\thepage} +\renewcommand{\chaptermark}[1]{\markboth{#1}{}} +\fancypagestyle{plain}{\fancyhf{}\fancyfoot[C]{\thepage}\renewcommand{\headrulewidth}{0pt}} + +\usepackage{graphicx} diff --git a/pdf/tests/test_documents.py b/pdf/tests/test_documents.py index e3d3523..9c81b88 100644 --- a/pdf/tests/test_documents.py +++ b/pdf/tests/test_documents.py @@ -59,3 +59,13 @@ def test_lit_le_sous_titre_optionnel(tmp_path): def test_refuse_un_catalogue_invalide(tmp_path, contenu, message): with pytest.raises(generer.ErreurBuild, match=re.escape(message)): generer.charger_documents(ecrire(tmp_path, contenu), tmp_path) + + +def test_selectionne_les_documents_demandes(): + a = generer.Document("a.pdf", "A", ()) + b = generer.Document("b.pdf", "B", ()) + + assert generer.selectionner([a, b], []) == [a, b] + assert generer.selectionner([a, b], ["b.pdf"]) == [b] + with pytest.raises(generer.ErreurBuild, match="inconnu"): + generer.selectionner([a, b], ["c.pdf"]) From 458ac3589d02ebcce2ccecbff5176171fd26a72e Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:35:59 +0200 Subject: [PATCH 14/32] fix: corrections de la revue de la Task 5 Co-Authored-By: Claude Opus 5.5 --- pdf/Dockerfile | 2 +- pdf/generer.py | 16 +++++++++++--- pdf/tests/test_compilation.py | 40 +++++++++++++++++++++++++++++++++++ pdf/tests/test_latex.py | 2 ++ 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 pdf/tests/test_compilation.py diff --git a/pdf/Dockerfile b/pdf/Dockerfile index aa6ddb2..7c911f3 100644 --- a/pdf/Dockerfile +++ b/pdf/Dockerfile @@ -3,7 +3,7 @@ FROM pandoc/extra:3.11.0.0-debian@sha256:fa8ccae75418449568f10f1842706aee1885ab24fba54f5aec08516260fc0166 RUN apt-get update \ - && apt-get install -y --no-install-recommends poppler-utils shellcheck \ + && apt-get install -y --no-install-recommends poppler-utils shellcheck fonts-dejavu-extra \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt /tmp/requirements.txt diff --git a/pdf/generer.py b/pdf/generer.py index 505513a..40fe03f 100644 --- a/pdf/generer.py +++ b/pdf/generer.py @@ -207,6 +207,10 @@ def commande_pandoc(document: Document, auteur: str, couverture: Path, sortie: P "monofont=DejaVu Sans Mono", "-V", "monofontoptions=Scale=0.85", + "-V", + "monofontoptions=ItalicFont=DejaVu Sans Mono Oblique", + "-V", + "monofontoptions=BoldItalicFont=DejaVu Sans Mono Bold Oblique", "-M", f"title-meta={document.titre}", "-M", @@ -249,8 +253,13 @@ def compiler(tex: Path) -> Path: texte_log = log.read_text(encoding="utf-8", errors="replace") if log.exists() else "" erreurs = erreurs_latex(texte_log) if resultat.returncode != 0 or erreurs: - detail = "\n".join(erreurs) or "\n".join(resultat.stdout.splitlines()[-30:]) - raise ErreurBuild(f"échec LaTeX pour {tex.name} (log complet : {log})\n{detail}") + sorties = (resultat.stdout + resultat.stderr).splitlines() + detail = "\n".join(erreurs) or "\n".join(sorties[-30:]) + chemin_log = log.relative_to(RACINE) if log.is_relative_to(RACINE) else log + raise ErreurBuild( + f"échec LaTeX pour {tex.name} (log complet : {chemin_log})\n{detail}\n" + "si l'erreur paraît incohérente : rm -rf build/pdf/debug" + ) return tex.with_suffix(".pdf") @@ -258,13 +267,14 @@ def construire(document: Document, auteur: str, sortie: Path) -> Path: """Produit sortie/ ; les intermédiaires restent dans sortie/debug/.""" debug = sortie / "debug" debug.mkdir(parents=True, exist_ok=True) + pdf = sortie / document.fichier + pdf.unlink(missing_ok=True) nom = Path(document.fichier).stem chapitres = [titre_chapitre(source) for source in document.sources] couverture = debug / f"{nom}-couverture.tex" couverture.write_text(generer_couverture(document, chapitres, LOGO_LICENCE), encoding="utf-8") tex = debug / f"{nom}.tex" executer(commande_pandoc(document, auteur, couverture, tex)) - pdf = sortie / document.fichier shutil.copy2(compiler(tex), pdf) return pdf diff --git a/pdf/tests/test_compilation.py b/pdf/tests/test_compilation.py new file mode 100644 index 0000000..47b285b --- /dev/null +++ b/pdf/tests/test_compilation.py @@ -0,0 +1,40 @@ +"""Tests de la compilation LaTeX et de la construction d'un document.""" + +import pytest + +import generer + +PREAMBULE = "\\documentclass{article}\n\\begin{document}\n" + + +def compiler_texte(tmp_path, corps): + tex = tmp_path / "essai.tex" + tex.write_text(PREAMBULE + corps + "\n\\end{document}\n", encoding="utf-8") + with pytest.raises(generer.ErreurBuild) as erreur: + generer.compiler(tex) + return str(erreur.value) + + +def test_compiler_signale_une_commande_inconnue(tmp_path): + message = compiler_texte(tmp_path, "\\nimporte") + + assert "Undefined control sequence" in message + assert "rm -rf build/pdf/debug" in message + + +def test_compiler_signale_un_caractere_absent_de_la_police(tmp_path): + message = compiler_texte(tmp_path, "\\tracinglostchars=3\nValidé ✅") + + assert "Missing character" in message + + +def test_construire_supprime_l_ancien_pdf_meme_en_cas_d_echec(tmp_path): + source = tmp_path / "a.md" + source.write_text("## Pas de titre de niveau 1\n", encoding="utf-8") + ancien = tmp_path / "doc.pdf" + ancien.write_bytes(b"%PDF perime") + + with pytest.raises(generer.ErreurBuild): + generer.construire(generer.Document("doc.pdf", "Doc", (source,)), "Auteur", tmp_path) + + assert not ancien.exists() diff --git a/pdf/tests/test_latex.py b/pdf/tests/test_latex.py index 703a7e6..e3b8459 100644 --- a/pdf/tests/test_latex.py +++ b/pdf/tests/test_latex.py @@ -50,6 +50,8 @@ def test_commande_pandoc(): str(generer.DOSSIER_PDF / "preambule.tex"), "/d/couv.tex", "/d/doc.tex", + "monofontoptions=ItalicFont=DejaVu Sans Mono Oblique", + "monofontoptions=BoldItalicFont=DejaVu Sans Mono Bold Oblique", ): assert attendu in commande for meta in ("title-meta=Module 2 — Navigation", "author-meta=Auteur"): From 57c49f568dcdadeb7f31ef4db6fe0b5281277aeb Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:41:16 +0200 Subject: [PATCH 15/32] feat: catalogue des 11 PDF de la formation Co-Authored-By: Claude Opus 5.5 --- pdf/documents.yaml | 71 +++++++++++++++++++++++++++++++++++++ pdf/tests/test_documents.py | 33 +++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/pdf/documents.yaml b/pdf/documents.yaml index 0bad219..cdbdc25 100644 --- a/pdf/documents.yaml +++ b/pdf/documents.yaml @@ -3,9 +3,80 @@ auteur: Pascal Guinet documents: + - fichier: module_01_decouverte.pdf + titre: Module 1 — Découverte + sources: + - cours_2026/module_01_decouverte/01_histoire_philosophie_distributions.md + - cours_2026/module_01_decouverte/02_premier_terminal.md - fichier: module_02_navigation.pdf titre: Module 2 — Navigation sources: - cours_2026/module_02_navigation/01_arborescence_chemins.md - cours_2026/module_02_navigation/02_se_deplacer_explorer.md - cours_2026/module_02_navigation/03_types_fichiers_liens.md + - fichier: module_03_manipulation.pdf + titre: Module 3 — Manipulation + sources: + - cours_2026/module_03_manipulation/01_creer_copier_deplacer_supprimer.md + - cours_2026/module_03_manipulation/02_rechercher_fichiers.md + - cours_2026/module_03_manipulation/03_archiver_compresser.md + - fichier: module_04_consultation.pdf + titre: Module 4 — Consultation et édition + sources: + - cours_2026/module_04_consultation/01_lire_fichiers.md + - cours_2026/module_04_consultation/02_editer_nano.md + - cours_2026/module_04_consultation/03_chercher_comparer.md + - fichier: module_05_droits.pdf + titre: Module 5 — Droits + sources: + - cours_2026/module_05_droits/01_utilisateurs_groupes.md + - cours_2026/module_05_droits/02_permissions.md + - cours_2026/module_05_droits/03_sudo_securite.md + - fichier: module_06_processus.pdf + titre: Module 6 — Processus et système + sources: + - cours_2026/module_06_processus/01_processus.md + - cours_2026/module_06_processus/02_surveillance_systeme.md + - cours_2026/module_06_processus/03_variables_historique.md + - fichier: module_07_reseaux.pdf + titre: Module 7 — Réseau et services + sources: + - cours_2026/module_07_reseaux/01_reseau_transferts.md + - cours_2026/module_07_reseaux/02_services_logs.md + - fichier: module_08_automatisation.pdf + titre: Module 8 — Automatisation + sources: + - cours_2026/module_08_automatisation/01_redirections_pipes.md + - cours_2026/module_08_automatisation/02_scripts_bash.md + - cours_2026/module_08_automatisation/03_cron_alias_personnalisation.md + - fichier: module_additionnel_git.pdf + titre: "Git : contrôle de version" + sous_titre: Module additionnel + sources: + - supports/modules_additionnels/module_git/01_introduction_git.md + - supports/modules_additionnels/module_git/02_commandes_base.md + - supports/modules_additionnels/module_git/03_branches_fusion.md + - supports/modules_additionnels/module_git/04_travail_collaboratif.md + - travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md + - travaux_pratiques/tp_additionnels/tp_git/tp02_branches_fusion.md + - travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md + - travaux_pratiques/tp_additionnels/tp_git/exercices_supplementaires.md + - fichier: module_additionnel_docker.pdf + titre: "Docker : conteneurisation" + sous_titre: Module additionnel + sources: + - supports/modules_additionnels/module_docker/01_introduction_docker.md + - supports/modules_additionnels/module_docker/02_images_conteneurs.md + - supports/modules_additionnels/module_docker/03_volumes_reseaux.md + - supports/modules_additionnels/module_docker/04_compose_orchestration.md + - travaux_pratiques/tp_additionnels/tp_docker/README.md + - travaux_pratiques/tp_additionnels/tp_docker/tp1_installation_premiers_conteneurs.md + - travaux_pratiques/tp_additionnels/tp_docker/tp2_images_personnalisees.md + - travaux_pratiques/tp_additionnels/tp_docker/tp3_volumes_donnees.md + - travaux_pratiques/tp_additionnels/tp_docker/tp4_reseaux_communication.md + - travaux_pratiques/tp_additionnels/tp_docker/tp5_compose_orchestration.md + - fichier: annexe_installation.pdf + titre: Installer son environnement de travail + sous_titre: Annexe + sources: + - cours_2026/annexes/installation.md diff --git a/pdf/tests/test_documents.py b/pdf/tests/test_documents.py index 9c81b88..f4ea002 100644 --- a/pdf/tests/test_documents.py +++ b/pdf/tests/test_documents.py @@ -69,3 +69,36 @@ def test_selectionne_les_documents_demandes(): assert generer.selectionner([a, b], ["b.pdf"]) == [b] with pytest.raises(generer.ErreurBuild, match="inconnu"): generer.selectionner([a, b], ["c.pdf"]) + + +ATTENDUS = [ + "module_01_decouverte.pdf", + "module_02_navigation.pdf", + "module_03_manipulation.pdf", + "module_04_consultation.pdf", + "module_05_droits.pdf", + "module_06_processus.pdf", + "module_07_reseaux.pdf", + "module_08_automatisation.pdf", + "module_additionnel_git.pdf", + "module_additionnel_docker.pdf", + "annexe_installation.pdf", +] + + +def test_le_catalogue_du_depot_declare_les_11_pdf(): + catalogue = generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) + + assert [document.fichier for document in catalogue.documents] == ATTENDUS + + +def test_chaque_chapitre_de_cours_2026_est_dans_un_pdf(): + catalogue = generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) + declares = {source for document in catalogue.documents for source in document.sources} + + oublies = sorted( + str(chemin.relative_to(generer.RACINE)) + for chemin in (generer.RACINE / "cours_2026").rglob("*.md") + if chemin not in declares + ) + assert oublies == [] From ed6da9d5140c9e89ea52ccb1f0bf86d1eff08a40 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:41:16 +0200 Subject: [PATCH 16/32] =?UTF-8?q?fix:=20remplacer=20les=20caract=C3=A8res?= =?UTF-8?q?=20Unicode=20non=20imprimables=20du=20module=20Git?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remplacements mécaniques : dessins de boîtes et de branches en ASCII (- | + / \ *), flèches en -> et <-, coches des print() Python en [OK]. Reformulations à la main (pour que le texte se lise naturellement) : - coches retirées des listes « Compétences acquises » et « Tests » des TP 1 à 3, et des titres de la grille d'auto-évaluation ; - « Bons / Mauvais messages » et « Bons / Mauvais noms » : coche et croix retirées des commentaires (les mots suffisent) ; - calculatrice : le symbole racine devient 'sqrt' et sqrt({a}) ; - emojis de fin de ligne supprimés. Co-Authored-By: Claude Opus 5.5 --- .../module_git/02_commandes_base.md | 6 +-- .../module_git/03_branches_fusion.md | 24 +++++------ .../module_git/04_travail_collaboratif.md | 8 ++-- .../tp_git/exercices_supplementaires.md | 34 ++++++++-------- .../tp_git/tp01_premiers_pas.md | 28 ++++++------- .../tp_git/tp02_branches_fusion.md | 12 +++--- .../tp_git/tp03_collaboration.md | 40 +++++++++---------- 7 files changed, 76 insertions(+), 76 deletions(-) diff --git a/supports/modules_additionnels/module_git/02_commandes_base.md b/supports/modules_additionnels/module_git/02_commandes_base.md index eb36326..80b4a11 100644 --- a/supports/modules_additionnels/module_git/02_commandes_base.md +++ b/supports/modules_additionnels/module_git/02_commandes_base.md @@ -165,12 +165,12 @@ Un bon message de commit doit : - **Expliquer le "pourquoi"** pas le "comment" ```bash -# ✅ Bons messages +# Bons messages git commit -m "Ajouter validation des emails" git commit -m "Corriger bug de connexion timeout" git commit -m "Améliorer performance de recherche" -# ❌ Mauvais messages +# Mauvais messages git commit -m "fix" git commit -m "changement" git commit -m "ça marche maintenant" @@ -465,7 +465,7 @@ git co master # au lieu de git checkout master 4. **git status** : état du dépôt 5. **git log** : historique des commits 6. **git diff** : voir les différences -7. **Workflow** : modifier → add → commit +7. **Workflow** : modifier -> add -> commit --- diff --git a/supports/modules_additionnels/module_git/03_branches_fusion.md b/supports/modules_additionnels/module_git/03_branches_fusion.md index e62c5bf..9b99e15 100644 --- a/supports/modules_additionnels/module_git/03_branches_fusion.md +++ b/supports/modules_additionnels/module_git/03_branches_fusion.md @@ -386,20 +386,20 @@ git commit -m "Ajouter système de login complet" ### Git Flow ``` -master ──●────●────●── (releases) +master --*----*----*-- (releases) / / / -develop ──●──●──●──●──●─── (intégration) - │ ╱ ╱ -features │●─● ●─● (fonctionnalités) - │ -hotfix ──●─● (corrections urgentes) +develop --*--*--*--*--*--- (intégration) + | / / +features |*-* *-* (fonctionnalités) + | +hotfix --*-* (corrections urgentes) ``` ### GitHub Flow (plus simple) ``` -master ──●────●────●────●── - ╲ ╱ ╱ -features ●──● ●──● +master --*----*----*----*-- + \ / / +features *--* *--* ``` ### Workflow d'équipe recommandé @@ -414,13 +414,13 @@ features ●──● ●──● ### Nommage des branches ```bash -# ✅ Bons noms +# Bons noms feature/user-authentication bugfix/login-timeout hotfix/security-patch docs/api-documentation -# ❌ Mauvais noms +# Mauvais noms branch1 test ma-branche @@ -549,7 +549,7 @@ git merge feature-footer 4. **git merge** : fusionner des branches 5. **Conflits** : résoudre manuellement quand Git ne peut pas 6. **Fast-forward vs Three-way merge** -7. **Workflow** : créer → développer → tester → fusionner → supprimer +7. **Workflow** : créer -> développer -> tester -> fusionner -> supprimer --- diff --git a/supports/modules_additionnels/module_git/04_travail_collaboratif.md b/supports/modules_additionnels/module_git/04_travail_collaboratif.md index 374d171..fe6bb71 100644 --- a/supports/modules_additionnels/module_git/04_travail_collaboratif.md +++ b/supports/modules_additionnels/module_git/04_travail_collaboratif.md @@ -287,7 +287,7 @@ git rebase --abort **1. Fork du projet** ``` -Projet original → Clic "Fork" → Votre fork +Projet original -> Clic "Fork" -> Votre fork ``` **2. Cloner votre fork** @@ -509,10 +509,10 @@ git push origin master echo "Version 2" > fichier.txt git add fichier.txt git commit -m "Version 2" -git push origin master # ← ÉCHEC +git push origin master # <- ÉCHEC # Résolution -git pull origin master # ← CONFLIT +git pull origin master # <- CONFLIT # Résoudre manuellement... git add fichier.txt git commit -m "Résoudre conflit versions" @@ -602,7 +602,7 @@ git push -u origin branche # Workflow GitHub git checkout -b feature-branch git push origin feature-branch -# → Créer PR sur GitHub +# -> Créer PR sur GitHub # Mise à jour fork git fetch upstream diff --git a/travaux_pratiques/tp_additionnels/tp_git/exercices_supplementaires.md b/travaux_pratiques/tp_additionnels/tp_git/exercices_supplementaires.md index 886ab49..e4d232c 100644 --- a/travaux_pratiques/tp_additionnels/tp_git/exercices_supplementaires.md +++ b/travaux_pratiques/tp_additionnels/tp_git/exercices_supplementaires.md @@ -56,12 +56,12 @@ echo "import calc def test_add(): assert calc.add(2, 3) == 5 assert calc.add(-1, 1) == 0 - print(\"✓ Tests addition réussis\") + print(\"[OK] Tests addition réussis\") def test_subtract(): assert calc.subtract(5, 3) == 2 assert calc.subtract(0, 1) == -1 - print(\"✓ Tests soustraction réussis\") + print(\"[OK] Tests soustraction réussis\") if __name__ == \"__main__\": test_add() @@ -87,7 +87,7 @@ echo " def test_multiply(): assert calc.multiply(3, 4) == 12 assert calc.multiply(-2, 3) == -6 - print(\"✓ Tests multiplication réussis\") + print(\"[OK] Tests multiplication réussis\") def test_divide(): assert calc.divide(10, 2) == 5 @@ -97,7 +97,7 @@ def test_divide(): assert False, \"Devrait lever une exception\" except ValueError: pass - print(\"✓ Tests division réussis\")" >> test_calc.py + print(\"[OK] Tests division réussis\")" >> test_calc.py git add calc.py test_calc.py git commit -m "feat: ajouter multiplication et division avec tests" @@ -214,7 +214,7 @@ git log --numstat | awk '{added += $1} END {print "Total lignes ajoutées:", add 2. Créer une branche `feature-history` pour ajouter un historique des calculs 3. Développer les deux fonctionnalités **sans les fusionner** 4. Créer une branche `feature-ui` qui améliore l'interface utilisateur -5. Fusionner dans l'ordre : scientific → master, history → master, ui → master +5. Fusionner dans l'ordre : scientific -> master, history -> master, ui -> master 6. Résoudre tous les conflits qui apparaissent
@@ -254,7 +254,7 @@ def test_scientific(): assert abs(calc.cos(0) - 1) < 0.0001 assert calc.sqrt(9) == 3 assert abs(calc.log(math.e) - 1) < 0.0001 - print(\"✓ Tests fonctions scientifiques réussis\")" >> test_calc.py + print(\"[OK] Tests fonctions scientifiques réussis\")" >> test_calc.py git add calc.py test_calc.py git commit -m "feat: ajouter fonctions scientifiques" @@ -461,8 +461,8 @@ def main(): a = get_number() try: result = calc.sqrt(a) - history.add_operation('√', a, 0, result) - print(f"Résultat: √{a} = {result}") + history.add_operation('sqrt', a, 0, result) + print(f"Résultat: sqrt({a}) = {result}") except ValueError as e: print(f"Erreur: {e}") elif choice == '8': @@ -587,7 +587,7 @@ Simulez 3 développeurs (Alice, Bob, Charlie) travaillant sur un projet : 2. Bob clone et ajoute une fonctionnalité 3. Charlie clone aussi et travaille sur autre chose **en parallèle** 4. Bob termine et pousse en premier -5. Charlie essaie de pousser → conflit ! +5. Charlie essaie de pousser -> conflit ! 6. Charlie résout le conflit et pousse 7. Alice récupère tout et fait une review @@ -821,19 +821,19 @@ def test_validate_email(): assert validation.validate_email('test@example.com') == True assert validation.validate_email('invalid-email') == False assert validation.validate_email('') == False - print('✓ Tests email validation') + print('[OK] Tests email validation') def test_validate_non_empty(): assert validation.validate_non_empty('hello') == True assert validation.validate_non_empty('') == False assert validation.validate_non_empty(None) == False - print('✓ Tests non-empty validation') + print('[OK] Tests non-empty validation') def test_validate_positive_number(): assert validation.validate_positive_number('5') == True assert validation.validate_positive_number('-1') == False assert validation.validate_positive_number('abc') == False - print('✓ Tests positive number validation') + print('[OK] Tests positive number validation') if __name__ == '__main__': test_validate_email() @@ -1177,28 +1177,28 @@ Simulez la migration d'un projet SVN (avec historique) vers Git en préservant l ## Grille d'auto-évaluation -### Niveau Débutant ✅ +### Niveau Débutant - [ ] Initialiser un dépôt Git - [ ] Faire des commits atomiques - [ ] Comprendre les états des fichiers - [ ] Utiliser .gitignore efficacement - [ ] Consulter l'historique (log, show, diff) -### Niveau Intermédiaire ✅ +### Niveau Intermédiaire - [ ] Créer et gérer des branches - [ ] Fusionner des branches (merge) - [ ] Résoudre des conflits simples - [ ] Utiliser les remotes (clone, push, pull) - [ ] Collaborer sur GitHub/GitLab -### Niveau Avancé ✅ +### Niveau Avancé - [ ] Utiliser rebase et rebase interactif - [ ] Gérer des conflits complexes - [ ] Contribuer à des projets open source - [ ] Récupérer après des erreurs - [ ] Maintenir un historique propre -### Niveau Expert 🚀 +### Niveau Expert - [ ] Implémenter des workflows complexes (GitFlow, GitHub Flow) - [ ] Utiliser reflog pour la récupération - [ ] Scripter des tâches Git répétitives @@ -1207,4 +1207,4 @@ Simulez la migration d'un projet SVN (avec historique) vers Git en préservant l --- -Félicitations ! Si vous avez complété ces exercices, vous maîtrisez Git de manière professionnelle. 🎉 \ No newline at end of file +Félicitations ! Si vous avez complété ces exercices, vous maîtrisez Git de manière professionnelle. \ No newline at end of file diff --git a/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md b/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md index ff306e8..8822c7e 100644 --- a/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md +++ b/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md @@ -265,14 +265,14 @@ Refaites l'exercice 5.1 en respectant ces règles pour les messages de commit : Créez un projet "blog" avec cette structure : ``` blog/ -├── articles/ -│ ├── 2023-12-01-premier-article.md -│ └── 2023-12-02-deuxieme-article.md -├── templates/ -│ ├── header.html -│ └── footer.html -├── style.css -└── index.html ++-- articles/ +| +-- 2023-12-01-premier-article.md +| +-- 2023-12-02-deuxieme-article.md ++-- templates/ +| +-- header.html +| +-- footer.html ++-- style.css ++-- index.html ``` **Consignes :** @@ -316,12 +316,12 @@ git log --oneline ## Points d'évaluation **Compétences acquises :** -- ✅ Initialiser un dépôt Git -- ✅ Configurer Git correctement -- ✅ Comprendre les états des fichiers (untracked, modified, staged, committed) -- ✅ Utiliser add, commit, status, log efficacement -- ✅ Écrire des messages de commit clairs -- ✅ Naviguer dans l'historique des commits +- Initialiser un dépôt Git +- Configurer Git correctement +- Comprendre les états des fichiers (untracked, modified, staged, committed) +- Utiliser add, commit, status, log efficacement +- Écrire des messages de commit clairs +- Naviguer dans l'historique des commits **Questions de révision :** 1. Quelle est la différence entre `git add .` et `git add -A` ? diff --git a/travaux_pratiques/tp_additionnels/tp_git/tp02_branches_fusion.md b/travaux_pratiques/tp_additionnels/tp_git/tp02_branches_fusion.md index 367d6a9..8c88c4a 100644 --- a/travaux_pratiques/tp_additionnels/tp_git/tp02_branches_fusion.md +++ b/travaux_pratiques/tp_additionnels/tp_git/tp02_branches_fusion.md @@ -585,12 +585,12 @@ git log --oneline --graph ## Points d'évaluation **Compétences acquises :** -- ✅ Créer et gérer des branches -- ✅ Naviguer entre les branches -- ✅ Comprendre les différents types de merge -- ✅ Résoudre des conflits de fusion -- ✅ Appliquer des stratégies de fusion appropriées -- ✅ Maintenir un historique propre et lisible +- Créer et gérer des branches +- Naviguer entre les branches +- Comprendre les différents types de merge +- Résoudre des conflits de fusion +- Appliquer des stratégies de fusion appropriées +- Maintenir un historique propre et lisible **Questions de révision :** 1. Quand utilise-t-on `--no-ff` lors d'un merge ? diff --git a/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md b/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md index 5535134..385a73f 100644 --- a/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md +++ b/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md @@ -36,7 +36,7 @@ cat ~/.ssh/id_ed25519.pub ``` **Ajouter la clé sur GitHub :** -1. Aller sur GitHub.com → Settings → SSH and GPG keys +1. Aller sur GitHub.com -> Settings -> SSH and GPG keys 2. Cliquer "New SSH key" 3. Coller la clé publique 4. Tester la connexion : `ssh -T git@github.com` @@ -477,10 +477,10 @@ git push -u origin feature-user-management - Gestion des erreurs appropriée ### Tests -- ✅ Ajout d'utilisateurs -- ✅ Gestion des doublons -- ✅ Suppression d'utilisateurs -- ✅ Récupération des données +- Ajout d'utilisateurs +- Gestion des doublons +- Suppression d'utilisateurs +- Récupération des données ### Comment tester ```bash @@ -688,15 +688,15 @@ git branch -a # Structure finale attendue : # src/ -# ├── main.js -# ├── auth.js -# └── user-manager.js +# +-- main.js +# +-- auth.js +# +-- user-manager.js # tests/ -# ├── main.test.js -# ├── auth.test.js -# └── user-manager.test.js +# +-- main.test.js +# +-- auth.test.js +# +-- user-manager.test.js # docs/ -# └── tech.md +# +-- tech.md # README.md # .gitignore ``` @@ -706,14 +706,14 @@ git branch -a ## Points d'évaluation **Compétences acquises :** -- ✅ Configurer et utiliser SSH/HTTPS pour GitHub -- ✅ Connecter un projet local à un remote -- ✅ Comprendre fetch vs pull vs push -- ✅ Résoudre des conflits distants -- ✅ Créer et gérer des Pull Requests -- ✅ Contribuer à des projets open source -- ✅ Maintenir des forks synchronisés -- ✅ Appliquer les bonnes pratiques collaboratives +- Configurer et utiliser SSH/HTTPS pour GitHub +- Connecter un projet local à un remote +- Comprendre fetch vs pull vs push +- Résoudre des conflits distants +- Créer et gérer des Pull Requests +- Contribuer à des projets open source +- Maintenir des forks synchronisés +- Appliquer les bonnes pratiques collaboratives **Questions de révision :** 1. Quelle est la différence entre `git fetch` et `git pull` ? From 8e5bc21e8692a00e2f6631157a0980c16b688596 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:45:50 +0200 Subject: [PATCH 17/32] fix: corrections de la revue de la Task 6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test de couverture étendu aux modules additionnels (test_chaque_source_active_est_dans_un_pdf) ; - nouveaux tests : aucune source déclarée deux fois, et chaque module de base reprend exactement les chapitres de son dossier, dans l'ordre ; - documents.yaml : commentaire d'en-tête sur l'ajout d'un PDF ; - arborescences ASCII des TP Git 1 et 3 : convention tree --charset=ascii (|-- pour les éléments, `-- pour le dernier). Co-Authored-By: Claude Opus 5.5 --- pdf/documents.yaml | 2 ++ pdf/tests/test_documents.py | 33 ++++++++++++++++--- .../tp_git/tp01_premiers_pas.md | 16 ++++----- .../tp_git/tp03_collaboration.md | 14 ++++---- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/pdf/documents.yaml b/pdf/documents.yaml index cdbdc25..48a0c0c 100644 --- a/pdf/documents.yaml +++ b/pdf/documents.yaml @@ -1,5 +1,7 @@ # Catalogue des PDF de la formation : un document par entrée. # Les chemins des sources sont relatifs à la racine du dépôt. +# Ajouter un PDF : ajouter une entrée ici et son nom dans ATTENDUS +# (pdf/tests/test_documents.py). sous_titre est facultatif. auteur: Pascal Guinet documents: diff --git a/pdf/tests/test_documents.py b/pdf/tests/test_documents.py index f4ea002..78c116b 100644 --- a/pdf/tests/test_documents.py +++ b/pdf/tests/test_documents.py @@ -86,19 +86,42 @@ def test_selectionne_les_documents_demandes(): ] +def catalogue_du_depot(): + return generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) + + def test_le_catalogue_du_depot_declare_les_11_pdf(): - catalogue = generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) + assert [document.fichier for document in catalogue_du_depot().documents] == ATTENDUS - assert [document.fichier for document in catalogue.documents] == ATTENDUS +RACINES_ACTIVES = [ + "cours_2026", + "supports/modules_additionnels", + "travaux_pratiques/tp_additionnels", +] -def test_chaque_chapitre_de_cours_2026_est_dans_un_pdf(): - catalogue = generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) + +def test_chaque_source_active_est_dans_un_pdf(): + catalogue = catalogue_du_depot() declares = {source for document in catalogue.documents for source in document.sources} oublies = sorted( str(chemin.relative_to(generer.RACINE)) - for chemin in (generer.RACINE / "cours_2026").rglob("*.md") + for racine in RACINES_ACTIVES + for chemin in (generer.RACINE / racine).rglob("*.md") if chemin not in declares ) assert oublies == [] + + +def test_aucune_source_n_est_declaree_deux_fois(): + sources = [source for document in catalogue_du_depot().documents for source in document.sources] + + assert sorted({s for s in sources if sources.count(s) > 1}) == [] + + +def test_un_module_de_base_reprend_tous_les_chapitres_de_son_dossier_dans_l_ordre(): + for document in catalogue_du_depot().documents: + if document.fichier.startswith("module_0"): + dossier = generer.RACINE / "cours_2026" / document.fichier.removesuffix(".pdf") + assert document.sources == tuple(sorted(dossier.glob("*.md"))), document.fichier diff --git a/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md b/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md index 8822c7e..3d739fb 100644 --- a/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md +++ b/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md @@ -265,14 +265,14 @@ Refaites l'exercice 5.1 en respectant ces règles pour les messages de commit : Créez un projet "blog" avec cette structure : ``` blog/ -+-- articles/ -| +-- 2023-12-01-premier-article.md -| +-- 2023-12-02-deuxieme-article.md -+-- templates/ -| +-- header.html -| +-- footer.html -+-- style.css -+-- index.html +|-- articles/ +| |-- 2023-12-01-premier-article.md +| `-- 2023-12-02-deuxieme-article.md +|-- templates/ +| |-- header.html +| `-- footer.html +|-- style.css +`-- index.html ``` **Consignes :** diff --git a/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md b/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md index 385a73f..fde9d75 100644 --- a/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md +++ b/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md @@ -688,15 +688,15 @@ git branch -a # Structure finale attendue : # src/ -# +-- main.js -# +-- auth.js -# +-- user-manager.js +# |-- main.js +# |-- auth.js +# `-- user-manager.js # tests/ -# +-- main.test.js -# +-- auth.test.js -# +-- user-manager.test.js +# |-- main.test.js +# |-- auth.test.js +# `-- user-manager.test.js # docs/ -# +-- tech.md +# `-- tech.md # README.md # .gitignore ``` From 54b5f23951b3893e7861c70f3bcbc429aa37a083 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:47:10 +0200 Subject: [PATCH 18/32] =?UTF-8?q?fix:=20prot=C3=A9ger=20les=20=C2=AB=20--?= =?UTF-8?q?=20=C2=BB=20du=20code=20dans=20les=20sources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hors code, la typographie de pandoc transforme « -- » en tiret (rw-r--r-- s'affichait rw-r–r–). Les 13 occurrences passent entre backticks ; test_sources.py empêche leur retour. Co-Authored-By: Claude Opus 5.5 --- cours_2026/module_05_droits/02_permissions.md | 16 +++---- pdf/tests/test_sources.py | 42 +++++++++++++++++++ .../tp_git/tp01_premiers_pas.md | 2 +- .../tp_git/tp02_branches_fusion.md | 4 +- 4 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 pdf/tests/test_sources.py diff --git a/cours_2026/module_05_droits/02_permissions.md b/cours_2026/module_05_droits/02_permissions.md index a151433..8c81777 100644 --- a/cours_2026/module_05_droits/02_permissions.md +++ b/cours_2026/module_05_droits/02_permissions.md @@ -130,9 +130,9 @@ Permissions les plus rencontrées : | Octal | Symbolique | Usage typique | |-------|-------------|---------------------------------| | 755 | rwxr-xr-x | script ou répertoire accessible | -| 644 | rw-r--r-- | fichier de données ordinaire | -| 600 | rw------- | fichier privé (clé SSH, config) | -| 700 | rwx------ | répertoire strictement privé | +| 644 | `rw-r--r--` | fichier de données ordinaire | +| 600 | `rw-------` | fichier privé (clé SSH, config) | +| 700 | `rwx------` | répertoire strictement privé | ```bash # Appliquer en une commande @@ -226,7 +226,7 @@ les autres utilisateurs ne peuvent que lire et traverser. | `ls -l fichier` | Afficher les permissions et le propriétaire | | `chmod u+x fichier` | Ajouter l'exécution pour le propriétaire | | `chmod go-w fichier` | Retirer l'écriture au groupe et aux autres | -| `chmod 644 fichier` | Définir rw-r--r-- (fichier de données standard) | +| `chmod 644 fichier` | Définir `rw-r--r--` (fichier de données standard) | | `chmod 755 fichier` | Définir rwxr-xr-x (script ou répertoire accessible) | | `chmod -R 755 repertoire/` | Appliquer récursivement à tout un arbre | | `chown alice fichier` | Changer le propriétaire | @@ -391,8 +391,8 @@ sans accorder plus de droits que nécessaire. ### Exercice 6 — Umask et création de fichiers -Vous souhaitez que vos prochains fichiers soient créés en `640` (rw-r-----) et -vos répertoires en `750` (rwxr-x---). +Vous souhaitez que vos prochains fichiers soient créés en `640` (`rw-r-----`) et +vos répertoires en `750` (`rwxr-x---`). a) Quelle valeur d'umask faut-il appliquer ? b) Écrivez la commande pour l'appliquer temporairement. @@ -512,8 +512,8 @@ a) L'umask est un masque de bits : les permissions qu'il contient sont Plus simplement : on retire `rw` => 6 Umask **027** : - - Fichiers : 666 masqué par 027 => u:6, g:6-2=4 (retire w), o:6-6=0 => 640 (rw-r-----) - - Répertoires : 777 masqué par 027 => u:7, g:7-2=5 (retire w), o:7-7=0 => 750 (rwxr-x---) + - Fichiers : 666 masqué par 027 => u:6, g:6-2=4 (retire w), o:6-6=0 => 640 (`rw-r-----`) + - Répertoires : 777 masqué par 027 => u:7, g:7-2=5 (retire w), o:7-7=0 => 750 (`rwxr-x---`) b) Appliquer temporairement : diff --git a/pdf/tests/test_sources.py b/pdf/tests/test_sources.py new file mode 100644 index 0000000..dad85c8 --- /dev/null +++ b/pdf/tests/test_sources.py @@ -0,0 +1,42 @@ +"""Vérifications des sources Markdown du catalogue.""" + +import json + +import generer + +CATALOGUE = generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) +SOURCES = sorted({source for document in CATALOGUE.documents for source in document.sources}) +# Sans « smart », pandoc garde « -- » tel quel : on peut le repérer dans l'AST. +FORMAT_SANS_TYPOGRAPHIE = f"{generer.FORMAT_MARKDOWN}-smart" +NOEUDS_DE_CODE = {"Code", "CodeBlock"} + + +def textes_hors_code(noeud): + """Contenu des nœuds Str d'un AST pandoc, en ignorant le code.""" + if isinstance(noeud, list): + for element in noeud: + yield from textes_hors_code(element) + elif isinstance(noeud, dict): + if noeud.get("t") in NOEUDS_DE_CODE: + return + if noeud.get("t") == "Str": + yield noeud["c"] + return + for valeur in noeud.values(): + yield from textes_hors_code(valeur) + + +def test_pas_de_double_tiret_hors_code(): + # Hors code, pandoc transforme « -- » en tiret : rw-r--r-- deviendrait rw-r–r–. + fautes = [] + for source in SOURCES: + ast = json.loads( + generer.executer(["pandoc", "-f", FORMAT_SANS_TYPOGRAPHIE, "-t", "json", str(source)]) + ) + fautes.extend( + f"{source.relative_to(generer.RACINE)} : {texte}" + for texte in textes_hors_code(ast["blocks"]) + if "--" in texte + ) + + assert fautes == [], "mettre ces valeurs entre backticks :\n" + "\n".join(fautes) diff --git a/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md b/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md index 3d739fb..d78edf1 100644 --- a/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md +++ b/travaux_pratiques/tp_additionnels/tp_git/tp01_premiers_pas.md @@ -336,5 +336,5 @@ git log --oneline Avant le prochain TP, explorez : - Les alias Git pour raccourcir les commandes - La commande `git commit --amend` -- Les options de `git log` (--graph, --author, --since) +- Les options de `git log` (`--graph`, `--author`, `--since`) - La différence entre `git diff`, `git diff --cached` et `git diff HEAD` \ No newline at end of file diff --git a/travaux_pratiques/tp_additionnels/tp_git/tp02_branches_fusion.md b/travaux_pratiques/tp_additionnels/tp_git/tp02_branches_fusion.md index 8c88c4a..259a6b8 100644 --- a/travaux_pratiques/tp_additionnels/tp_git/tp02_branches_fusion.md +++ b/travaux_pratiques/tp_additionnels/tp_git/tp02_branches_fusion.md @@ -377,7 +377,7 @@ git branch -d branch-a branch-b feature-footer ## Partie 6 : Stratégies de fusion avancées -### Exercice 6.1 : Merge avec --no-ff +### Exercice 6.1 : Merge avec `--no-ff` ```bash # Créer une branche pour tester le --no-ff git checkout -b feature-contact @@ -410,7 +410,7 @@ git merge --no-ff feature-contact -m "Intégrer page de contact" git log --oneline --graph -n 5 ``` -**Question :** Quelle est la différence visuelle dans l'historique avec --no-ff ? +**Question :** Quelle est la différence visuelle dans l'historique avec `--no-ff` ? ### Exercice 6.2 : Squash merge ```bash From 6e001dfdc3639a3b548eb77d082292a5d312cf35 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:53:11 +0200 Subject: [PATCH 19/32] =?UTF-8?q?fix:=20autoriser=20la=20coupure=20du=20co?= =?UTF-8?q?de=20en=20ligne=20apr=C3=A8s=20/=20et=20\?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LaTeX ne coupe jamais un \texttt : les chemins longs débordaient de 65pt (annexe : D:\PrenomNOM\VirtualBox\..., module 3 : archivage/config/parametres.txt). Le préambule insère une coupure possible, sans trait d'union, après chaque / et \ du code en ligne. Co-Authored-By: Claude Opus 5.5 --- pdf/preambule.tex | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pdf/preambule.tex b/pdf/preambule.tex index fb2fd92..c023249 100644 --- a/pdf/preambule.tex +++ b/pdf/preambule.tex @@ -21,3 +21,18 @@ \fancypagestyle{plain}{\fancyhf{}\fancyfoot[C]{\thepage}\renewcommand{\headrulewidth}{0pt}} \usepackage{graphicx} + +% Code en ligne : LaTeX ne coupe jamais un \texttt, un long chemin déborde +% donc dans la marge. On autorise une coupure (sans trait d'union) après +% chaque / et chaque \ du code en ligne. +\ExplSyntaxOn +\cs_new_eq:NN \__formation_texttt:n \texttt +\tl_new:N \l__formation_code_tl +\RenewDocumentCommand \texttt { m } + { + \tl_set:Nn \l__formation_code_tl {#1} + \tl_replace_all:Nnn \l__formation_code_tl { / } { / \allowbreak } + \tl_replace_all:Nnn \l__formation_code_tl { \textbackslash } { \textbackslash \allowbreak } + \exp_args:NV \__formation_texttt:n \l__formation_code_tl + } +\ExplSyntaxOff From ce0bdb188a255b005c9bf10b32499ffc3ef9c277 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:53:11 +0200 Subject: [PATCH 20/32] =?UTF-8?q?fix:=20blocs=20Markdown=20imbriqu=C3=A9s?= =?UTF-8?q?=20du=20TP=20Git=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les exemples de README contenaient des blocs ```bash qui fermaient le bloc ```markdown englobant ; « Jest pour les tests » suivi de ======= devenait un titre de niveau 1 (chapitre parasite dans le PDF). Les blocs englobants passent à quatre backticks. Co-Authored-By: Claude Opus 5.5 --- .../tp_additionnels/tp_git/tp03_collaboration.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md b/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md index fde9d75..2f19120 100644 --- a/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md +++ b/travaux_pratiques/tp_additionnels/tp_git/tp03_collaboration.md @@ -303,7 +303,7 @@ cat README.md ``` **Le fichier devrait contenir des marqueurs de conflit :** -```markdown +````markdown Ce projet illustre la collaboration avec Git et GitHub. ## Objectifs @@ -336,7 +336,7 @@ npm test Ce projet nécessite Node.js version 14 ou supérieure. >>>>>>> origin/master -``` +```` **Résoudre le conflit :** ```bash @@ -345,7 +345,7 @@ nano README.md ``` **Version résolue suggérée :** -```markdown +````markdown # TP Collaboration Git Ce projet illustre la collaboration avec Git et GitHub. @@ -371,7 +371,7 @@ Ce projet nécessite Node.js version 14 ou supérieure. ## Technologies utilisées - Node.js - Jest pour les tests -``` +```` ```bash # Finaliser la résolution @@ -467,7 +467,7 @@ git push -u origin feature-user-management 3. Cliquer dessus 4. Rédiger une description détaillée : -```markdown +````markdown ## Ajout du système de gestion d'utilisateurs ### Changements apportés @@ -491,7 +491,7 @@ npm test tests/user-manager.test.js - La classe utilise une Map pour un accès rapide O(1) - Gestion des erreurs explicite pour les cas d'edge - Interface simple et intuitive -``` +```` 5. Créer la Pull Request From 5590dfc38828e2d0fd666421a4c1069a41e5eb6c Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:53:11 +0200 Subject: [PATCH 21/32] fix: titres des chapitres avec l'apostrophe des signets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pandoc écrit ’ en ' dans le LaTeX : le texte imprimé garde ’ mais les signets du PDF reçoivent '. titres_niveau1 renvoie désormais le titre tel qu'il apparaît dans les signets (la couverture reste typographique). Co-Authored-By: Claude Opus 5.5 --- pdf/generer.py | 8 ++++++-- pdf/tests/test_titres.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pdf/generer.py b/pdf/generer.py index 40fe03f..ff47832 100644 --- a/pdf/generer.py +++ b/pdf/generer.py @@ -122,7 +122,11 @@ def executer(commande: list[str], entree: str | None = None, cwd: Path | None = def titres_niveau1(source: Path) -> list[str]: - """Titres de niveau 1 d'un fichier Markdown, tels que pandoc les rendra.""" + """Titres de niveau 1 d'un fichier Markdown, tels qu'ils apparaîtront dans les signets. + + Pandoc écrit l'apostrophe typographique ’ en ' dans le LaTeX : le texte + imprimé garde ’ (ligature TeX) mais les signets reçoivent '. + """ ast = json.loads(executer(["pandoc", "-f", FORMAT_MARKDOWN, "-t", "json", str(source)])) titres = [] for bloc in ast["blocks"]: @@ -135,7 +139,7 @@ def titres_niveau1(source: Path) -> list[str]: texte = executer( ["pandoc", "-f", "json", "-t", "plain", "--wrap=none"], entree=json.dumps(titre) ) - titres.append(texte.strip()) + titres.append(texte.strip().replace("’", "'")) return titres diff --git a/pdf/tests/test_titres.py b/pdf/tests/test_titres.py index ed76e6a..e2e0822 100644 --- a/pdf/tests/test_titres.py +++ b/pdf/tests/test_titres.py @@ -17,7 +17,7 @@ def test_extrait_le_titre_avec_la_typographie_de_pandoc(tmp_path): "# Chapitre 1.1 — L'été `ls`\n\n## Section\n\n```\n# pas un titre\n```\n", ) - assert generer.titre_chapitre(chemin) == "Chapitre 1.1 — L’été ls" + assert generer.titre_chapitre(chemin) == "Chapitre 1.1 — L'été ls" @pytest.mark.parametrize("contenu", ["## Section seule\n", "# Un\n\n# Deux\n"]) From ac97271bb22f2fd594e13df1d57d89ccc4a76175 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 17:53:11 +0200 Subject: [PATCH 22/32] =?UTF-8?q?test:=20v=C3=A9rification=20du=20contenu?= =?UTF-8?q?=20des=2011=20PDF=20produits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- pdf/tests/test_pdfs.py | 79 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 pdf/tests/test_pdfs.py diff --git a/pdf/tests/test_pdfs.py b/pdf/tests/test_pdfs.py new file mode 100644 index 0000000..fc544e2 --- /dev/null +++ b/pdf/tests/test_pdfs.py @@ -0,0 +1,79 @@ +"""Vérification des PDF produits (lancer ./pdf/build pdf avant).""" + +import re +import subprocess + +import pypdf +import pytest + +import generer + +SORTIE = generer.RACINE / "build" / "pdf" +CATALOGUE = generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) +CARACTERES_FRANCAIS = set("éèêëàâçùûüîïôœÉÈÊÀÇÔ«»") +SEUIL_DEBORDEMENT_PT = 20.0 + + +@pytest.fixture(scope="module", params=CATALOGUE.documents, ids=lambda d: d.fichier) +def document(request): + return request.param + + +@pytest.fixture(scope="module") +def lecteur(document): + chemin = SORTIE / document.fichier + assert chemin.is_file(), f"{chemin} absent : lancer ./pdf/build pdf" + return pypdf.PdfReader(chemin) + + +@pytest.fixture(scope="module") +def log(document): + chemin = SORTIE / "debug" / f"{document.fichier.removesuffix('.pdf')}.log" + assert chemin.is_file(), f"{chemin} absent : lancer ./pdf/build pdf" + return chemin.read_text(encoding="utf-8", errors="replace") + + +def test_au_moins_deux_pages(lecteur): + assert len(lecteur.pages) >= 2 + + +def test_metadonnees(document, lecteur): + assert lecteur.metadata.title == document.titre + assert lecteur.metadata.author == CATALOGUE.auteur + assert lecteur.trailer["/Root"].get("/Lang") == "fr" + + +def test_signets_de_niveau_1_dans_l_ordre_des_sources(document, lecteur): + signets = [entree.title for entree in lecteur.outline if not isinstance(entree, list)] + + assert signets == [generer.titre_chapitre(source) for source in document.sources] + + +def test_caracteres_francais_preserves(document): + texte = subprocess.run( + ["pdftotext", str(SORTIE / document.fichier), "-"], + capture_output=True, + text=True, + check=True, + ).stdout + dans_les_sources = { + caractere + for source in document.sources + for caractere in source.read_text(encoding="utf-8") + if caractere in CARACTERES_FRANCAIS + } + + assert dans_les_sources - set(texte) == set() + + +def test_aucun_caractere_manquant(log): + assert "Missing character" not in log + + +def test_aucun_debordement_notable(log): + debordements = [ + float(largeur) + for largeur in re.findall(r"Overfull \\hbox \((\d+(?:\.\d+)?)pt too wide\)", log) + ] + + assert [d for d in debordements if d > SEUIL_DEBORDEMENT_PT] == [] From 8b73e79464cf3ee2dc02f37c846d7739dcee388e Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:04:41 +0200 Subject: [PATCH 23/32] =?UTF-8?q?fix:=20signets=20typographiques=20(=C3=A9?= =?UTF-8?q?crivain=20LaTeX=20sans=20smart)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'extension smart de l'écrivain LaTeX de pandoc réécrivait ’ “ ” — en ' `` '' --- ; hyperref transmettait ces formes ASCII aux signets. Pandoc écrit désormais vers latex-smart : les signets gardent ’ et les guillemets, et titres_niveau1 n'a plus besoin de remplacer l'apostrophe (annule le contournement de 5590dfc). Un test compile un petit document et lit ses signets. Co-Authored-By: Claude Opus 5.5 --- pdf/generer.py | 12 ++++++------ pdf/tests/test_compilation.py | 13 +++++++++++++ pdf/tests/test_latex.py | 3 +++ pdf/tests/test_titres.py | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/pdf/generer.py b/pdf/generer.py index ff47832..173deb2 100644 --- a/pdf/generer.py +++ b/pdf/generer.py @@ -122,11 +122,7 @@ def executer(commande: list[str], entree: str | None = None, cwd: Path | None = def titres_niveau1(source: Path) -> list[str]: - """Titres de niveau 1 d'un fichier Markdown, tels qu'ils apparaîtront dans les signets. - - Pandoc écrit l'apostrophe typographique ’ en ' dans le LaTeX : le texte - imprimé garde ’ (ligature TeX) mais les signets reçoivent '. - """ + """Titres de niveau 1 d'un fichier Markdown, tels que pandoc les rendra.""" ast = json.loads(executer(["pandoc", "-f", FORMAT_MARKDOWN, "-t", "json", str(source)])) titres = [] for bloc in ast["blocks"]: @@ -139,7 +135,7 @@ def titres_niveau1(source: Path) -> list[str]: texte = executer( ["pandoc", "-f", "json", "-t", "plain", "--wrap=none"], entree=json.dumps(titre) ) - titres.append(texte.strip().replace("’", "'")) + titres.append(texte.strip()) return titres @@ -192,6 +188,10 @@ def commande_pandoc(document: Document, auteur: str, couverture: Path, sortie: P "pandoc", "-f", FORMAT_MARKDOWN, + # Sans « -smart », l'écrivain LaTeX réécrit ’ “ ” — en ' `` '' --- : + # le texte imprimé n'y perd rien, mais les signets du PDF si. + "--to", + "latex-smart", "--standalone", "--output", str(sortie), diff --git a/pdf/tests/test_compilation.py b/pdf/tests/test_compilation.py index 47b285b..f0d21cd 100644 --- a/pdf/tests/test_compilation.py +++ b/pdf/tests/test_compilation.py @@ -1,5 +1,6 @@ """Tests de la compilation LaTeX et de la construction d'un document.""" +import pypdf import pytest import generer @@ -38,3 +39,15 @@ def test_construire_supprime_l_ancien_pdf_meme_en_cas_d_echec(tmp_path): generer.construire(generer.Document("doc.pdf", "Doc", (source,)), "Auteur", tmp_path) assert not ancien.exists() + + +def test_signets_gardent_la_typographie_des_titres(tmp_path): + source = tmp_path / "a.md" + source.write_text("# Un \"guillemet\" et 'simple' -- l'été\n\nTexte.\n", encoding="utf-8") + document = generer.Document("doc.pdf", "Doc", (source,)) + + pdf = generer.construire(document, "Auteur", tmp_path) + + signets = [entree.title for entree in pypdf.PdfReader(pdf).outline] + assert signets == ["Un “guillemet” et ‘simple’ – l’été"] + assert signets == [generer.titre_chapitre(source)] diff --git a/pdf/tests/test_latex.py b/pdf/tests/test_latex.py index e3b8459..b6b8220 100644 --- a/pdf/tests/test_latex.py +++ b/pdf/tests/test_latex.py @@ -44,6 +44,9 @@ def test_commande_pandoc(): assert commande[:3] == ["pandoc", "-f", generer.FORMAT_MARKDOWN] assert commande[-2:] == ["/s/a.md", "/s/b.md"] + # Sans « -smart », l'écrivain LaTeX réécrit ’ en ' (et “ ” en `` '') : les + # signets du PDF perdraient la typographie des titres. + assert commande[commande.index("--to") + 1] == "latex-smart" for attendu in ( "lang=fr", "documentclass=report", diff --git a/pdf/tests/test_titres.py b/pdf/tests/test_titres.py index e2e0822..ed76e6a 100644 --- a/pdf/tests/test_titres.py +++ b/pdf/tests/test_titres.py @@ -17,7 +17,7 @@ def test_extrait_le_titre_avec_la_typographie_de_pandoc(tmp_path): "# Chapitre 1.1 — L'été `ls`\n\n## Section\n\n```\n# pas un titre\n```\n", ) - assert generer.titre_chapitre(chemin) == "Chapitre 1.1 — L'été ls" + assert generer.titre_chapitre(chemin) == "Chapitre 1.1 — L’été ls" @pytest.mark.parametrize("contenu", ["## Section seule\n", "# Un\n\n# Deux\n"]) From 1cd468fcfaf5bbd39868f22b42d47a5ee259a6a5 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:05:30 +0200 Subject: [PATCH 24/32] =?UTF-8?q?fix:=20chercher=20les=20titres=20de=20niv?= =?UTF-8?q?eau=201=20=C3=A0=20toute=20profondeur=20de=20l'AST?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un titre de niveau 1 dans une liste, une citation ou un Div devient un chapitre LaTeX, mais titres_niveau1 ne regardait que les blocs de premier niveau : le chapitre parasite du TP Git 3 (« Jest pour les tests ») lui échappait. Le parcours est désormais récursif ; l'ancienne version du TP Git 3 est maintenant refusée par la règle « exactement un titre ». Co-Authored-By: Claude Opus 5.5 --- pdf/generer.py | 37 ++++++++++++++++++++++++++----------- pdf/tests/test_titres.py | 4 +++- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/pdf/generer.py b/pdf/generer.py index 173deb2..641b342 100644 --- a/pdf/generer.py +++ b/pdf/generer.py @@ -125,20 +125,35 @@ def titres_niveau1(source: Path) -> list[str]: """Titres de niveau 1 d'un fichier Markdown, tels que pandoc les rendra.""" ast = json.loads(executer(["pandoc", "-f", FORMAT_MARKDOWN, "-t", "json", str(source)])) titres = [] - for bloc in ast["blocks"]: - if bloc["t"] == "Header" and bloc["c"][0] == 1: - titre = { - "pandoc-api-version": ast["pandoc-api-version"], - "meta": {}, - "blocks": [{"t": "Plain", "c": bloc["c"][2]}], - } - texte = executer( - ["pandoc", "-f", "json", "-t", "plain", "--wrap=none"], entree=json.dumps(titre) - ) - titres.append(texte.strip()) + for contenu in _contenus_titres_niveau1(ast["blocks"]): + titre = { + "pandoc-api-version": ast["pandoc-api-version"], + "meta": {}, + "blocks": [{"t": "Plain", "c": contenu}], + } + texte = executer( + ["pandoc", "-f", "json", "-t", "plain", "--wrap=none"], entree=json.dumps(titre) + ) + titres.append(texte.strip()) return titres +def _contenus_titres_niveau1(noeud: object): + """Contenu des Header de niveau 1 d'un AST pandoc, à toute profondeur. + + Un titre dans une liste, une citation ou un Div devient aussi un chapitre. + """ + if isinstance(noeud, list): + for element in noeud: + yield from _contenus_titres_niveau1(element) + elif isinstance(noeud, dict): + if noeud.get("t") == "Header" and noeud["c"][0] == 1: + yield noeud["c"][2] + else: + for valeur in noeud.values(): + yield from _contenus_titres_niveau1(valeur) + + def titre_chapitre(source: Path) -> str: """Titre unique d'un fichier source (règle : exactement un titre de niveau 1).""" titres = titres_niveau1(source) diff --git a/pdf/tests/test_titres.py b/pdf/tests/test_titres.py index ed76e6a..149af28 100644 --- a/pdf/tests/test_titres.py +++ b/pdf/tests/test_titres.py @@ -20,7 +20,9 @@ def test_extrait_le_titre_avec_la_typographie_de_pandoc(tmp_path): assert generer.titre_chapitre(chemin) == "Chapitre 1.1 — L’été ls" -@pytest.mark.parametrize("contenu", ["## Section seule\n", "# Un\n\n# Deux\n"]) +@pytest.mark.parametrize( + "contenu", ["## Section seule\n", "# Un\n\n# Deux\n", "- # Un\n\n# Deux\n"] +) def test_refuse_zero_ou_plusieurs_titres_de_niveau_1(tmp_path, contenu): with pytest.raises(generer.ErreurBuild, match="exactement 1 attendu"): generer.titre_chapitre(source(tmp_path, contenu)) From c7592f9f5e445a96daedf0bf20e71847b4468387 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:06:20 +0200 Subject: [PATCH 25/32] =?UTF-8?q?test:=20messages=20d'=C3=A9chec=20plus=20?= =?UTF-8?q?utiles=20et=20fixture=20chemin=5Fpdf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test_pdfs : fixture chemin_pdf partagée, caractères manquants nommés, chaque débordement affiché avec sa largeur et ses lignes du .tex ; - test_sources : consigne code -> backticks, prose -> — ou – ; - preambule.tex : limite connue de la coupure des \texttt (groupes {...}). Co-Authored-By: Claude Opus 5.5 --- pdf/preambule.tex | 2 ++ pdf/tests/test_pdfs.py | 26 ++++++++++++++++++-------- pdf/tests/test_sources.py | 3 ++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/pdf/preambule.tex b/pdf/preambule.tex index c023249..25a6e49 100644 --- a/pdf/preambule.tex +++ b/pdf/preambule.tex @@ -25,6 +25,8 @@ % Code en ligne : LaTeX ne coupe jamais un \texttt, un long chemin déborde % donc dans la marge. On autorise une coupure (sans trait d'union) après % chaque / et chaque \ du code en ligne. +% Limite connue : le remplacement ne descend pas dans les groupes {...} +% de l'argument (un / entre accolades reste insécable). \ExplSyntaxOn \cs_new_eq:NN \__formation_texttt:n \texttt \tl_new:N \l__formation_code_tl diff --git a/pdf/tests/test_pdfs.py b/pdf/tests/test_pdfs.py index fc544e2..8aa1ef9 100644 --- a/pdf/tests/test_pdfs.py +++ b/pdf/tests/test_pdfs.py @@ -20,10 +20,15 @@ def document(request): @pytest.fixture(scope="module") -def lecteur(document): +def chemin_pdf(document): chemin = SORTIE / document.fichier assert chemin.is_file(), f"{chemin} absent : lancer ./pdf/build pdf" - return pypdf.PdfReader(chemin) + return chemin + + +@pytest.fixture(scope="module") +def lecteur(chemin_pdf): + return pypdf.PdfReader(chemin_pdf) @pytest.fixture(scope="module") @@ -49,9 +54,9 @@ def test_signets_de_niveau_1_dans_l_ordre_des_sources(document, lecteur): assert signets == [generer.titre_chapitre(source) for source in document.sources] -def test_caracteres_francais_preserves(document): +def test_caracteres_francais_preserves(document, chemin_pdf): texte = subprocess.run( - ["pdftotext", str(SORTIE / document.fichier), "-"], + ["pdftotext", str(chemin_pdf), "-"], capture_output=True, text=True, check=True, @@ -63,7 +68,8 @@ def test_caracteres_francais_preserves(document): if caractere in CARACTERES_FRANCAIS } - assert dans_les_sources - set(texte) == set() + manquants = "".join(sorted(dans_les_sources - set(texte))) + assert manquants == "", f"caractères des sources absents du PDF : {manquants}" def test_aucun_caractere_manquant(log): @@ -72,8 +78,12 @@ def test_aucun_caractere_manquant(log): def test_aucun_debordement_notable(log): debordements = [ - float(largeur) - for largeur in re.findall(r"Overfull \\hbox \((\d+(?:\.\d+)?)pt too wide\)", log) + f"{largeur}pt {lignes}" + for largeur, lignes in re.findall( + r"Overfull \\hbox \((\d+(?:\.\d+)?)pt too wide\) (.*)", + log, + ) + if float(largeur) > SEUIL_DEBORDEMENT_PT ] - assert [d for d in debordements if d > SEUIL_DEBORDEMENT_PT] == [] + assert debordements == [], "lignes du .tex de build/pdf/debug :\n" + "\n".join(debordements) diff --git a/pdf/tests/test_sources.py b/pdf/tests/test_sources.py index dad85c8..ef4eacc 100644 --- a/pdf/tests/test_sources.py +++ b/pdf/tests/test_sources.py @@ -39,4 +39,5 @@ def test_pas_de_double_tiret_hors_code(): if "--" in texte ) - assert fautes == [], "mettre ces valeurs entre backticks :\n" + "\n".join(fautes) + consigne = "« -- » hors code : code -> backticks ; prose -> écrire — ou – :\n" + assert fautes == [], consigne + "\n".join(fautes) From 4adb6fc95f3b174264a6cb5ffc90a1f5b65c34ce Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:09:34 +0200 Subject: [PATCH 26/32] ci: workflow PDF unique (build, tests, release sur tag) Co-Authored-By: Claude Opus 5.5 --- .github/workflows/pdf.yml | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/pdf.yml diff --git a/.github/workflows/pdf.yml b/.github/workflows/pdf.yml new file mode 100644 index 0000000..9b02a72 --- /dev/null +++ b/.github/workflows/pdf.yml @@ -0,0 +1,63 @@ +# Génération et vérification des PDF : mêmes commandes qu'en local. +name: PDF + +on: + push: + branches: [master] + tags: ["v*"] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + pdf: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + + - name: Lint + run: ./pdf/build check + + - name: PDF + run: ./pdf/build pdf + + - name: Tests + run: ./pdf/build test + + - name: Publier les PDF (artifact) + uses: actions/upload-artifact@v7 + with: + name: pdf-${{ github.sha }} + path: build/pdf/*.pdf + if-no-files-found: error + + - name: Publier le diagnostic (en cas d'échec) + if: failure() + uses: actions/upload-artifact@v7 + with: + name: debug-${{ github.sha }} + path: build/pdf/debug/ + if-no-files-found: ignore + + release: + needs: pdf + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/download-artifact@v8 + with: + name: pdf-${{ github.sha }} + path: pdf + + - name: Créer la release + env: + GH_TOKEN: ${{ github.token }} + run: > + gh release create "${{ github.ref_name }}" pdf/*.pdf + --repo "${{ github.repository }}" + --title "Formation Linux ${{ github.ref_name }}" + --generate-notes From 20bff98fb5d1f3ad5168f8e86b133fd4ca2c8781 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:10:52 +0200 Subject: [PATCH 27/32] =?UTF-8?q?refactor:=20supprimer=20l'ancienne=20cha?= =?UTF-8?q?=C3=AEne=20PDF=20(scripts=20et=20workflows)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .github/WORKFLOWS.md | 168 -------- .github/workflows/build-artifacts-only.yml | 109 ----- .github/workflows/build-pdfs.yml | 255 ------------ .github/workflows/test-build.yml | 152 ------- scripts/README.md | 152 ------- scripts/build_docker_module.sh | 185 --------- scripts/build_formations.sh | 448 --------------------- scripts/build_formations_ci.sh | 227 ----------- scripts/build_git_module.sh | 158 -------- scripts/build_modules_additionnels.sh | 247 ------------ scripts/build_pdf.sh | 233 ----------- scripts/build_pdf_with_cover.sh | 243 ----------- scripts/clean_unicode.sh | 117 ------ scripts/config.sh | 178 -------- scripts/templates/couverture.tex | 153 ------- scripts/templates/couverture_module.tex | 103 ----- scripts/templates/formation_template.tex | 224 ----------- scripts/templates/pdf_template.tex | 152 ------- scripts/templates/template.tex | 163 -------- 19 files changed, 3667 deletions(-) delete mode 100644 .github/WORKFLOWS.md delete mode 100644 .github/workflows/build-artifacts-only.yml delete mode 100644 .github/workflows/build-pdfs.yml delete mode 100644 .github/workflows/test-build.yml delete mode 100644 scripts/README.md delete mode 100755 scripts/build_docker_module.sh delete mode 100755 scripts/build_formations.sh delete mode 100755 scripts/build_formations_ci.sh delete mode 100755 scripts/build_git_module.sh delete mode 100755 scripts/build_modules_additionnels.sh delete mode 100755 scripts/build_pdf.sh delete mode 100755 scripts/build_pdf_with_cover.sh delete mode 100755 scripts/clean_unicode.sh delete mode 100755 scripts/config.sh delete mode 100644 scripts/templates/couverture.tex delete mode 100644 scripts/templates/couverture_module.tex delete mode 100644 scripts/templates/formation_template.tex delete mode 100644 scripts/templates/pdf_template.tex delete mode 100644 scripts/templates/template.tex diff --git a/.github/WORKFLOWS.md b/.github/WORKFLOWS.md deleted file mode 100644 index 9551ef5..0000000 --- a/.github/WORKFLOWS.md +++ /dev/null @@ -1,168 +0,0 @@ -# 🤖 Documentation des Workflows GitHub Actions - -Ce fichier documente les workflows GitHub Actions qui automatisent la génération des PDFs de formation. - -## 📋 Workflows disponibles - -### 1. `build-pdfs.yml` - Build Production 📚 - -**Déclencheurs :** -- Push sur `master`/`main` -- Modification des fichiers dans `supports/`, `travaux_pratiques/`, `scripts/` -- Déclenchement manuel via GitHub UI - -**Actions :** -- ✅ Installation de LaTeX et pandoc -- ✅ Génération des modules Git et Docker -- ✅ Upload des PDFs comme artifacts -- ✅ Création automatique d'une release avec les PDFs - -**Artifacts générés :** -- `formation-linux-pdfs-{SHA}` - Tous les PDFs -- `module-git-pdf` - Module Git uniquement -- `module-docker-pdf` - Module Docker uniquement - -### 2. `test-build.yml` - Tests PR 🧪 - -**Déclencheurs :** -- Pull Requests vers `master`/`main` -- Modification des fichiers de contenu ou scripts - -**Actions :** -- ✅ Test de compilation des modules -- ✅ Validation de la génération PDF -- ✅ Rapport de test dans la PR - -### 3. `build-artifacts-only.yml` - Build Manuel 📦 - -**Déclencheurs :** -- Déclenchement manuel uniquement -- Utile si problème de permissions pour les releases - -**Actions :** -- ✅ Génération des PDFs -- ✅ Upload artifacts uniquement (pas de release) -- ✅ Rapport détaillé des résultats - -## 🚀 Utilisation - -### Récupérer les PDFs automatiques - -#### Option 1 : Depuis les Releases -1. Aller sur [Releases](../../releases) -2. Télécharger la dernière version -3. Les PDFs sont attachés comme assets - -#### Option 2 : Depuis les Actions -1. Aller sur [Actions](../../actions) -2. Cliquer sur un build réussi -3. Télécharger les artifacts en bas de page - -#### Option 3 : Déclenchement manuel -1. Aller sur [Actions](../../actions/workflows/build-pdfs.yml) -2. Cliquer sur "Run workflow" -3. Attendre la fin du build (~5-10 minutes) - -#### Option 4 : Build de contournement (si problème de permissions) -1. Aller sur [Actions](../../actions/workflows/build-artifacts-only.yml) -2. Cliquer sur "Run workflow" -3. Télécharger les artifacts (pas de release créée) - -### Pour les développeurs - -#### Test local avant push -```bash -# Vérifier que les scripts fonctionnent -./scripts/build_git_module.sh -./scripts/build_docker_module.sh - -# Vérifier les caractères Unicode -find supports/ travaux_pratiques/ -name "*.md" -exec grep -l "🔥\|⚠️\|✅" {} \; -``` - -#### Débugger un échec de build -1. Regarder les logs dans l'onglet Actions -2. Les erreurs LaTeX sont souvent liées aux caractères Unicode -3. Utiliser `./scripts/clean_unicode.sh` sur les fichiers problématiques - -## 📋 Configuration - -### Variables d'environnement -Aucune variable personnalisée n'est requise. Le workflow utilise : -- `GITHUB_TOKEN` (automatique) -- `github.sha`, `github.ref` (automatiques) - -### Permissions requises -Le workflow a besoin des permissions pour : -- ✅ Lire le code source (`contents: read`) -- ✅ Écrire les releases (`contents: write`) -- ✅ Upload des artifacts (automatique) - -### Durée de rétention -- **Artifacts** : 90 jours -- **Releases** : Permanent -- **Logs de build** : 30 jours (paramètre GitHub) - -## 🐛 Dépannage - -### Problèmes courants - -#### Erreur 403 lors de la création de release -``` -⚠️ GitHub release failed with status: 403 -``` -**Solutions :** -1. **Vérifier les permissions du repository** (Settings → Actions → General) -2. **Utiliser le workflow de contournement** `build-artifacts-only.yml` -3. **Vérifier les tokens** dans les secrets du repository -4. **Alternative :** Récupérer les PDFs via les artifacts - -#### Build qui échoue sur LaTeX -``` -! LaTeX Error: Unicode character ⚠️ (U+26A0) not set up for use with LaTeX -``` -**Solution :** Lancer `./scripts/clean_unicode.sh` sur le fichier problématique - -#### Build lent ou timeout -``` -timeout 300s ./scripts/build_docker_module.sh -``` -**Solution :** Le timeout est normal sur les grosses documentations, augmenter si nécessaire - -#### Pas d'artifacts générés -**Vérification :** -1. Le build s'est-il bien terminé ? -2. Y a-t-il des fichiers dans `build/**/*.pdf` ? -3. Les permissions GitHub sont-elles correctes ? - -### Logs utiles -```bash -# Voir la taille des PDFs générés -find build/ -name "*.pdf" -exec ls -lh {} \; - -# Vérifier les caractères problématiques -grep -r "🔥\|⚠️\|✅\|⏰" supports/ travaux_pratiques/ - -# Tester la génération manuelle -./scripts/build_modules_additionnels.sh -``` - -## 🔧 Maintenance - -### Mise à jour des dépendances LaTeX -Si des nouveaux packages LaTeX sont nécessaires, les ajouter dans `build-pdfs.yml` : -```yaml -sudo apt-get install -y \ - texlive-latex-recommended \ - texlive-fonts-extra \ - # Nouveau package ici -``` - -### Optimisation des builds -- Utiliser le cache GitHub pour les dépendances LaTeX -- Paralléliser la génération des modules -- Compresser les artifacts volumineux - ---- - -💡 **Tip :** Les workflows s'activent automatiquement dès que ce repository est poussé sur GitHub avec les workflows dans `.github/workflows/`. \ No newline at end of file diff --git a/.github/workflows/build-artifacts-only.yml b/.github/workflows/build-artifacts-only.yml deleted file mode 100644 index 692ff71..0000000 --- a/.github/workflows/build-artifacts-only.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: 📦 Build PDFs (Artifacts Only) - -# Workflow de fallback qui génère seulement les artifacts sans créer de releases -# Utile si les permissions pour les releases posent problème - -on: - workflow_dispatch: # Déclenchement manuel uniquement - inputs: - reason: - description: 'Raison du build manuel' - required: false - default: 'Build de test' - -jobs: - build-artifacts: - runs-on: ubuntu-latest - permissions: - contents: read - actions: write - - steps: - - name: 📥 Checkout repository - uses: actions/checkout@v4 - - - name: 📝 Build reason - run: | - echo "🎯 **Raison du build:** ${{ github.event.inputs.reason || 'Build manuel' }}" >> $GITHUB_STEP_SUMMARY - echo "👤 **Déclenché par:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - - name: 🛠️ Install LaTeX and dependencies - run: | - sudo apt-get update -qq - sudo apt-get install -y \ - pandoc \ - texlive-latex-recommended \ - texlive-latex-extra \ - texlive-fonts-recommended \ - texlive-lang-french \ - ghostscript > /dev/null 2>&1 - - echo "✅ Dépendances installées" - - - name: 📁 Create build directories - run: | - mkdir -p build/modules_additionnels - chmod +x scripts/*.sh - - - name: 🔨 Build Git module - run: | - echo "🔨 Construction du module Git..." - if ./scripts/build_git_module.sh > /dev/null 2>&1; then - echo "✅ Module Git: Success" - echo "GIT_SUCCESS=true" >> $GITHUB_ENV - else - echo "❌ Module Git: Failed" - echo "GIT_SUCCESS=false" >> $GITHUB_ENV - fi - - - name: 🐳 Build Docker module - run: | - echo "🐳 Construction du module Docker..." - if ./scripts/build_docker_module.sh > /dev/null 2>&1; then - echo "✅ Module Docker: Success" - echo "DOCKER_SUCCESS=true" >> $GITHUB_ENV - else - echo "❌ Module Docker: Failed" - echo "DOCKER_SUCCESS=false" >> $GITHUB_ENV - fi - - - name: 📊 Build Summary - run: | - echo "## 📊 Résultats de Construction" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$GIT_SUCCESS" = "true" ] && [ -f "build/modules_additionnels/module_additionnel_git.pdf" ]; then - size_git=$(du -h build/modules_additionnels/module_additionnel_git.pdf | cut -f1) - echo "✅ **Module Git:** $size_git" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Module Git:** Échec de génération" >> $GITHUB_STEP_SUMMARY - fi - - if [ "$DOCKER_SUCCESS" = "true" ] && [ -f "build/modules_additionnels/module_additionnel_docker.pdf" ]; then - size_docker=$(du -h build/modules_additionnels/module_additionnel_docker.pdf | cut -f1) - echo "✅ **Module Docker:** $size_docker" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Module Docker:** Échec de génération" >> $GITHUB_STEP_SUMMARY - fi - - - name: 📤 Upload All PDFs - uses: actions/upload-artifact@v4 - if: always() - with: - name: formation-pdfs-manual-${{ github.run_number }} - path: build/**/*.pdf - retention-days: 30 - if-no-files-found: warn - - - name: 📋 Final Instructions - if: always() - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "## 📥 Comment récupérer les PDFs" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "1. Faire défiler vers le bas de cette page" >> $GITHUB_STEP_SUMMARY - echo "2. Dans la section **Artifacts**, cliquer sur \`formation-pdfs-manual-${{ github.run_number }}\`" >> $GITHUB_STEP_SUMMARY - echo "3. Le fichier ZIP contient tous les PDFs générés" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "💡 **Note:** Ce workflow ne crée pas de release, seulement des artifacts temporaires (30 jours)." >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/build-pdfs.yml b/.github/workflows/build-pdfs.yml deleted file mode 100644 index 001909a..0000000 --- a/.github/workflows/build-pdfs.yml +++ /dev/null @@ -1,255 +0,0 @@ -name: 📚 Build Formation PDFs - -on: - push: - branches: [ master, main ] - paths: - - 'supports/**' - - 'travaux_pratiques/**' - - 'scripts/**' - - '.github/workflows/build-pdfs.yml' - workflow_dispatch: # Permet de déclencher manuellement - -jobs: - build-pdfs: - runs-on: ubuntu-latest - permissions: - contents: write # Nécessaire pour créer des releases - actions: read # Lire les actions - packages: read # Lire les packages (pour les images Docker si besoin) - - steps: - - name: 📥 Checkout repository - uses: actions/checkout@v4 - - - name: 🛠️ Install LaTeX and dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - pandoc \ - texlive-latex-recommended \ - texlive-latex-extra \ - texlive-fonts-recommended \ - texlive-lang-french \ - texlive-xetex \ - texlive-fonts-extra \ - texlive-science \ - ghostscript - - - name: ✅ Verify installations - run: | - pandoc --version - pdflatex --version - gs --version - echo "📋 Testing French support..." - pdflatex -interaction=nonstopmode <<< '\documentclass{article}\usepackage[french]{babel}\usepackage[utf8]{inputenc}\begin{document}Test français: é à ç\end{document}' && echo "✅ French support OK" || echo "❌ French support failed" - - - name: 📁 Create build directories - run: | - mkdir -p build/formations - mkdir -p build/modules_base - mkdir -p build/modules_additionnels - chmod +x scripts/*.sh - - - name: 📚 Build all formations and modules - run: | - echo "📚 Building all formations and modules..." - ./scripts/build_formations.sh || { - echo "⚠️ Main script failed, trying CI-optimized script..." - ./scripts/build_formations_ci.sh || echo "⚠️ Both scripts failed" - } - - - name: 📊 List generated files - run: | - echo "📊 Generated PDFs:" - find build/ -name "*.pdf" -exec ls -lh {} \; || echo "No PDFs found" - - - name: 📤 Upload all PDFs as artifacts - uses: actions/upload-artifact@v4 - if: always() # Upload même si certains builds ont échoué - with: - name: formation-linux-pdfs-${{ github.sha }} - path: | - build/**/*.pdf - retention-days: 90 - - - name: 📤 Upload formation accélérée - uses: actions/upload-artifact@v4 - if: always() - with: - name: formation-acceleree-pdf - path: build/formations/formation_acceleree.pdf - retention-days: 90 - if-no-files-found: warn - - - name: 📤 Upload formation longue - uses: actions/upload-artifact@v4 - if: always() - with: - name: formation-longue-pdf - path: build/formations/formation_longue.pdf - retention-days: 90 - if-no-files-found: warn - - - name: 📤 Upload modules de base individuels - uses: actions/upload-artifact@v4 - if: always() - with: - name: modules-base-individuels-pdf - path: build/modules_base/*.pdf - retention-days: 90 - if-no-files-found: warn - - - name: 📤 Upload Git module PDF - uses: actions/upload-artifact@v4 - if: always() - with: - name: module-git-pdf - path: build/modules_additionnels/module_additionnel_git.pdf - retention-days: 90 - if-no-files-found: warn - - - name: 📤 Upload Docker module PDF - uses: actions/upload-artifact@v4 - if: always() - with: - name: module-docker-pdf - path: build/modules_additionnels/module_additionnel_docker.pdf - retention-days: 90 - if-no-files-found: warn - - - name: 📋 Build summary - if: always() - run: | - echo "## 📚 Build Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Formation accélérée - if [ -f "build/formations/formation_acceleree.pdf" ]; then - size_acceleree=$(du -h build/formations/formation_acceleree.pdf | cut -f1) - echo "✅ **Formation accélérée** : $size_acceleree" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Formation accélérée** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - # Formation longue - if [ -f "build/formations/formation_longue.pdf" ]; then - size_longue=$(du -h build/formations/formation_longue.pdf | cut -f1) - echo "✅ **Formation longue** : $size_longue" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Formation longue** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - # Modules de base individuels - modules_count=$(find build/modules_base/ -name "*.pdf" 2>/dev/null | wc -l || echo "0") - if [ "$modules_count" -gt 0 ]; then - echo "✅ **Modules de base individuels** : $modules_count modules générés" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Modules de base individuels** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - # Modules additionnels - if [ -f "build/modules_additionnels/module_additionnel_git.pdf" ]; then - size_git=$(du -h build/modules_additionnels/module_additionnel_git.pdf | cut -f1) - echo "✅ **Module Git** : $size_git" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Module Git** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - if [ -f "build/modules_additionnels/module_additionnel_docker.pdf" ]; then - size_docker=$(du -h build/modules_additionnels/module_additionnel_docker.pdf | cut -f1) - echo "✅ **Module Docker** : $size_docker" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Module Docker** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "📥 **Artifacts disponibles :**" >> $GITHUB_STEP_SUMMARY - echo "- \`formation-linux-pdfs-${{ github.sha }}\` - Tous les PDFs" >> $GITHUB_STEP_SUMMARY - echo "- \`formation-acceleree-pdf\` - Formation accélérée (8h)" >> $GITHUB_STEP_SUMMARY - echo "- \`formation-longue-pdf\` - Formation longue (37h30)" >> $GITHUB_STEP_SUMMARY - echo "- \`modules-base-individuels-pdf\` - 8 modules de base séparés" >> $GITHUB_STEP_SUMMARY - echo "- \`module-git-pdf\` - Module Git uniquement" >> $GITHUB_STEP_SUMMARY - echo "- \`module-docker-pdf\` - Module Docker uniquement" >> $GITHUB_STEP_SUMMARY - - # Job optionnel pour créer une release automatique - create-release: - needs: build-pdfs - runs-on: ubuntu-latest - permissions: - contents: write # Nécessaire pour créer des releases - actions: read # Lire les actions - if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' && github.event_name == 'push' - - steps: - - name: 📥 Checkout repository - uses: actions/checkout@v4 - - - name: 📥 Download all artifacts - uses: actions/download-artifact@v4 - with: - pattern: "*pdf*" - path: ./pdfs - - - name: 📋 List downloaded files - run: | - echo "📋 Downloaded PDFs:" - find ./pdfs -name "*.pdf" -exec ls -lh {} \; || echo "⚠️ Aucun PDF trouvé" - - - name: ✅ Verify PDFs exist - run: | - if find ./pdfs -name "*.pdf" | head -1 | grep -q .; then - echo "✅ PDFs found, proceeding with release" - echo "PDF_EXISTS=true" >> $GITHUB_ENV - else - echo "❌ No PDFs found, skipping release" - echo "PDF_EXISTS=false" >> $GITHUB_ENV - fi - - - name: 🏷️ Create Release - if: success() && env.PDF_EXISTS == 'true' - uses: softprops/action-gh-release@v2 - continue-on-error: true - with: - tag_name: v${{ github.run_number }} - name: "Formation Linux - Build ${{ github.run_number }}" - body: | - ## 📚 Formation Linux - PDFs automatiques - - **Commit:** `${{ github.sha }}` - **Branche:** `${{ github.ref_name }}` - **Date:** ${{ github.event.head_commit.timestamp }} - - ### 📄 Modules disponibles : - - **Module Git** : Contrôle de version (6-8h) - - **Module Docker** : Conteneurisation (12-15h) - - ### 🚀 Utilisation : - 1. Télécharger les PDFs depuis les assets ci-dessous - 2. Consulter la [documentation](https://github.com/${{ github.repository }}) pour les prérequis - 3. Suivre les modules dans l'ordre recommandé - - --- - 🤖 Build automatique via GitHub Actions - files: | - ./pdfs/**/*.pdf - draft: false - prerelease: false - fail_on_unmatched_files: false - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: 📋 Release Status - if: always() - run: | - echo "## 🏷️ Release Information" >> $GITHUB_STEP_SUMMARY - if [ "${{ env.PDF_EXISTS }}" = "true" ]; then - echo "✅ **PDFs disponibles** pour release" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Aucun PDF** généré pour cette release" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Alternative :** Les PDFs sont toujours disponibles via les artifacts du build principal." >> $GITHUB_STEP_SUMMARY - echo "Aller dans [Actions](/${{ github.repository }}/actions) → Build #${{ github.run_number }} → Artifacts" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml deleted file mode 100644 index 4ec4917..0000000 --- a/.github/workflows/test-build.yml +++ /dev/null @@ -1,152 +0,0 @@ -name: 🧪 Test Formation PDFs - -on: - pull_request: - branches: [ master, main ] - paths: - - 'supports/**' - - 'travaux_pratiques/**' - - 'scripts/**' - - '.github/workflows/test-build.yml' - - '.github/workflows/build-pdfs.yml' - workflow_dispatch: # Permet de déclencher manuellement - -jobs: - test-build-pdfs: - runs-on: ubuntu-latest - permissions: - actions: read # Lire les actions - packages: read # Lire les packages - - steps: - - name: 📥 Checkout repository - uses: actions/checkout@v4 - - - name: 🛠️ Install LaTeX and dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - pandoc \ - texlive-latex-recommended \ - texlive-latex-extra \ - texlive-fonts-recommended \ - texlive-lang-french \ - texlive-xetex \ - ghostscript - - - name: ✅ Verify installations - run: | - pandoc --version - pdflatex --version - gs --version - echo "📋 Testing French support..." - pdflatex -interaction=nonstopmode <<< '\documentclass{article}\usepackage[french]{babel}\usepackage[utf8]{inputenc}\begin{document}Test français: é à ç\end{document}' && echo "✅ French support OK" || echo "❌ French support failed" - - - name: 📁 Create build directories - run: | - mkdir -p build/formations - mkdir -p build/modules_base - mkdir -p build/modules_additionnels - chmod +x scripts/*.sh - - - name: 📚 Test PDF generation - run: | - echo "🧪 Testing PDF generation according to specifications..." - ./scripts/build_formations.sh - - - name: 📊 Validate generated files - run: | - echo "📊 Validation des PDFs générés:" - find build/ -name "*.pdf" -exec ls -lh {} \; || echo "❌ No PDFs found" - - # Vérifications de base - if [ -f "build/formations/formation_acceleree.pdf" ]; then - echo "✅ Formation accélérée générée" - else - echo "❌ Formation accélérée manquante" - exit 1 - fi - - if [ -f "build/formations/formation_longue.pdf" ]; then - echo "✅ Formation longue générée" - else - echo "❌ Formation longue manquante" - exit 1 - fi - - # Vérifier les modules de base (au moins quelques-uns) - modules_count=$(find build/modules_base/ -name "*.pdf" 2>/dev/null | wc -l || echo "0") - if [ "$modules_count" -gt 0 ]; then - echo "✅ $modules_count modules de base générés" - else - echo "❌ Aucun module de base généré" - exit 1 - fi - - echo "🎉 Toutes les validations passées!" - - - name: 📤 Upload test artifacts - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-formation-linux-pdfs-${{ github.sha }} - path: | - build/**/*.pdf - retention-days: 7 # Conservation courte pour les tests - - - name: 📋 Test Summary - if: always() - run: | - echo "## 🧪 Test Build Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Formation accélérée - if [ -f "build/formations/formation_acceleree.pdf" ]; then - size_acceleree=$(du -h build/formations/formation_acceleree.pdf | cut -f1) - echo "✅ **Formation accélérée** : $size_acceleree" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Formation accélérée** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - # Formation longue - if [ -f "build/formations/formation_longue.pdf" ]; then - size_longue=$(du -h build/formations/formation_longue.pdf | cut -f1) - echo "✅ **Formation longue** : $size_longue" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Formation longue** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - # Modules de base individuels - modules_count=$(find build/modules_base/ -name "*.pdf" 2>/dev/null | wc -l || echo "0") - if [ "$modules_count" -gt 0 ]; then - echo "✅ **Modules de base individuels** : $modules_count modules générés" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Modules de base individuels** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - # Modules additionnels - if [ -f "build/modules_additionnels/module_additionnel_git.pdf" ]; then - size_git=$(du -h build/modules_additionnels/module_additionnel_git.pdf | cut -f1) - echo "✅ **Module Git** : $size_git" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Module Git** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - if [ -f "build/modules_additionnels/module_additionnel_docker.pdf" ]; then - size_docker=$(du -h build/modules_additionnels/module_additionnel_docker.pdf | cut -f1) - echo "✅ **Module Docker** : $size_docker" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Module Docker** : Failed to build" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📋 Informations du test" >> $GITHUB_STEP_SUMMARY - echo "- **Type :** Test (sans release)" >> $GITHUB_STEP_SUMMARY - echo "- **Trigger :** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY - echo "- **Branche :** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY - echo "- **Commit :** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📥 Artifacts de test" >> $GITHUB_STEP_SUMMARY - echo "Les PDFs de test sont disponibles dans l'artifact \`test-formation-linux-pdfs-${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY - echo "Conservation : 7 jours" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index eb9b146..0000000 --- a/scripts/README.md +++ /dev/null @@ -1,152 +0,0 @@ -# Scripts de génération des supports - -Ce dossier contient tous les scripts nécessaires à la génération automatique des supports PDF de la formation Linux. - -## Scripts principaux - -### `build_all.sh` -Script principal qui génère tous les formats de supports : -- Formation complète (PDF complet avec tous les modules et TP) -- Formation accélérée (PDF condensé pour le format 8h) -- Supports par module (PDF individuels pour chaque module) - -```bash -./scripts/build_all.sh -``` - -### `build_pdf.sh` -Génération d'un PDF complet spécifique : - -```bash -# Formation complète -./scripts/build_pdf.sh complete - -# Formation accélérée -./scripts/build_pdf.sh acceleree -``` - -### `build_modules.sh` -Génération des PDFs individuels pour chaque module standard : - -```bash -./scripts/build_modules.sh -``` - -### `build_modules_additionnels.sh` -Génération des PDFs pour les modules additionnels (Git, Docker, etc.) : - -```bash -./scripts/build_modules_additionnels.sh -``` - -### `build_git_module.sh` -Génération rapide du module Git uniquement (pour test) : - -```bash -./scripts/build_git_module.sh -``` - -## Configuration - -### `config.sh` -Fichier de configuration centrale contenant : -- Paramètres de génération (auteur, titre, version) -- Chemins des répertoires -- Configuration Pandoc et LaTeX -- Fonctions utilitaires partagées - -### `templates/` -- `pdf_template.tex` : Template LaTeX personnalisé pour une mise en page professionnelle - -## Prérequis - -### Installation des dépendances (Ubuntu/Debian) -```bash -sudo apt-get update -sudo apt-get install pandoc texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended -``` - -### Vérification des dépendances -```bash -# Vérifier que pandoc est installé -pandoc --version - -# Vérifier que pdflatex est disponible -pdflatex --version -``` - -## Utilisation - -1. **Génération complète** (recommandé) : - ```bash - ./scripts/build_all.sh - ``` - -2. **Génération d'un format spécifique** : - ```bash - ./scripts/build_pdf.sh complete # Formation complète - ./scripts/build_pdf.sh acceleree # Formation accélérée - ./scripts/build_modules.sh # Modules individuels - ``` - -## Structure des fichiers générés - -``` -build/ -├── formation_complete.pdf # Formation complète (tous modules + TP) -├── formation_acceleree.pdf # Formation accélérée (modules essentiels) -├── supports_par_module/ # PDFs individuels par module standard -│ ├── module_01_decouverte.pdf -│ ├── module_02_navigation.pdf -│ └── ... -└── modules_additionnels/ # PDFs modules additionnels - ├── module_additionnel_git.pdf - └── module_additionnel_docker.pdf (exemple futur) -``` - -## Personnalisation - -### Modifier le template -Éditer `templates/pdf_template.tex` pour personnaliser : -- Couleurs et mise en page -- En-têtes et pieds de page -- Page de titre -- Styles de code - -### Ajouter un module standard -1. Créer le dossier `supports/module_XX_nom/` -2. Ajouter les fichiers `.md` du contenu -3. Créer le dossier `travaux_pratiques/tpXX_nom/` -4. Mettre à jour `build_modules.sh` avec le nouveau module - -### Ajouter un module additionnel -1. Créer le dossier `supports/modules_additionnels/module_nom/` -2. Ajouter les fichiers `.md` du contenu (ex: 01_introduction.md, 02_concepts.md) -3. Créer le dossier `travaux_pratiques/tp_additionnels/tp_nom/` -4. Le script `build_modules_additionnels.sh` détectera automatiquement le nouveau module - -#### Exemple pour un module Docker -``` -supports/modules_additionnels/module_docker/ -├── 01_introduction_docker.md -├── 02_conteneurs.md -└── 03_orchestration.md - -travaux_pratiques/tp_additionnels/tp_docker/ -├── tp01_premiers_conteneurs.md -└── tp02_volumes_reseaux.md -``` - -## Dépannage - -### Erreur "pandoc not found" -Installer pandoc : `sudo apt-get install pandoc` - -### Erreur "pdflatex not found" -Installer LaTeX : `sudo apt-get install texlive-latex-base texlive-latex-extra` - -### Erreur de génération LaTeX -Le script génère automatiquement une version simplifiée en cas d'erreur avec le template personnalisé. - -### PDF vides ou incorrects -Vérifier que les fichiers `.md` existent dans les dossiers de modules et qu'ils contiennent du contenu valide. \ No newline at end of file diff --git a/scripts/build_docker_module.sh b/scripts/build_docker_module.sh deleted file mode 100755 index f4ae84f..0000000 --- a/scripts/build_docker_module.sh +++ /dev/null @@ -1,185 +0,0 @@ -#!/bin/bash - -# Script de génération PDF pour le module Docker uniquement -# Usage: ./build_docker_module.sh - -set -e - -# Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR="$(dirname "$SCRIPT_DIR")" -BUILD_DIR="$ROOT_DIR/build/modules_additionnels" -DOCKER_DIR="$ROOT_DIR/supports/modules_additionnels/module_docker" -DOCKER_TP_DIR="$ROOT_DIR/travaux_pratiques/tp_additionnels/tp_docker" - -echo "🐳 Génération PDF du module Docker..." - -# Créer le répertoire de build s'il n'existe pas -mkdir -p "$BUILD_DIR" - -# Vérifier que le module Docker existe -if [ ! -d "$DOCKER_DIR" ]; then - echo "❌ Erreur: Répertoire du module Docker non trouvé: $DOCKER_DIR" - exit 1 -fi - -# Créer le fichier temporaire combiné -TEMP_FILE="$BUILD_DIR/temp_module_docker.md" - -echo "📄 Compilation du contenu Docker..." - -# En-tête du document -cat > "$TEMP_FILE" << 'EOF' ---- -title: "Module additionnel : Docker" -subtitle: "Conteneurisation et orchestration" -author: "Formation Linux" -date: \today -lang: fr -documentclass: article -geometry: margin=2cm -fontsize: 11pt -toc: true -toc-depth: 3 -numbersections: true -colorlinks: true -linkcolor: blue -urlcolor: blue ---- - -\newpage - -EOF - -# Ajouter tous les chapitres du module Docker avec décalage des titres -echo " 📝 Ajout des chapitres théoriques..." -for chapter in "$DOCKER_DIR"/*.md; do - if [ -f "$chapter" ]; then - chapter_name=$(basename "$chapter" .md) - echo " - $chapter_name" - echo "" >> "$TEMP_FILE" - echo "\\newpage" >> "$TEMP_FILE" - echo "" >> "$TEMP_FILE" - # Décaler tous les titres d'un niveau vers le bas (# devient ##, ## devient ###, etc.) - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/# /g; s/TEMP3/## /g; s/TEMP4/### /g; s/TEMP5/#### /g; s/TEMP6/##### /g' "$chapter" >> "$TEMP_FILE" - echo "" >> "$TEMP_FILE" - fi -done - -# Ajouter les travaux pratiques -if [ -d "$DOCKER_TP_DIR" ]; then - echo " 📝 Ajout des travaux pratiques..." - echo "" >> "$TEMP_FILE" - echo "\\newpage" >> "$TEMP_FILE" - echo "" >> "$TEMP_FILE" - echo "# Travaux pratiques" >> "$TEMP_FILE" - echo "" >> "$TEMP_FILE" - - for tp in "$DOCKER_TP_DIR"/*.md; do - if [ -f "$tp" ] && [[ "$(basename "$tp")" != "README.md" ]]; then - tp_name=$(basename "$tp" .md) - echo " - $tp_name" - echo "" >> "$TEMP_FILE" - echo "\\newpage" >> "$TEMP_FILE" - echo "" >> "$TEMP_FILE" - # Décaler tous les titres d'un niveau vers le bas pour les TP aussi - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/## /g; s/TEMP3/### /g; s/TEMP4/#### /g; s/TEMP5/##### /g; s/TEMP6/###### /g' "$tp" >> "$TEMP_FILE" - echo "" >> "$TEMP_FILE" - fi - done -fi - -# Nettoyage des caractères Unicode -echo "🧹 Nettoyage des caractères Unicode..." -"$SCRIPT_DIR/clean_unicode.sh" "$TEMP_FILE" - -# Génération du PDF -echo "📚 Génération du PDF..." -cd "$BUILD_DIR" - -# Créer un fichier header LaTeX temporaire pour configurer la numérotation -HEADER_TEX="$BUILD_DIR/header_docker.tex" -cat > "$HEADER_TEX" << 'EOF' -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage{lmodern} -\usepackage{titlesec} -\usepackage{tocloft} - -% Configurer la numérotation : sections non numérotées, subsections et subsubsections numérotées -\setcounter{secnumdepth}{2} - -% Supprimer la numérotation des sections (chapitres du module) -\titleformat{\section}{\Large\bfseries}{}{0pt}{} -\titlespacing*{\section}{0pt}{3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex} - -% Garder la numérotation normale pour subsections et subsubsections -\titleformat{\subsection}{\large\bfseries}{\thesubsection.}{1em}{} -\titleformat{\subsubsection}{\normalsize\bfseries}{\thesubsubsection.}{1em}{} - -% Table des matières - supprimer complètement la numérotation des sections -\renewcommand{\cftsecpresnum}{} -\renewcommand{\cftsecaftersnum}{} -\renewcommand{\cftsecnumwidth}{0pt} -\renewcommand{\cftsecfont}{\bfseries} -\renewcommand{\cftsecpagefont}{\bfseries} - -% Ajuster la profondeur de numérotation dans la table des matières -\setcounter{tocdepth}{3} -EOF - -# Tentative de génération avec couverture -if pandoc \ - --from markdown \ - --to pdf \ - --pdf-engine=pdflatex \ - --include-in-header="$HEADER_TEX" \ - --template="$SCRIPT_DIR/../templates/formation_template.tex" \ - --toc \ - --toc-depth=3 \ - --number-sections \ - --highlight-style=tango \ - --variable fontsize=11pt \ - --variable geometry:margin=2cm \ - --variable colorlinks=true \ - --variable linkcolor=blue \ - --variable urlcolor=blue \ - --output="module_additionnel_docker.pdf" \ - "temp_module_docker.md" 2>/dev/null; then - - echo "✅ PDF généré avec succès: module_additionnel_docker.pdf" -else - echo "⚠️ Erreur avec template, tentative version simplifiée..." - - # Génération simplifiée sans template personnalisé - if pandoc \ - --from markdown \ - --to pdf \ - --pdf-engine=pdflatex \ - --include-in-header="$HEADER_TEX" \ - --toc \ - --toc-depth=3 \ - --number-sections \ - --highlight-style=tango \ - --variable fontsize=11pt \ - --variable geometry:margin=2cm \ - --variable colorlinks=true \ - --variable linkcolor=blue \ - --variable urlcolor=blue \ - --output="module_additionnel_docker.pdf" \ - "temp_module_docker.md"; then - - echo "✅ PDF généré en mode simplifié: module_additionnel_docker.pdf" - else - echo "❌ Échec de la génération PDF" - echo "💡 Vérifiez les erreurs ci-dessus et le contenu du fichier temp_module_docker.md" - exit 1 - fi -fi - -# Nettoyage des fichiers temporaires -rm -f "$HEADER_TEX" -rm -f temp_cover_docker.* 2>/dev/null || true - -echo "🎉 Module Docker PDF généré dans: $BUILD_DIR/module_additionnel_docker.pdf" -echo "📊 Taille du fichier: $(du -h "$BUILD_DIR/module_additionnel_docker.pdf" | cut -f1)" \ No newline at end of file diff --git a/scripts/build_formations.sh b/scripts/build_formations.sh deleted file mode 100755 index cecf068..0000000 --- a/scripts/build_formations.sh +++ /dev/null @@ -1,448 +0,0 @@ -#!/bin/bash - -# Script principal de génération des formations selon les nouvelles consignes -# Génère : formation_acceleree.pdf, formation_longue.pdf, modules individuels - -set -e - -# Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -BUILD_DIR="$PROJECT_DIR/build" -SUPPORTS_DIR="$PROJECT_DIR/supports" -TP_DIR="$PROJECT_DIR/travaux_pratiques" -TEMPLATE_DIR="$SCRIPT_DIR/templates" - -echo "🚀 Génération des formations Linux selon les nouvelles spécifications" -echo "📁 Répertoire projet: $PROJECT_DIR" - -# Vérification des dépendances -echo "🔍 Vérification des dépendances..." -if ! command -v pandoc &> /dev/null; then - echo "❌ Pandoc n'est pas installé" - exit 1 -fi - -if ! command -v pdflatex &> /dev/null; then - echo "❌ pdflatex n'est pas installé" - exit 1 -fi - -echo "✅ Dépendances OK" - -# Nettoyage et préparation -echo "🧹 Préparation des répertoires..." -rm -rf "$BUILD_DIR" -mkdir -p "$BUILD_DIR"/{formations,modules_base,modules_additionnels} - -# Fonction pour générer la formation accélérée -generate_formation_acceleree() { - echo "⚡ Génération formation accélérée..." - - local output_file="$BUILD_DIR/formations/formation_acceleree.pdf" - local temp_md="$BUILD_DIR/temp_formation_acceleree.md" - - # En-tête avec métadonnées - cat > "$temp_md" << 'EOF' ---- -title: "Formation Linux Accélérée" -author: "Pascal Guinet - Prima Solutions" -date: \today -module-content: | - **Durée :** 2 séances de 4 heures (8h total) - - **Public :** Utilisateurs avec VM Linux et accès SSH - - **Objectifs :** - - Maîtriser les commandes Linux essentielles - - Naviguer efficacement dans le système de fichiers - - Gérer les droits et permissions - - Automatiser les tâches courantes - - **Approche :** Formation intensive axée sur la pratique -reset-chapter-numbering: true ---- - -# Présentation de la formation - -Cette formation accélérée couvre l'essentiel de Linux en 8 heures intensives. - -## Jour 1 (4h) : Fondamentaux -- Modules 1, 2, 3 : Découverte, navigation, manipulation de fichiers - -## Jour 2 (4h) : Utilisation avancée -- Modules 4, 5, 6, 7, 8 : Consultation, droits, processus, réseaux, scripts - -\newpage - -EOF - - # Ajouter les modules sélectionnés pour la formation accélérée - local modules_acceleres=(1 2 3 4 5 6 7 8) - - for module_num in "${modules_acceleres[@]}"; do - add_module_to_document "$module_num" "$temp_md" "condensed" - done - - generate_pdf_from_markdown "$temp_md" "$output_file" "Formation accélérée" -} - -# Fonction pour générer la formation longue -generate_formation_longue() { - echo "📚 Génération formation longue..." - - local output_file="$BUILD_DIR/formations/formation_longue.pdf" - local temp_md="$BUILD_DIR/temp_formation_longue.md" - - # En-tête avec métadonnées - cat > "$temp_md" << 'EOF' ---- -title: "Formation Linux Complète" -author: "Pascal Guinet - Prima Solutions" -date: \today -module-content: | - **Durée :** 25 séances de 1h30 (37h30 total) - - **Public :** Utilisateurs Windows avec VirtualBox - - **Objectifs :** - - Apprentissage progressif et détaillé de Linux - - Installation et configuration d'un environnement - - Maîtrise complète des outils et commandes - - Autonomie dans l'administration système - - **Approche :** Formation étalée avec nombreux travaux pratiques -reset-chapter-numbering: true ---- - -# Présentation de la formation - -Formation complète Linux sur 25 séances, adaptée aux débutants. - -## Organisation -- **Séances 1-3 :** Installation et découverte (Module 1) -- **Séances 4-7 :** Navigation système (Module 2) -- **Séances 8-12 :** Manipulation fichiers (Module 3) -- **Séances 13-16 :** Édition et recherche (Module 4) -- **Séances 17-20 :** Droits et sécurité (Module 5) -- **Séances 21-23 :** Processus et système (Module 6) -- **Séances 24-25 :** Réseau et automatisation (Modules 7-8) - -\newpage - -EOF - - # Ajouter tous les modules avec contenu détaillé - for module_num in {1..8}; do - add_module_to_document "$module_num" "$temp_md" "detailed" - done - - generate_pdf_from_markdown "$temp_md" "$output_file" "Formation longue" -} - -# Fonction pour ajouter un module au document -add_module_to_document() { - local module_num=$1 - local temp_md=$2 - local level=${3:-"normal"} # condensed, normal, detailed - - local module_num_fmt=$(printf "%02d" $module_num) - local module_dir=$(ls -d "$SUPPORTS_DIR/module_${module_num_fmt}_"* 2>/dev/null | head -1) - - if [ ! -d "$module_dir" ]; then - echo " ⚠️ Module $module_num non trouvé" - return - fi - - local module_name=$(basename "$module_dir" | sed 's/module_[0-9]*_//' | tr '_' ' ') - local module_title=$(echo "$module_name" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2); print}') - - echo " 📄 Ajout Module $module_num: $module_title" - - # Titre de partie (nouvelle page + titre non numéroté) - version simplifiée - echo "" >> "$temp_md" - echo "\\newpage" >> "$temp_md" - echo "" >> "$temp_md" - echo "# Module $module_num : $module_title {.unnumbered}" >> "$temp_md" - echo "" >> "$temp_md" - - # Ajouter les chapitres - for chapter in "$module_dir"/*.md; do - if [ -f "$chapter" ]; then - local chapter_name=$(basename "$chapter" .md | sed 's/^[0-9]*_//' | tr '_' ' ') - echo "" >> "$temp_md" - echo "# $chapter_name" >> "$temp_md" - echo "" >> "$temp_md" - - # Contenu du chapitre selon le niveau - if [ "$level" = "condensed" ]; then - # Version condensée : prendre le contenu jusqu'au deuxième titre de niveau 2 - # ou les 50 premières lignes si pas de deuxième ## - local second_h2_line=$(grep -n "^## " "$chapter" | sed -n '2p' | cut -d: -f1) - if [ -n "$second_h2_line" ] && [ "$second_h2_line" -gt 10 ]; then - # Prendre jusqu'au deuxième ## (exclu) - sed -n "1,$((second_h2_line - 1))p" "$chapter" >> "$temp_md" - else - # Prendre les 50 premières lignes comme version condensée - head -50 "$chapter" >> "$temp_md" - fi - else - # Version complète - cat "$chapter" >> "$temp_md" - fi - - # IMPORTANT: S'assurer qu'il y a une ligne vide à la fin pour éviter - # la concaténation accidentelle avec le titre suivant - echo "" >> "$temp_md" - echo "" >> "$temp_md" - echo "" >> "$temp_md" - fi - done - - # Ajouter les travaux pratiques si pas condensé - if [ "$level" != "condensed" ]; then - local tp_dir=$(ls -d "$TP_DIR/tp${module_num_fmt}_"* 2>/dev/null | head -1) - if [ -d "$tp_dir" ]; then - echo "" >> "$temp_md" - echo "# Travaux Pratiques" >> "$temp_md" - echo "" >> "$temp_md" - - for tp_file in "$tp_dir"/*.md; do - if [ -f "$tp_file" ]; then - local tp_name=$(basename "$tp_file" .md | tr '_' ' ') - echo "## $tp_name" >> "$temp_md" - echo "" >> "$temp_md" - cat "$tp_file" >> "$temp_md" - echo "" >> "$temp_md" - fi - done - fi - fi -} - - -# Fonction pour générer un module individuel -generate_individual_module() { - local module_num=$1 - - local module_num_fmt=$(printf "%02d" $module_num) - local module_dir=$(ls -d "$SUPPORTS_DIR/module_${module_num_fmt}_"* 2>/dev/null | head -1) - - if [ ! -d "$module_dir" ]; then - echo " ⚠️ Module $module_num non trouvé" - return 1 - fi - - local module_name=$(basename "$module_dir" | sed 's/module_[0-9]*_//' | tr '_' ' ') - local module_title=$(echo "$module_name" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2); print}') - local output_file="$BUILD_DIR/modules_base/module_${module_num_fmt}_$(echo $module_name | tr ' ' '_').pdf" - local temp_md="$BUILD_DIR/temp_module_${module_num_fmt}.md" - - echo " 📄 Module $module_num: $module_title" - - # En-tête du module - cat > "$temp_md" << EOF ---- -title: "Module $module_num : $module_title" -author: "Pascal Guinet - Prima Solutions" -date: \today -module-content: | - **Durée estimée :** 3-4 heures - - **Objectifs :** Maîtriser les concepts et outils du module $module_num - - **Prérequis :** Modules précédents complétés - - **Contenu :** Voir sommaire détaillé ci-après -reset-chapter-numbering: true ---- - -EOF - - # Ajouter le contenu du module (sans numéro de partie) - for chapter in "$module_dir"/*.md; do - if [ -f "$chapter" ]; then - local chapter_name=$(basename "$chapter" .md | sed 's/^[0-9]*_//' | tr '_' ' ') - echo "" >> "$temp_md" - echo "# $chapter_name" >> "$temp_md" - echo "" >> "$temp_md" - cat "$chapter" >> "$temp_md" - # S'assurer qu'il y a bien un retour à la ligne à la fin - echo "" >> "$temp_md" - echo "" >> "$temp_md" - fi - done - - # Ajouter les travaux pratiques - local tp_dir=$(ls -d "$TP_DIR/tp${module_num_fmt}_"* 2>/dev/null | head -1) - if [ -d "$tp_dir" ]; then - echo "" >> "$temp_md" - echo "\\newpage" >> "$temp_md" - echo "" >> "$temp_md" - echo "# Travaux Pratiques" >> "$temp_md" - echo "" >> "$temp_md" - - for tp_file in "$tp_dir"/*.md; do - if [ -f "$tp_file" ]; then - local tp_name=$(basename "$tp_file" .md | tr '_' ' ') - echo "## $tp_name" >> "$temp_md" - echo "" >> "$temp_md" - cat "$tp_file" >> "$temp_md" - echo "" >> "$temp_md" - fi - done - fi - - generate_pdf_from_markdown "$temp_md" "$output_file" "Module $module_num" -} - -# Fonction de génération PDF commune - template unique -generate_pdf_from_markdown() { - local temp_md=$1 - local output_file=$2 - local description=$3 - - # Vérifier que le fichier markdown existe - if [ ! -f "$temp_md" ]; then - echo " ❌ Fichier markdown introuvable: $temp_md" - return 1 - fi - - # Créer le répertoire de sortie s'il n'existe pas - mkdir -p "$(dirname "$output_file")" - - # Nettoyage des caractères problématiques pour LaTeX - echo " 🧹 Nettoyage des caractères problématiques..." - - # S'assurer que le fichier se termine par un retour à la ligne - if [ -s "$temp_md" ] && [ "$(tail -c1 "$temp_md" | wc -l)" -eq 0 ]; then - echo "" >> "$temp_md" - fi - - # Nettoyage minimal - laisser Pandoc gérer l'échappement LaTeX - - # Caractères Unicode problématiques - sed -i 's/→/->/g' "$temp_md" # Flèche droite - sed -i 's/←/<--/g' "$temp_md" # Flèche gauche - sed -i 's/▶/>/g' "$temp_md" # Triangle droit - sed -i 's/◀/=/g' "$temp_md" # Supérieur ou égal - - # Plus de nettoyage d'échappement agressif - laisser Pandoc gérer - - # Nettoyage Unicode standard - "$SCRIPT_DIR/clean_unicode.sh" "$temp_md" > /dev/null 2>&1 || true - - # Génération avec le template unique - local current_dir=$(pwd) - local output_dir="$(dirname "$output_file")" - - # S'assurer que le répertoire de sortie existe - mkdir -p "$output_dir" || { - echo " ❌ Impossible de créer le répertoire: $output_dir" - return 1 - } - - cd "$output_dir" || { - echo " ❌ Impossible de changer vers le répertoire: $output_dir" - return 1 - } - - echo " 🔨 Génération PDF: $description..." - - # Génération simplifiée (plus fiable que le template custom) - echo " 🔍 Debug: fichier markdown temporaire: $temp_md" - - if pandoc "$temp_md" \ - --pdf-engine=pdflatex \ - --toc \ - --toc-depth=2 \ - --highlight-style=tango \ - --variable geometry:margin=2.5cm \ - --variable fontsize=11pt \ - --variable documentclass:article \ - --variable papersize=a4 \ - --variable lang=fr \ - --variable babel-lang=french \ - -o "$(basename "$output_file")" 2>&1; then - - echo " ✅ $description généré: $(basename "$output_file")" - cd "$current_dir" - rm -f "$temp_md" - return 0 - else - echo " ❌ Erreur génération $description" - echo " 🔍 Fichier markdown conservé pour debug: $temp_md" - cd "$current_dir" - return 1 - fi -} - -# Exécution principale -echo "" -echo "📚 Génération des formations complètes..." -mkdir -p "$BUILD_DIR/formations" - -# Générer formation accélérée -generate_formation_acceleree - -# Générer formation longue -generate_formation_longue - -echo "" -echo "📖 Génération des modules individuels de base..." -success_count=0 -echo " 🔍 Début de la boucle pour les modules 1 à 8" -for module_num in {1..8}; do - echo " 🔄 Début traitement module $module_num" - # Ne pas arrêter le script si un module échoue - set +e # Désactiver arrêt sur erreur temporairement - generate_individual_module "$module_num" - result=$? - set -e # Réactiver arrêt sur erreur - - if [ $result -eq 0 ]; then - success_count=$((success_count + 1)) - echo " ✅ Module $module_num traité avec succès (total: $success_count)" - else - echo " ❌ Échec traitement module $module_num (code: $result)" - fi - echo " 🔄 Fin traitement module $module_num" -done -echo " 📊 Modules traités avec succès: $success_count/8" - -echo "" -echo "🔧 Génération des modules additionnels..." -set +e # Désactiver arrêt sur erreur temporairement -"$SCRIPT_DIR/build_modules_additionnels.sh" -additionnels_result=$? -set -e # Réactiver arrêt sur erreur - -if [ $additionnels_result -eq 0 ]; then - echo " ✅ Modules additionnels générés avec succès" -else - echo " ⚠️ Échec génération modules additionnels (code: $additionnels_result)" -fi - -# Résumé -echo "" -echo "📊 Résumé de génération:" -if [ -f "$BUILD_DIR/formations/formation_acceleree.pdf" ]; then - echo " ✅ Formation accélérée" -else - echo " ❌ Formation accélérée" -fi - -if [ -f "$BUILD_DIR/formations/formation_longue.pdf" ]; then - echo " ✅ Formation longue" -else - echo " ❌ Formation longue" -fi - -echo " ✅ Modules de base: $success_count/8" - -echo "" -echo "📁 Fichiers générés dans: $BUILD_DIR" -echo "🎉 Génération terminée!" \ No newline at end of file diff --git a/scripts/build_formations_ci.sh b/scripts/build_formations_ci.sh deleted file mode 100755 index cca053e..0000000 --- a/scripts/build_formations_ci.sh +++ /dev/null @@ -1,227 +0,0 @@ -#!/bin/bash - -# Version robuste du script de génération spécialement adaptée pour GitHub Actions CI -# Avec nettoyage et gestion d'erreurs renforcés - -set -e - -echo "🚀 Génération formations Linux - Version CI robuste" -echo "====================================================" - -# Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -BUILD_DIR="$PROJECT_DIR/build" -SUPPORTS_DIR="$PROJECT_DIR/supports" -TP_DIR="$PROJECT_DIR/travaux_pratiques" - -echo "📁 Répertoires:" -echo " - Projet: $PROJECT_DIR" -echo " - Build: $BUILD_DIR" - -# Vérification des dépendances -echo "🔍 Vérification des dépendances..." -if ! command -v pandoc &> /dev/null; then - echo "❌ Pandoc n'est pas installé" - exit 1 -fi - -if ! command -v pdflatex &> /dev/null; then - echo "❌ pdflatex n'est pas installé" - exit 1 -fi - -echo "🛠️ Versions:" -pandoc --version | head -1 -pdflatex --version | head -1 - -echo "✅ Dépendances OK" - -# Nettoyage et préparation -echo "🧹 Préparation des répertoires..." -rm -rf "$BUILD_DIR" -mkdir -p "$BUILD_DIR"/{formations,modules_base,modules_additionnels} - -# Fonction de nettoyage minimal pour CI -clean_markdown_for_ci() { - local file=$1 - - echo " 🧹 Nettoyage CI: $(basename "$file")" - - # Utiliser le script de nettoyage existant - if [ -f "$SCRIPT_DIR/clean_unicode.sh" ]; then - "$SCRIPT_DIR/clean_unicode.sh" "$file" > /dev/null 2>&1 || true - fi -} - -# Fonction de génération PDF ultra-robuste pour CI -generate_pdf_ci_safe() { - local temp_md=$1 - local output_file=$2 - local description=$3 - - echo " 🔨 Génération PDF CI sécurisée: $description..." - - # Nettoyage agressif - clean_markdown_for_ci "$temp_md" - - # Créer répertoire de sortie - mkdir -p "$(dirname "$output_file")" - - # Variables LaTeX pour maximum de compatibilité - local pandoc_args=( - --pdf-engine=pdflatex - --toc - --toc-depth=2 - --highlight-style=tango - --variable geometry:margin=2.5cm - --variable fontsize=11pt - --variable documentclass:article - --variable papersize=a4 - --variable lang=fr - --variable babel-lang=french - --variable fontenc=T1 - --variable inputenc=utf8 - ) - - echo " 🔍 Fichier markdown: $temp_md ($(wc -l < "$temp_md") lignes)" - - # Tentative de génération avec timeout - if timeout 300 pandoc "$temp_md" "${pandoc_args[@]}" -o "$output_file" 2>&1; then - echo " ✅ $description généré: $(basename "$output_file")" - rm -f "$temp_md" - return 0 - else - echo " ❌ Erreur génération $description" - echo " 🔍 Conservation fichier pour debug: $temp_md" - echo " 🔍 Taille: $(wc -l < "$temp_md") lignes" - echo " 🔍 Échantillon problématique (lignes 580-590):" - sed -n '580,590p' "$temp_md" | head -10 || echo " (lignes non disponibles)" - return 1 - fi -} - -# Fonction pour générer un module individuel (version CI) -generate_individual_module_ci() { - local module_num=$1 - - local module_num_fmt=$(printf "%02d" $module_num) - local module_dir=$(ls -d "$SUPPORTS_DIR/module_${module_num_fmt}_"* 2>/dev/null | head -1) - - if [ ! -d "$module_dir" ]; then - echo " ⚠️ Module $module_num non trouvé" - return 1 - fi - - local module_name=$(basename "$module_dir" | sed 's/module_[0-9]*_//' | tr '_' ' ') - local module_title=$(echo "$module_name" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2); print}') - local output_file="$BUILD_DIR/modules_base/module_${module_num_fmt}_$(echo $module_name | tr ' ' '_').pdf" - local temp_md="$BUILD_DIR/temp_module_ci_${module_num_fmt}.md" - - echo " 📄 Module $module_num: $module_title" - - # En-tête simplifié pour CI - cat > "$temp_md" << EOF ---- -title: "Module $module_num : $module_title" -author: "Pascal Guinet - Prima Solutions" -date: \\today ---- - -# Module $module_num : $module_title - -EOF - - # Ajouter les chapitres avec limitations - local chapter_count=0 - for chapter in "$module_dir"/*.md; do - if [ -f "$chapter" ] && [ $chapter_count -lt 10 ]; then # Limiter à 10 chapitres max - local chapter_name=$(basename "$chapter" .md | sed 's/^[0-9]*_//' | tr '_' ' ') - echo "" >> "$temp_md" - echo "# $chapter_name" >> "$temp_md" - echo "" >> "$temp_md" - - # Limiter la taille du chapitre (éviter les fichiers trop volumineux) - if [ $(wc -l < "$chapter") -gt 500 ]; then - echo " ⚠️ Chapitre volumineux $(wc -l < "$chapter") lignes, limitation à 500" - head -500 "$chapter" >> "$temp_md" - echo "" >> "$temp_md" - echo "*[Chapitre tronqué pour compatibilité CI]*" >> "$temp_md" - else - cat "$chapter" >> "$temp_md" - fi - echo "" >> "$temp_md" - - ((chapter_count++)) - fi - done - - # Génération PDF - generate_pdf_ci_safe "$temp_md" "$output_file" "Module $module_num" -} - -# Génération des modules individuels de base avec robustesse CI -echo "" -echo "📖 Génération des modules individuels (version CI robuste)..." -success_count=0 - -for module_num in {1..8}; do - echo " 🔄 Traitement module $module_num..." - - # Gestion d'erreur robuste - set +e - generate_individual_module_ci "$module_num" - result=$? - set -e - - if [ $result -eq 0 ]; then - ((success_count++)) - echo " ✅ Module $module_num: OK (total: $success_count)" - else - echo " ❌ Module $module_num: ECHEC (code: $result)" - fi -done - -echo " 📊 Modules générés: $success_count/8" - -# Générer modules additionnels si possible -echo "" -echo "🔧 Génération modules additionnels (si disponible)..." -if [ -f "$SCRIPT_DIR/build_modules_additionnels.sh" ]; then - set +e - "$SCRIPT_DIR/build_modules_additionnels.sh" - additionnels_result=$? - set -e - - if [ $additionnels_result -eq 0 ]; then - echo " ✅ Modules additionnels générés" - else - echo " ⚠️ Échec modules additionnels (code: $additionnels_result)" - fi -else - echo " ⚠️ Script modules additionnels non trouvé" -fi - -# Résumé final -echo "" -echo "📊 Résumé génération CI:" -echo " ✅ Modules de base: $success_count/8" - -modules_count=$(find "$BUILD_DIR" -name "*.pdf" 2>/dev/null | wc -l || echo "0") -echo " 📄 Total PDFs générés: $modules_count" - -if [ $modules_count -gt 0 ]; then - echo " 📁 Fichiers générés:" - find "$BUILD_DIR" -name "*.pdf" -exec ls -lh {} \; | head -10 -fi - -echo "" -echo "🎯 Génération CI terminée (modules: $success_count/8, PDFs: $modules_count)" - -# Code de sortie selon succès -if [ $success_count -ge 4 ]; then # Au moins la moitié des modules - exit 0 -else - echo "❌ Trop peu de modules générés avec succès" - exit 1 -fi \ No newline at end of file diff --git a/scripts/build_git_module.sh b/scripts/build_git_module.sh deleted file mode 100755 index 11b8df7..0000000 --- a/scripts/build_git_module.sh +++ /dev/null @@ -1,158 +0,0 @@ -#!/bin/bash - -# Script de génération du PDF pour le module Git uniquement -# Version rapide pour tester le module Git - -set -e - -# Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -BUILD_DIR="$PROJECT_DIR/build/modules_additionnels" -SUPPORTS_DIR="$PROJECT_DIR/supports/modules_additionnels" -TP_DIR="$PROJECT_DIR/travaux_pratiques/tp_additionnels" - -DATE=$(date "+%d/%m/%Y") - -echo "🔧 Génération du module Git..." - -mkdir -p "$BUILD_DIR" - -# Vérification que le module Git existe -MODULE_DIR="$SUPPORTS_DIR/module_git" -TP_GIT_DIR="$TP_DIR/tp_git" - -if [ ! -d "$MODULE_DIR" ]; then - echo "❌ Module Git non trouvé dans $MODULE_DIR" - exit 1 -fi - -# Configuration du module Git -TITLE="Module additionnel : Git" -OUTPUT_FILE="$BUILD_DIR/module_additionnel_git.pdf" -TEMP_MD="$BUILD_DIR/temp_git.md" - -echo "📄 Génération: $TITLE" - -# Création du fichier temporaire de contenu -cat > "$TEMP_MD" << EOF ---- -title: "$TITLE" -author: "Pascal Guinet - Prima Solutions" -date: "$DATE" -geometry: "margin=2.5cm" ---- - -EOF - -# Ajout du contenu du module Git dans l'ordre -for file in "$MODULE_DIR"/0*.md; do - if [ -f "$file" ]; then - echo " 📖 Ajout: $(basename "$file")" - # Décaler tous les titres d'un niveau vers le bas - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/## /g; s/TEMP3/### /g; s/TEMP4/#### /g; s/TEMP5/##### /g; s/TEMP6/###### /g' "$file" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - echo "\\newpage" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi -done - -# Ajout des TP Git -if [ -d "$TP_GIT_DIR" ]; then - echo "# Travaux Pratiques" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - - for tp_file in "$TP_GIT_DIR"/tp*.md "$TP_GIT_DIR"/exercices*.md; do - if [ -f "$tp_file" ]; then - tp_name=$(basename "$tp_file" .md | tr '_' ' ' | sed 's/\b\w/\U&/g') - echo " 📝 Ajout TP: $tp_name" - echo "## $tp_name" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - # Décaler tous les titres d'un niveau vers le bas pour les TP - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/### /g; s/TEMP3/#### /g; s/TEMP4/##### /g; s/TEMP5/###### /g; s/TEMP6/####### /g' "$tp_file" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - echo "\\newpage" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi - done -fi - -# Nettoyage des caractères Unicode problématiques pour LaTeX -echo "🧹 Nettoyage des caractères Unicode..." -"$SCRIPT_DIR/clean_unicode.sh" "$TEMP_MD" - -# Génération du PDF -echo "📄 Génération du PDF..." - -# Créer un fichier header LaTeX temporaire pour configurer la numérotation -HEADER_TEX="$BUILD_DIR/header_git.tex" -cat > "$HEADER_TEX" << 'EOF' -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage{lmodern} -\usepackage{titlesec} -\usepackage{tocloft} - -% Configurer la numérotation : sections non numérotées, subsections et subsubsections numérotées -\setcounter{secnumdepth}{2} - -% Supprimer la numérotation des sections (chapitres du module) -\titleformat{\section}{\Large\bfseries}{}{0pt}{} -\titlespacing*{\section}{0pt}{3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex} - -% Garder la numérotation normale pour subsections et subsubsections -\titleformat{\subsection}{\large\bfseries}{\thesubsection.}{1em}{} -\titleformat{\subsubsection}{\normalsize\bfseries}{\thesubsubsection.}{1em}{} - -% Table des matières - supprimer complètement la numérotation des sections -\renewcommand{\cftsecpresnum}{} -\renewcommand{\cftsecaftersnum}{} -\renewcommand{\cftsecnumwidth}{0pt} -\renewcommand{\cftsecfont}{\bfseries} -\renewcommand{\cftsecpagefont}{\bfseries} - -% Ajuster la profondeur de numérotation dans la table des matières -\setcounter{tocdepth}{3} -EOF - -pandoc "$TEMP_MD" \ - --include-in-header="$HEADER_TEX" \ - --pdf-engine=pdflatex \ - --toc \ - --toc-depth=3 \ - --number-sections \ - --highlight-style=tango \ - --variable=geometry:"margin=2.5cm" \ - --variable=fontsize:11pt \ - --variable=documentclass:article \ - --variable=papersize:a4 \ - --variable=lang:fr \ - -o "$OUTPUT_FILE" 2>/dev/null || { - echo "⚠️ Erreur avec la version complète, tentative version simplifiée..." - pandoc "$TEMP_MD" \ - --pdf-engine=pdflatex \ - --toc \ - --number-sections \ - -V lang=fr \ - -V geometry:"margin=2.5cm" \ - -o "$OUTPUT_FILE" - } - -# Nettoyage -rm -f "$TEMP_MD" "$HEADER_TEX" - -if [ -f "$OUTPUT_FILE" ]; then - echo "✅ Module Git généré avec succès: $(basename "$OUTPUT_FILE")" - echo "📁 Fichier: $OUTPUT_FILE" - echo "📊 Taille: $(du -h "$OUTPUT_FILE" | cut -f1)" - echo "" - echo "🔍 Contenu du PDF:" - echo " - 4 chapitres théoriques" - echo " - 3 TP pratiques" - echo " - Exercices supplémentaires avec corrections" - echo "" - echo "📖 Pour visualiser: xdg-open '$OUTPUT_FILE'" -else - echo "❌ Erreur lors de la génération du module Git" - exit 1 -fi \ No newline at end of file diff --git a/scripts/build_modules_additionnels.sh b/scripts/build_modules_additionnels.sh deleted file mode 100755 index 6459353..0000000 --- a/scripts/build_modules_additionnels.sh +++ /dev/null @@ -1,247 +0,0 @@ -#!/bin/bash - -# Script de génération des PDFs pour les modules additionnels -# Génère un PDF pour chaque module additionnel individuellement - -set -e - -# Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -BUILD_DIR="$PROJECT_DIR/build/modules_additionnels" -SUPPORTS_DIR="$PROJECT_DIR/supports/modules_additionnels" -TP_DIR="$PROJECT_DIR/travaux_pratiques/tp_additionnels" -TEMPLATE_DIR="$SCRIPT_DIR/templates" - -DATE=$(date "+%d/%m/%Y") -AUTHOR="Formation Linux - Prima Solutions" - -echo "📚 Génération des PDFs pour les modules additionnels..." - -mkdir -p "$BUILD_DIR" - -# Modules additionnels disponibles -declare -A MODULES_ADDITIONNELS -# Auto-détection des modules additionnels -for module_dir in "$SUPPORTS_DIR"/module_*; do - if [ -d "$module_dir" ]; then - module_name=$(basename "$module_dir" | sed 's/^module_//') - MODULES_ADDITIONNELS["$module_name"]="$module_name" - fi -done - -# Si aucun module détecté, vérifier s'il y a au moins module_git -if [ ${#MODULES_ADDITIONNELS[@]} -eq 0 ]; then - if [ -d "$SUPPORTS_DIR/module_git" ]; then - MODULES_ADDITIONNELS["git"]="git" - fi -fi - -# Fonction de génération pour un module additionnel -generate_module_additionnel_pdf() { - local module_name=$1 - local module_dir="$SUPPORTS_DIR/module_${module_name}" - local tp_dir="$TP_DIR/tp_${module_name}" - - if [ ! -d "$module_dir" ]; then - echo "⚠️ Module additionnel $module_name non trouvé dans $module_dir" - return 1 - fi - - # Nom formaté du module - local formatted_name=$(echo "$module_name" | tr '_' ' ' | sed 's/\b\w/\U&/g') - local title="Module additionnel : $formatted_name" - local output_file="$BUILD_DIR/module_additionnel_${module_name}.pdf" - local temp_md="$BUILD_DIR/temp_module_${module_name}.md" - - echo " 📄 Génération: $title" - - # === GÉNÉRATION AVEC COUVERTURE === - local cover_tex="$BUILD_DIR/temp_cover_${module_name}.tex" - local cover_pdf="$BUILD_DIR/temp_cover_${module_name}.pdf" - local content_pdf="$BUILD_DIR/temp_content_${module_name}.pdf" - - # Génération de la couverture du module - if [ -f "$TEMPLATE_DIR/couverture_module.tex" ]; then - cp "$TEMPLATE_DIR/couverture_module.tex" "$cover_tex" - sed -i "s/\\\$title\\\$/${title//\//\\\/}/g" "$cover_tex" - sed -i "s/\\\$date\\\$/${DATE//\//\\\/}/g" "$cover_tex" - - cd "$PROJECT_DIR" - pdflatex -output-directory="$BUILD_DIR" -interaction=nonstopmode "$cover_tex" > /dev/null 2>&1 - - # Vérifier si le PDF de couverture a été créé (même avec des warnings) - if [ ! -f "$cover_pdf" ]; then - echo " ⚠️ Erreur génération couverture pour $title" - fi - fi - - # Création du fichier temporaire de contenu avec le nouveau format - cat > "$temp_md" << EOF ---- -title: "$title" -author: "Pascal Guinet - Prima Solutions" -date: "$DATE" -module-content: | - **Durée estimée :** $([ "$module_name" = "git" ] && echo "6-8 heures" || echo "12-15 heures") - - **Prérequis :** Modules 1-4 (navigation et manipulation de fichiers) - - **Objectifs :** Maîtriser $([ "$module_name" = "git" ] && echo "le contrôle de version avec Git" || echo "la conteneurisation avec Docker") - - **Public :** Formation complémentaire aux modules de base -reset-chapter-numbering: true ---- - -EOF - - # Ajout du contenu du module - for file in "$module_dir"/*.md; do - if [ -f "$file" ]; then - # Décaler tous les titres d'un niveau vers le bas (# devient ##, ## devient ###, etc.) - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/## /g; s/TEMP3/### /g; s/TEMP4/#### /g; s/TEMP5/##### /g; s/TEMP6/###### /g' "$file" >> "$temp_md" - echo "" >> "$temp_md" - echo "\\newpage" >> "$temp_md" - echo "" >> "$temp_md" - fi - done - - # Ajout des TP - if [ -d "$tp_dir" ]; then - echo "# Travaux Pratiques" >> "$temp_md" - echo "" >> "$temp_md" - - for tp_file in "$tp_dir"/*.md; do - if [ -f "$tp_file" ]; then - local tp_name=$(basename "$tp_file" .md | tr '_' ' ' | sed 's/\b\w/\U&/g') - echo "## $tp_name" >> "$temp_md" - echo "" >> "$temp_md" - # Décaler tous les titres d'un niveau vers le bas pour les TP aussi - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/### /g; s/TEMP3/#### /g; s/TEMP4/##### /g; s/TEMP5/###### /g; s/TEMP6/####### /g' "$tp_file" >> "$temp_md" - echo "" >> "$temp_md" - echo "\\newpage" >> "$temp_md" - echo "" >> "$temp_md" - fi - done - fi - - # Nettoyage des caractères Unicode problématiques - "$SCRIPT_DIR/clean_unicode.sh" "$temp_md" - - # Nettoyage supplémentaire pour les caractères qui causent des erreurs LaTeX - sed -i 's/☐/□/g' "$temp_md" # Carré vide - sed -i 's/■/■/g' "$temp_md" # Carré plein - sed -i 's/▪/•/g' "$temp_md" # Petit carré - sed -i 's/▫/◦/g' "$temp_md" # Petit carré vide - sed -i 's/◻/□/g' "$temp_md" # Grand carré vide - sed -i 's/◼/■/g' "$temp_md" # Grand carré plein - - # Échapper les caractères qui pourraient poser problème - sed -i 's/\[x\]/[x]/g' "$temp_md" # Cases à cocher - sed -i 's/\[ \]/[ ]/g' "$temp_md" # Cases vides - - # Créer un fichier header LaTeX temporaire pour configurer la numérotation - local header_tex="$BUILD_DIR/header_${module_name}.tex" - cat > "$header_tex" << 'EOF' -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage{lmodern} -\usepackage{titlesec} -\usepackage{tocloft} - -% Configurer la numérotation : sections non numérotées, subsections et subsubsections numérotées -\setcounter{secnumdepth}{2} - -% Supprimer la numérotation des sections (chapitres du module) -\titleformat{\section}{\Large\bfseries}{}{0pt}{} -\titlespacing*{\section}{0pt}{3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex} - -% Garder la numérotation normale pour subsections et subsubsections -\titleformat{\subsection}{\large\bfseries}{\thesubsection.}{1em}{} -\titleformat{\subsubsection}{\normalsize\bfseries}{\thesubsubsection.}{1em}{} - -% Table des matières - supprimer complètement la numérotation des sections -\renewcommand{\cftsecpresnum}{} -\renewcommand{\cftsecaftersnum}{} -\renewcommand{\cftsecnumwidth}{0pt} -\renewcommand{\cftsecfont}{\bfseries} -\renewcommand{\cftsecpagefont}{\bfseries} - -% Ajuster la profondeur de numérotation dans la table des matières -\setcounter{tocdepth}{3} -EOF - - # Génération du PDF avec le template unique - if ! pandoc "$temp_md" \ - --template="$TEMPLATE_DIR/formation_template.tex" \ - --pdf-engine=pdflatex \ - --toc \ - --toc-depth=3 \ - --highlight-style=tango \ - --variable=geometry:"margin=2.5cm" \ - --variable=fontsize:11pt \ - --variable=documentclass:article \ - --variable=papersize:a4 \ - --variable=lang:fr \ - -o "$content_pdf" 2>&1; then - - echo " ⚠️ Erreur avec template, tentative version simplifiée..." - # Génération simplifiée sans template personnalisé - if ! pandoc "$temp_md" \ - --pdf-engine=pdflatex \ - --toc \ - --toc-depth=3 \ - --highlight-style=tango \ - --variable=geometry:"margin=2.5cm" \ - --variable=fontsize:11pt \ - --variable=documentclass:article \ - --variable=papersize:a4 \ - --variable=lang:fr \ - -o "$content_pdf" 2>&1; then - echo " ❌ Erreur génération contenu pour $title" - return 1 - fi - fi - - # === FUSION COUVERTURE + CONTENU === - if [ -f "$cover_pdf" ] && [ -f "$content_pdf" ]; then - # Utilisation de pdfunite si disponible - if command -v pdfunite &> /dev/null; then - pdfunite "$cover_pdf" "$content_pdf" "$output_file" 2>/dev/null || cp "$content_pdf" "$output_file" - # Sinon utilisation de ghostscript - elif command -v gs &> /dev/null; then - gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE="$output_file" -dBATCH "$cover_pdf" "$content_pdf" 2>/dev/null || cp "$content_pdf" "$output_file" - else - cp "$content_pdf" "$output_file" - fi - else - # Pas de couverture, utilisation du contenu seul - if [ -f "$content_pdf" ]; then - cp "$content_pdf" "$output_file" - else - echo " ❌ Impossible de générer le PDF pour $title" - return 1 - fi - fi - - # Nettoyage - rm -f "$temp_md" "$cover_tex" "$cover_pdf" "$content_pdf" "$header_tex" - - echo " ✅ Généré: $(basename "$output_file")" -} - -# Vérification qu'il y a des modules additionnels -if [ ${#MODULES_ADDITIONNELS[@]} -eq 0 ]; then - echo "ℹ️ Aucun module additionnel trouvé dans $SUPPORTS_DIR" - echo " Les modules additionnels doivent être dans des dossiers nommés 'module_*'" - exit 0 -fi - -# Génération de tous les modules additionnels -echo "🔍 Modules additionnels détectés: ${!MODULES_ADDITIONNELS[*]}" -for module_name in "${!MODULES_ADDITIONNELS[@]}"; do - generate_module_additionnel_pdf "$module_name" -done - -echo "✅ Tous les modules additionnels ont été générés dans: $BUILD_DIR" -echo "📁 $(ls -1 "$BUILD_DIR"/*.pdf 2>/dev/null | wc -l) fichiers PDF créés" \ No newline at end of file diff --git a/scripts/build_pdf.sh b/scripts/build_pdf.sh deleted file mode 100755 index 05aef43..0000000 --- a/scripts/build_pdf.sh +++ /dev/null @@ -1,233 +0,0 @@ -#!/bin/bash - -# Script de génération PDF pour la formation Linux -# Usage: ./build_pdf.sh [complete|acceleree] - -set -e - -# Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -BUILD_DIR="$PROJECT_DIR/build" -SUPPORTS_DIR="$PROJECT_DIR/supports" -TP_DIR="$PROJECT_DIR/travaux_pratiques" -TEMPLATE_DIR="$SCRIPT_DIR/templates" - -# Paramètres -FORMAT=${1:-complete} -DATE=$(date "+%d/%m/%Y") -AUTHOR="Formation Linux - Prima Solutions" - -case "$FORMAT" in - "complete") - TITLE="Formation Linux Complète" - OUTPUT_FILE="$BUILD_DIR/formation_complete.pdf" - INCLUDE_ALL=true - ;; - "acceleree") - TITLE="Formation Linux Accélérée" - OUTPUT_FILE="$BUILD_DIR/formation_acceleree.pdf" - INCLUDE_ALL=false - ;; - *) - echo "Usage: $0 [complete|acceleree]" - exit 1 - ;; -esac - -echo "📄 Génération du PDF: $TITLE" - -# Création du fichier temporaire de contenu -TEMP_MD="$BUILD_DIR/temp_formation.md" -mkdir -p "$BUILD_DIR" - -# En-tête du document -cat > "$TEMP_MD" << EOF ---- -title: "$TITLE" -author: "$AUTHOR" -date: "$DATE" -geometry: "margin=2.5cm" ---- - -\setcounter{section}{-1} - -# $TITLE - -## Présentation de la formation - -Cette formation Linux s'adresse à un public généraliste souhaitant découvrir et maîtriser les bases du système d'exploitation Linux. - -### Objectifs pédagogiques - -- Comprendre les concepts fondamentaux de Linux -- Maîtriser les commandes de base du terminal -- Gérer les fichiers et dossiers efficacement -- Comprendre les droits et la sécurité -- Automatiser des tâches simples - -## Licence - -![Licence Creative Commons](ressources/images/licenses/cc-by-nc-sa.png) - -Ce document est mis à disposition selon les termes de la [Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International](http://creativecommons.org/licenses/by-nc-sa/4.0/). - -**Vous êtes autorisé à :** -- **Partager** — copier, distribuer et communiquer le matériel par tous moyens et sous tous formats -- **Adapter** — remixer, transformer et créer à partir du matériel - -**Selon les conditions suivantes :** -- **Attribution** — Vous devez créditer l'Œuvre, intégrer un lien vers la licence et indiquer si des modifications ont été effectuées à l'Œuvre -- **Pas d'Utilisation Commerciale** — Vous n'êtes pas autorisé à faire un usage commercial de cette Œuvre -- **Partage dans les Mêmes Conditions** — Dans le cas où vous effectuez un remix, que vous transformez, ou créez à partir du matériel composant l'Œuvre originale, vous devez diffuser l'Œuvre modifiée dans les même conditions - -EOF - -# Fonction pour ajouter un module -add_module() { - local module_num=$1 - local module_name=$2 - local module_dir="$SUPPORTS_DIR/module_${module_num}_${module_name}" - local tp_dir="$TP_DIR/tp${module_num}_${module_name}" - - if [ -d "$module_dir" ]; then - # Ajouter un saut de page avant chaque nouveau module (sauf le premier) - if [ "$module_num" != "01" ]; then - echo "\\newpage" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi - - echo "# Module $module_num : $(echo $module_name | tr '_' ' ' | sed 's/\b\w/\U&/g')" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - - # Ajouter tous les fichiers .md du module - for file in "$module_dir"/*.md; do - if [ -f "$file" ]; then - # Décaler tous les titres d'un niveau vers le bas (# devient ##, ## devient ###, etc.) - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/## /g; s/TEMP3/### /g; s/TEMP4/#### /g; s/TEMP5/##### /g; s/TEMP6/###### /g' "$file" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - echo "\\newpage" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi - done - - # Ajouter les TP si format complet - if [ "$INCLUDE_ALL" = true ] && [ -d "$tp_dir" ]; then - echo "## Travaux Pratiques - Module $module_num" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - - for tp_file in "$tp_dir"/*.md; do - if [ -f "$tp_file" ]; then - echo "### $(basename "$tp_file" .md | tr '_' ' ' | sed 's/\b\w/\U&/g')" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - # Décaler tous les titres d'un niveau vers le bas pour les TP aussi - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/## /g; s/TEMP3/### /g; s/TEMP4/#### /g; s/TEMP5/##### /g; s/TEMP6/###### /g' "$tp_file" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi - done - echo "\\newpage" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi - fi -} - -# Ajout des modules selon le format -if [ "$INCLUDE_ALL" = true ]; then - # Format complet : tous les modules - add_module "01" "decouverte" - add_module "02" "navigation" - add_module "03" "manipulation" - add_module "04" "consultation" - add_module "05" "droits" - add_module "06" "processus" - add_module "07" "reseaux" - add_module "08" "automatisation" -else - # Format accéléré : modules essentiels uniquement - add_module "01" "decouverte" - add_module "02" "navigation" - add_module "03" "manipulation" - add_module "04" "consultation" - add_module "05" "droits" - - # Ajout d'un résumé des modules avancés - cat >> "$TEMP_MD" << EOF -# Modules Avancés (Survol) - -## Module 6 : Processus et Système -Gestion des processus, monitoring système, variables d'environnement. - -## Module 7 : Réseaux et Services -Configuration réseau de base, transferts de fichiers, services système. - -## Module 8 : Automatisation et Scripts -Scripts bash, tâches programmées, personnalisation. - -EOF -fi - -# Nettoyage des caractères Unicode problématiques -echo "🧹 Nettoyage des caractères Unicode..." -"$SCRIPT_DIR/clean_unicode.sh" "$TEMP_MD" - -# Génération du PDF avec Pandoc -echo "🔄 Conversion Markdown vers PDF..." - -# Créer un fichier header LaTeX temporaire pour désactiver la numérotation niveau 1 -HEADER_TEX="$BUILD_DIR/header.tex" -cat > "$HEADER_TEX" << 'EOF' -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage{lmodern} -\usepackage{titlesec} -\usepackage{tocloft} - -% Configurer la numérotation : sections non numérotées, subsections et subsubsections numérotées -\setcounter{secnumdepth}{2} - -% Supprimer la numérotation des sections (modules) mais garder le comptage -\titleformat{\section}{\Large\bfseries}{}{0pt}{} -\titlespacing*{\section}{0pt}{3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex} - -% Réinitialiser le compteur de subsections à chaque nouvelle section -\makeatletter -\@addtoreset{subsection}{section} -\@addtoreset{subsubsection}{subsection} -\makeatother - -% Garder la numérotation normale pour subsections et subsubsections -\titleformat{\subsection}{\large\bfseries}{\thesubsection.}{1em}{} -\titleformat{\subsubsection}{\normalsize\bfseries}{\thesubsubsection.}{1em}{} - -% Table des matières - supprimer complètement la numérotation des sections -\renewcommand{\cftsecpresnum}{} -\renewcommand{\cftsecaftersnum}{} -\renewcommand{\cftsecnumwidth}{0pt} -\renewcommand{\cftsecfont}{\bfseries} -\renewcommand{\cftsecpagefont}{\bfseries} - -% Ajuster la profondeur de la table des matières -\setcounter{tocdepth}{3} -EOF - -pandoc "$TEMP_MD" \ - --include-in-header="$HEADER_TEX" \ - --pdf-engine=pdflatex \ - --toc \ - --toc-depth=3 \ - --number-sections \ - --highlight-style=tango \ - --variable=geometry:"margin=2.5cm" \ - --variable=fontsize:11pt \ - --variable=documentclass:article \ - --variable=papersize:a4 \ - --variable=lang:fr \ - -o "$OUTPUT_FILE" - -# Nettoyage -rm -f "$HEADER_TEX" - -# Nettoyage (désactivé pour debug) -# rm -f "$TEMP_MD" - -echo "✅ PDF généré: $OUTPUT_FILE" \ No newline at end of file diff --git a/scripts/build_pdf_with_cover.sh b/scripts/build_pdf_with_cover.sh deleted file mode 100755 index 4dbaa58..0000000 --- a/scripts/build_pdf_with_cover.sh +++ /dev/null @@ -1,243 +0,0 @@ -#!/bin/bash - -# Script de génération PDF avec couverture pour la formation Linux -# Usage: ./build_pdf_with_cover.sh [complete|acceleree] - -set -e - -# Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -BUILD_DIR="$PROJECT_DIR/build" -SUPPORTS_DIR="$PROJECT_DIR/supports" -TP_DIR="$PROJECT_DIR/travaux_pratiques" -TEMPLATE_DIR="$SCRIPT_DIR/templates" - -# Paramètres -FORMAT=${1:-complete} -DATE=$(date "+%d/%m/%Y") -AUTHOR="Pascal Guinet - Prima Solutions" - -case "$FORMAT" in - "complete") - TITLE="Formation Linux Complète" - OUTPUT_FILE="$BUILD_DIR/formation_complete_avec_couverture.pdf" - INCLUDE_ALL=true - ;; - "acceleree") - TITLE="Formation Linux Accélérée" - OUTPUT_FILE="$BUILD_DIR/formation_acceleree_avec_couverture.pdf" - INCLUDE_ALL=false - ;; - *) - echo "Usage: $0 [complete|acceleree]" - exit 1 - ;; -esac - -echo "📄 Génération du PDF avec couverture: $TITLE" - -# Création du répertoire de build -mkdir -p "$BUILD_DIR" - -# === GÉNÉRATION DE LA COUVERTURE === -echo "🎨 Génération de la couverture..." - -COVER_TEX="$BUILD_DIR/temp_couverture.tex" -COVER_PDF="$BUILD_DIR/temp_couverture.pdf" - -# Copie et personnalisation du template de couverture -cp "$TEMPLATE_DIR/couverture.tex" "$COVER_TEX" -sed -i "s/\\\$title\\\$/${TITLE//\//\\\/}/g" "$COVER_TEX" -sed -i "s/\\\$date\\\$/${DATE//\//\\\/}/g" "$COVER_TEX" - -# Génération du PDF de couverture -cd "$PROJECT_DIR" -pdflatex -output-directory="$BUILD_DIR" -interaction=nonstopmode "$COVER_TEX" > /dev/null 2>&1 - -# Vérifier si le PDF de couverture a été créé (même avec des warnings) -if [ ! -f "$COVER_PDF" ]; then - echo "⚠️ Erreur lors de la génération de la couverture, utilisation du contenu seul" -else - echo "✅ Couverture générée avec succès" -fi - -# === GÉNÉRATION DU CONTENU PRINCIPAL === -echo "📝 Génération du contenu principal..." - -TEMP_MD="$BUILD_DIR/temp_formation.md" - -# En-tête du document -cat > "$TEMP_MD" << EOF ---- -title: "$TITLE" -author: "$AUTHOR" -date: "$DATE" -geometry: "margin=2.5cm" ---- - -EOF - -# Fonction pour ajouter un module -add_module() { - local module_num=$1 - local module_name=$2 - local module_dir="$SUPPORTS_DIR/module_${module_num}_${module_name}" - local tp_dir="$TP_DIR/tp${module_num}_${module_name}" - - if [ -d "$module_dir" ]; then - # Ajouter un saut de page avant chaque nouveau module (sauf le premier) - if [ "$module_num" != "01" ]; then - echo "\\newpage" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi - - echo "# Module $module_num : $(echo $module_name | tr '_' ' ' | sed 's/\b\w/\U&/g')" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - - # Ajouter tous les fichiers .md du module - for file in "$module_dir"/*.md; do - if [ -f "$file" ]; then - # Décaler tous les titres d'un niveau vers le bas (# devient ##, ## devient ###, etc.) - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/## /g; s/TEMP3/### /g; s/TEMP4/#### /g; s/TEMP5/##### /g; s/TEMP6/###### /g' "$file" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - echo "\\newpage" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi - done - - # Ajouter les TP si format complet - if [ "$INCLUDE_ALL" = true ] && [ -d "$tp_dir" ]; then - echo "## Travaux Pratiques - Module $module_num" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - - for tp_file in "$tp_dir"/*.md; do - if [ -f "$tp_file" ]; then - echo "### $(basename "$tp_file" .md | tr '_' ' ' | sed 's/\b\w/\U&/g')" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - # Décaler tous les titres d'un niveau vers le bas pour les TP aussi - sed 's/^##### /TEMP6/g; s/^#### /TEMP5/g; s/^### /TEMP4/g; s/^## /TEMP3/g; s/^# /TEMP2/g; s/TEMP2/## /g; s/TEMP3/### /g; s/TEMP4/#### /g; s/TEMP5/##### /g; s/TEMP6/###### /g' "$tp_file" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi - done - echo "\\newpage" >> "$TEMP_MD" - echo "" >> "$TEMP_MD" - fi - fi -} - -# Ajout des modules selon le format -if [ "$INCLUDE_ALL" = true ]; then - # Format complet : tous les modules - add_module "01" "decouverte" - add_module "02" "navigation" - add_module "03" "manipulation" - add_module "04" "consultation" - add_module "05" "droits" - add_module "06" "processus" - add_module "07" "reseaux" - add_module "08" "automatisation" -else - # Format accéléré : modules essentiels uniquement - add_module "01" "decouverte" - add_module "02" "navigation" - add_module "03" "manipulation" - add_module "04" "consultation" - add_module "05" "droits" - - # Ajout d'un résumé des modules avancés - cat >> "$TEMP_MD" << EOF -# Modules Avancés (Survol) - -## Module 6 : Processus et Système -Gestion des processus, monitoring système, variables d'environnement. - -## Module 7 : Réseaux et Services -Configuration réseau de base, transferts de fichiers, services système. - -## Module 8 : Automatisation et Scripts -Scripts bash, tâches programmées, personnalisation. - -EOF -fi - -# Nettoyage des caractères Unicode problématiques -echo "🧹 Nettoyage des caractères Unicode..." -"$SCRIPT_DIR/clean_unicode.sh" "$TEMP_MD" - -# === GÉNÉRATION DU CONTENU PRINCIPAL === -echo "🔄 Conversion Markdown vers PDF..." - -CONTENT_PDF="$BUILD_DIR/temp_content.pdf" - -# Créer un fichier header LaTeX temporaire pour désactiver la numérotation niveau 1 -HEADER_TEX="$BUILD_DIR/header.tex" -cat > "$HEADER_TEX" << 'EOF' -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage{lmodern} -\usepackage{titlesec} -\usepackage{tocloft} - -% Configurer la numérotation : sections non numérotées, subsections et subsubsections numérotées -\setcounter{secnumdepth}{2} - -% Supprimer la numérotation des sections (modules) -\titleformat{\section}{\Large\bfseries}{}{0pt}{} -\titlespacing*{\section}{0pt}{3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex} - -% Garder la numérotation normale pour subsections et subsubsections -\titleformat{\subsection}{\large\bfseries}{\thesubsection.}{1em}{} -\titleformat{\subsubsection}{\normalsize\bfseries}{\thesubsubsection.}{1em}{} - -% Table des matières - supprimer complètement la numérotation des sections -\renewcommand{\cftsecpresnum}{} -\renewcommand{\cftsecaftersnum}{} -\renewcommand{\cftsecnumwidth}{0pt} -\renewcommand{\cftsecfont}{\bfseries} -\renewcommand{\cftsecpagefont}{\bfseries} - -% Ajuster la profondeur de numérotation dans la table des matières -\setcounter{tocdepth}{3} -EOF - -pandoc "$TEMP_MD" \ - --include-in-header="$HEADER_TEX" \ - --pdf-engine=pdflatex \ - --toc \ - --toc-depth=3 \ - --number-sections \ - --highlight-style=tango \ - --variable=geometry:"margin=2.5cm" \ - --variable=fontsize:11pt \ - --variable=documentclass:article \ - --variable=papersize:a4 \ - --variable=lang:fr \ - -o "$CONTENT_PDF" - -# Nettoyage -rm -f "$HEADER_TEX" - -# === FUSION DES PDFs === -echo "📎 Fusion de la couverture et du contenu..." - -if [ -f "$COVER_PDF" ] && [ -f "$CONTENT_PDF" ]; then - # Utilisation de pdfunite si disponible - if command -v pdfunite &> /dev/null; then - pdfunite "$COVER_PDF" "$CONTENT_PDF" "$OUTPUT_FILE" - # Sinon utilisation de ghostscript - elif command -v gs &> /dev/null; then - gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE="$OUTPUT_FILE" -dBATCH "$COVER_PDF" "$CONTENT_PDF" - else - echo "⚠️ Aucun outil de fusion PDF disponible. Utilisation du contenu seul." - cp "$CONTENT_PDF" "$OUTPUT_FILE" - fi -else - echo "⚠️ Problème avec la couverture. Utilisation du contenu seul." - cp "$CONTENT_PDF" "$OUTPUT_FILE" -fi - -# Nettoyage des fichiers temporaires -rm -f "$BUILD_DIR"/temp_* - -echo "✅ PDF généré avec couverture: $OUTPUT_FILE" \ No newline at end of file diff --git a/scripts/clean_unicode.sh b/scripts/clean_unicode.sh deleted file mode 100755 index 846c1ad..0000000 --- a/scripts/clean_unicode.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/bash - -# Script de nettoyage complet des caractères Unicode problématiques -# Usage: ./clean_unicode.sh fichier.md - -FILE="$1" - -if [ ! -f "$FILE" ]; then - echo "Erreur: fichier $FILE non trouvé" - exit 1 -fi - -echo "Nettoyage Unicode de $FILE..." - -# Caractères d'arborescence (tous les variants) -sed -i 's/├/+/g' "$FILE" -sed -i 's/└/+/g' "$FILE" -sed -i 's/┌/+/g' "$FILE" -sed -i 's/┐/+/g' "$FILE" -sed -i 's/┘/+/g' "$FILE" -sed -i 's/│/|/g' "$FILE" -sed -i 's/─/-/g' "$FILE" -sed -i 's/┬/+/g' "$FILE" -sed -i 's/┴/+/g' "$FILE" -sed -i 's/┤/+/g' "$FILE" -sed -i 's/┼/+/g' "$FILE" -sed -i 's/┊/|/g' "$FILE" -sed -i 's/┋/|/g' "$FILE" -sed -i 's/╱/\//g' "$FILE" -sed -i 's/╲/\\/g' "$FILE" - -# Symboles mathématiques et flèches -sed -i 's/≠/!=/g' "$FILE" -sed -i 's/≤/<=/g' "$FILE" -sed -i 's/≥/>=/g' "$FILE" -sed -i 's/↔/<-->/g' "$FILE" -sed -i 's/→/->/g' "$FILE" -sed -i 's/←/<-/g' "$FILE" -sed -i 's/↑/^/g' "$FILE" -sed -i 's/↓/v/g' "$FILE" -sed -i 's/▶/>/g' "$FILE" -sed -i 's/◀/ /dev/null; then - perl -CSD -pi -e 's/[\x{1F300}-\x{1F5FF}]//g' "$FILE" # Symboles divers - perl -CSD -pi -e 's/[\x{1F600}-\x{1F64F}]//g' "$FILE" # Emoticons - perl -CSD -pi -e 's/[\x{1F680}-\x{1F6FF}]//g' "$FILE" # Transport - perl -CSD -pi -e 's/[\x{1F700}-\x{1F77F}]//g' "$FILE" # Alchimiques - perl -CSD -pi -e 's/[\x{1F900}-\x{1F9FF}]//g' "$FILE" # Supplémentaires -fi - -# Guillemets et ponctuation -sed -i 's/«/"/g' "$FILE" -sed -i 's/»/"/g' "$FILE" -sed -i "s/'/'/g" "$FILE" -sed -i "s/'/'/g" "$FILE" -sed -i 's/"/"/g' "$FILE" -sed -i 's/"/"/g' "$FILE" -sed -i 's/…/.../g' "$FILE" -sed -i 's/–/-/g' "$FILE" -sed -i 's/—/-/g' "$FILE" - -# Espaces insécables et autres -sed -i 's/ / /g' "$FILE" -sed -i 's/·/*/g' "$FILE" -sed -i 's/•/*/g' "$FILE" -sed -i 's/●/*/g' "$FILE" - -# Indices et exposants -sed -i 's/₀/0/g' "$FILE" -sed -i 's/₁/1/g' "$FILE" -sed -i 's/₂/2/g' "$FILE" -sed -i 's/₃/3/g' "$FILE" -sed -i 's/₄/4/g' "$FILE" -sed -i 's/₅/5/g' "$FILE" -sed -i 's/₆/6/g' "$FILE" -sed -i 's/₇/7/g' "$FILE" -sed -i 's/₈/8/g' "$FILE" -sed -i 's/₉/9/g' "$FILE" - -# Ajout de quelques autres caractères problématiques -sed -i 's/ℹ/[INFO]/g' "$FILE" -sed -i 's/©/(c)/g' "$FILE" -sed -i 's/®/(R)/g' "$FILE" -sed -i 's/™/(TM)/g' "$FILE" - -# NE PAS utiliser iconv pour préserver les caractères accentués français -# Le script se contente du nettoyage sélectif des caractères problématiques ci-dessus -echo "Caractères accentués français préservés" - -echo "Nettoyage terminé." \ No newline at end of file diff --git a/scripts/config.sh b/scripts/config.sh deleted file mode 100755 index 42dfe90..0000000 --- a/scripts/config.sh +++ /dev/null @@ -1,178 +0,0 @@ -#!/bin/bash - -# Configuration globale pour les scripts de génération -# Ce fichier contient les paramètres partagés entre tous les scripts - -# === CONFIGURATION GÉNÉRALE === -export FORMATION_TITLE="Formation Linux" -export FORMATION_AUTHOR="Formation Linux - Pascal GUINET" -export FORMATION_VERSION="1.0" - -# === CHEMINS === -export PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -export SCRIPTS_DIR="$PROJECT_ROOT/scripts" -export TEMPLATES_DIR="$SCRIPTS_DIR/templates" -export BUILD_DIR="$PROJECT_ROOT/build" -export SUPPORTS_DIR="$PROJECT_ROOT/supports" -export TP_DIR="$PROJECT_ROOT/travaux_pratiques" -export RESOURCES_DIR="$PROJECT_ROOT/ressources" - -# === CONFIGURATION PANDOC === -export PDF_ENGINE="pdflatex" -export HIGHLIGHT_STYLE="tango" -export TOC_DEPTH=3 -export FONT_SIZE="11pt" -export PAPER_SIZE="a4" -export MARGIN="2.5cm" -export LANGUAGE="fr" - -# === MODULES DISPONIBLES === -# Format: "numero:nom_interne:titre_affichage" -export AVAILABLE_MODULES=( - "01:decouverte:Découverte et premiers pas" - "02:navigation:Navigation et système de fichiers" - "03:manipulation:Manipulation de fichiers et dossiers" - "04:consultation:Consultation et édition de fichiers" - "05:droits:Droits et sécurité" - "06:processus:Processus et système" - "07:reseaux:Réseaux et services" - "08:automatisation:Automatisation et scripts" -) - -# === MODULES POUR FORMATION ACCÉLÉRÉE === -# Modules inclus dans la version accélérée (8h) -export ACCELERATED_MODULES=( - "01:decouverte" - "02:navigation" - "03:manipulation" - "04:consultation" - "05:droits" -) - -# === COULEURS POUR LES LOGS === -export COLOR_RED='\033[0;31m' -export COLOR_GREEN='\033[0;32m' -export COLOR_YELLOW='\033[1;33m' -export COLOR_BLUE='\033[0;34m' -export COLOR_PURPLE='\033[0;35m' -export COLOR_CYAN='\033[0;36m' -export COLOR_NC='\033[0m' # No Color - -# === FONCTIONS UTILITAIRES === - -# Fonction de log avec couleur -log_info() { - echo -e "${COLOR_BLUE}ℹ️ $1${COLOR_NC}" -} - -log_success() { - echo -e "${COLOR_GREEN}✅ $1${COLOR_NC}" -} - -log_warning() { - echo -e "${COLOR_YELLOW}⚠️ $1${COLOR_NC}" -} - -log_error() { - echo -e "${COLOR_RED}❌ $1${COLOR_NC}" -} - -log_step() { - echo -e "${COLOR_PURPLE}🔄 $1${COLOR_NC}" -} - -# Fonction pour vérifier les dépendances -check_dependencies() { - local missing_deps=() - - # Vérification de pandoc - if ! command -v pandoc &> /dev/null; then - missing_deps+=("pandoc") - fi - - # Vérification de pdflatex - if ! command -v pdflatex &> /dev/null; then - missing_deps+=("texlive-latex-base") - fi - - if [ ${#missing_deps[@]} -gt 0 ]; then - log_error "Dépendances manquantes: ${missing_deps[*]}" - log_info "Installation sur Ubuntu/Debian:" - log_info "sudo apt-get update" - log_info "sudo apt-get install pandoc texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended" - return 1 - fi - - return 0 -} - -# Fonction pour créer les répertoires nécessaires -ensure_directories() { - mkdir -p "$BUILD_DIR" - mkdir -p "$BUILD_DIR/supports_par_module" - mkdir -p "$BUILD_DIR/temp" -} - -# Fonction pour nettoyer les fichiers temporaires -cleanup_temp() { - rm -rf "$BUILD_DIR/temp" - rm -f "$BUILD_DIR"/*.tmp - rm -f "$BUILD_DIR"/*.aux - rm -f "$BUILD_DIR"/*.log - rm -f "$BUILD_DIR"/*.toc -} - -# Fonction pour obtenir la date formatée -get_formatted_date() { - date "+%d/%m/%Y" -} - -# Fonction pour parser les informations d'un module -parse_module_info() { - local module_string="$1" - local field="$2" - - IFS=':' read -r num name title <<< "$module_string" - - case "$field" in - "number") echo "$num" ;; - "name") echo "$name" ;; - "title") echo "$title" ;; - esac -} - -# === CONFIGURATION DES TEMPLATES === -# Template LaTeX unifié pour tous les PDFs -export TEMPLATE_FILE="$TEMPLATES_DIR/formation_template.tex" - -# Options Pandoc communes -export PANDOC_COMMON_OPTIONS=( - "--pdf-engine=$PDF_ENGINE" - "--highlight-style=$HIGHLIGHT_STYLE" - "--variable" "fontsize=$FONT_SIZE" - "--variable" "papersize=$PAPER_SIZE" - "--variable" "geometry:margin=$MARGIN" - "--variable" "lang=$LANGUAGE" - "--variable" "colorlinks=true" - "--variable" "linkcolor=blue" - "--variable" "urlcolor=blue" -) - -# Options pour les formations complètes -export PANDOC_FULL_OPTIONS=( - "--toc" - "--toc-depth=$TOC_DEPTH" - "--number-sections" -) - -# Options pour les modules individuels -export PANDOC_MODULE_OPTIONS=( - "--toc" - "--toc-depth=2" - "--number-sections" -) - -# Export des fonctions pour qu'elles soient disponibles dans les autres scripts -export -f log_info log_success log_warning log_error log_step -export -f check_dependencies ensure_directories cleanup_temp -export -f get_formatted_date parse_module_info diff --git a/scripts/templates/couverture.tex b/scripts/templates/couverture.tex deleted file mode 100644 index 6ed76ce..0000000 --- a/scripts/templates/couverture.tex +++ /dev/null @@ -1,153 +0,0 @@ -% Template de couverture pour les PDFs Formation Linux -% Version LaTeX - -\documentclass[12pt,a4paper]{article} -\usepackage[utf8]{inputenc} -\usepackage[french]{babel} -\usepackage{geometry} -\usepackage{graphicx} -\usepackage{xcolor} -\usepackage{tikz} -\usepackage{fancyhdr} -\usepackage{tcolorbox} - -% Configuration de la page -\geometry{margin=0cm} -\pagestyle{empty} - -% Définition des couleurs -\definecolor{primaryblue}{RGB}{44, 82, 130} -\definecolor{lightgray}{RGB}{245, 245, 245} -\definecolor{darkgray}{RGB}{90, 90, 90} - -\begin{document} - -% === PAGE DE COUVERTURE === -\begin{tikzpicture}[remember picture, overlay] - % Fond avec dégradé - \fill[primaryblue] (current page.north west) rectangle ([yshift=-8cm]current page.north east); - \fill[lightgray] ([yshift=-8cm]current page.north west) rectangle (current page.south east); - - % Bande décorative - \fill[darkgray] ([yshift=-8.2cm]current page.north west) rectangle ([yshift=-8.5cm]current page.north east); -\end{tikzpicture} - -\vspace{1cm} - -% Image de couverture -\begin{center} - \includegraphics[width=8cm]{ressources/images/Formation_Linux.png} -\end{center} - -\vspace{1cm} - -% Titre principal -\begin{center} - {\Huge\color{white}\textbf{FORMATION LINUX}} - - \vspace{1cm} - - {\LARGE\color{white} $title$ } - - \vspace{0.5cm} - - {\large\color{white} Guide complet pour maîtriser Linux} -\end{center} - -\vspace{2cm} - -% Section centrale avec informations -\begin{center} -\begin{tcolorbox}[colback=white, colframe=primaryblue, width=12cm, arc=5mm] - \centering - \textbf{\large Public cible} - - \vspace{0.3cm} - - Débutants et utilisateurs souhaitant\\ - maîtriser les fondamentaux de Linux - - \vspace{0.5cm} - - \textbf{\large Contenu} - - \vspace{0.3cm} - - 8 modules de formation • Travaux pratiques\\ - Navigation • Fichiers • Droits • Processus\\ - Réseaux • Scripts • Automatisation -\end{tcolorbox} -\end{center} - -\vspace{\fill} - -% Bas de page -\begin{center} - \textcolor{darkgray}{\large $date$} -\end{center} - -\newpage - -% === QUATRIÈME DE COUVERTURE === -\begin{tikzpicture}[remember picture, overlay] - % Fond avec dégradé inversé - \fill[lightgray] (current page.north west) rectangle ([yshift=-16cm]current page.north east); - \fill[primaryblue] ([yshift=-16cm]current page.north west) rectangle (current page.south east); - - % Bande décorative - \fill[darkgray] ([yshift=-15.8cm]current page.north west) rectangle ([yshift=-16.2cm]current page.north east); -\end{tikzpicture} - -\vspace{2cm} - -% À propos de l'auteur -\begin{center} -\begin{tcolorbox}[colback=white, colframe=primaryblue, width=13cm, arc=5mm] - \textbf{\Large À propos de l'auteur} - - \vspace{0.5cm} - - \textbf{Pascal Guinet} est expert DevOps chez Prima Solutions, leader français de l'AssurTech. Spécialisé dans les infrastructures cloud et l'intégration continue, il accompagne les équipes dans leur transformation digitale. - - \vspace{0.3cm} - - Fort de plusieurs années d'expérience dans l'écosystème Linux et les technologies cloud, Pascal forme régulièrement des équipes aux bonnes pratiques DevOps et à la maîtrise des systèmes Unix/Linux. - - \vspace{0.3cm} - - \textit{Prima Solutions - Expert DevOps \& Formateur} -\end{tcolorbox} -\end{center} - -\vspace{2cm} - -% Licence Creative Commons -\begin{center} -\begin{tcolorbox}[colback=white, colframe=darkgray, width=13cm, arc=3mm] - \centering - - \includegraphics[width=3cm]{ressources/images/licenses/cc-by-nc-sa.png} - - \vspace{0.3cm} - - \textbf{Licence Creative Commons}\\ - \textit{Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0} - - \vspace{0.2cm} - - {\small Ce document peut être partagé et adapté selon les conditions de la licence CC BY-NC-SA 4.0} -\end{tcolorbox} -\end{center} - -\vspace{\fill} - -% Pied de page avec informations -\begin{center} -\textcolor{white}{\large\textbf{Formation Linux - Prima Solutions}} - -\vspace{0.3cm} - -\textcolor{white}{www.prima-solutions.com} -\end{center} - -\end{document} \ No newline at end of file diff --git a/scripts/templates/couverture_module.tex b/scripts/templates/couverture_module.tex deleted file mode 100644 index ef26a07..0000000 --- a/scripts/templates/couverture_module.tex +++ /dev/null @@ -1,103 +0,0 @@ -% Template de couverture pour les modules PDF -% Version LaTeX - -\documentclass[12pt,a4paper]{article} -\usepackage[utf8]{inputenc} -% \usepackage[french]{babel} % Désactivé car non disponible sur ce système -\usepackage{geometry} -\usepackage{graphicx} -\usepackage{xcolor} -\usepackage{tikz} -\usepackage{fancyhdr} -\usepackage{tcolorbox} - -% Configuration de la page -\geometry{margin=0cm} -\pagestyle{empty} - -% Définition des couleurs -\definecolor{primaryblue}{RGB}{44, 82, 130} -\definecolor{secondaryblue}{RGB}{70, 130, 180} -\definecolor{lightgray}{RGB}{245, 245, 245} -\definecolor{darkgray}{RGB}{90, 90, 90} - -\begin{document} - -% === PAGE DE COUVERTURE MODULE === -\begin{tikzpicture}[remember picture, overlay] - % Fond avec dégradé - \fill[secondaryblue] (current page.north west) rectangle ([yshift=-6cm]current page.north east); - \fill[lightgray] ([yshift=-6cm]current page.north west) rectangle (current page.south east); - - % Bande décorative - \fill[darkgray] ([yshift=-6.2cm]current page.north west) rectangle ([yshift=-6.5cm]current page.north east); - - % Motif décoratif - \foreach \x in {0,2,...,20} { - \fill[white,opacity=0.1] (\x,-1) circle (0.3); - \fill[white,opacity=0.05] (\x+1,-2) circle (0.5); - } -\end{tikzpicture} - -\vspace{1.5cm} - -% Titre du module -\begin{center} - {\huge\color{white}\textbf{FORMATION LINUX}} - - \vspace{0.8cm} - - {\LARGE\color{white} $title$ } -\end{center} - -\vspace{2.5cm} - -% Section centrale avec objectifs -\begin{center} -\begin{tcolorbox}[colback=white, colframe=secondaryblue, width=13cm, arc=5mm] - \centering - \textbf{\large Objectifs du module} - - \vspace{0.5cm} - - Ce module vous permettra de maîtriser\\ - les concepts essentiels et les commandes\\ - de base pour une utilisation efficace de Linux - - \vspace{0.5cm} - - \textbf{\large Contenu} - - \vspace{0.3cm} - - Cours théoriques • Travaux pratiques\\ - Exemples concrets • Exercices corrigés -\end{tcolorbox} -\end{center} - -\vspace{\fill} - -% Section auteur et licence -\begin{center} -\begin{tcolorbox}[colback=white, colframe=darkgray, width=10cm, arc=3mm] - \centering - - \textbf{Pascal Guinet}\\ - Expert DevOps - Prima Solutions - - \vspace{0.3cm} - - \includegraphics[width=2cm]{ressources/images/licenses/cc-by-nc-sa.png} - - {\small CC BY-NC-SA 4.0} -\end{tcolorbox} -\end{center} - -\vspace{0.5cm} - -% Bas de page -\begin{center} - \textcolor{darkgray}{\large $date$} -\end{center} - -\end{document} \ No newline at end of file diff --git a/scripts/templates/formation_template.tex b/scripts/templates/formation_template.tex deleted file mode 100644 index d7f06e5..0000000 --- a/scripts/templates/formation_template.tex +++ /dev/null @@ -1,224 +0,0 @@ -\documentclass[a4paper,11pt]{article} - -% Packages essentiels pour le français et la mise en page -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage[french]{babel} -\usepackage{lmodern} -\usepackage{geometry} -\usepackage{fancyhdr} -\usepackage{graphicx} -\usepackage{xcolor} -\usepackage{hyperref} -\usepackage{listings} -\usepackage{tcolorbox} -\usepackage{enumitem} -\usepackage{titlesec} -\usepackage{lastpage} -\usepackage{longtable} -\usepackage{booktabs} -\usepackage{array} - -% Configuration de la page selon tes spécifications -\geometry{ - left=2.5cm, - right=2.5cm, - top=3cm, - bottom=3cm, - headheight=15pt -} - -% Configuration des couleurs -\definecolor{primarycolor}{RGB}{51,102,153} -\definecolor{secondarycolor}{RGB}{102,153,204} -\definecolor{modulecolor}{RGB}{204,102,51} -\definecolor{codecolor}{RGB}{245,245,245} -\definecolor{shadecolor}{RGB}{248,248,248} - -% Configuration des liens -\hypersetup{ - colorlinks=true, - linkcolor=primarycolor, - urlcolor=primarycolor, - citecolor=primarycolor, - pdfborder={0 0 0} -} - -% Configuration des listings (code) -\lstset{ - backgroundcolor=\color{codecolor}, - basicstyle=\ttfamily\small, - breaklines=true, - frame=single, - rulecolor=\color{gray}, - numbers=left, - numberstyle=\tiny\color{gray}, - keywordstyle=\color{blue}, - commentstyle=\color{green!60!black}, - stringstyle=\color{red}, - showstringspaces=false, - tabsize=2 -} - -% Définition de l'environnement Shaded pour les blocs de code Pandoc -\usepackage{fancyvrb} -\usepackage{framed} -\newcommand{\VerbBar}{|} -\newcommand{\VERB}{\Verb[commandchars=\\\{\}]} -\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}} -% Ajouter des couleurs de base pour la coloration syntaxique -\newenvironment{Shaded}{\begin{snugshade}}{\end{snugshade}} -\newcommand{\AlertTok}[1]{\textcolor[rgb]{0.94,0.16,0.16}{#1}} -\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.77,0.63,0.00}{#1}} -\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.00,0.00,0.81}{#1}} -\newcommand{\BuiltInTok}[1]{#1} -\newcommand{\CharTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}} -\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textit{#1}}} -\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.00,0.00,0.00}{#1}} -\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{\textbf{#1}}} -\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{#1}} -\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.00,0.00,0.81}{#1}} -\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\newcommand{\ErrorTok}[1]{\textcolor[rgb]{0.64,0.00,0.00}{\textbf{#1}}} -\newcommand{\ExtensionTok}[1]{#1} -\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.00,0.00,0.81}{#1}} -\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.00,0.00,0.00}{#1}} -\newcommand{\ImportTok}[1]{#1} -\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{\textbf{#1}}} -\newcommand{\NormalTok}[1]{#1} -\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.81,0.36,0.00}{\textbf{#1}}} -\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{#1}} -\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textit{#1}}} -\newcommand{\RegionMarkerTok}[1]{#1} -\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.00,0.00,0.00}{#1}} -\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}} -\newcommand{\StringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}} -\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.00,0.00,0.00}{#1}} -\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}} -\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} - -% Configuration des boîtes colorées (version simplifiée et compatible) -\newtcolorbox{moduleintro}{ - colback=modulecolor!10!white, - colframe=modulecolor, - boxrule=2pt, - title={Contenu du module}, - fonttitle=\bfseries, - coltitle=white, - colbacktitle=modulecolor -} - -\newtcolorbox{infobox}{ - colback=blue!5!white, - colframe=blue!50!black, - title=Information -} - -% Configuration des en-têtes et pieds selon tes spécifications exactes -\pagestyle{fancy} -\fancyhf{} - -% Numéros de page : gauche pour pages paires, droite pour impaires -\fancyhead[LE]{\thepage} % Left Even (pages paires) -\fancyhead[RO]{\thepage} % Right Odd (pages impaires) - -% Titre de partie en haut à droite des pages paires -\fancyhead[RE]{\textcolor{primarycolor}{\nouppercase{\leftmark}}} - -% Configuration simple et robuste de la numérotation -\setcounter{secnumdepth}{1} % Seuls les chapitres (sections) sont numérotés - -% Redéfinition simple des titres de sections -\titleformat{\section}{\Large\bfseries\color{primarycolor}}{\thesection.}{1em}{} -\titleformat{name=\section,numberless}{\Huge\bfseries\color{modulecolor}\centering}{}{0pt}{} -\titleformat{\subsection}{\large\bfseries\color{secondarycolor}}{}{0pt}{} -\titleformat{\subsubsection}{\normalsize\bfseries}{}{0pt}{} - -% Configuration simple de la table des matières -\renewcommand{\contentsname}{Sommaire} - -% Cartouche des droits -\newcommand{\cartouchedroits}{ - \vfill - \begin{tcolorbox}[colback=gray!5!white,colframe=gray!50!black,boxrule=1pt] - \small - \textbf{Droits et licence}\\ - Ce document de formation est la propriété de Prima Solutions.\\ - Reproduction et distribution autorisées à des fins pédagogiques uniquement.\\ - Version générée automatiquement le \today - \end{tcolorbox} -} - -% Métadonnées du document -\title{$title$} -\author{$author$} -\date{$date$} - -% Définition de tightlist pour les listes Pandoc -\providecommand{\tightlist}{% - \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} - -% Début du document -\begin{document} - -% Configuration spéciale pour les pages préliminaires (numérotation romaine) -\pagenumbering{roman} - -% Page de titre personnalisée selon tes spécifications -\begin{titlepage} - \centering - - % Titre principal avec cadre de couleur - \begin{tcolorbox}[ - colback=modulecolor!15!white, - colframe=modulecolor, - boxrule=3pt, - width=0.9\textwidth - ] - \centering - {\Huge\bfseries\color{modulecolor}{$title$}} - \end{tcolorbox} - - \vspace{1cm} - - % Contenu du module dans une boîte - \begin{moduleintro} - $if(module-content)$ - $module-content$ - $else$ - Ce module couvre les aspects essentiels de la formation Linux.\\ - Consultez le sommaire pour le détail des chapitres abordés. - $endif$ - \end{moduleintro} - - \vspace{2cm} - - {\large\textbf{Auteur :} $author$\par} - \vspace{0.5cm} - {\large\textbf{Date :} $date$\par} - - % Cartouche des droits - \cartouchedroits -\end{titlepage} - -% Table des matières (numérotation romaine) -\newpage -\tableofcontents -\newpage - -% Début du contenu principal (numérotation arabe à partir de 1) -\pagenumbering{arabic} -\setcounter{page}{1} - -% Commencer la numérotation des chapitres à 1 pour le premier module -$if(reset-chapter-numbering)$ -\setcounter{section}{0} -$endif$ - -% Contenu principal -$body$ - -\end{document} \ No newline at end of file diff --git a/scripts/templates/pdf_template.tex b/scripts/templates/pdf_template.tex deleted file mode 100644 index 7106cc1..0000000 --- a/scripts/templates/pdf_template.tex +++ /dev/null @@ -1,152 +0,0 @@ -\documentclass[a4paper,11pt]{article} - -% Packages essentiels -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage[english]{babel} -\usepackage{geometry} -\usepackage{fancyhdr} -\usepackage{graphicx} -\usepackage{xcolor} -\usepackage{hyperref} -\usepackage{listings} -\usepackage{tcolorbox} -\usepackage{enumitem} -\usepackage{titlesec} -\usepackage{lastpage} -\usepackage{longtable} -\usepackage{booktabs} -\usepackage{array} - -% Configuration de la page -\geometry{ - left=2.5cm, - right=2.5cm, - top=3cm, - bottom=3cm -} - -% Configuration des couleurs -\definecolor{primarycolor}{RGB}{51,102,153} -\definecolor{secondarycolor}{RGB}{102,153,204} -\definecolor{codecolor}{RGB}{245,245,245} -\definecolor{terminalcolor}{RGB}{40,44,52} - -% Configuration des liens -\hypersetup{ - colorlinks=true, - linkcolor=primarycolor, - urlcolor=primarycolor, - citecolor=primarycolor, - pdfborder={0 0 0} -} - -% Configuration des listings (code) -\lstset{ - backgroundcolor=\color{codecolor}, - basicstyle=\ttfamily\small, - breaklines=true, - frame=single, - rulecolor=\color{gray}, - numbers=left, - numberstyle=\tiny\color{gray}, - keywordstyle=\color{blue}, - commentstyle=\color{green!60!black}, - stringstyle=\color{red}, - showstringspaces=false, - tabsize=2 -} - -% Configuration des boîtes colorées -\newtcolorbox{infobox}{ - colback=blue!5!white, - colframe=blue!50!black, - title=Information -} - -\newtcolorbox{warningbox}{ - colback=orange!5!white, - colframe=orange!50!black, - title=Attention -} - -\newtcolorbox{tipbox}{ - colback=green!5!white, - colframe=green!50!black, - title=Conseil -} - -% Configuration des en-têtes et pieds de page -\pagestyle{fancy} -\fancyhf{} -\fancyhead[L]{\textcolor{primarycolor}{Formation Linux}} -\fancyhead[R]{\textcolor{primarycolor}{\nouppercase{\leftmark}}} -\fancyfoot[C]{\textcolor{primarycolor}{Page \thepage\ sur \pageref{LastPage}}} - -% Configuration des titres -\titleformat{\section} -{\Large\bfseries\color{primarycolor}} -{\thesection}{1em}{} - -\titleformat{\subsection} -{\large\bfseries\color{secondarycolor}} -{\thesubsection}{1em}{} - -% Métadonnées du document -\title{\Huge\textbf{\textcolor{primarycolor}{$title$}}} -\author{$author$} -\date{$date$} - -% Définition de tightlist pour les listes Pandoc -\providecommand{\tightlist}{% - \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} - -% Début du document -\begin{document} - -% Page de titre personnalisée -\begin{titlepage} - \centering - \vspace*{2cm} - - {\Huge\textbf{\textcolor{primarycolor}{Formation Linux}}\par} - \vspace{0.5cm} - {\Large\textcolor{secondarycolor}{$title$}\par} - \vspace{4cm} - {\Large Manuel de formation\par} - \vspace{1cm} - {\large $author$\par} - \vspace{0.5cm} - {\large $date$\par} - - \vfill - \textcolor{gray}{ - \small - Ce document est généré automatiquement à partir des sources Markdown.\\ - Version PDF - Tous droits réservés - } -\end{titlepage} - -% Table des matières -\newpage -\tableofcontents -\newpage - -% Contenu principal -$body$ - -% Page de fin -\newpage -\vspace*{\fill} -\begin{center} - \textcolor{primarycolor}{\Large\textbf{Fin de la formation}} - \vspace{1cm} - - \textcolor{gray}{ - Formation Linux - Version $date$\\ - Généré avec Pandoc et LaTeX - } -\end{center} -\vspace*{\fill} - -\end{document} \ No newline at end of file diff --git a/scripts/templates/template.tex b/scripts/templates/template.tex deleted file mode 100644 index c925971..0000000 --- a/scripts/templates/template.tex +++ /dev/null @@ -1,163 +0,0 @@ -% Template LaTeX personnalisé pour Formation Linux -% Désactive la numérotation des sections de niveau 1 - -\documentclass[$if(fontsize)$$fontsize$,$endif$$if(lang)$$babel-lang$,$endif$$if(papersize)$$papersize$paper,$endif$$for(classoption)$$classoption$$sep$,$endfor$]{$documentclass$} - -% === PACKAGES === -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage{lmodern} -% Pas de babel pour éviter les problèmes de langue -\usepackage{geometry} -\usepackage{graphicx} -\usepackage{hyperref} -\usepackage{fancyhdr} -\usepackage{titlesec} -\usepackage{tocloft} -\usepackage{xcolor} -\usepackage{longtable} -\usepackage{booktabs} -\usepackage{array} -\usepackage{calc} -\usepackage{etoolbox} -\usepackage{footnote} -\usepackage{listings} -\usepackage{fancyvrb} - -% === CONFIGURATION GÉOMÉTRIE === -$if(geometry)$ -\geometry{$for(geometry)$$geometry$$sep$,$endfor$} -$else$ -\geometry{margin=2.5cm} -$endif$ - -% === CONFIGURATION COULEURS === -\definecolor{darkblue}{RGB}{44, 82, 130} -\definecolor{lightblue}{RGB}{70, 130, 180} - -% === CONFIGURATION HYPERLIENS === -\hypersetup{ - colorlinks=true, - linkcolor=darkblue, - urlcolor=darkblue, - citecolor=darkblue, - bookmarksnumbered=true, - bookmarksopen=true -} - -% === REDÉFINITION DE LA NUMÉROTATION === -% Désactiver la numérotation pour \section (niveau 1) -\setcounter{secnumdepth}{5} % Autoriser la numérotation jusqu'au niveau 5 -\titleformat{\section} - {\Large\bfseries\color{darkblue}} % Format du titre - {} % Pas de numéro - {0pt} % Pas d'espacement - {} % Pas de préfixe - -% Configurer la numérotation pour les niveaux 2-5 (subsection, subsubsection, etc.) -\titleformat{\subsection} - {\large\bfseries\color{lightblue}} - {\thesubsection} - {1em} - {} - -\titleformat{\subsubsection} - {\normalsize\bfseries} - {\thesubsubsection} - {1em} - {} - -% === CONFIGURATION TABLE DES MATIÈRES === -% Enlever la numérotation des sections dans la TOC -\renewcommand{\cftsecpresnum}{} -\renewcommand{\cftsecaftersnum}{} -\setlength{\cftsecnumwidth}{0pt} - -% Garder la numérotation pour les subsections dans la TOC -\renewcommand{\cftsubsecpresnum}{} -\renewcommand{\cftsubsecaftersnum}{ } -\setlength{\cftsubsecnumwidth}{2em} - -% === EN-TÊTES ET PIEDS DE PAGE === -\pagestyle{fancy} -\fancyhf{} -\fancyhead[L]{$title-short$} -\fancyhead[R]{\thepage} -\fancyfoot[C]{Formation Linux - Prima Solutions} -\renewcommand{\headrulewidth}{0.4pt} -\renewcommand{\footrulewidth}{0.4pt} - -% === DÉFINITIONS PANDOC === -% Définition de tightlist pour les listes Pandoc -\providecommand{\tightlist}{% - \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} - -% Définition de l'environnement Shaded pour la coloration syntaxique -\usepackage{framed} -\definecolor{shadecolor}{RGB}{248,248,248} -\newenvironment{Shaded}{\begin{snugshade}}{\end{snugshade}} -\newenvironment{Highlighting}{\begin{Shaded}}{\end{Shaded}} -\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{\textbf{#1}}} -\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{#1}} -\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.00,0.00,0.81}{#1}} -\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.00,0.00,0.81}{#1}} -\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.00,0.00,0.81}{#1}} -\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.00,0.00,0.00}{#1}} -\newcommand{\CharTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}} -\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.00,0.00,0.00}{#1}} -\newcommand{\StringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}} -\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}} -\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}} -\newcommand{\ImportTok}[1]{#1} -\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textit{#1}}} -\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{#1}} -\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.00,0.00,0.00}{#1}} -\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.00,0.00,0.00}{#1}} -\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{\textbf{#1}}} -\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.81,0.36,0.00}{\textbf{#1}}} -\newcommand{\BuiltInTok}[1]{#1} -\newcommand{\ExtensionTok}[1]{#1} -\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textit{#1}}} -\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.77,0.63,0.00}{#1}} -\newcommand{\RegionMarkerTok}[1]{#1} -\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}} -\newcommand{\AlertTok}[1]{\textcolor[rgb]{0.94,0.16,0.16}{#1}} -\newcommand{\ErrorTok}[1]{\textcolor[rgb]{0.64,0.00,0.00}{\textbf{#1}}} -\newcommand{\NormalTok}[1]{#1} - -% === MÉTADONNÉES DU DOCUMENT === -$if(title)$ -\title{$title$} -$endif$ -$if(author)$ -\author{$author$} -$endif$ -$if(date)$ -\date{$date$} -$endif$ - -% === DÉBUT DU DOCUMENT === -\begin{document} - -$if(title)$ -\maketitle -\thispagestyle{empty} -\newpage -$endif$ - -$if(toc)$ -{ -\hypersetup{linkcolor=black} -\tableofcontents -\newpage -} -$endif$ - -% === CONTENU PRINCIPAL === -$body$ - -\end{document} \ No newline at end of file From 0685470b7cc5a9db0f715f6a3d6bdaaf911567ff Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:13:02 +0200 Subject: [PATCH 28/32] =?UTF-8?q?docs:=20documenter=20la=20cha=C3=AEne=20P?= =?UTF-8?q?DF=202026=20(./pdf/build,=20release=20sur=20tag)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 188 ++++++++---------------------------------------- CONTRIBUTING.md | 31 ++++---- README.md | 13 ++-- 3 files changed, 52 insertions(+), 180 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 64c42d9..089eabf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -170,22 +170,17 @@ formation_linux/ evaluations/ (quiz et evaluations finales de l'ancien cours) ressources/ images/ - scripts/ - references/ - scripts/ - build_formations.sh - build_formations_ci.sh - build_pdf.sh - build_modules_additionnels.sh - build_git_module.sh - build_docker_module.sh - clean_unicode.sh - config.sh - templates/ - formation_template.tex + pdf/ (chaîne de génération PDF) + build (point d'entrée unique) + generer.py + documents.yaml (catalogue des PDF) + preambule.tex + Dockerfile + tests/ docs/ superpowers/ - specs/ (specifications et plans de refonte) + specs/ (spécifications de conception) + plans/ (plans d'implémentation) ``` **Remarque** : `archives/cours_2025/` contient l'ancien matériau du cours de base issu de la version précédente. Ne plus le modifier et ne plus le générer en PDF. Tout nouveau contenu de cours va dans `cours_2026/`. `supports/` et `travaux_pratiques/` ne contiennent plus que les modules additionnels Git et Docker, toujours actifs. @@ -197,7 +192,7 @@ Chaque chapitre de `cours_2026/` suit obligatoirement ce template. ### Structure ``` -# Chapitre X.Y -- Titre du chapitre +# Chapitre X.Y — Titre du chapitre > **Objectifs** : [liste des competences acquises a l'issue du chapitre] > **Duree** : environ 30 min @@ -244,156 +239,31 @@ Chaque chapitre de `cours_2026/` suit obligatoirement ce template. Chaque concept n'est enseigné que dans UN chapitre. Dans les autres chapitres, utiliser un renvoi explicite : "(voir chapitre X.Y)". -## Gestion des caractères français et génération PDF +## Caractères dans les sources -### Problème récurrent : Caractères accentués dans les PDFs +- Les **accents français** sont toujours préservés dans les fichiers source. +- Pas de caractères de dessin de boîtes, de flèches Unicode ni d'emojis : utiliser `+`, `-`, `|` pour les diagrammes, `->` et `<-` pour les flèches. +- Un caractère absent de la police fait échouer `./pdf/build` avec `Missing character` (le caractère fautif est indiqué) : la correction se fait dans la source. -IMPORTANT : Les caractères accentués français (é, è, à, ç, œ, guillemets français) peuvent être remplacés par des 'x' dans les PDFs générés si l'encodage LaTeX n'est pas correctement configuré. +## Génération PDF -### Solution mise en place - -**Scripts de nettoyage :** -- Utiliser OBLIGATOIREMENT `clean_unicode.sh` qui **préserve les accents français** -- NE JAMAIS utiliser `clean_unicode_comprehensive.sh` qui est trop agressif -- Le script supprime les caractères Unicode problématiques tout en gardant les caractères français - -**Configuration LaTeX pour les caractères français :** -```latex -\usepackage[utf8]{inputenc} % Encodage UTF-8 -\usepackage[T1]{fontenc} % Encodage des fontes T1 -\usepackage[french]{babel} % Support du francais -\usepackage{lmodern} % Fontes vectorielles -``` - -**Caractères Unicode problématiques à corriger dans les contenus :** -- Caractères de dessin de boîtes : utiliser +, -, | à la place (jamais les caractères de tableaux unicode) -- Flèches unicode : utiliser ->, <-, ^, v à la place -- Symboles mathématiques spéciaux : utiliser les équivalents ASCII -- Emojis : ne pas en utiliser dans les fichiers source - -**Règle d'or :** -- Les **accents français** doivent TOUJOURS être préservés dans les fichiers source -- Les **diagrammes ASCII** doivent utiliser des caractères simples (+, -, |, <, >) -- Tester la génération avec `./scripts/build_git_module.sh` pour validation rapide - -### Commandes utiles pour diagnostic +**Commandes** (depuis la racine du dépôt) : ```bash -# Tester la generation du module Git (rapide) -./scripts/build_git_module.sh - -# Tester la generation du module Docker -./scripts/build_docker_module.sh - -# Nettoyer manuellement un fichier -./scripts/clean_unicode.sh fichier.md - -# Generer tous les modules additionnels -./scripts/build_modules_additionnels.sh - -# Rechercher des caracteres problematiques dans les modules additionnels -grep -rn "caracteres_boites\|fleches_unicode" supports/modules_additionnels/ -``` - -Cette configuration garantit que les PDFs affichent correctement les caractères français tout en évitant les erreurs LaTeX dues aux caractères Unicode non supportés. - -## Spécifications de génération PDF - -NOTE : chaîne de génération en cours de refonte -- ne reflète pas encore cours_2026/. - -### Cohérence locale/GitHub Actions -Les fichiers PDF générés doivent être identiques lors de la génération locale avec les scripts et lors de la génération avec le workflow GitHub Actions. - -### Types de PDFs à générer - -**PDFs par module de base :** -- `module_01_decouverte.pdf` -- `module_02_navigation.pdf` -- `module_03_manipulation.pdf` -- `module_04_consultation.pdf` -- `module_05_droits.pdf` -- `module_06_processus.pdf` -- `module_07_reseaux.pdf` -- `module_08_automatisation.pdf` - -**PDFs par module additionnel :** -- `module_additionnel_git.pdf` -- `module_additionnel_docker.pdf` - -### Règles de numérotation - -**Chapitres uniquement :** -- Seuls les chapitres sont numérotés (pas les parties/modules) -- La numérotation commence à 1 avec le premier module -- La première partie (présentation de la formation) est à zéro pour que le module 1 soit numéroté 1 - -**Pages :** -- Pages de contenu : numérotation arabe à partir de 1 -- Pages de sommaire et introduction : numérotation romaine minuscule (i, ii, iii...) -- Numéros de page en haut à gauche pour les pages paires -- Numéros de page en haut à droite pour les pages impaires - -### Mise en page - -**Première page de chaque PDF :** -- Titre du module avec mise en forme distinctive (cadre de couleur) -- Contenu détaillé du module -- Cartouche des droits d'auteur/licence - -**Organisation des pages :** -- Chaque partie commence sur une nouvelle page (y compris l'introduction) -- En-têtes : titre de la partie rappelé en haut à droite des pages paires - -### Configuration LaTeX requise - -```latex -% Numerotation des pages -\usepackage{fancyhdr} -\pagestyle{fancy} -\fancyhf{} -% Pages paires : numero a gauche, titre de partie a droite -\fancyhead[LE]{\thepage} -\fancyhead[RE]{\leftmark} -% Pages impaires : numero a droite -\fancyhead[RO]{\thepage} - -% Numerotation des sections (chapitres uniquement) -\setcounter{secnumdepth}{1} -% La partie presentation sera a 0 pour que Module 1 = 1 +./pdf/build # tous les PDF, puis les tests +./pdf/build pdf [FICHIER.pdf] # PDF seuls (tous, ou ceux indiqués) +./pdf/build test # tests seuls (pytest) +./pdf/build check # lint : ruff et shellcheck +./pdf/build shell # shell dans l'image ``` -## Automatisation GitHub Actions - -NOTE : chaîne de génération en cours de refonte -- ne reflète pas encore cours_2026/. - -### Workflows configurés - -Le projet utilise GitHub Actions pour automatiser la génération des PDFs : - -**`.github/workflows/build-pdfs.yml` - Production** -- Se déclenche à chaque push sur master/main -- Génère tous les modules en PDF (formations + modules individuels) -- Publie les artifacts et crée des releases automatiques -- Durée typique : 5-10 minutes - -**`.github/workflows/test-build.yml` - Tests** -- Se déclenche sur les Pull Requests -- Valide que les PDFs se génèrent correctement -- Pas de publication, uniquement validation - -**`.github/workflows/build-artifacts-only.yml` - Fallback** -- Déclenchement manuel uniquement -- Génère les PDFs sans créer de release -- Utile si problème de permissions - -### Utilisation +- Tout tourne dans l'image Docker décrite par `pdf/Dockerfile` (pandoc, LuaLaTeX, Python) : le seul prérequis est Docker. +- Les PDF sont produits dans `build/pdf/`, les fichiers de diagnostic en cas d'échec dans `build/pdf/debug/`. +- Ajouter un PDF : ajouter une entrée dans `pdf/documents.yaml` (et son nom dans `ATTENDUS`, `pdf/tests/test_documents.py`). Chaque fichier source contient exactement un titre de niveau 1 (`#`). +- CI : `.github/workflows/pdf.yml` lance les mêmes commandes. Les PDF sont publiés en artifact à chaque push sur master et à chaque PR ; une release est créée quand on pousse un tag `v*` : -**Pour récupérer les PDFs à jour :** -1. Aller sur [Releases](https://github.com/votre-repo/formation_linux/releases) -2. Télécharger la dernière version -3. Les PDFs sont attachés comme assets + ```bash + git tag v2026.1 && git push origin v2026.1 + ``` -**Pour déclencher manuellement :** -1. Aller dans l'onglet Actions sur GitHub -2. Sélectionner "Build Formation PDFs" -3. Cliquer "Run workflow" +- La mise en page riche (cadre de couleur, numérotation romaine des pages liminaires, recto-verso) est une évolution possible, pas l'état actuel. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc80130..ff2f892 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,20 +7,19 @@ 1. **Fork** le repository 2. **Cloner** votre fork localement 3. **Modifier** les fichiers Markdown dans `cours_2026/` (cours de base) ou dans les modules additionnels (`supports/modules_additionnels/`, `travaux_pratiques/tp_additionnels/`) -4. **Tester** localement (optionnel) : +4. **Tester** localement (optionnel, Docker requis) : ```bash - ./scripts/build_git_module.sh # Test module Git - ./scripts/build_docker_module.sh # Test module Docker + ./pdf/build # Génère les 11 PDF et lance les tests ``` 5. **Commit** et **push** vos modifications 6. **Créer** une Pull Request ### 🤖 Tests automatiques -Dès que vous créez une PR, GitHub Actions va : -- ✅ Tester la génération des PDFs -- ✅ Vérifier qu'il n'y a pas d'erreurs LaTeX -- ✅ Afficher un rapport dans la PR +Dès que vous créez une PR, le workflow GitHub Actions `PDF` lance les mêmes commandes qu'en local (`./pdf/build`) : +- vérification du code de la chaîne (lint) ; +- génération des 11 PDF ; +- tests des PDF produits, résultat visible dans la PR. **Pas besoin d'installer LaTeX localement !** @@ -40,13 +39,13 @@ Dès que vous créez une PR, GitHub Actions va : `archives/cours_2025/` contient l'ancien cours de base : il est conservé en référence et ne doit plus être modifié. ### 🔧 Scripts et outils -- Amélioration des scripts de génération -- Optimisation des workflows GitHub Actions +- Amélioration de la chaîne de génération PDF +- Optimisation du workflow GitHub Actions - Correction de bugs de génération PDF **Fichiers concernés :** -- `scripts/` -- `.github/workflows/` +- `pdf/` (point d'entrée `./pdf/build`, catalogue `documents.yaml`, tests) +- `.github/workflows/pdf.yml` ### 📖 Documentation - Mise à jour du README @@ -67,13 +66,11 @@ Ces caractères sont correctement supportés par la configuration LaTeX. ### Test avant contribution ```bash -# Vérifier les caractères problématiques -grep -r "🔥\|⚠️\|✅\|→" cours_2026/ supports/ travaux_pratiques/ - -# Nettoyer si nécessaire -./scripts/clean_unicode.sh fichier-problematique.md +./pdf/build ``` +Un caractère non imprimable fait échouer la génération avec `Missing character` : le message indique le caractère fautif, à corriger dans la source. + ## 🔄 Workflow de contribution ### Pour les modifications mineures @@ -90,7 +87,7 @@ grep -r "🔥\|⚠️\|✅\|→" cours_2026/ supports/ travaux_pratiques/ 6. **Push** : `git push origin amelioration-module-docker` 7. **Pull Request** sur GitHub -📖 **Documentation workflows** : Voir [.github/WORKFLOWS.md](.github/WORKFLOWS.md) pour les détails techniques. +📖 **Génération PDF** : voir la section « Génération PDF » de CLAUDE.md pour les détails techniques. ## 📋 Checklist avant PR diff --git a/README.md b/README.md index 0f3f37c..3d1c558 100644 --- a/README.md +++ b/README.md @@ -134,16 +134,21 @@ formation_linux/ | +-- evaluations/ # Quiz et évaluations de l'ancien cours | +-- ressources/ # Images, schémas, références -+-- scripts/ # Scripts de génération PDF ++-- pdf/ # Génération des PDF (./pdf/build) ``` ## Génération PDF -La chaîne de génération est en cours de refonte et ne couvre pas encore `cours_2026/`. +Les PDF de la formation (un par module, plus les modules additionnels et l'annexe d'installation) sont disponibles dans les **Releases** du dépôt. -Seuls les PDFs des modules additionnels Git et Docker sont générés pour l'instant ; l'ancien cours archivé n'est plus généré. -Pour suivre l'avancement ou déclencher manuellement une génération, consulter l'onglet Actions du dépôt. +Pour les générer localement, Docker suffit : + +```bash +./pdf/build +``` + +Les PDF sont produits dans `build/pdf/`. ## Licence From 1d76a600606e03bd3057618289ee99c326d31364 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:13:07 +0200 Subject: [PATCH 29/32] =?UTF-8?q?fix:=20=C3=A9viter=20l'injection=20de=20s?= =?UTF-8?q?cript=20dans=20le=20workflow=20PDF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .github/workflows/pdf.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pdf.yml b/.github/workflows/pdf.yml index 9b02a72..08cda2b 100644 --- a/.github/workflows/pdf.yml +++ b/.github/workflows/pdf.yml @@ -56,8 +56,10 @@ jobs: - name: Créer la release env: GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} + REPO: ${{ github.repository }} run: > - gh release create "${{ github.ref_name }}" pdf/*.pdf - --repo "${{ github.repository }}" - --title "Formation Linux ${{ github.ref_name }}" + gh release create "$TAG" pdf/*.pdf + --repo "$REPO" + --title "Formation Linux $TAG" --generate-notes From 68f0ad125beba4afeda62cf1faf542af87cfbc26 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:15:44 +0200 Subject: [PATCH 30/32] =?UTF-8?q?docs:=20finitions=20apr=C3=A8s=20la=20rev?= =?UTF-8?q?ue=20de=20la=20cha=C3=AEne=20PDF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 2 +- CONTRIBUTING.md | 20 ++++++++++---------- README.md | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 089eabf..c096eb9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -258,7 +258,7 @@ Chaque concept n'est enseigné que dans UN chapitre. Dans les autres chapitres, ``` - Tout tourne dans l'image Docker décrite par `pdf/Dockerfile` (pandoc, LuaLaTeX, Python) : le seul prérequis est Docker. -- Les PDF sont produits dans `build/pdf/`, les fichiers de diagnostic en cas d'échec dans `build/pdf/debug/`. +- Les PDF sont produits dans `build/pdf/`. Les fichiers intermédiaires (`.tex`, `.log`, etc.), utiles au diagnostic, restent toujours dans `build/pdf/debug/` ; en CI, l'artifact de diagnostic n'est publié qu'en cas d'échec. - Ajouter un PDF : ajouter une entrée dans `pdf/documents.yaml` (et son nom dans `ATTENDUS`, `pdf/tests/test_documents.py`). Chaque fichier source contient exactement un titre de niveau 1 (`#`). - CI : `.github/workflows/pdf.yml` lance les mêmes commandes. Les PDF sont publiés en artifact à chaque push sur master et à chaque PR ; une release est créée quand on pousse un tag `v*` : diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ff2f892..1ca0c83 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,10 +16,12 @@ ### 🤖 Tests automatiques -Dès que vous créez une PR, le workflow GitHub Actions `PDF` lance les mêmes commandes qu'en local (`./pdf/build`) : -- vérification du code de la chaîne (lint) ; -- génération des 11 PDF ; -- tests des PDF produits, résultat visible dans la PR. +Dès que vous créez une PR, le workflow GitHub Actions `PDF` lance les mêmes commandes qu'en local : +- `./pdf/build check` : vérification du code de la chaîne (lint) ; +- `./pdf/build pdf` : génération des 11 PDF ; +- `./pdf/build test` : tests des PDF produits, résultat visible dans la PR. + +En local, `./pdf/build` seul enchaîne la génération et les tests, sans le lint : si vous modifiez `pdf/`, lancez aussi `./pdf/build check`. **Pas besoin d'installer LaTeX localement !** @@ -50,7 +52,6 @@ Dès que vous créez une PR, le workflow GitHub Actions `PDF` lance les mêmes c ### 📖 Documentation - Mise à jour du README - Amélioration de CLAUDE.md -- Documentation des workflows ## ⚠️ Points d'attention @@ -58,7 +59,7 @@ Dès que vous créez une PR, le workflow GitHub Actions `PDF` lance les mêmes c **❌ Éviter :** `🔥 ⚠️ ✅ → ← ↑ ↓ ┌ └ ├ ┤ ●` **✅ Utiliser :** `[FIRE] [WARN] [OK] -> <- ^ v + + + + *` -**Pourquoi ?** LaTeX ne supporte pas tous les caractères Unicode. +**Pourquoi ?** La police utilisée pour les PDF ne contient pas tous les caractères Unicode. ### Accents français **✅ Conserver :** `é è à ç ù œ « »` @@ -69,7 +70,7 @@ Ces caractères sont correctement supportés par la configuration LaTeX. ./pdf/build ``` -Un caractère non imprimable fait échouer la génération avec `Missing character` : le message indique le caractère fautif, à corriger dans la source. +Un caractère absent de la police fait échouer la génération avec `Missing character` : le message indique le caractère fautif, à corriger dans la source. ## 🔄 Workflow de contribution @@ -87,7 +88,7 @@ Un caractère non imprimable fait échouer la génération avec `Missing charact 6. **Push** : `git push origin amelioration-module-docker` 7. **Pull Request** sur GitHub -📖 **Génération PDF** : voir la section « Génération PDF » de CLAUDE.md pour les détails techniques. +📖 **Génération PDF** : voir la section « Génération PDF » de [CLAUDE.md](CLAUDE.md#génération-pdf) pour les détails techniques. ## 📋 Checklist avant PR @@ -101,8 +102,7 @@ Un caractère non imprimable fait échouer la génération avec `Missing charact ### Bug de génération PDF 1. Aller dans [Issues](../../issues) -2. Utiliser le template "Bug PDF" -3. Inclure les logs d'erreur depuis Actions +2. Ouvrir une issue en joignant l'artifact de diagnostic (`debug-...`) du run Actions en échec ### Erreur de contenu 1. Aller dans [Issues](../../issues) diff --git a/README.md b/README.md index 3d1c558..14670d5 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ formation_linux/ ## Génération PDF -Les PDF de la formation (un par module, plus les modules additionnels et l'annexe d'installation) sont disponibles dans les **Releases** du dépôt. +Les PDF de la formation (un par module, plus les modules additionnels et l'annexe d'installation) sont disponibles dans les [Releases](../../releases/latest) du dépôt. Pour les générer localement, Docker suffit : From a1db238ed00d5891a82ecebdc52cbbc60da1d2e9 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:22:21 +0200 Subject: [PATCH 31/32] =?UTF-8?q?chore:=20finitions=20de=20la=20revue=20fi?= =?UTF-8?q?nale=20(suivi=20des=20t=C3=A2ches,=20statut=20de=20la=20spec)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../plans/2026-10-01-chaine-pdf-2026.md | 3 +++ .../plans/2026-10-01-chaine-pdf-2026.md.tasks.json | 14 +++++++------- .../specs/2026-10-01-chaine-pdf-2026-design.md | 5 ++++- pdf/build | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md index 0b5416d..0d5739e 100644 --- a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md +++ b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md @@ -19,6 +19,9 @@ PyYAML, pytest, pypdf, ruff, shellcheck, GitHub Actions. **Branche :** `chaine-pdf-2026` (déjà créée, contient la spec). +**STATUT : Tasks 1 à 10 terminées et relues (2026-10-01) ; Task 11 : vérification +locale faite (build à zéro, 104 tests, PDF identiques à l'octet), validation CI à faire.** + --- ## Faits vérifiés pendant la préparation (2026-10-01) diff --git a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json index f06e3d0..f9c465d 100644 --- a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json +++ b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json @@ -33,7 +33,7 @@ { "id": 4, "subject": "Task 5: Préambule, compilation et premier PDF réel (module 2)", - "status": "pending", + "status": "completed", "blockedBy": [ 3 ] @@ -41,7 +41,7 @@ { "id": 5, "subject": "Task 6: Catalogue complet et nettoyage des sources Git", - "status": "pending", + "status": "completed", "blockedBy": [ 4 ] @@ -49,7 +49,7 @@ { "id": 6, "subject": "Task 7: Tests des PDF produits", - "status": "pending", + "status": "completed", "blockedBy": [ 5 ] @@ -57,7 +57,7 @@ { "id": 7, "subject": "Task 8: Workflow GitHub Actions unique", - "status": "pending", + "status": "completed", "blockedBy": [ 6 ] @@ -65,7 +65,7 @@ { "id": 8, "subject": "Task 9: Suppression de l'ancienne chaîne", - "status": "pending", + "status": "completed", "blockedBy": [ 7 ] @@ -73,7 +73,7 @@ { "id": 9, "subject": "Task 10: Documentation", - "status": "pending", + "status": "completed", "blockedBy": [ 8 ] @@ -87,5 +87,5 @@ ] } ], - "lastUpdated": "2026-10-01T17:28:20+02:00" + "lastUpdated": "2026-10-01T18:15:59+02:00" } \ No newline at end of file diff --git a/docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md b/docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md index 1f0a288..1604e7b 100644 --- a/docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md +++ b/docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md @@ -1,7 +1,10 @@ # Chaîne de génération PDF 2026 -- Design Date : 2026-10-01 -Statut : validé +Statut : validé, implémenté (2026-10-01). Les écarts d'implémentation +(template pandoc par défaut + `preambule.tex`, `generer.py`, sous-commandes, +pas de cache CI) sont décrits dans la section « Écarts assumés » du plan +`docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md`. ## Contexte diff --git a/pdf/build b/pdf/build index 38081a7..23d5213 100755 --- a/pdf/build +++ b/pdf/build @@ -26,6 +26,7 @@ date_source="$(git -C "$racine" log -1 --format=%ct 2>/dev/null || date +%s)" docker build --quiet --tag "$image" "$racine/pdf" > /dev/null +options_tty=() dans_image() { docker run --rm \ --user "$(id -u):$(id -g)" \ @@ -39,7 +40,6 @@ dans_image() { "${options_tty[@]}" \ "$image" "$@" } -options_tty=() case "$commande" in tout) From a97b4cd3e9244bdb83bab92f3f6e16100a559cc1 Mon Sep 17 00:00:00 2001 From: Pascal GUINET Date: Thu, 1 Oct 2026 18:28:40 +0200 Subject: [PATCH 32/32] =?UTF-8?q?docs:=20clore=20le=20plan=20de=20la=20cha?= =?UTF-8?q?=C3=AEne=20PDF=202026?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md | 5 +++-- .../plans/2026-10-01-chaine-pdf-2026.md.tasks.json | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md index 0d5739e..08fcad3 100644 --- a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md +++ b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md @@ -19,8 +19,9 @@ PyYAML, pytest, pypdf, ruff, shellcheck, GitHub Actions. **Branche :** `chaine-pdf-2026` (déjà créée, contient la spec). -**STATUT : Tasks 1 à 10 terminées et relues (2026-10-01) ; Task 11 : vérification -locale faite (build à zéro, 104 tests, PDF identiques à l'octet), validation CI à faire.** +**STATUT : terminé (2026-10-01).** Build à zéro : 11 PDF, 104 tests, PDF identiques +à l'octet d'un build à l'autre. CI verte au premier passage (PR #3), PDF de la CI +identiques aux PDF locaux (texte et pages). Première release : tag `v2026.1`. --- diff --git a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json index f9c465d..a209faf 100644 --- a/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json +++ b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md.tasks.json @@ -81,11 +81,11 @@ { "id": 10, "subject": "Task 11: Vérification finale et validation en CI", - "status": "pending", + "status": "completed", "blockedBy": [ 9 ] } ], - "lastUpdated": "2026-10-01T18:15:59+02:00" + "lastUpdated": "2026-10-01T18:28:40+02:00" } \ No newline at end of file