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/pdf.yml b/.github/workflows/pdf.yml new file mode 100644 index 0000000..08cda2b --- /dev/null +++ b/.github/workflows/pdf.yml @@ -0,0 +1,65 @@ +# 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 }} + TAG: ${{ github.ref_name }} + REPO: ${{ github.repository }} + run: > + gh release create "$TAG" pdf/*.pdf + --repo "$REPO" + --title "Formation Linux $TAG" + --generate-notes 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/.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/CLAUDE.md b/CLAUDE.md index 9b89afd..c096eb9 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,30 +163,27 @@ formation_linux/ tp3_volumes_donnees.md tp4_reseaux_communication.md tp5_compose_orchestration.md - evaluations/ - quiz/ - exercices/ + archives/ + cours_2025/ (ANCIEN cours de base, ne plus modifier, conserve en reference) + supports/ (modules 01-08) + travaux_pratiques/ (TP 01-08) + 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** : `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 @@ -201,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 @@ -248,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 - -### ProblĂšme rĂ©current : CaractĂšres accentuĂ©s dans les PDFs +## CaractĂšres dans les sources -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Ă©. +- 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. -### Solution mise en place +## GĂ©nĂ©ration PDF -**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 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*` : -**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 6de8263..1ca0c83 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,21 +6,22 @@ 1. **Fork** le repository 2. **Cloner** votre fork localement -3. **Modifier** les fichiers Markdown dans `supports/` ou `travaux_pratiques/` -4. **Tester** localement (optionnel) : +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, 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 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 !** @@ -33,23 +34,24 @@ 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 -- 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 - AmĂ©lioration de CLAUDE.md -- Documentation des workflows ## ⚠ Points d'attention @@ -57,7 +59,7 @@ DĂšs que vous crĂ©ez une PR, GitHub Actions va : **❌ É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 :** `Ă© Ăš Ă  ç Ăč Ɠ « »` @@ -65,13 +67,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 "đŸ”„\|⚠\|✅\|→" supports/ travaux_pratiques/ - -# Nettoyer si nĂ©cessaire -./scripts/clean_unicode.sh fichier-problematique.md +./pdf/build ``` +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 ### Pour les modifications mineures @@ -88,7 +88,7 @@ grep -r "đŸ”„\|⚠\|✅\|→" 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](CLAUDE.md#gĂ©nĂ©ration-pdf) pour les dĂ©tails techniques. ## 📋 Checklist avant PR @@ -102,8 +102,7 @@ grep -r "đŸ”„\|⚠\|✅\|→" supports/ travaux_pratiques/ ### 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) @@ -121,7 +120,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..14670d5 100644 --- a/README.md +++ b/README.md @@ -117,30 +117,38 @@ 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/ | -+-- evaluations/ # Quiz et exercices ++-- archives/ +| +-- 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 +| +-- 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](../../releases/latest) du dĂ©pĂŽt. + +Pour les gĂ©nĂ©rer localement, Docker suffit : + +```bash +./pdf/build +``` -Les PDFs actuellement gĂ©nĂ©rĂ©s par GitHub Actions couvrent l'ancien contenu (`supports/`). -Pour suivre l'avancement ou dĂ©clencher manuellement une gĂ©nĂ©ration, consulter l'onglet Actions du dĂ©pĂŽt. +Les PDF sont produits dans `build/pdf/`. ## Licence 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 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 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/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..08fcad3 --- /dev/null +++ b/docs/superpowers/plans/2026-10-01-chaine-pdf-2026.md @@ -0,0 +1,1460 @@ +# 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/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. + +**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). + +**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`. + +--- + +## 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 `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 + 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/generer.py + dans_image python3 -m pytest pdf/tests + ;; + pdf) + dans_image python3 pdf/generer.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/generer.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 generer + +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 = generer.charger_documents(ecrire(tmp_path, VALIDE), tmp_path) + + assert catalogue.auteur == "Auteur" + assert catalogue.documents == [ + 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 = generer.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(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** + +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/generer.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/generer.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/generer.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 generer + + +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 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(generer.ErreurBuild, match="exactement 1 attendu"): + generer.titre_chapitre(source(tmp_path, contenu)) + + +def test_signale_une_commande_en_echec(): + with pytest.raises(generer.ErreurBuild, match="Ă©chec de : false"): + generer.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/generer.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/generer.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/generer.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 generer + +DOCUMENT = generer.Document("doc.pdf", "Module 2 — Navigation", (Path("/s/a.md"), Path("/s/b.md"))) + + +def test_echappe_les_caracteres_speciaux_latex(): + 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 = generer.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 = generer.Document("doc.pdf", "Titre", (Path("/s/a.md"),), "Le sous-titre") + + assert "Le sous-titre" in generer.generer_couverture(document, ["Chap"], Path("/logo.png")) + + +def test_commande_pandoc(): + commande = generer.commande_pandoc(DOCUMENT, "Auteur", Path("/d/couv.tex"), Path("/d/doc.tex")) + + 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", # passĂ©s par -M (Ă©chappĂ©s par pandoc) + str(generer.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 = 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 generer.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/generer.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", + "-M", + f"title-meta={document.titre}", + "-M", + 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/generer.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/generer.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/generer.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 = 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"]) +``` + +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 `generer.py` tant que ce n'est pas le cas. + +**Step 7 : commit** + +```bash +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)" +``` + +--- + +### 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 = 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 == [] +``` + +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 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] == [] +``` + +**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) + generer.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..a209faf --- /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": "completed" + }, + { + "id": 1, + "subject": "Task 2: Chargement et validation de documents.yaml", + "status": "completed", + "blockedBy": [ + 0 + ] + }, + { + "id": 2, + "subject": "Task 3: Extraction des titres de chapitre via pandoc", + "status": "completed", + "blockedBy": [ + 1 + ] + }, + { + "id": 3, + "subject": "Task 4: Couverture, commande pandoc et analyse du log LaTeX", + "status": "completed", + "blockedBy": [ + 2 + ] + }, + { + "id": 4, + "subject": "Task 5: PrĂ©ambule, compilation et premier PDF rĂ©el (module 2)", + "status": "completed", + "blockedBy": [ + 3 + ] + }, + { + "id": 5, + "subject": "Task 6: Catalogue complet et nettoyage des sources Git", + "status": "completed", + "blockedBy": [ + 4 + ] + }, + { + "id": 6, + "subject": "Task 7: Tests des PDF produits", + "status": "completed", + "blockedBy": [ + 5 + ] + }, + { + "id": 7, + "subject": "Task 8: Workflow GitHub Actions unique", + "status": "completed", + "blockedBy": [ + 6 + ] + }, + { + "id": 8, + "subject": "Task 9: Suppression de l'ancienne chaĂźne", + "status": "completed", + "blockedBy": [ + 7 + ] + }, + { + "id": 9, + "subject": "Task 10: Documentation", + "status": "completed", + "blockedBy": [ + 8 + ] + }, + { + "id": 10, + "subject": "Task 11: VĂ©rification finale et validation en CI", + "status": "completed", + "blockedBy": [ + 9 + ] + } + ], + "lastUpdated": "2026-10-01T18:28:40+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 new file mode 100644 index 0000000..1604e7b --- /dev/null +++ b/docs/superpowers/specs/2026-10-01-chaine-pdf-2026-design.md @@ -0,0 +1,158 @@ +# ChaĂźne de gĂ©nĂ©ration PDF 2026 -- Design + +Date : 2026-10-01 +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 + +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 ». diff --git a/pdf/Dockerfile b/pdf/Dockerfile new file mode 100644 index 0000000..7c911f3 --- /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 fonts-dejavu-extra \ + && 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..23d5213 --- /dev/null +++ b/pdf/build @@ -0,0 +1,67 @@ +#!/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 + +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)" + +docker build --quiet --tag "$image" "$racine/pdf" > /dev/null + +options_tty=() +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" "$@" +} + +case "$commande" in + tout) + dans_image python3 pdf/generer.py + dans_image python3 -m pytest pdf/tests + ;; + pdf) + dans_image python3 pdf/generer.py "$@" + ;; + test) + if [ $# -eq 0 ] || [[ "$1" == -* ]]; 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) + options_tty=(--interactive) + if [ -t 0 ]; then options_tty+=(--tty); fi + if [ $# -eq 0 ]; then set -- bash; fi + dans_image "$@" + ;; +esac diff --git a/pdf/documents.yaml b/pdf/documents.yaml new file mode 100644 index 0000000..48a0c0c --- /dev/null +++ b/pdf/documents.yaml @@ -0,0 +1,84 @@ +# 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: + - 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/generer.py b/pdf/generer.py new file mode 100644 index 0000000..641b342 --- /dev/null +++ b/pdf/generer.py @@ -0,0 +1,330 @@ +"""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 + +import argparse +import json +import re +import shutil +import subprocess +import sys +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"} +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): + """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") + 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: + 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", "")) + + +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 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) + 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, + # 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), + "--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", + "monofontoptions=ItalicFont=DejaVu Sans Mono Oblique", + "-V", + "monofontoptions=BoldItalicFont=DejaVu Sans Mono Bold Oblique", + "-M", + f"title-meta={document.titre}", + "-M", + 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+:\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: + 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") + + +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)) + 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..25a6e49 --- /dev/null +++ b/pdf/preambule.tex @@ -0,0 +1,40 @@ +% 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} + +% 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 +\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 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 diff --git a/pdf/tests/test_compilation.py b/pdf/tests/test_compilation.py new file mode 100644 index 0000000..f0d21cd --- /dev/null +++ b/pdf/tests/test_compilation.py @@ -0,0 +1,53 @@ +"""Tests de la compilation LaTeX et de la construction d'un document.""" + +import pypdf +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() + + +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_documents.py b/pdf/tests/test_documents.py new file mode 100644 index 0000000..78c116b --- /dev/null +++ b/pdf/tests/test_documents.py @@ -0,0 +1,127 @@ +"""Tests du chargement de documents.yaml.""" + +import re + +import pytest + +import generer + +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 = generer.charger_documents(ecrire(tmp_path, VALIDE), tmp_path) + + assert catalogue.auteur == "Auteur" + assert catalogue.documents == [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 = generer.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"), + (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): + 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"]) + + +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 catalogue_du_depot(): + return generer.charger_documents(generer.DOSSIER_PDF / "documents.yaml", generer.RACINE) + + +def test_le_catalogue_du_depot_declare_les_11_pdf(): + assert [document.fichier for document in catalogue_du_depot().documents] == ATTENDUS + + +RACINES_ACTIVES = [ + "cours_2026", + "supports/modules_additionnels", + "travaux_pratiques/tp_additionnels", +] + + +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 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/pdf/tests/test_latex.py b/pdf/tests/test_latex.py new file mode 100644 index 0000000..b6b8220 --- /dev/null +++ b/pdf/tests/test_latex.py @@ -0,0 +1,105 @@ +"""Tests de la partie LaTeX : Ă©chappement, couverture, commande, log.""" + +from pathlib import Path + +import generer + +DOCUMENT = generer.Document("doc.pdf", "Module 2 — Navigation", (Path("/s/a.md"), Path("/s/b.md"))) + + +def test_echappe_les_caracteres_speciaux_latex(): + 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 = generer.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_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") + + assert "Le sous-titre" in generer.generer_couverture(document, ["Chap"], Path("/logo.png")) + + +def test_commande_pandoc(): + commande = generer.commande_pandoc(DOCUMENT, "Auteur", Path("/d/couv.tex"), Path("/d/doc.tex")) + + 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", + 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"): + 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(): + log = ( + "ligne normale\n" + "Missing character: There is no ✅ (U+2705) in font [lmroman10-regular]\n" + "suite\n" + "! ==> Fatal error occurred, no output PDF file produced!\n" + ) + + 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_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") == [] diff --git a/pdf/tests/test_pdfs.py b/pdf/tests/test_pdfs.py new file mode 100644 index 0000000..8aa1ef9 --- /dev/null +++ b/pdf/tests/test_pdfs.py @@ -0,0 +1,89 @@ +"""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 chemin_pdf(document): + chemin = SORTIE / document.fichier + assert chemin.is_file(), f"{chemin} absent : lancer ./pdf/build pdf" + return chemin + + +@pytest.fixture(scope="module") +def lecteur(chemin_pdf): + return pypdf.PdfReader(chemin_pdf) + + +@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, chemin_pdf): + texte = subprocess.run( + ["pdftotext", str(chemin_pdf), "-"], + 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 + } + + 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): + assert "Missing character" not in log + + +def test_aucun_debordement_notable(log): + debordements = [ + 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 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 new file mode 100644 index 0000000..ef4eacc --- /dev/null +++ b/pdf/tests/test_sources.py @@ -0,0 +1,43 @@ +"""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 + ) + + consigne = "« -- » hors code : code -> backticks ; prose -> Ă©crire — ou – :\n" + assert fautes == [], consigne + "\n".join(fautes) diff --git a/pdf/tests/test_titres.py b/pdf/tests/test_titres.py new file mode 100644 index 0000000..149af28 --- /dev/null +++ b/pdf/tests/test_titres.py @@ -0,0 +1,33 @@ +"""Tests de l'extraction des titres de niveau 1 (via pandoc).""" + +import pytest + +import generer + + +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 generer.titre_chapitre(chemin) == "Chapitre 1.1 — L’étĂ© ls" + + +@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)) + + +def test_signale_une_commande_en_echec(): + with pytest.raises(generer.ErreurBuild, match="Ă©chec de : false"): + generer.executer(["false"]) 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 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..d78edf1 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` ? @@ -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 367d6a9..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 @@ -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..2f19120 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` @@ -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 @@ -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 @@ -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 @@ -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` ?