feat(cms): U3 derive article dates from content/ front-matter
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>
This commit is contained in:
1 parent
596290acf8
commit
8ff968739a
9 files changed
+174
-14
No files matched your search
@@ -72,19 +72,23 @@ def _text(node):
|
||||
|
||||
|
||||
def _iso_date(source_folder, slug, en_html=None):
|
||||
"""ISO date: prefer the central ARTICLE_DATES registry, then fall back to
|
||||
reading it off the English page (mirrors build_listings' behaviour)."""
|
||||
"""(iso, kind) for an entry: prefer the central ARTICLE_DATES registry
|
||||
(preserving whether the date is a 'published' or 'modified' one), then fall
|
||||
back to reading it off the English page (mirrors build_listings)."""
|
||||
key = f"{source_folder}/{slug}"
|
||||
rec = seo_inject.ARTICLE_DATES.get(key, {})
|
||||
iso = rec.get("published") or rec.get("modified")
|
||||
if iso:
|
||||
return iso
|
||||
if rec.get("published"):
|
||||
return rec["published"], "published"
|
||||
if rec.get("modified"):
|
||||
return rec["modified"], "modified"
|
||||
if en_html is not None:
|
||||
try:
|
||||
return seo_inject.extract_publish_date(en_html)
|
||||
iso = seo_inject.extract_publish_date(en_html)
|
||||
if iso:
|
||||
return iso, "published"
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
pass
|
||||
return None, None
|
||||
|
||||
|
||||
def _parse_hero(figure):
|
||||
@@ -154,10 +158,13 @@ def parse_page(html, source_folder, slug, lang, en_html=None):
|
||||
if cat and not re.search(r"\bpublished\b|\d{4}", cat, re.I):
|
||||
record["category"] = cat
|
||||
|
||||
iso = _iso_date(source_folder, slug, en_html=en_html)
|
||||
iso, kind = _iso_date(source_folder, slug, en_html=en_html)
|
||||
if not iso:
|
||||
return None, "no date (missing from ARTICLE_DATES and unparseable)"
|
||||
record["date"] = iso
|
||||
if kind == "modified":
|
||||
# 'published' is the implicit default; only record the exception.
|
||||
record["date_type"] = "modified"
|
||||
if date_line:
|
||||
record["date_line"] = date_line
|
||||
|
||||
@@ -175,7 +182,7 @@ def parse_page(html, source_folder, slug, lang, en_html=None):
|
||||
# Deterministic front-matter key order for readable, stable output.
|
||||
_KEY_ORDER = [
|
||||
"type", "slug", "lang", "source_folder",
|
||||
"title", "subtitle", "category", "date", "date_line", "hero", "body_format",
|
||||
"title", "subtitle", "category", "date", "date_type", "date_line", "hero", "body_format",
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user