Compare commits
5
Commits
170588dc2c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
503aa66dc0 | ||
|
|
5b312f7459 | ||
|
|
47528bae8d | ||
|
|
d4faa4872c | ||
|
|
90982e71c9 |
@@ -1,2 +1,6 @@
|
|||||||
*.odt binary
|
*.odt binary
|
||||||
* text=auto
|
* text=auto
|
||||||
|
# Fins de ligne impos?es pour les lanceurs (sinon ils ?chouent sur l'autre syst?me)
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.command text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
@echo off
|
||||||
|
rem Assistant de generation des supports (Windows) : double-cliquer sur ce fichier.
|
||||||
|
setlocal
|
||||||
|
chcp 65001 >nul <nul
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
call :trouver_python
|
||||||
|
if defined PY goto lancer
|
||||||
|
|
||||||
|
echo Python est nécessaire mais n'a pas été trouvé sur cet ordinateur.
|
||||||
|
choice /c ON /m "Installer Python automatiquement (via winget)"
|
||||||
|
if errorlevel 2 goto manuel
|
||||||
|
winget install -e --id Python.Python.3.12 --accept-source-agreements --accept-package-agreements
|
||||||
|
call :trouver_python
|
||||||
|
if not defined PY goto manuel
|
||||||
|
|
||||||
|
:lancer
|
||||||
|
%PY% scripts\assistant.py
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b
|
||||||
|
|
||||||
|
:manuel
|
||||||
|
echo.
|
||||||
|
echo Installez Python depuis la page qui va s'ouvrir,
|
||||||
|
echo en cochant la case "Add python.exe to PATH", puis relancez ce fichier.
|
||||||
|
start "" https://www.python.org/downloads/
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:trouver_python
|
||||||
|
set "PY="
|
||||||
|
py -3 -c "import venv" <nul >nul 2>nul && set "PY=py -3" && exit /b
|
||||||
|
python -c "import venv" <nul >nul 2>nul && set "PY=python" && exit /b
|
||||||
|
for /d %%D in ("%LOCALAPPDATA%\Programs\Python\Python3*") do if exist "%%~D\python.exe" set PY="%%~D\python.exe"
|
||||||
|
exit /b
|
||||||
Executable
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Assistant de génération des supports (macOS) : double-cliquer sur ce fichier dans le Finder.
|
||||||
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
|
trouver_python() {
|
||||||
|
for c in python3 /opt/homebrew/bin/python3 /usr/local/bin/python3; do
|
||||||
|
if command -v "$c" >/dev/null 2>&1 && "$c" -c "import venv" >/dev/null 2>&1; then
|
||||||
|
PY="$c"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! trouver_python; then
|
||||||
|
echo "Python 3 est nécessaire mais n'a pas été trouvé."
|
||||||
|
read -r -p "L'installer automatiquement (outils de développement Apple) ? [O/n] " rep
|
||||||
|
if [[ ! "$rep" =~ ^[nN] ]]; then
|
||||||
|
xcode-select --install 2>/dev/null
|
||||||
|
read -r -p "Suivez la fenêtre d'installation, puis appuyez sur Entrée une fois terminé... "
|
||||||
|
fi
|
||||||
|
if ! trouver_python; then
|
||||||
|
echo "Installez Python depuis la page qui va s'ouvrir, puis relancez ce fichier."
|
||||||
|
open "https://www.python.org/downloads/"
|
||||||
|
read -r -p "Appuyez sur Entrée pour fermer... "
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$PY" scripts/assistant.py
|
||||||
|
echo
|
||||||
|
read -r -p "Appuyez sur Entrée pour fermer... "
|
||||||
@@ -20,10 +20,33 @@ Les sources sont en Markdown [Quarto](https://quarto.org) (`modules/*.qmd`) et p
|
|||||||
| `12-projetfinal` | Projet final |
|
| `12-projetfinal` | Projet final |
|
||||||
| `synthese-materiel` | Synthèse et liste de matériel |
|
| `synthese-materiel` | Synthèse et liste de matériel |
|
||||||
|
|
||||||
|
## Générer les documents sans ligne de commande
|
||||||
|
|
||||||
|
Après avoir téléchargé le dépôt (bouton « Télécharger » → ZIP, puis décompresser), double-cliquez sur le fichier
|
||||||
|
correspondant à votre système :
|
||||||
|
|
||||||
|
| Système | Fichier à lancer |
|
||||||
|
|---|---|
|
||||||
|
| Windows | `Generer-documents.bat` |
|
||||||
|
| macOS | `Generer-documents.command` (la première fois : clic droit → Ouvrir, pour passer l'avertissement de sécurité) |
|
||||||
|
| Linux | `generer-documents.sh` (clic droit → « Exécuter comme un programme », ou `./generer-documents.sh` dans un terminal) |
|
||||||
|
|
||||||
|
Une fenêtre s'ouvre et vous guide :
|
||||||
|
|
||||||
|
1. elle vérifie que **Python**, **Quarto** et les polices **Noto Sans / Noto Sans Mono** sont installés,
|
||||||
|
et propose de les installer automatiquement sinon (sous Linux, les polices sont généralement déjà présentes) ;
|
||||||
|
2. elle prépare l'environnement Python des schémas (dossier `.venv`, une seule fois) ;
|
||||||
|
3. elle vous demande le **format** (PDF, LibreOffice .odt ou site web HTML) et les **supports** à produire
|
||||||
|
(tous, ou un seul module) ;
|
||||||
|
4. elle génère les documents et ouvre le dossier `_output/modules` qui les contient.
|
||||||
|
|
||||||
|
Il suffit de taper le numéro de votre choix puis Entrée (Entrée seul = choix par défaut).
|
||||||
|
|
||||||
## Organisation
|
## Organisation
|
||||||
|
|
||||||
```
|
```
|
||||||
modules/ sources des supports (.qmd)
|
modules/ sources des supports (.qmd)
|
||||||
|
scripts/ assistant de génération (appelé par les fichiers Generer-documents.*)
|
||||||
modele/ gabarit PDF (Typst) et thème de coloration du code
|
modele/ gabarit PDF (Typst) et thème de coloration du code
|
||||||
schemas/ schémas décrits en Python (schemdraw + matplotlib), rendus en svg/ et png/
|
schemas/ schémas décrits en Python (schemdraw + matplotlib), rendus en svg/ et png/
|
||||||
_quarto.yml configuration du projet
|
_quarto.yml configuration du projet
|
||||||
@@ -32,7 +55,9 @@ _output/ PDF générés (non versionnés)
|
|||||||
|
|
||||||
## Produire les PDF
|
## Produire les PDF
|
||||||
|
|
||||||
Prérequis : [Quarto](https://quarto.org/docs/get-started/) (inclut Pandoc et Typst).
|
Prérequis : [Quarto](https://quarto.org/docs/get-started/) (inclut Pandoc et Typst) et les polices libres
|
||||||
|
[Noto Sans et Noto Sans Mono](https://notofonts.github.io/) (paquets `noto-fonts` sous Arch,
|
||||||
|
`fonts-noto-core fonts-noto-mono` sous Debian/Ubuntu).
|
||||||
|
|
||||||
```
|
```
|
||||||
quarto render # tous les supports
|
quarto render # tous les supports
|
||||||
|
|||||||
+2
-2
@@ -17,9 +17,9 @@ format:
|
|||||||
margin:
|
margin:
|
||||||
x: 2cm
|
x: 2cm
|
||||||
y: 2.2cm
|
y: 2.2cm
|
||||||
mainfont: "Segoe UI"
|
mainfont: "Noto Sans"
|
||||||
fontsize: 10pt
|
fontsize: 10pt
|
||||||
codefont: "Consolas"
|
codefont: "Noto Sans Mono"
|
||||||
toc: false
|
toc: false
|
||||||
highlight-style: modele/code.theme
|
highlight-style: modele/code.theme
|
||||||
include-in-header: modele/entete.typ
|
include-in-header: modele/entete.typ
|
||||||
|
|||||||
Executable
+53
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Assistant de génération des supports (Linux) : clic droit > « Exécuter comme un programme »,
|
||||||
|
# ou dans un terminal : ./generer-documents.sh
|
||||||
|
cd "$(dirname "$(readlink -f "$0")")" || exit 1
|
||||||
|
|
||||||
|
# Lancé hors d'un terminal (double-clic) : on se relance dans une fenêtre de terminal.
|
||||||
|
if [ ! -t 0 ]; then
|
||||||
|
for t in x-terminal-emulator gnome-terminal konsole xfce4-terminal xterm; do
|
||||||
|
if command -v "$t" >/dev/null 2>&1; then
|
||||||
|
case "$t" in
|
||||||
|
gnome-terminal) exec "$t" -- "$0" ;;
|
||||||
|
*) exec "$t" -e "$0" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
trouver_python() {
|
||||||
|
if command -v python3 >/dev/null 2>&1 && python3 -c "import venv, ensurepip" >/dev/null 2>&1; then
|
||||||
|
PY=python3
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! trouver_python; then
|
||||||
|
echo "Python 3 (avec le module venv) est nécessaire mais n'a pas été trouvé."
|
||||||
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
|
cmd="sudo apt-get install -y python3 python3-venv"
|
||||||
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
|
cmd="sudo dnf install -y python3"
|
||||||
|
elif command -v pacman >/dev/null 2>&1; then
|
||||||
|
cmd="sudo pacman -S --needed --noconfirm python"
|
||||||
|
elif command -v zypper >/dev/null 2>&1; then
|
||||||
|
cmd="sudo zypper install -y python3"
|
||||||
|
fi
|
||||||
|
if [ -n "$cmd" ]; then
|
||||||
|
read -r -p "Lancer l'installation ($cmd) ? Votre mot de passe sera demandé. [O/n] " rep
|
||||||
|
if [[ ! "$rep" =~ ^[nN] ]]; then
|
||||||
|
$cmd
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if ! trouver_python; then
|
||||||
|
echo "Installez Python 3 et son module venv avec le gestionnaire de paquets de votre distribution,"
|
||||||
|
echo "puis relancez ce fichier."
|
||||||
|
read -r -p "Appuyez sur Entrée pour fermer... "
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$PY" scripts/assistant.py
|
||||||
|
echo
|
||||||
|
read -r -p "Appuyez sur Entrée pour fermer... "
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
"""Assistant de génération des supports de la formation (Windows, macOS, Linux).
|
||||||
|
|
||||||
|
Lancé par Generer-documents.bat / Generer-documents.command / generer-documents.sh.
|
||||||
|
N'utilise que la bibliothèque standard de Python.
|
||||||
|
|
||||||
|
Étapes : vérifier (ou installer) Quarto, préparer l'environnement Python des schémas,
|
||||||
|
demander quoi générer, lancer Quarto, ouvrir le dossier des documents produits.
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
import webbrowser
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
RACINE = Path(__file__).resolve().parent.parent
|
||||||
|
MODULES = RACINE / "modules"
|
||||||
|
SORTIE = RACINE / "_output"
|
||||||
|
VENV = RACINE / ".venv"
|
||||||
|
EXIGENCES = RACINE / "schemas" / "requirements.txt"
|
||||||
|
PAGE_QUARTO = "https://quarto.org/docs/get-started/"
|
||||||
|
SYSTEME = platform.system() # "Windows", "Darwin" (macOS) ou "Linux"
|
||||||
|
|
||||||
|
FORMATS = [ # (libellé, option --to de Quarto, extension produite)
|
||||||
|
("PDF", "typst", ".pdf"),
|
||||||
|
("LibreOffice (.odt)", "odt", ".odt"),
|
||||||
|
("Site web (HTML)", "html", ".html"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# --- Dialogue ----------------------------------------------------------------
|
||||||
|
|
||||||
|
def titre(texte):
|
||||||
|
print("\n" + "=" * 64 + f"\n {texte}\n" + "=" * 64)
|
||||||
|
|
||||||
|
|
||||||
|
def oui_non(question, defaut=True):
|
||||||
|
suffixe = " [O/n] " if defaut else " [o/N] "
|
||||||
|
while True:
|
||||||
|
reponse = input(question + suffixe).strip().lower()
|
||||||
|
if not reponse:
|
||||||
|
return defaut
|
||||||
|
if reponse in ("o", "oui", "y", "yes"):
|
||||||
|
return True
|
||||||
|
if reponse in ("n", "non", "no"):
|
||||||
|
return False
|
||||||
|
print(" Répondez par o (oui) ou n (non).")
|
||||||
|
|
||||||
|
|
||||||
|
def choisir(question, options, defaut=1):
|
||||||
|
"""Affiche une liste numérotée et renvoie l'index (0-based) choisi."""
|
||||||
|
print(f"\n{question}")
|
||||||
|
for i, option in enumerate(options, 1):
|
||||||
|
print(f" {i:>2}. {option}" + (" (par défaut)" if i == defaut else ""))
|
||||||
|
while True:
|
||||||
|
reponse = input(f"Votre choix [{defaut}] : ").strip()
|
||||||
|
if not reponse:
|
||||||
|
return defaut - 1
|
||||||
|
if reponse.isdigit() and 1 <= int(reponse) <= len(options):
|
||||||
|
return int(reponse) - 1
|
||||||
|
print(f" Tapez un nombre entre 1 et {len(options)}.")
|
||||||
|
|
||||||
|
|
||||||
|
def attendre(message="Appuyez sur Entrée pour continuer..."):
|
||||||
|
try:
|
||||||
|
input(message)
|
||||||
|
except EOFError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# --- Quarto --------------------------------------------------------------------
|
||||||
|
|
||||||
|
def trouver_quarto():
|
||||||
|
trouve = shutil.which("quarto")
|
||||||
|
if trouve:
|
||||||
|
return trouve
|
||||||
|
candidats = {
|
||||||
|
"Windows": [Path(os.environ.get("ProgramFiles", r"C:\Program Files")) / "Quarto/bin/quarto.exe",
|
||||||
|
Path(os.environ.get("LOCALAPPDATA", "")) / "Programs/Quarto/bin/quarto.exe"],
|
||||||
|
"Darwin": [Path("/Applications/quarto/bin/quarto"), Path("/usr/local/bin/quarto"),
|
||||||
|
Path("/opt/homebrew/bin/quarto")],
|
||||||
|
"Linux": [Path("/opt/quarto/bin/quarto"), Path("/usr/local/bin/quarto"),
|
||||||
|
Path.home() / ".local/bin/quarto"],
|
||||||
|
}.get(SYSTEME, [])
|
||||||
|
for chemin in candidats:
|
||||||
|
if chemin.is_file():
|
||||||
|
return str(chemin)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def telecharger_quarto(motif):
|
||||||
|
"""Télécharge le fichier d'installation de la dernière version de Quarto dont le nom contient `motif`."""
|
||||||
|
with urllib.request.urlopen("https://api.github.com/repos/quarto-dev/quarto-cli/releases/latest",
|
||||||
|
timeout=30) as reponse:
|
||||||
|
version = json.load(reponse)
|
||||||
|
fichier = next(a for a in version["assets"] if a["name"].endswith(motif))
|
||||||
|
destination = Path(tempfile.gettempdir()) / fichier["name"]
|
||||||
|
print(f" Téléchargement de {fichier['name']} ({fichier['size'] // 1_000_000} Mo)...")
|
||||||
|
urllib.request.urlretrieve(fichier["browser_download_url"], destination)
|
||||||
|
return destination
|
||||||
|
|
||||||
|
|
||||||
|
def installer_quarto():
|
||||||
|
try:
|
||||||
|
if SYSTEME == "Windows":
|
||||||
|
if shutil.which("winget"):
|
||||||
|
subprocess.run(["winget", "install", "-e", "--id", "Posit.Quarto",
|
||||||
|
"--accept-source-agreements", "--accept-package-agreements"])
|
||||||
|
else:
|
||||||
|
subprocess.run(["msiexec", "/i", str(telecharger_quarto("win.msi"))])
|
||||||
|
elif SYSTEME == "Darwin":
|
||||||
|
if shutil.which("brew"):
|
||||||
|
subprocess.run(["brew", "install", "--cask", "quarto"])
|
||||||
|
else:
|
||||||
|
print(" L'installateur va s'ouvrir : suivez ses étapes puis revenez ici.")
|
||||||
|
subprocess.run(["open", "-W", str(telecharger_quarto("macos.pkg"))])
|
||||||
|
elif SYSTEME == "Linux" and shutil.which("apt-get"):
|
||||||
|
arch = "arm64" if platform.machine().lower() in ("aarch64", "arm64") else "amd64"
|
||||||
|
paquet = telecharger_quarto(f"linux-{arch}.deb")
|
||||||
|
print(" Votre mot de passe peut être demandé (installation système).")
|
||||||
|
subprocess.run(["sudo", "apt-get", "install", "-y", str(paquet)])
|
||||||
|
elif SYSTEME == "Linux" and shutil.which("pacman"):
|
||||||
|
return installer_quarto_arch()
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except Exception as erreur: # réseau indisponible, fichier introuvable…
|
||||||
|
print(f" L'installation automatique a échoué : {erreur}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def installer_quarto_arch():
|
||||||
|
"""Arch Linux et dérivées : dépôts officiels si le paquet y existe, sinon AUR via yay (ou paru)."""
|
||||||
|
for nom in ("quarto-cli", "quarto"):
|
||||||
|
if subprocess.run(["pacman", "-Si", nom], stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL).returncode == 0:
|
||||||
|
print(" Votre mot de passe peut être demandé (installation système).")
|
||||||
|
subprocess.run(["sudo", "pacman", "-S", "--needed", nom])
|
||||||
|
return True
|
||||||
|
paquet_aur = "quarto-cli-bin" # binaire officiel empaqueté, à jour (quarto-cli compile depuis les sources)
|
||||||
|
for assistant_aur in ("yay", "paru"):
|
||||||
|
if shutil.which(assistant_aur):
|
||||||
|
print(f" Quarto n'est disponible que dans l'AUR : installation de « {paquet_aur} » avec {assistant_aur}.")
|
||||||
|
print(" Validez les questions posées ; votre mot de passe sera demandé à la fin.")
|
||||||
|
# Jamais avec sudo : yay et paru s'exécutent en utilisateur normal et appellent sudo eux-mêmes.
|
||||||
|
subprocess.run([assistant_aur, "-S", "--needed", paquet_aur])
|
||||||
|
return True
|
||||||
|
print(" Sous Arch, Quarto n'est disponible que dans l'AUR (paquet « quarto-cli-bin »),")
|
||||||
|
print(" et aucun assistant AUR (yay ou paru) n'a été trouvé sur cet ordinateur.")
|
||||||
|
print(" Installez yay (https://github.com/Jguer/yay) puis relancez, ou installez le paquet à la main.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def verifier_quarto():
|
||||||
|
titre("1/4 Quarto (outil de mise en page)")
|
||||||
|
quarto = trouver_quarto()
|
||||||
|
if quarto:
|
||||||
|
print(f" Quarto est installé : {quarto}")
|
||||||
|
return quarto
|
||||||
|
print(" Quarto n'est pas installé. Il est indispensable pour produire les documents.")
|
||||||
|
if oui_non(" L'installer automatiquement maintenant ?"):
|
||||||
|
installer_quarto()
|
||||||
|
quarto = trouver_quarto()
|
||||||
|
while not quarto:
|
||||||
|
print(f"\n Installez Quarto depuis la page qui va s'ouvrir : {PAGE_QUARTO}")
|
||||||
|
webbrowser.open(PAGE_QUARTO)
|
||||||
|
attendre(" Une fois l'installation terminée, appuyez sur Entrée...")
|
||||||
|
quarto = trouver_quarto()
|
||||||
|
if not quarto and not oui_non(" Quarto reste introuvable. Réessayer ?"):
|
||||||
|
raise SystemExit(1)
|
||||||
|
print(f" Quarto est prêt : {quarto}")
|
||||||
|
return quarto
|
||||||
|
|
||||||
|
|
||||||
|
# --- Polices -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
DEPOT_NOTO = "https://github.com/notofonts/notofonts.github.io/raw/main/fonts"
|
||||||
|
POLICES = { # famille attendue par _quarto.yml -> fichiers à installer si elle manque
|
||||||
|
"Noto Sans": [f"NotoSans/hinted/ttf/NotoSans-{s}.ttf" for s in
|
||||||
|
("Regular", "Italic", "SemiBold", "SemiBoldItalic", "Bold", "BoldItalic")],
|
||||||
|
"Noto Sans Mono": [f"NotoSansMono/hinted/ttf/NotoSansMono-{s}.ttf" for s in ("Regular", "Bold")],
|
||||||
|
}
|
||||||
|
PAQUETS_POLICES = { # gestionnaire Linux -> commande d'installation des deux familles
|
||||||
|
"apt-get": ["sudo", "apt-get", "install", "-y", "fonts-noto-core", "fonts-noto-mono"],
|
||||||
|
"pacman": ["sudo", "pacman", "-S", "--needed", "noto-fonts"],
|
||||||
|
"dnf": ["sudo", "dnf", "install", "-y", "google-noto-sans-fonts", "google-noto-sans-mono-fonts"],
|
||||||
|
"zypper": ["sudo", "zypper", "install", "-y", "noto-sans-fonts", "noto-sans-mono-fonts"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def polices_manquantes(quarto):
|
||||||
|
"""Familles absentes, d'après la liste des polices réellement visibles par Typst."""
|
||||||
|
resultat = subprocess.run([quarto, "typst", "fonts"], capture_output=True, text=True,
|
||||||
|
encoding="utf-8", errors="replace")
|
||||||
|
sortie = resultat.stdout + resultat.stderr # Quarto écrit la liste sur la sortie d'erreur
|
||||||
|
familles = {ligne.strip() for ligne in sortie.splitlines()}
|
||||||
|
return [f for f in POLICES if f not in familles]
|
||||||
|
|
||||||
|
|
||||||
|
def dossier_polices_utilisateur():
|
||||||
|
if SYSTEME == "Windows":
|
||||||
|
return Path(os.environ["LOCALAPPDATA"]) / "Microsoft/Windows/Fonts"
|
||||||
|
if SYSTEME == "Darwin":
|
||||||
|
return Path.home() / "Library/Fonts"
|
||||||
|
return Path.home() / ".local/share/fonts"
|
||||||
|
|
||||||
|
|
||||||
|
def telecharger_polices(familles):
|
||||||
|
"""Installe les polices pour l'utilisateur courant (aucun droit administrateur nécessaire)."""
|
||||||
|
dossier = dossier_polices_utilisateur()
|
||||||
|
dossier.mkdir(parents=True, exist_ok=True)
|
||||||
|
for famille in familles:
|
||||||
|
for chemin in POLICES[famille]:
|
||||||
|
cible = dossier / Path(chemin).name
|
||||||
|
print(f" Téléchargement de {cible.name}...")
|
||||||
|
urllib.request.urlretrieve(f"{DEPOT_NOTO}/{chemin}", cible)
|
||||||
|
if SYSTEME == "Windows": # déclarer la police pour l'utilisateur (comme « Installer » dans l'Explorateur)
|
||||||
|
import winreg
|
||||||
|
with winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||||
|
r"Software\Microsoft\Windows NT\CurrentVersion\Fonts", 0,
|
||||||
|
winreg.KEY_SET_VALUE) as cle:
|
||||||
|
winreg.SetValueEx(cle, f"{cible.stem} (TrueType)", 0, winreg.REG_SZ, str(cible))
|
||||||
|
if SYSTEME == "Linux" and shutil.which("fc-cache"):
|
||||||
|
subprocess.run(["fc-cache", "-f", str(dossier)], stdout=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
|
def verifier_polices(quarto):
|
||||||
|
titre("2/4 Polices (Noto Sans et Noto Sans Mono)")
|
||||||
|
manquantes = polices_manquantes(quarto)
|
||||||
|
if not manquantes:
|
||||||
|
print(" Les polices sont installées.")
|
||||||
|
return
|
||||||
|
print(f" Polices manquantes : {', '.join(manquantes)}.")
|
||||||
|
print(" Sans elles, les documents seraient produits avec une police de remplacement.")
|
||||||
|
if not oui_non(" Les installer maintenant ?"):
|
||||||
|
return
|
||||||
|
gestionnaire = next((g for g in PAQUETS_POLICES if SYSTEME == "Linux" and shutil.which(g)), None)
|
||||||
|
try:
|
||||||
|
if gestionnaire:
|
||||||
|
print(" Votre mot de passe peut être demandé (installation système).")
|
||||||
|
subprocess.run(PAQUETS_POLICES[gestionnaire])
|
||||||
|
if not gestionnaire or polices_manquantes(quarto):
|
||||||
|
telecharger_polices(polices_manquantes(quarto))
|
||||||
|
except Exception as erreur:
|
||||||
|
print(f" L'installation des polices a échoué : {erreur}")
|
||||||
|
reste = polices_manquantes(quarto)
|
||||||
|
print(" Polices installées." if not reste else
|
||||||
|
f" Toujours introuvables : {', '.join(reste)} (les documents seront produits quand même).")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Environnement Python des schémas ----------------------------------------------
|
||||||
|
|
||||||
|
def python_du_venv():
|
||||||
|
return VENV / ("Scripts/python.exe" if SYSTEME == "Windows" else "bin/python")
|
||||||
|
|
||||||
|
|
||||||
|
def preparer_venv():
|
||||||
|
titre("3/4 Environnement Python (schémas)")
|
||||||
|
empreinte = hashlib.sha256(EXIGENCES.read_bytes()).hexdigest()
|
||||||
|
temoin = VENV / ".empreinte-requirements"
|
||||||
|
if python_du_venv().exists() and temoin.exists() and temoin.read_text().strip() == empreinte:
|
||||||
|
print(" Déjà prêt.")
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
if not python_du_venv().exists():
|
||||||
|
print(" Création de l'environnement virtuel .venv ...")
|
||||||
|
subprocess.run([sys.executable, "-m", "venv", str(VENV)], check=True)
|
||||||
|
print(" Installation des bibliothèques (la première fois, cela peut prendre quelques minutes)...")
|
||||||
|
subprocess.run([str(python_du_venv()), "-m", "pip", "install", "--disable-pip-version-check",
|
||||||
|
"-q", "-r", str(EXIGENCES)], check=True)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print(" Impossible de préparer l'environnement Python (connexion Internet ?).")
|
||||||
|
print(" Ce n'est pas bloquant : les documents peuvent être produits avec les schémas existants.")
|
||||||
|
return False
|
||||||
|
temoin.write_text(empreinte)
|
||||||
|
print(" Environnement prêt.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# --- Génération --------------------------------------------------------------------
|
||||||
|
|
||||||
|
def lister_supports():
|
||||||
|
supports = []
|
||||||
|
for qmd in sorted(MODULES.glob("*.qmd")):
|
||||||
|
m = re.search(r'^title:\s*"?(.*?)"?\s*$', qmd.read_text(encoding="utf-8"), re.M)
|
||||||
|
supports.append((qmd, m.group(1) if m else qmd.stem))
|
||||||
|
return supports
|
||||||
|
|
||||||
|
|
||||||
|
def ouvrir_dossier(dossier):
|
||||||
|
try:
|
||||||
|
if SYSTEME == "Windows":
|
||||||
|
os.startfile(dossier)
|
||||||
|
elif SYSTEME == "Darwin":
|
||||||
|
subprocess.run(["open", str(dossier)])
|
||||||
|
else:
|
||||||
|
subprocess.run(["xdg-open", str(dossier)])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def generer(quarto, venv_ok):
|
||||||
|
titre("4/4 Génération des documents")
|
||||||
|
i_format = choisir("Quel format ?", [f[0] for f in FORMATS])
|
||||||
|
libelle, cible, extension = FORMATS[i_format]
|
||||||
|
supports = lister_supports()
|
||||||
|
i_portee = choisir("Quels supports ?", ["Tous les supports"] + [t for _, t in supports])
|
||||||
|
if venv_ok and oui_non("\nRégénérer aussi les schémas (utile seulement si vous les avez modifiés) ?",
|
||||||
|
defaut=False):
|
||||||
|
subprocess.run([str(python_du_venv()), str(RACINE / "schemas" / "generer.py")], cwd=RACINE)
|
||||||
|
|
||||||
|
debut = time.time()
|
||||||
|
if i_portee == 0:
|
||||||
|
print(f"\nProduction de tous les supports en {libelle} : patientez...\n")
|
||||||
|
commandes = [[quarto, "render", "--to", cible]]
|
||||||
|
else:
|
||||||
|
qmd = supports[i_portee - 1][0]
|
||||||
|
print(f"\nProduction de « {supports[i_portee - 1][1]} » en {libelle}...\n")
|
||||||
|
commandes = [[quarto, "render", str(qmd.relative_to(RACINE)), "--to", cible]]
|
||||||
|
for commande in commandes:
|
||||||
|
if subprocess.run(commande, cwd=RACINE).returncode != 0:
|
||||||
|
print("\n*** La génération a échoué. Le message d'erreur se trouve juste au-dessus. ***")
|
||||||
|
return False
|
||||||
|
|
||||||
|
produits = sorted(p for p in SORTIE.rglob(f"*{extension}") if p.stat().st_mtime >= debut - 1)
|
||||||
|
titre("Terminé")
|
||||||
|
for p in produits:
|
||||||
|
print(f" {p.relative_to(RACINE)}")
|
||||||
|
print(f"\n {len(produits)} document(s) dans le dossier : {SORTIE}")
|
||||||
|
ouvrir_dossier(SORTIE / "modules" if (SORTIE / "modules").is_dir() else SORTIE)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Assistant de génération — Formation ESP32-S3 avec Arduino Framework")
|
||||||
|
if sys.version_info < (3, 9):
|
||||||
|
print("Python 3.9 ou plus récent est nécessaire.")
|
||||||
|
raise SystemExit(1)
|
||||||
|
quarto = verifier_quarto()
|
||||||
|
verifier_polices(quarto)
|
||||||
|
venv_ok = preparer_venv()
|
||||||
|
while True:
|
||||||
|
generer(quarto, venv_ok)
|
||||||
|
if not oui_non("\nGénérer d'autres documents ?", defaut=False):
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nInterrompu.")
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except Exception as erreur:
|
||||||
|
print(f"\nErreur inattendue : {erreur}")
|
||||||
|
raise SystemExit(1)
|
||||||
Reference in New Issue
Block a user