Compare commits
3
Commits
170588dc2c
...
47528bae8d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47528bae8d | ||
|
|
d4faa4872c | ||
|
|
90982e71c9 |
@@ -1,2 +1,6 @@
|
||||
*.odt binary
|
||||
* 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,32 @@ Les sources sont en Markdown [Quarto](https://quarto.org) (`modules/*.qmd`) et p
|
||||
| `12-projetfinal` | Projet final |
|
||||
| `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** et **Quarto** sont installés, et propose de les installer automatiquement sinon ;
|
||||
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
|
||||
|
||||
```
|
||||
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
|
||||
schemas/ schémas décrits en Python (schemdraw + matplotlib), rendus en svg/ et png/
|
||||
_quarto.yml configuration du projet
|
||||
|
||||
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,263 @@
|
||||
"""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)])
|
||||
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 verifier_quarto():
|
||||
titre("1/3 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
|
||||
|
||||
|
||||
# --- Environnement Python des schémas ----------------------------------------------
|
||||
|
||||
def python_du_venv():
|
||||
return VENV / ("Scripts/python.exe" if SYSTEME == "Windows" else "bin/python")
|
||||
|
||||
|
||||
def preparer_venv():
|
||||
titre("2/3 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("3/3 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()
|
||||
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