Files
RoadTripsGenerator/rtg/accueil.py
T
FabriceandClaude Opus 5.5 06153a227e Interface locale : rtg interface (voyages, sources, journées, récit, génération)
Serveur web local sans dépendance nouvelle (http.server, Jinja2, htmx et
Leaflet fournis). Écrans : Mes voyages, nouveau voyage, un voyage (journées
avec carte, rattachement des traces sans heures à un jour avec suggestion
d'après les photos, sources avec choix de dossier, infos et récit,
génération avec avancement et arrêt), réglages du site et vérification
des outils.

Chaîne : jours imposés ([dates_traces] dans voyage.toml), arrêt propre de
la compression, suivi des étapes, état structuré (site.tableau_etat),
titre du voyage repris de voyage.toml sur l'accueil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
2026-10-09 14:07:47 +00:00

165 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Page d'accueil du site et listes complètes par rubrique.
index.html une rubrique par section, avec les ACCUEIL_NB voyages les
plus récents (2 par défaut) et un lien « Tout voir »
voyages.html tous les voyages rangés à la racine du site
balades.html tous ceux du dossier Balades/ (une page par regroupement)
carte_globale.html les voyages (pas les balades) et les cartes My Maps
sur une même carte, affichée en grand sous les rubriques
de l'accueil (voir genere_carte_globale.py et mymaps.txt)
Appelé par `rtg generer` / `rtg accueil`. Réglages (site.toml) : titre,
accueil_nb_recents, carte_globale.mymaps.
Un voyage est un dossier qui contient mini.html (produit par genere_carte.py).
Les voyages rangés à la racine forment la rubrique « Voyages » ; ceux rangés
un niveau plus bas, dans un dossier de regroupement (Balades/Aisne-06-2026),
forment une rubrique du nom de ce dossier.
La moto d'un voyage (voyage.toml, ou ancien moto.txt) est affichée dans la
légende de la carte globale.
Les voyages sont triés du plus récent au plus ancien d'après les dates des
traces GPX (routes/AAAA-MM-JJ.gpx), sinon d'après l'année trouvée dans le nom.
"""
import os
import unicodedata
import re
from datetime import date
from urllib.parse import quote
from jinja2 import Environment, FileSystemLoader
from rtg.carte_globale import SORTIE as CARTE_GLOBALE, generer_carte_globale
from rtg.config import reglages_site, reglages_voyage
DOSSIER_TEMPLATES = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
EXCLURE = {'html', 'Scripts', 'RoadTripsGenerator', '__pycache__', 'node_modules',
'assets', 'videos', 'photos', 'photos_web', 'apercus', 'rushs', 'routes'}
TITRE_SECTION_RACINE = "Voyages"
MOIS = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet",
"août", "septembre", "octobre", "novembre", "décembre"]
def sous_dossiers(dossier):
for d in sorted(os.listdir(dossier)):
chemin = os.path.join(dossier, d)
if d in EXCLURE or d.startswith(('.', '_')) or not os.path.isdir(chemin):
continue
yield d, chemin
def est_voyage(chemin):
return os.path.exists(os.path.join(chemin, 'mini.html'))
def dates_du_voyage(chemin):
routes = os.path.join(chemin, 'routes')
if not os.path.isdir(routes):
return []
dates = set()
for f in os.listdir(routes):
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', f)
if m and not f.startswith('.'):
try:
dates.add(date(*map(int, m.groups())))
except ValueError:
pass
return sorted(dates)
def periode(dates):
"""« juin 2026 · 1 jour », « juillet – août 2025 · 15 jours »…"""
if not dates:
return ""
d1, d2 = dates[0], dates[-1]
if (d1.year, d1.month) == (d2.year, d2.month):
texte = f"{MOIS[d1.month - 1]} {d1.year}"
elif d1.year == d2.year:
texte = f"{MOIS[d1.month - 1]} – {MOIS[d2.month - 1]} {d2.year}"
else:
texte = f"{MOIS[d1.month - 1]} {d1.year} – {MOIS[d2.month - 1]} {d2.year}"
n = len(dates)
return f"{texte} · {n} jour{'s' if n > 1 else ''}"
def fiche(racine, chemin):
nom = os.path.basename(chemin)
dates = dates_du_voyage(chemin)
if dates:
quand = dates[0]
else:
m = re.search(r'(19|20)\d{2}', nom)
quand = date(int(m.group(0)), 1, 1) if m else date.min
return {
'dossier': chemin,
'titre': reglages_voyage(chemin).get('titre') or nom.replace('-', ' ').replace('_', ' '),
'chemin': quote(os.path.relpath(chemin, racine).replace(os.sep, '/')),
'periode': periode(dates),
'moto': reglages_voyage(chemin).get('moto', ''),
'quand': quand,
}
def trier(voyages):
# Plus récent d'abord ; à date égale, ordre alphabétique.
voyages.sort(key=lambda v: v['titre'].lower())
voyages.sort(key=lambda v: v['quand'], reverse=True)
return voyages
def nom_de_page(titre):
"""« Balades » → « balades.html » (minuscules, sans accents ni espaces)."""
t = unicodedata.normalize('NFKD', titre).encode('ascii', 'ignore').decode().lower()
return re.sub(r'[^a-z0-9]+', '-', t).strip('-') + '.html'
def rubrique(titre, voyages):
n = len(voyages)
unite = titre.lower() if n > 1 else titre.lower().rstrip('s')
return {'titre': titre, 'voyages': trier(voyages), 'page': nom_de_page(titre), 'unite': unite}
def collecter_rubriques(racine):
principaux, rubriques = [], []
for nom, chemin in sous_dossiers(racine):
if est_voyage(chemin):
principaux.append(fiche(racine, chemin))
else:
voyages = [fiche(racine, c) for _, c in sous_dossiers(chemin) if est_voyage(c)]
if voyages:
rubriques.append(rubrique(nom.replace('-', ' ').replace('_', ' '), voyages))
if principaux:
rubriques.insert(0, rubrique(TITRE_SECTION_RACINE, principaux))
return rubriques
def generer(racine=".", cfg=None, mettre_a_jour_mymaps=False):
racine = os.path.abspath(racine)
cfg = cfg or reglages_site(racine)
TITRE_SITE, NB_RECENTS = cfg["titre"], int(cfg["accueil_nb_recents"])
rubriques = collecter_rubriques(racine)
total = sum(len(r['voyages']) for r in rubriques)
env = Environment(loader=FileSystemLoader(DOSSIER_TEMPLATES), autoescape=True,
trim_blocks=True, lstrip_blocks=True)
def ecrire(nom, contenu):
with open(os.path.join(racine, nom), "w", encoding="utf-8") as f:
f.write(contenu)
ecrire("index.html", env.get_template("accueil.html").render(
titre_site=TITRE_SITE, rubriques=rubriques, nb_recents=NB_RECENTS,
carte_globale=CARTE_GLOBALE if any(r['titre'] == TITRE_SECTION_RACINE for r in rubriques) else None,
sous_titre=f"{total} voyage{'s' if total > 1 else ''}" if total else ""))
for r in rubriques:
ecrire(r['page'], env.get_template("liste.html").render(titre_site=TITRE_SITE, r=r))
# Carte globale : la rubrique Voyages seulement, sans les balades.
voyages = [r for r in rubriques if r['titre'] == TITRE_SECTION_RACINE]
if voyages:
generer_carte_globale(racine, voyages, cfg["carte_globale"]["mymaps"], mettre_a_jour_mymaps)
detail = ", ".join(f"{r['titre']} : {len(r['voyages'])} → {r['page']}" for r in rubriques)
print(f"🌍 Accueil : {total} voyage(s) ({detail})")