diff --git a/themes/vfb-nova/assets/css/compat.css b/themes/vfb-nova/assets/css/compat.css index ca2049e..ddbb2ad 100644 --- a/themes/vfb-nova/assets/css/compat.css +++ b/themes/vfb-nova/assets/css/compat.css @@ -106,3 +106,35 @@ background: var(--surface); translate: 0 -.15em; } + +/* -------------------------------------------------------------------------- + Bootstrap `alert`, for the legacy Docsy shortcodes reimplemented in + layouts/shortcodes/{alert,pageinfo}.html. Superseded term pages call them, + and without a template the whole build fails; without these rules they + would render as unstyled text. Uses the theme's own tokens rather than + Bootstrap's palette so they sit with the rest of the page. + -------------------------------------------------------------------------- */ +.prose .alert { + display: block; + padding: var(--sp-4) var(--sp-5); + margin: var(--sp-5) 0; + border: 1px solid var(--line); + border-left-width: 3px; + border-radius: var(--r-md); + background: var(--elev-1); +} +.prose .alert > :first-child { margin-top: 0; } +.prose .alert > :last-child { margin-bottom: 0; } +.prose .alert-heading { + margin: 0 0 var(--sp-2); + font-size: 1rem; + font-weight: 600; +} +.prose .alert-primary, +.prose .alert-info { border-left-color: rgba(76, 141, 255, .7); } +.prose .alert-success { border-left-color: rgba(64, 192, 128, .7); } +.prose .alert-warning { border-left-color: rgba(230, 168, 64, .7); } +.prose .alert-danger { border-left-color: rgba(230, 96, 96, .7); } +.prose .alert-secondary, +.prose .alert-light, +.prose .alert-dark { border-left-color: var(--line); } diff --git a/themes/vfb-nova/layouts/shortcodes/alert.html b/themes/vfb-nova/layouts/shortcodes/alert.html new file mode 100644 index 0000000..e55d15c --- /dev/null +++ b/themes/vfb-nova/layouts/shortcodes/alert.html @@ -0,0 +1,18 @@ +{{/* + Docsy's `alert`, reimplemented so legacy generated pages still render. + + Term pages written by vfbterms.py version 6 and earlier used Docsy + shortcodes. Those files are superseded but survive in the corpus whenever a + term drops out of one run's ID list, and a single one aborts the entire + build — twice now, at 93 and 38 minutes a time. The cost of shipping this is + one small template; the cost of not shipping it is a lost build per orphan. + + Same signature as Docsy: {{< alert title="..." color="..." >}} ... {{< /alert >}} + `.Inner | markdownify` matches Docsy's own implementation, so pages written + against it render as they did. +*/}} +{{- $color := .Get "color" | default "primary" -}} + diff --git a/themes/vfb-nova/layouts/shortcodes/pageinfo.html b/themes/vfb-nova/layouts/shortcodes/pageinfo.html new file mode 100644 index 0000000..a8c91d1 --- /dev/null +++ b/themes/vfb-nova/layouts/shortcodes/pageinfo.html @@ -0,0 +1,9 @@ +{{/* + Docsy's `pageinfo`, for the same reason as alert.html: legacy generated pages + call it, and a missing shortcode template is a hard build error rather than a + warning. Shipped pre-emptively so the next orphan does not cost another build. +*/}} +{{- $color := .Get "color" | default "info" -}} +
+{{ .Inner | markdownify }} +
diff --git a/vfbterms.py b/vfbterms.py index 66951f1..83e047e 100644 --- a/vfbterms.py +++ b/vfbterms.py @@ -757,12 +757,51 @@ def fetch_ids(label, query): print(f" Retrieved {len(ids)} IDs for {label}") return ids +_VERSION_SUFFIX = re.compile(r"_v(\d+)\.md$") + + +def prune_old_versions(label): + """Delete superseded term pages in the current directory. + + process_term() used to remove only version-1 after writing a page. A term + absent from a single run's ID list therefore kept its older file forever: + the corpus still held _v6 pages long after version reached 9. Those orphans + are live pages -- they can collide on URL with the current version, they + are walked and parsed on every build, and pages old enough to predate the + move off Docsy call shortcodes the theme no longer ships, which aborts the + whole build. + + One directory scan rather than a stat per term per version. The per-term + form costs version-1 extra syscalls for every term, which at ~763k terms is + millions of round trips; on the NFS volume this corpus lives on, metadata + operations run at a few hundred per second, so that is hours. This also + removes one stat per term relative to the old code. + """ + removed = 0 + try: + with os.scandir(".") as entries: + for entry in entries: + match = _VERSION_SUFFIX.search(entry.name) + if match and int(match.group(1)) < version: + try: + os.remove(entry.name) + removed += 1 + except OSError: + pass + except OSError as e: + print(f"WARNING: could not prune {label}: {e}") + return + if removed: + print(f" pruned {removed} superseded pages from {label}") + + def process_group(base_path, relative_dir, label, query): """Change directory, fetch IDs, and generate pages for one ontology group.""" target_dir = os.path.normpath(join(base_path, relative_dir)) print(f"\n[{label}] {target_dir}") chdir(target_dir) save_terms(fetch_ids(label, query)) + prune_old_versions(label) def process_term(term_id): """Fetch, render and write one term page. Returns a status string. @@ -786,13 +825,6 @@ def process_term(term_id): f.write(page_content) os.replace(tmp, filename) - # Clean up previous version - old_filename = term_id + "_v" + str(version - 1) + ".md" - if os.path.isfile(old_filename): - try: - os.remove(old_filename) - except OSError: - pass return "ok" def save_terms(ids):