From 23d6a3ef8afb0fddc0cc7c4591014b971ee2edf4 Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Thu, 7 May 2026 08:48:02 +0300 Subject: docs: render llms.txt at build time (circinus) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parity with [#1874](https://github.com/vyos/vyos-documentation/pull/1874) (current) and [#1876](https://github.com/vyos/vyos-documentation/pull/1876) (sagitta). Replaces the static `docs/_html_extra/llms.txt` with a Jinja template (`docs/_templates/llms.txt.j2`) rendered at build time via a `build-finished` hook in `conf.py`. URLs and the version line are interpolated from `html_baseurl` and `release`, so the rendered file always matches what circinus actually serves. Hook behavior matches `current`/`sagitta` exactly, including the two review-cycle hardenings landed on those PRs: - Skips on builders other than `html` and `readthedocs` — the curated template encodes `.html` URLs which don't match `dirhtml` output. - `RuntimeError` if `html_baseurl` is empty rather than silently rendering broken root-relative URLs. - Jinja `Environment(undefined=StrictUndefined)` so a typo in `llms.txt.j2` raises at build time instead of writing a half-blank `llms.txt`. The closed PR [#1875](https://github.com/vyos/vyos-documentation/pull/1875) was the previous attempt; this re-lands the change after the post-flip rebase situation cleared. Verified locally: hook tests (happy-path render, fail-fast on `html_baseurl=None`/`''`, `StrictUndefined` catches `{{ relase }}` typo, skips `dirhtml`, skips on build exception) pass against this worktree. \xf0\x9f\xa4\x96 Generated by [robots](https://vyos.io) --- docs/conf.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) (limited to 'docs/conf.py') diff --git a/docs/conf.py b/docs/conf.py index 23c76d3e..ec95bf76 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -147,8 +147,10 @@ html_context = { # sphinx-sitemap: baseurl already includes /en/1.5/, so skip lang+version sitemap_url_scheme = '{link}' -# sphinx-llms-txt: disable auto-generated llms.txt, keep curated one from -# _html_extra; llms-full.txt is still auto-generated +# sphinx-llms-txt: disable the package's auto-generated index llms.txt. +# The curated llms.txt is rendered at build time from +# _templates/llms.txt.j2 by the _write_llms_txt() build-finished hook +# below; llms-full.txt is still auto-generated by sphinx-llms-txt. llms_txt_file = False # Custom sidebar templates, must be a dictionary that maps document names @@ -258,6 +260,38 @@ def _copy_md_sources(app, exception): shutil.copy2(path, dest) +def _write_llms_txt(app, exception): + # Skip dirhtml: the curated template encodes `.html` URLs (e.g. + # `quick-start.html`), which don't exist under `dirhtml` output + # (`quick-start/index.html`). Production publishes via the html / + # readthedocs builders, so dirhtml output would just be misleading. + if exception is not None or app.builder.name not in ( + 'html', 'readthedocs'): + return + if not app.config.html_baseurl: + # Fail loudly rather than rendering /quick-start.html etc. as a + # silently-broken root-relative URL — every supported branch + # sets html_baseurl, so a missing value is a regression. + raise RuntimeError( + 'html_baseurl must be set to render llms.txt') + from pathlib import Path + from jinja2 import Environment, StrictUndefined + tpl_path = Path(app.srcdir) / '_templates' / 'llms.txt.j2' + out_path = Path(app.outdir) / 'llms.txt' + baseurl = app.config.html_baseurl.rstrip('/') + '/' + # StrictUndefined: missing template variables raise rather than + # silently render as empty strings, so a typo in llms.txt.j2 fails + # the build instead of shipping a half-blank llms.txt. + env = Environment(undefined=StrictUndefined, keep_trailing_newline=True) + template = env.from_string(tpl_path.read_text(encoding='utf-8')) + rendered = template.render( + baseurl=baseurl, + release=app.config.release, + ) + out_path.write_text(rendered, encoding='utf-8') + + def setup(app): app.connect('builder-inited', _prefer_webp) app.connect('build-finished', _copy_md_sources) + app.connect('build-finished', _write_llms_txt) -- cgit v1.2.3