89 lines
2.5 KiB
Python
Executable File
89 lines
2.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""Horodate la dernière modification substantielle d’une fiche."""
|
||
|
||
import argparse
|
||
import re
|
||
import sys
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
CONTENT = (ROOT / "src" / "content").resolve()
|
||
|
||
FRONT_MATTER_RE = re.compile(
|
||
r"\A\+\+\+\r?\n(?P<body>.*?)(?:\r?\n\+\+\+(?=\r?\n|\Z))",
|
||
re.DOTALL,
|
||
)
|
||
UPDATED_RE = re.compile(r"(?m)^(?:#\s*)?updated\s*=.*?(?=\r?$)")
|
||
|
||
|
||
def parse_args() -> argparse.Namespace:
|
||
parser = argparse.ArgumentParser(description=__doc__)
|
||
parser.add_argument(
|
||
"fiche",
|
||
help="chemin depuis la racine du dépôt ou depuis src/content/",
|
||
)
|
||
return parser.parse_args()
|
||
|
||
|
||
def resolve_path(value: str) -> Path:
|
||
path = Path(value).expanduser()
|
||
|
||
if not path.is_absolute():
|
||
path = ROOT / path if path.parts[:2] == ("src", "content") else CONTENT / path
|
||
|
||
path = path.resolve()
|
||
|
||
if not path.is_relative_to(CONTENT):
|
||
raise ValueError("la fiche doit se trouver dans src/content/")
|
||
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")
|
||
|
||
return path
|
||
|
||
|
||
def update_timestamp(path: Path) -> str:
|
||
text = path.read_bytes().decode("utf-8")
|
||
front_matter = FRONT_MATTER_RE.match(text)
|
||
|
||
if front_matter is None:
|
||
raise ValueError("front matter TOML délimité par +++ absent")
|
||
|
||
matches = list(UPDATED_RE.finditer(front_matter.group("body")))
|
||
if len(matches) != 1:
|
||
raise ValueError("la fiche doit contenir exactement une ligne updated")
|
||
|
||
timestamp = datetime.now(ZoneInfo("Europe/Paris")).isoformat(timespec="seconds")
|
||
|
||
match = matches[0]
|
||
offset = front_matter.start("body")
|
||
start = offset + match.start()
|
||
end = offset + match.end()
|
||
|
||
path.write_bytes(
|
||
(text[:start] + f"updated = {timestamp}" + text[end:]).encode("utf-8")
|
||
)
|
||
return timestamp
|
||
|
||
|
||
def main() -> int:
|
||
args = parse_args()
|
||
|
||
try:
|
||
path = resolve_path(args.fiche)
|
||
timestamp = update_timestamp(path)
|
||
except (OSError, UnicodeError, ValueError, ZoneInfoNotFoundError) as exc:
|
||
print(f"Erreur : {exc}", file=sys.stderr)
|
||
return 1
|
||
|
||
print(f"Fiche mise à jour : {path.relative_to(ROOT)}")
|
||
print(f"updated = {timestamp}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|