Replace the hand-maintained ARTICLE_DATES registry as the source of
truth with dates read from content/ front-matter, so publishing a post
via the CMS needs no manual registry edit (plan R4).
- tools/content_index.py: load_dates() reads content/ front-matter into
{source_folder/slug: {published|modified: iso}}. Standalone (os+yaml),
no import cycle with seo_inject.
- tools/seo_inject.py: ARTICLE_DATES is now {**static_fallback,
**content_dates}; content wins. The static table is kept only as a
fallback for entries not present in content/.
- migration now preserves the published-vs-modified distinction
(date_type), so the switch is byte-identical: content-derived dates
equal the old static table exactly (17/17, incl. the one 'modified').
Verified: full `seo_inject --all --sitemap` + build_listings +
build_botlinks run produces ZERO changes to any site page. build_content
--check still reproduces all 51 pages.
build_botlinks.py needs no change (it scrapes built pages, which are a
pure function of content/). Listings get content-driven dates for free
via seo_inject.ARTICLE_DATES.
tools/tests/test_content_index.py: 5 tests (dates == legacy registry,
merged registry unchanged, published/modified distinction, events key on
projects/<slug>, one ISO date per entry).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
2.1 KiB
Python
66 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
content_index.py -- read the content/ source tree (U1) as structured data.
|
|
|
|
Implements the date half of U3: the hand-maintained ARTICLE_DATES registry in
|
|
seo_inject.py is replaced by dates derived from content/ front-matter, so adding
|
|
a post via the CMS needs no manual registry edit. Standalone (only os + yaml) so
|
|
seo_inject can import it without a cycle.
|
|
|
|
See docs/content-model.md for the front-matter schema.
|
|
"""
|
|
import os
|
|
|
|
import yaml
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
CONTENT = os.path.join(ROOT, "content")
|
|
LANGS = ("en", "mk", "si")
|
|
_TYPE_DIRS = ("blog", "news", "events")
|
|
|
|
|
|
def _read_front_matter(path):
|
|
with open(path, encoding="utf-8") as f:
|
|
text = f.read()
|
|
if not text.startswith("---\n"):
|
|
return None
|
|
_, front, _body = text.split("---\n", 2)
|
|
return yaml.safe_load(front) or {}
|
|
|
|
|
|
def _canonical_meta(slug_dir):
|
|
"""Front-matter for an entry, from en.md if present else any language.
|
|
Dates/source_folder are language-independent, so any file will do."""
|
|
for lang in LANGS:
|
|
p = os.path.join(slug_dir, f"{lang}.md")
|
|
if os.path.exists(p):
|
|
meta = _read_front_matter(p)
|
|
if meta:
|
|
return meta
|
|
return None
|
|
|
|
|
|
def load_dates():
|
|
"""Return {"<source_folder>/<slug>": {"published"|"modified": "YYYY-MM-DD"}}
|
|
built from content/ front-matter. Empty dict if content/ is absent."""
|
|
dates = {}
|
|
if not os.path.isdir(CONTENT):
|
|
return dates
|
|
for tdir in _TYPE_DIRS:
|
|
base = os.path.join(CONTENT, tdir)
|
|
if not os.path.isdir(base):
|
|
continue
|
|
for slug in sorted(os.listdir(base)):
|
|
meta = _canonical_meta(os.path.join(base, slug))
|
|
if not meta or not meta.get("date"):
|
|
continue
|
|
key = f"{meta.get('source_folder', tdir)}/{slug}"
|
|
kind = meta.get("date_type", "published") # published is the default
|
|
dates[key] = {kind: str(meta["date"])}
|
|
return dates
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import json
|
|
print(json.dumps(load_dates(), indent=2, ensure_ascii=False))
|