Ajout d'un script d'horodatage des fiches, affichage des fiches récement modifiées sur la page d'accueil
This commit is contained in:
+30
-10
@@ -82,7 +82,7 @@ Le générateur place les éléments communs suivants en tête du front matter :
|
|||||||
```toml
|
```toml
|
||||||
title = "Titre"
|
title = "Titre"
|
||||||
date = 2026-08-05T12:30:00+02:00
|
date = 2026-08-05T12:30:00+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
```
|
```
|
||||||
@@ -94,12 +94,16 @@ Les tables propres à chaque type de contenu viennent ensuite dans le même fron
|
|||||||
- `description` est un résumé court, rédigé manuellement lorsqu’il est utile ;
|
- `description` est un résumé court, rédigé manuellement lorsqu’il est utile ;
|
||||||
- `draft` détermine si la page est publiée.
|
- `draft` détermine si la page est publiée.
|
||||||
|
|
||||||
`date` est une valeur temporelle TOML écrite sans guillemets. La ligne commentée `# updated = YYYY-MM-DD` est un repère inactif. Après une modification substantielle, elle peut être décommentée et remplacée par la date correspondante :
|
`date` est un horodatage TOML écrit sans guillemets dans le fuseau `Europe/Paris`. La ligne commentée `# updated = YYYY-MM-DDTHH:MM:SS+HH:MM` est un repère inactif. Après une modification substantielle, `update-content.py` l’active, remplace sa valeur ou insère `updated` après `date` lorsqu’il est absent.
|
||||||
|
|
||||||
|
Le résultat utilise le même format que `date` :
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T16:47:00+02:00
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Lorsqu’il est actif, `updated` détermine la position de la fiche dans le bloc « Fiches récentes » de la page d’accueil. En son absence, la date de publication est utilisée.
|
||||||
|
|
||||||
Lorsque `description` reste vide :
|
Lorsque `description` reste vide :
|
||||||
|
|
||||||
- une fiche de vocabulaire affiche une phrase construite à partir de sa nature, de son genre et de son sens ;
|
- une fiche de vocabulaire affiche une phrase construite à partir de sa nature, de son genre et de son sens ;
|
||||||
@@ -162,6 +166,22 @@ Le générateur normalise l’identifiant et le titre en NFC, prépare tous les
|
|||||||
|
|
||||||
Pour un article, un texte ou une note de grammaire, le titre est déduit de l’identifiant en remplaçant les tirets par des espaces et en mettant l’initiale en majuscule. Pour le vocabulaire, il reprend l’identifiant. Pour un signe, il reste vide sauf si `--title` est fourni.
|
Pour un article, un texte ou une note de grammaire, le titre est déduit de l’identifiant en remplaçant les tirets par des espaces et en mettant l’initiale en majuscule. Pour le vocabulaire, il reprend l’identifiant. Pour un signe, il reste vide sauf si `--title` est fourni.
|
||||||
|
|
||||||
|
## Horodatage des mises à jour
|
||||||
|
|
||||||
|
Après une modification substantielle, lancer :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/update-content.py src/content/vocabulaire/bītum.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Le chemin complet se sélectionne rapidement avec la touche Tab du shell. Une forme abrégée relative à `src/content/` est aussi acceptée :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/update-content.py vocabulaire/bītum.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Le script refuse les index, les fichiers extérieurs à `src/content/` et les sections inconnues. Il active le repère commenté, remplace l’horodatage déjà présent ou insère `updated` après `date` lorsqu’il est absent, sans modifier le reste du fichier.
|
||||||
|
|
||||||
## Taxonomies
|
## Taxonomies
|
||||||
|
|
||||||
Les taxonomies publiques sont :
|
Les taxonomies publiques sont :
|
||||||
@@ -192,7 +212,7 @@ Toutes les fiches lexicales utilisent la même structure générale :
|
|||||||
+++
|
+++
|
||||||
title = "lemme"
|
title = "lemme"
|
||||||
date = 2026-08-05T12:30:00+02:00
|
date = 2026-08-05T12:30:00+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
@@ -275,7 +295,7 @@ produit :
|
|||||||
+++
|
+++
|
||||||
title = "BE"
|
title = "BE"
|
||||||
date = 2026-08-05T12:30:00+02:00
|
date = 2026-08-05T12:30:00+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
@@ -314,7 +334,7 @@ Une fiche de signe peut rester sans corps si son front matter suffit.
|
|||||||
+++
|
+++
|
||||||
title = "Titre de la fiche"
|
title = "Titre de la fiche"
|
||||||
date = 2026-08-05T12:30:00+02:00
|
date = 2026-08-05T12:30:00+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
@@ -331,7 +351,7 @@ themes = []
|
|||||||
+++
|
+++
|
||||||
title = "Titre du texte"
|
title = "Titre du texte"
|
||||||
date = 2026-08-05T12:30:00+02:00
|
date = 2026-08-05T12:30:00+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
@@ -361,7 +381,7 @@ Elle peut être adaptée à chaque texte.
|
|||||||
+++
|
+++
|
||||||
title = "Titre de l’article"
|
title = "Titre de l’article"
|
||||||
date = 2026-08-05T12:30:00+02:00
|
date = 2026-08-05T12:30:00+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
@@ -403,7 +423,7 @@ Pour les fichiers `_index.md`, il exige un titre non vide.
|
|||||||
|
|
||||||
Pour les autres pages, `draft` doit être un booléen. Les brouillons ne sont pas soumis aux contrôles de complétude, à l’exception du nom des fiches de signes.
|
Pour les autres pages, `draft` doit être un booléen. Les brouillons ne sont pas soumis aux contrôles de complétude, à l’exception du nom des fiches de signes.
|
||||||
|
|
||||||
Pour toute page publiée, il exige un titre non vide. Une date TOML valide est également obligatoire lorsque la page appartient à une section. Les pages permanentes placées directement dans `src/content/`, comme la bibliographie et les mentions légales, n’ont pas besoin de date.
|
Pour toute page publiée, il exige un titre non vide. Lorsque la page appartient à une section, `date` doit être un horodatage TOML avec fuseau horaire. Si `updated` est renseigné, il doit respecter le même format. Les pages permanentes placées directement dans `src/content/`, comme la bibliographie et les mentions légales, n’ont pas besoin de date.
|
||||||
|
|
||||||
Pour une fiche de vocabulaire publiée, il contrôle en plus :
|
Pour une fiche de vocabulaire publiée, il contrôle en plus :
|
||||||
|
|
||||||
@@ -424,7 +444,7 @@ Zola reste responsable de la construction, des liens internes, des taxonomies et
|
|||||||
- ne renseigner que des informations établies ou utiles ;
|
- ne renseigner que des informations établies ou utiles ;
|
||||||
- garder une graphie stable pour chaque lemme, lecture et terme de taxonomie ;
|
- garder une graphie stable pour chaque lemme, lecture et terme de taxonomie ;
|
||||||
- employer `draft = true` pour un contenu incomplet ou temporairement hors ligne ;
|
- employer `draft = true` pour un contenu incomplet ou temporairement hors ligne ;
|
||||||
- n’activer `updated` qu’après une modification substantielle ;
|
- exécuter `update-content.py` uniquement après une modification substantielle ;
|
||||||
- éviter de répéter dans le corps une donnée déjà claire dans le front matter, sauf lorsqu’une explication apporte quelque chose ;
|
- éviter de répéter dans le corps une donnée déjà claire dans le front matter, sauf lorsqu’une explication apporte quelque chose ;
|
||||||
- rédiger des descriptions courtes et informatives lorsqu’un résumé spécifique est utile ;
|
- rédiger des descriptions courtes et informatives lorsqu’un résumé spécifique est utile ;
|
||||||
- ne pas ajouter de rubriques vides ni de textes d’attente.
|
- ne pas ajouter de rubriques vides ni de textes d’attente.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Le site est construit avec [Zola](https://www.getzola.org/) à partir de fichier
|
|||||||
| `textes` | translittérations, traductions et commentaires | genres |
|
| `textes` | translittérations, traductions et commentaires | genres |
|
||||||
| `vocabulaire` | fiches lexicales adaptées à leur nature grammaticale | natures |
|
| `vocabulaire` | fiches lexicales adaptées à leur nature grammaticale | natures |
|
||||||
|
|
||||||
La page d’accueil présente le dernier article et les trois fiches datées les plus récentes parmi les signes, le vocabulaire, la grammaire et les textes. Des flux Atom et RSS sont également générés.
|
La page d’accueil présente le dernier article et les trois fiches les plus récemment publiées ou mises à jour parmi les signes, le vocabulaire, la grammaire et les textes. Des flux Atom et RSS sont également générés.
|
||||||
|
|
||||||
Les conventions de rédaction et les champs disponibles sont décrits dans le [guide de rédaction](GUIDE_REDACTION.md).
|
Les conventions de rédaction et les champs disponibles sont décrits dans le [guide de rédaction](GUIDE_REDACTION.md).
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ Exemples :
|
|||||||
Chaque fichier est créé avec :
|
Chaque fichier est créé avec :
|
||||||
|
|
||||||
- une date au format TOML dans le fuseau `Europe/Paris` ;
|
- une date au format TOML dans le fuseau `Europe/Paris` ;
|
||||||
- un repère commenté `# updated = YYYY-MM-DD` pour les mises à jour substantielles ;
|
- un repère commenté `# updated = YYYY-MM-DDTHH:MM:SS+HH:MM` pour les mises à jour substantielles ;
|
||||||
- `draft = true` ;
|
- `draft = true` ;
|
||||||
- tous les champs prévus par son modèle ;
|
- tous les champs prévus par son modèle ;
|
||||||
- des chaînes vides ou des listes vides pour les données à compléter.
|
- des chaînes vides ou des listes vides pour les données à compléter.
|
||||||
@@ -103,6 +103,22 @@ Le générateur refuse d’écraser un fichier existant. Son aide complète est
|
|||||||
./scripts/new-content.py --help
|
./scripts/new-content.py --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Horodater une mise à jour
|
||||||
|
|
||||||
|
Après une modification substantielle, renseigner automatiquement `updated` avec l’heure courante de `Europe/Paris` :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/update-content.py src/content/vocabulaire/bītum.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce chemin se complète directement avec la touche Tab du shell. La forme abrégée, relative à `src/content/`, est également acceptée :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/update-content.py vocabulaire/bītum.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Le script active le repère commenté, remplace l’horodatage existant ou insère `updated` après `date` lorsqu’il est absent.
|
||||||
|
|
||||||
## Publier un contenu
|
## Publier un contenu
|
||||||
|
|
||||||
Le cycle de rédaction recommandé est simple :
|
Le cycle de rédaction recommandé est simple :
|
||||||
@@ -124,7 +140,8 @@ Le validateur contrôle les invariants qui doivent rester vrais indépendamment
|
|||||||
- front matter TOML valide ;
|
- front matter TOML valide ;
|
||||||
- section connue et statut de brouillon valide ;
|
- section connue et statut de brouillon valide ;
|
||||||
- titre des index et des pages publiées ;
|
- titre des index et des pages publiées ;
|
||||||
- date des pages publiées dans une section ;
|
- horodatage de publication des pages publiées dans une section ;
|
||||||
|
- horodatage de `updated` lorsqu’il est renseigné ;
|
||||||
- nom et numéro MZL des fiches de signes ;
|
- nom et numéro MZL des fiches de signes ;
|
||||||
- nature, champs et types des fiches de vocabulaire publiées.
|
- nature, champs et types des fiches de vocabulaire publiées.
|
||||||
|
|
||||||
@@ -145,7 +162,8 @@ Une erreur renvoie un code de sortie non nul et bloque également la constructio
|
|||||||
├── scripts
|
├── scripts
|
||||||
│ ├── _schema.py
|
│ ├── _schema.py
|
||||||
│ ├── check-content.py
|
│ ├── check-content.py
|
||||||
│ └── new-content.py
|
│ ├── new-content.py
|
||||||
|
│ └── update-content.py
|
||||||
└── src
|
└── src
|
||||||
├── content
|
├── content
|
||||||
├── sass
|
├── sass
|
||||||
@@ -160,8 +178,9 @@ Une erreur renvoie un code de sortie non nul et bloque également la constructio
|
|||||||
- `src/static/` contient les ressources servies telles quelles ;
|
- `src/static/` contient les ressources servies telles quelles ;
|
||||||
- `src/zola.toml` contient la configuration de Zola ;
|
- `src/zola.toml` contient la configuration de Zola ;
|
||||||
- `scripts/new-content.py` crée les brouillons ;
|
- `scripts/new-content.py` crée les brouillons ;
|
||||||
|
- `scripts/update-content.py` horodate les modifications substantielles ;
|
||||||
- `scripts/check-content.py` valide les contenus ;
|
- `scripts/check-content.py` valide les contenus ;
|
||||||
- `scripts/_schema.py` contient les définitions communes aux deux scripts.
|
- `scripts/_schema.py` contient les définitions communes aux outils de gestion des contenus.
|
||||||
|
|
||||||
Les chemins et les taxonomies utilisent la stratégie de slugification `safe` de Zola. Les diacritiques sont donc conservés : des lectures comme `tim` et `ṭim` restent distinctes.
|
Les chemins et les taxonomies utilisent la stratégie de slugification `safe` de Zola. Les diacritiques sont donc conservés : des lectures comme `tim` et `ṭim` restent distinctes.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
"""Définitions communes au générateur et au validateur de contenus."""
|
"""Définitions communes aux outils de gestion des contenus."""
|
||||||
|
|
||||||
SECTION_ALIASES = {
|
SECTION_ALIASES = {
|
||||||
"article": "articles",
|
"article": "articles",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import tomllib
|
import tomllib
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from datetime import date
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -93,13 +93,22 @@ def require_string(
|
|||||||
validator.error(path, f"{key} doit être une chaîne non vide")
|
validator.error(path, f"{key} doit être une chaîne non vide")
|
||||||
|
|
||||||
|
|
||||||
def require_date(
|
def require_timestamp(
|
||||||
metadata: dict[str, Any],
|
metadata: dict[str, Any],
|
||||||
|
key: str,
|
||||||
path: Path,
|
path: Path,
|
||||||
validator: Validator,
|
validator: Validator,
|
||||||
|
*,
|
||||||
|
optional: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not isinstance(metadata.get("date"), date):
|
value = metadata.get(key)
|
||||||
validator.error(path, "date doit être une date TOML valide")
|
if optional and value is None:
|
||||||
|
return
|
||||||
|
if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None:
|
||||||
|
validator.error(
|
||||||
|
path,
|
||||||
|
f"{key} doit être un horodatage TOML avec fuseau horaire",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_structure(
|
def check_structure(
|
||||||
@@ -130,7 +139,8 @@ def check_structure(
|
|||||||
|
|
||||||
require_string(metadata, "title", path, validator)
|
require_string(metadata, "title", path, validator)
|
||||||
if section:
|
if section:
|
||||||
require_date(metadata, path, validator)
|
require_timestamp(metadata, "date", path, validator)
|
||||||
|
require_timestamp(metadata, "updated", path, validator, optional=True)
|
||||||
return section, False
|
return section, False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ def build_vocabulary(title: str, timestamp: str, nature: str) -> str:
|
|||||||
return f"""+++
|
return f"""+++
|
||||||
title = {toml_string(title)}
|
title = {toml_string(title)}
|
||||||
date = {timestamp}
|
date = {timestamp}
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ def build_sign(title: str, timestamp: str, mzl: str) -> str:
|
|||||||
return f"""+++
|
return f"""+++
|
||||||
title = {toml_string(title)}
|
title = {toml_string(title)}
|
||||||
date = {timestamp}
|
date = {timestamp}
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ def build_page(section: str, title: str, timestamp: str) -> str:
|
|||||||
return f"""+++
|
return f"""+++
|
||||||
title = {toml_string(title)}
|
title = {toml_string(title)}
|
||||||
date = {timestamp}
|
date = {timestamp}
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ banner_credit = ""
|
|||||||
return f"""+++
|
return f"""+++
|
||||||
title = {toml_string(title)}
|
title = {toml_string(title)}
|
||||||
date = {timestamp}
|
date = {timestamp}
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ themes = []
|
|||||||
return f"""+++
|
return f"""+++
|
||||||
title = {toml_string(title)}
|
title = {toml_string(title)}
|
||||||
date = {timestamp}
|
date = {timestamp}
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
|
|||||||
Executable
+140
@@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Horodate la dernière modification substantielle d’une fiche."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import tomllib
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
|
|
||||||
|
from _schema import KNOWN_SECTIONS
|
||||||
|
|
||||||
|
ROOT_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
CONTENT_DIR = ROOT_DIR / "src" / "content"
|
||||||
|
TIMEZONE_NAME = "Europe/Paris"
|
||||||
|
|
||||||
|
UPDATED_RE = re.compile(r"\s*(?:#\s*)?updated\s*=.*")
|
||||||
|
DATE_RE = re.compile(r"\s*date\s*=.*")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Horodater la dernière modification substantielle d’une fiche."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"fiche",
|
||||||
|
help=(
|
||||||
|
"chemin depuis la racine du dépôt ou depuis src/content/ "
|
||||||
|
"(par exemple vocabulaire/bītum.md)"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def current_timestamp() -> str:
|
||||||
|
try:
|
||||||
|
timezone = ZoneInfo(TIMEZONE_NAME)
|
||||||
|
except ZoneInfoNotFoundError as exc:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"fuseau {TIMEZONE_NAME} indisponible ; installer les données de fuseaux horaires"
|
||||||
|
) from exc
|
||||||
|
return datetime.now(timezone).isoformat(timespec="seconds")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_content_path(value: str) -> Path:
|
||||||
|
supplied = Path(value).expanduser()
|
||||||
|
if supplied.is_absolute():
|
||||||
|
path = supplied
|
||||||
|
elif supplied.parts[:2] == ("src", "content"):
|
||||||
|
path = ROOT_DIR / supplied
|
||||||
|
else:
|
||||||
|
path = CONTENT_DIR / supplied
|
||||||
|
|
||||||
|
path = path.resolve()
|
||||||
|
try:
|
||||||
|
relative = path.relative_to(CONTENT_DIR.resolve())
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError("la fiche doit se trouver dans src/content/") from exc
|
||||||
|
|
||||||
|
if not path.is_file():
|
||||||
|
raise ValueError(f"fichier introuvable : {path}")
|
||||||
|
if path.suffix != ".md" or path.name == "_index.md":
|
||||||
|
raise ValueError("le chemin doit désigner une fiche Markdown, pas un index")
|
||||||
|
if len(relative.parts) < 2 or relative.parts[0] not in KNOWN_SECTIONS:
|
||||||
|
raise ValueError("la fiche doit appartenir à une section éditoriale connue")
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def update_front_matter(path: Path, timestamp: str) -> None:
|
||||||
|
try:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
except (OSError, UnicodeDecodeError) as exc:
|
||||||
|
raise ValueError(f"lecture impossible ({exc})") from exc
|
||||||
|
|
||||||
|
lines = text.splitlines(keepends=True)
|
||||||
|
if not lines or lines[0].rstrip("\r\n") != "+++":
|
||||||
|
raise ValueError("front matter TOML initial absent")
|
||||||
|
|
||||||
|
try:
|
||||||
|
closing_index = next(
|
||||||
|
index
|
||||||
|
for index, line in enumerate(lines[1:], start=1)
|
||||||
|
if line.rstrip("\r\n") == "+++"
|
||||||
|
)
|
||||||
|
except StopIteration as exc:
|
||||||
|
raise ValueError("délimiteur final +++ absent") from exc
|
||||||
|
|
||||||
|
try:
|
||||||
|
tomllib.loads("".join(lines[1:closing_index]))
|
||||||
|
except tomllib.TOMLDecodeError as exc:
|
||||||
|
raise ValueError(f"front matter TOML invalide ({exc})") from exc
|
||||||
|
|
||||||
|
updated_indexes = [
|
||||||
|
index
|
||||||
|
for index in range(1, closing_index)
|
||||||
|
if UPDATED_RE.fullmatch(lines[index].rstrip("\r\n")) is not None
|
||||||
|
]
|
||||||
|
if len(updated_indexes) > 1:
|
||||||
|
raise ValueError("plusieurs champs updated sont présents dans le front matter")
|
||||||
|
|
||||||
|
newline = "\r\n" if lines[0].endswith("\r\n") else "\n"
|
||||||
|
updated_line = f"updated = {timestamp}{newline}"
|
||||||
|
|
||||||
|
if updated_indexes:
|
||||||
|
lines[updated_indexes[0]] = updated_line
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
date_index = next(
|
||||||
|
index
|
||||||
|
for index in range(1, closing_index)
|
||||||
|
if DATE_RE.fullmatch(lines[index].rstrip("\r\n")) is not None
|
||||||
|
)
|
||||||
|
except StopIteration as exc:
|
||||||
|
raise ValueError("champ date absent du front matter") from exc
|
||||||
|
lines.insert(date_index + 1, updated_line)
|
||||||
|
|
||||||
|
try:
|
||||||
|
path.write_text("".join(lines), encoding="utf-8")
|
||||||
|
except OSError as exc:
|
||||||
|
raise ValueError(f"écriture impossible ({exc})") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
try:
|
||||||
|
path = resolve_content_path(args.fiche)
|
||||||
|
timestamp = current_timestamp()
|
||||||
|
update_front_matter(path, timestamp)
|
||||||
|
except (ValueError, RuntimeError) as exc:
|
||||||
|
print(f"Erreur : {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print(f"Fiche mise à jour : {path.relative_to(ROOT_DIR)}")
|
||||||
|
print(f"updated = {timestamp}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "Les Carnets sont en ligne !"
|
title = "Les Carnets sont en ligne !"
|
||||||
date = 2026-07-29T10:09:28+02:00
|
date = 2026-07-29T10:09:28+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = "Ou comment le bazar de mes notes a donné naissance à ce site."
|
description = "Ou comment le bazar de mes notes a donné naissance à ce site."
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "La forme liée"
|
title = "La forme liée"
|
||||||
date = 2026-07-28T11:35:06+02:00
|
date = 2026-07-28T11:35:06+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = "Fiche d’exemple consacrée à la forme liée du nom akkadien."
|
description = "Fiche d’exemple consacrée à la forme liée du nom akkadien."
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "AŠ"
|
title = "AŠ"
|
||||||
date = 2026-07-28T13:23:24+02:00
|
date = 2026-07-28T13:23:24+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "ḪAL"
|
title = "ḪAL"
|
||||||
date = 2026-07-30T14:29:32+02:00
|
date = 2026-07-30T14:29:32+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "AN"
|
title = "AN"
|
||||||
date = 2026-07-31T14:04:03+02:00
|
date = 2026-07-31T14:04:03+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "BA"
|
title = "BA"
|
||||||
date = 2026-07-30T15:48:16+02:00
|
date = 2026-07-30T15:48:16+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "LA"
|
title = "LA"
|
||||||
date = 2026-07-31T12:20:49+02:00
|
date = 2026-07-31T12:20:49+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "MAḪ"
|
title = "MAḪ"
|
||||||
date = 2026-07-30T15:00:43+02:00
|
date = 2026-07-30T15:00:43+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "MU"
|
title = "MU"
|
||||||
date = 2026-07-31T10:17:36+02:00
|
date = 2026-07-31T10:17:36+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "NA"
|
title = "NA"
|
||||||
date = 2026-07-31T11:36:48+02:00
|
date = 2026-07-31T11:36:48+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "NU"
|
title = "NU"
|
||||||
date = 2026-07-31T12:41:08+02:00
|
date = 2026-07-31T12:41:08+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "BE"
|
title = "BE"
|
||||||
date = 2026-07-31T19:43:12+02:00
|
date = 2026-07-31T19:43:12+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "TIM"
|
title = "TIM"
|
||||||
date = 2026-07-31T19:25:30+02:00
|
date = 2026-07-31T19:25:30+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "Lettre d’exemple"
|
title = "Lettre d’exemple"
|
||||||
date = 2026-07-28T18:40:02+02:00
|
date = 2026-07-28T18:40:02+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = "Court texte fictif destiné à tester la présentation des translittérations, traductions et commentaires."
|
description = "Court texte fictif destiné à tester la présentation des translittérations, traductions et commentaires."
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "-ma"
|
title = "-ma"
|
||||||
date = 2026-08-03T19:37:21+02:00
|
date = 2026-08-03T19:37:21+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T10:30:57+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "anāku"
|
title = "anāku"
|
||||||
date = 2026-08-03T09:19:49+02:00
|
date = 2026-08-03T09:19:49+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T16:26:50+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "bītum"
|
title = "bītum"
|
||||||
date = 2026-07-28T15:32:28+02:00
|
date = 2026-07-28T15:32:28+02:00
|
||||||
# updated = YYYY-MM-DD
|
updated = 2026-08-05T17:29:50+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "damqum"
|
title = "damqum"
|
||||||
date = 2026-08-03T19:13:17+02:00
|
date = 2026-08-03T19:13:17+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T13:06:12+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "ina"
|
title = "ina"
|
||||||
date = 2026-08-03T10:58:47+02:00
|
date = 2026-08-03T10:58:47+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T15:46:57+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "ištēn"
|
title = "ištēn"
|
||||||
date = 2026-08-03T16:43:23+02:00
|
date = 2026-08-03T16:43:23+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "narkabtum"
|
title = "narkabtum"
|
||||||
date = 2026-07-31T15:46:34+02:00
|
date = 2026-07-31T15:46:34+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T11:37:56+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "narûm"
|
title = "narûm"
|
||||||
date = 2026-08-03T20:00:00+02:00
|
date = 2026-08-03T20:00:00+02:00
|
||||||
# updated = YYYY-MM-DD
|
# updated = YYYY-MM-DDTHH:MM:SS+HH:MM
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "nasākum"
|
title = "nasākum"
|
||||||
date = 2026-08-03T19:40:00+02:00
|
date = 2026-08-03T19:40:00+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T13:57:33+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "parāsum"
|
title = "parāsum"
|
||||||
date = 2026-08-03T13:12:16+02:00
|
date = 2026-08-03T13:12:16+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T15:40:09+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "u"
|
title = "u"
|
||||||
date = 2026-08-03T10:06:00+02:00
|
date = 2026-08-03T10:06:00+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T15:54:24+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "ul"
|
title = "ul"
|
||||||
date = 2026-08-03T11:13:23+02:00
|
date = 2026-08-03T11:13:23+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T13:40:23+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "zakûm"
|
title = "zakûm"
|
||||||
date = 2026-08-03T19:50:00+02:00
|
date = 2026-08-03T19:50:00+02:00
|
||||||
updated = 2026-08-05
|
updated = 2026-08-05T10:11:35+02:00
|
||||||
description = ""
|
description = ""
|
||||||
draft = false
|
draft = false
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,22 @@
|
|||||||
{% set recent = signes.pages | concat(with=vocabulaire.pages) %}
|
{% set recent = signes.pages | concat(with=vocabulaire.pages) %}
|
||||||
{% set recent = recent | concat(with=grammaire.pages) %}
|
{% set recent = recent | concat(with=grammaire.pages) %}
|
||||||
{% set recent = recent | concat(with=textes.pages) %}
|
{% set recent = recent | concat(with=textes.pages) %}
|
||||||
{% set recent = recent | filter(attribute="date") | sort(attribute="date") | reverse %}
|
{% set recent = recent | filter(attribute="date") %}
|
||||||
|
|
||||||
|
{# Les pages proviennent de plusieurs sections. Tera ne peut pas les trier
|
||||||
|
directement sur « updated, sinon date » ; on trie donc des clés composées
|
||||||
|
de la date d’activité et du permalien. #}
|
||||||
|
{% set recent_keys = [] %}
|
||||||
|
{% for item in recent %}
|
||||||
|
{% if item.updated %}
|
||||||
|
{% set activity_date = item.updated | date(format="%Y%m%d%H%M%S") %}
|
||||||
|
{% else %}
|
||||||
|
{% set activity_date = item.date | date(format="%Y%m%d%H%M%S") %}
|
||||||
|
{% endif %}
|
||||||
|
{% set item_key = activity_date ~ item.permalink %}
|
||||||
|
{% set_global recent_keys = recent_keys | concat(with=item_key) %}
|
||||||
|
{% endfor %}
|
||||||
|
{% set recent_keys = recent_keys | sort | reverse %}
|
||||||
|
|
||||||
{% include "partials/section-header.html" %}
|
{% include "partials/section-header.html" %}
|
||||||
|
|
||||||
@@ -82,11 +97,19 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{% if recent %}
|
{% if recent_keys %}
|
||||||
<section aria-labelledby="recent-title">
|
<section aria-labelledby="recent-title">
|
||||||
<h2 id="recent-title">Dernières fiches</h2>
|
<h2 id="recent-title">Fiches récentes</h2>
|
||||||
<ul class="entry-list">
|
<ul class="entry-list">
|
||||||
{% for item in recent | slice(end=3) %}
|
{% for recent_key in recent_keys | slice(end=3) %}
|
||||||
|
{% for item in recent %}
|
||||||
|
{% if item.updated %}
|
||||||
|
{% set activity_date = item.updated | date(format="%Y%m%d%H%M%S") %}
|
||||||
|
{% else %}
|
||||||
|
{% set activity_date = item.date | date(format="%Y%m%d%H%M%S") %}
|
||||||
|
{% endif %}
|
||||||
|
{% set item_key = activity_date ~ item.permalink %}
|
||||||
|
{% if item_key == recent_key %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ item.permalink | safe }}">
|
<a href="{{ item.permalink | safe }}">
|
||||||
{% if "/signes/" in item.path %}
|
{% if "/signes/" in item.path %}
|
||||||
@@ -108,9 +131,16 @@
|
|||||||
{% elif "/textes/" in item.path %}
|
{% elif "/textes/" in item.path %}
|
||||||
Texte
|
Texte
|
||||||
{% endif %}
|
{% endif %}
|
||||||
· <time datetime="{{ item.date }}">{{ item.date | date(format="%d/%m/%Y") }}</time>
|
{% if item.updated %}
|
||||||
|
· Mise à jour le <time datetime="{{ item.updated }}">{{ item.updated | date(format="%d/%m/%Y") }}</time>
|
||||||
|
{% else %}
|
||||||
|
· Publication le <time datetime="{{ item.date }}">{{ item.date | date(format="%d/%m/%Y") }}</time>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
{% break %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
<dt>Nature grammaticale</dt>
|
<dt>Nature grammaticale</dt>
|
||||||
<dd>{{ macros::taxonomy_links(terms=page.taxonomies.natures, kind="natures") }}</dd>
|
<dd>{{ macros::taxonomy_links(terms=page.taxonomies.natures, kind="natures") }}</dd>
|
||||||
|
|
||||||
{# Tera 1.x n’accepte pas les tableaux imbriqués dans un littéral set.
|
{# Tera n’accepte pas les tableaux imbriqués dans un littéral set.
|
||||||
Les champs restent donc explicites et leur balisage est factorisé par macro. #}
|
Les champs sont donc déclarés explicitement et leur balisage est factorisé par macro. #}
|
||||||
{{ macros::metadata_field(extra=page.extra, key="meaning", label="Sens") }}
|
{{ macros::metadata_field(extra=page.extra, key="meaning", label="Sens") }}
|
||||||
{{ macros::metadata_list(extra=page.extra, key="logograms", label="Logogrammes", lang="sux-Latn") }}
|
{{ macros::metadata_list(extra=page.extra, key="logograms", label="Logogrammes", lang="sux-Latn") }}
|
||||||
{{ macros::metadata_field(extra=page.extra, key="gender", label="Genre") }}
|
{{ macros::metadata_field(extra=page.extra, key="gender", label="Genre") }}
|
||||||
|
|||||||
Reference in New Issue
Block a user