From bcdbec955d8376753b192c8cade02f7393aca7cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Mon, 7 Sep 2026 15:30:49 +0200 Subject: [PATCH] Remove edition support from the llms.txt pipeline Ticket 03 already stripped every edition marker from the docs; this removes the now-dead code that produced the "Editions: X, Y" line in llms.txt output. - Delete editions_from_frontmatter() and FRONTMATTER_EDITION_DISPLAY (llmstxt_preprocess.py): the frontmatter edition reader has no input left. - Drop the editions parameter and emitted line from inject_page_metadata() and update its caller in hooks.py accordingly. - Delete _process_inline_pills() / _pill_edition() (llmstxt_preprocess.py): unreachable now that the edition badge snippets are gone from docs/ and custom.js's runtime-injected pill carries no edition class. - PILL_CLASS_TO_EDITION stays: still used by _process_release_note_tags() for the surviving release-note categories. - Update module/function docstrings that referenced the removed helpers. Tests: remove the 7 tests asserting on editions_from_frontmatter/the editions line, and the 3 tests covering the now-deleted inline-pill merging. Retain and adjust the tests covering description injection, the llms.txt pointer, and their ordering (drop the editions kwarg/assertions). Change test_release_note_tags_appended_to_heading's sample pill classes to surviving categories (new-feature/first-release) since that function is kept. Rename test_description_and_editions_lines_are_kept to test_description_and_trailing_content_are_kept: it exercises strip_llms_txt_pointer's handling of trailing content, not editions. Co-Authored-By: Claude Opus 5 (1M context) --- tests/python/test_build_package_docs.py | 6 +- tests/python/test_page_metadata.py | 42 +---------- tests/python/test_preprocess.py | 34 +-------- tools/llms_txt/hooks.py | 4 +- tools/llms_txt/llmstxt_preprocess.py | 94 ++----------------------- 5 files changed, 16 insertions(+), 164 deletions(-) diff --git a/tests/python/test_build_package_docs.py b/tests/python/test_build_package_docs.py index 7977b251da..6e22ae252d 100644 --- a/tests/python/test_build_package_docs.py +++ b/tests/python/test_build_package_docs.py @@ -236,7 +236,7 @@ def test_pointer_is_removed_without_leaving_a_double_blank_line(self): ) assert strip_llms_txt_pointer(content) == "# Getting started\n\nBody text here.\n" - def test_description_and_editions_lines_are_kept(self): + def test_description_and_trailing_content_are_kept(self): content = ( "# Heading\n" "\n" @@ -244,10 +244,10 @@ def test_description_and_editions_lines_are_kept(self): "\n" "Some page description.\n" "\n" - "Editions: Content, Experience\n" + "See also: Some related page.\n" ) assert strip_llms_txt_pointer(content) == ( - "# Heading\n\nSome page description.\n\nEditions: Content, Experience\n" + "# Heading\n\nSome page description.\n\nSee also: Some related page.\n" ) def test_nested_project_url_is_also_stripped(self): diff --git a/tests/python/test_page_metadata.py b/tests/python/test_page_metadata.py index 0638a53485..41c05b62c2 100644 --- a/tests/python/test_page_metadata.py +++ b/tests/python/test_page_metadata.py @@ -1,5 +1,4 @@ from llms_txt.llmstxt_preprocess import ( - editions_from_frontmatter, expand_macros, inject_page_metadata, ) @@ -13,18 +12,11 @@ def test_llms_txt_pointer_always_inserted_after_first_h1(): assert inject_page_metadata(content) == ( f"# Title\n\n{LLMS_TXT_LINE}\n\nBody text." ) - assert inject_page_metadata(content, description="", editions=[]) == ( + assert inject_page_metadata(content, description="") == ( f"# Title\n\n{LLMS_TXT_LINE}\n\nBody text." ) -def test_editions_inserted_after_first_h1(): - content = "# Title\n\nBody text." - assert inject_page_metadata(content, editions=["Experience"]) == ( - f"# Title\n\n{LLMS_TXT_LINE}\n\nEditions: Experience\n\nBody text." - ) - - def test_description_inserted_after_first_h1(): content = "# Title\n\nBody text." assert inject_page_metadata(content, description="Configure the Storefront.") == ( @@ -32,17 +24,10 @@ def test_description_inserted_after_first_h1(): ) -def test_description_comes_before_editions(): - content = "# Title\n\nBody text." - assert inject_page_metadata(content, description="A description.", editions=["Experience"]) == ( - f"# Title\n\n{LLMS_TXT_LINE}\n\nA description.\n\nEditions: Experience\n\nBody text." - ) - - def test_prepended_when_no_h1(): content = "Body text." - assert inject_page_metadata(content, description="A description.", editions=["Experience"]) == ( - f"{LLMS_TXT_LINE}\n\nA description.\n\nEditions: Experience\n\nBody text." + assert inject_page_metadata(content, description="A description.") == ( + f"{LLMS_TXT_LINE}\n\nA description.\n\nBody text." ) @@ -57,27 +42,6 @@ def test_llms_txt_url_respects_nested_site_path(): assert result == f"# Title\n\n{nested_line}\n\nBody text." -def test_frontmatter_edition_string(): - assert editions_from_frontmatter({"edition": "headless experience"}) == ["Headless", "Experience"] - - -def test_frontmatter_editions_list(): - assert editions_from_frontmatter({"editions": ["headless", "lts-update"]}) == ["Headless", "LTS Update"] - - -def test_frontmatter_edition_and_editions_merged(): - result = editions_from_frontmatter({"edition": "experience", "editions": ["headless"]}) - assert result == ["Experience", "Headless"] - - -def test_unknown_edition_passes_through(): - assert editions_from_frontmatter({"edition": "custom"}) == ["custom"] - - -def test_empty_frontmatter(): - assert editions_from_frontmatter({}) == [] - - def test_expand_macros_substitutes_scalars(): variables = {"product_name": "Ibexa DXP", "product_name_cdp": "Ibexa CDP"} assert expand_macros("Install [[= product_name_cdp =]] with [[= product_name =]].", variables) == ( diff --git a/tests/python/test_preprocess.py b/tests/python/test_preprocess.py index 81914e48c7..d2cda8277d 100644 --- a/tests/python/test_preprocess.py +++ b/tests/python/test_preprocess.py @@ -50,11 +50,11 @@ def test_release_note_tags_appended_to_heading(): html = ( '

Connector v5.0.7

' '
' - '
' - '
' + '
' + '
' "
" ) - assert "## Connector v5.0.7 (Headless, Experience)" in to_markdown(html) + assert "## Connector v5.0.7 (New feature, First release)" in to_markdown(html) def test_admonition_becomes_blockquote(): @@ -106,34 +106,6 @@ def test_card_with_empty_title_raises(): to_markdown(html) -def test_inline_pill_becomes_parenthetical(): - html = '

Feature is available.

' - assert "Feature (Experience) is available." in to_markdown(html) - - -def test_adjacent_inline_pills_merged(): - # Structure from update_from_5.0: pills separated by a space in a heading. - html = ( - '

Database update ' - ' ' - '' - '

' - ) - result = to_markdown(html) - assert "### Database update (Headless, Experience)" in result - assert ") (" not in result - - -def test_three_adjacent_inline_pills_merged(): - html = ( - "

Feature" - ' ' - ' ' - ' is available.

' - ) - assert "Feature (Headless, Experience, LTS Update) is available." in to_markdown(html) - - def test_ol_start_attribute_preserved(): #
    (a list interrupted by other content) keeps its numbering. html = "
    1. a
    2. b

    note

    1. c
    2. d
    " diff --git a/tools/llms_txt/hooks.py b/tools/llms_txt/hooks.py index ddce34edfe..4457f99e98 100644 --- a/tools/llms_txt/hooks.py +++ b/tools/llms_txt/hooks.py @@ -18,7 +18,6 @@ from llms_txt.llmstxt_preprocess import ( absolutize_image_urls, - editions_from_frontmatter, expand_macros, inject_page_metadata, renumber_ordered_lists, @@ -66,7 +65,6 @@ def on_page_content(html: str, *, page: "Page", config: "MkDocsConfig", **kwargs return frontmatter = _read_frontmatter(page, config) - editions = editions_from_frontmatter(frontmatter) description = expand_macros(str(frontmatter.get("description") or ""), config.get("extra") or {}) if "[[=" in description: # Unresolved macros must not leak into the output. @@ -75,7 +73,7 @@ def on_page_content(html: str, *, page: "Page", config: "MkDocsConfig", **kwargs # userguide project published under /projects/userguide/), unlike # a hardcoded root-relative "/llms.txt". llms_txt_url = urljoin(llmstxt._base_url, "llms.txt") - content = inject_page_metadata(page_info.content, description, editions, llms_txt_url) + content = inject_page_metadata(page_info.content, description, llms_txt_url) content = renumber_ordered_lists(content) # Same base URL and page directory the plugin uses for making link hrefs absolute. page_dir = PurePosixPath(page.file.dest_uri).parent.as_posix() diff --git a/tools/llms_txt/llmstxt_preprocess.py b/tools/llms_txt/llmstxt_preprocess.py index f1055ce6d4..7e915713d2 100644 --- a/tools/llms_txt/llmstxt_preprocess.py +++ b/tools/llms_txt/llmstxt_preprocess.py @@ -8,8 +8,8 @@ mkdocs-llmstxt plugin (configured in ``plugins.yml``) before the HTML is converted to Markdown. - Markdown post-processing helpers (``renumber_ordered_lists``, - ``inject_edition_badges``, ``editions_from_frontmatter``) applied by - ``hooks.py`` to the Markdown the plugin generated. + ``inject_page_metadata``) applied by ``hooks.py`` to the Markdown the + plugin generated. This package (``llms_txt``) is installed as a dependency by other Ibexa doc sites, which each keep a thin root-level ``llmstxt_preprocess.py`` shim @@ -33,13 +33,6 @@ "pill--first-release": "First release", } -FRONTMATTER_EDITION_DISPLAY = { - "lts-update": "LTS Update", - "experience": "Experience", - "headless": "Headless", -} - - def preprocess(soup: Soup, output: str) -> None: """ Preprocess HTML to improve markdown conversion. @@ -47,16 +40,11 @@ def preprocess(soup: Soup, output: str) -> None: Runs with autoclean disabled so we can control the order: 1. Expand tabbed sets with labels before autoclean removes tabbed-labels. 2. Run autoclean-equivalent cleanup. - 3. Replace inline edition badge spans with readable text. - 4. Remove release notes filter UI. - 5. Convert card macros to markdown lists. - - Note: frontmatter edition injection is handled in hooks.py on_page_content, - where page.file.src_path is available directly. + 3. Remove release notes filter UI. + 4. Convert card macros to markdown lists. """ _process_tabbed_sets(soup) _autoclean(soup) - _process_inline_pills(soup) _process_release_note_tags(soup) _process_release_note_dates(soup) _process_release_notes_filters(soup) @@ -183,60 +171,6 @@ def _should_remove(tag) -> bool: Soup(f"{html_module.escape(code_elem.get_text())}", "html.parser") ) -# --------------------------------------------------------------------------- -# Inline edition badge spans (from snippet includes) -# --------------------------------------------------------------------------- - -def _pill_edition(node) -> str: - """Return the edition name of an inline pill span, or '' if not one.""" - if getattr(node, "name", None) != "span": - return "" - classes = node.get("class") or [] - if "pill--inline" not in classes: - return "" - for pill_cls, edition_name in PILL_CLASS_TO_EDITION.items(): - if pill_cls in classes: - return edition_name - return "" - - -def _process_inline_pills(soup: Soup) -> None: - """Replace inline edition pill spans with readable text. - - Consecutive pills (possibly separated by whitespace) are merged into a - single parenthetical, e.g. ' (Headless, Experience)' instead of - ' (Headless) (Experience)'. - """ - for span in soup.find_all("span", class_="pill--inline"): - if span.parent is None: # already consumed as part of a previous run - continue - edition = _pill_edition(span) - if not edition: - continue - - # Collect the run of pills that follow, skipping whitespace between them. - editions = [edition] - consumed = [] - node = span.next_sibling - pending_whitespace = [] - while node is not None: - if isinstance(node, NavigableString) and not node.strip(): - pending_whitespace.append(node) - node = node.next_sibling - continue - next_edition = _pill_edition(node) - if not next_edition: - break - editions.append(next_edition) - consumed += pending_whitespace + [node] - pending_whitespace = [] - node = node.next_sibling - - for extra_node in consumed: - extra_node.extract() - span.replace_with(soup.new_string(f" ({', '.join(editions)})")) - - def _process_release_note_tags(soup: Soup) -> None: """Append edition labels from release-note__tags divs to their preceding heading. @@ -399,20 +333,6 @@ def _process_cards(soup: Soup) -> None: # Markdown post-processing (applied by hooks.py to the generated Markdown) # --------------------------------------------------------------------------- -def editions_from_frontmatter(frontmatter: dict) -> list: - """Map ``edition``/``editions`` frontmatter values to display names.""" - - def _to_list(value): - if isinstance(value, list): - return value - if isinstance(value, str): - return value.split() - return [] - - all_editions = _to_list(frontmatter.get("edition")) + _to_list(frontmatter.get("editions") or []) - return [FRONTMATTER_EDITION_DISPLAY.get(e, e) for e in all_editions if e] - - _MACRO_RE = re.compile(r"\[\[=\s*(\w+)\s*=\]\]") @@ -431,9 +351,9 @@ def _substitute(match: re.Match) -> str: def inject_page_metadata( - content: str, description: str = "", editions: list = (), llms_txt_url: str = "/llms.txt" + content: str, description: str = "", llms_txt_url: str = "/llms.txt" ) -> str: - """Insert the llms.txt pointer, page description, and an 'Editions: X, Y' line after the first h1 heading. + """Insert the llms.txt pointer and page description after the first h1 heading. ``llms_txt_url`` must be the absolute URL of *this site's own* llms.txt (e.g. via ``urljoin(base_url, "llms.txt")``), not a hardcoded root-relative @@ -443,8 +363,6 @@ def inject_page_metadata( metadata_lines = ["", f"> For the complete documentation index, see [llms.txt]({llms_txt_url})."] if description: metadata_lines += ["", description] - if editions: - metadata_lines += ["", "Editions: " + ", ".join(editions)] lines = content.split("\n") for i, line in enumerate(lines):