.*?', body + "\n ", base, "article-body") return base def build_news_page(meta, body, base): """news pages (main.news-article).""" title = html_mod.escape(meta["title"]) base = _sub1(r".*?", f"{title}{_title_suffix(base, title)}", base, "title") base = _sub1(r"

.*?

", f"

{title}

", base, "news h1") base = _sub1(r'', f'', base, "news date") hero = render_hero(meta.get("hero"), "news-article-hero") if hero: base = _sub1(r'
.*?
', hero, base, "news hero") base = _sub1(r'
.*?', body + "\n ", base, "news body") return base def build_entry(meta, body, base_html): if meta["type"] == "news": return build_news_page(meta, body, base_html) return build_article_page(meta, body, base_html) def _template_html(meta): """Self-template: use the page's own current file as the chrome skeleton.""" out = output_path(meta) if not os.path.exists(out): raise FileNotFoundError( f"no template page for {meta['lang']}/{meta['type']}/{meta['slug']} " f"(new-page templates are handled in a later unit)") with open(out, encoding="utf-8") as f: return f.read() def run(write=False, check=False): built = 0 mismatches = [] for meta, body, path in iter_entries(): base = _template_html(meta) rendered = build_entry(meta, body, base) out = output_path(meta) if check: with open(out, encoding="utf-8") as f: current = f.read() if _normalize(rendered) != _normalize(current): mismatches.append(os.path.relpath(out, ROOT).replace(os.sep, "/")) if write: with open(out, "w", encoding="utf-8", newline="\n") as f: f.write(rendered) built += 1 print(f"Rendered {built} pages from content/.") if check: if mismatches: print(f"\n{len(mismatches)} page(s) differ from the current site " f"(semantic comparison):") for m in mismatches: print(f" ~ {m}") else: print("All rendered pages are equivalent to the current site. [OK]") return built, mismatches # ----------------------------------------------------- normalization (for --check) def _normalize(html_text): """Collapse insignificant whitespace and canonicalise attribute order so the comparison ignores formatting-only differences (per plan R5).""" from bs4 import BeautifulSoup soup = BeautifulSoup(html_text, "html.parser") for tag in soup.find_all(True): if tag.attrs: tag.attrs = {k: tag.attrs[k] for k in sorted(tag.attrs)} text = str(soup) text = re.sub(r">\s+<", "><", text) # drop whitespace between tags text = re.sub(r"\s+", " ", text) # collapse runs of whitespace return text.strip() if __name__ == "__main__": ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--write", action="store_true", help="write the detail pages") ap.add_argument("--check", action="store_true", help="compare rendered output to the current pages (no write)") args = ap.parse_args() if not (args.write or args.check): args.check = True run(write=args.write, check=args.check)