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
165 lines
6.4 KiB
Python
165 lines
6.4 KiB
Python
"""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})")
|