From 8485d4e376ad534108fbb1f1a7eda175e3471c1c Mon Sep 17 00:00:00 2001 From: Hunter Lovell Date: Fri, 10 Oct 2025 21:28:34 -0700 Subject: [PATCH] feat: add constants syntax --- pipeline/preprocessors/constants_map.py | 16 ++++++ pipeline/preprocessors/handle_constants.py | 44 +++++++++++++++ .../preprocessors/markdown_preprocessor.py | 4 ++ tests/unit_tests/test_handle_constants.py | 55 +++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 pipeline/preprocessors/constants_map.py create mode 100644 pipeline/preprocessors/handle_constants.py create mode 100644 tests/unit_tests/test_handle_constants.py diff --git a/pipeline/preprocessors/constants_map.py b/pipeline/preprocessors/constants_map.py new file mode 100644 index 000000000..de3fdaf7e --- /dev/null +++ b/pipeline/preprocessors/constants_map.py @@ -0,0 +1,16 @@ +"""Central mapping for reusable documentation constants. + +This module stores string constants that can be reused across the +documentation set. Use the ``$[constant-name]`` syntax in markdown/MDX +files to reference any entry defined here. +""" + +from typing import Final + +CONSTANTS_MAP: Final[dict[str, str]] = {} +"""Global registry of reusable documentation constants. + +Add entries here with keys representing the constant token and values +containing the string that should replace the token during preprocessing. +""" + diff --git a/pipeline/preprocessors/handle_constants.py b/pipeline/preprocessors/handle_constants.py new file mode 100644 index 000000000..0329d2761 --- /dev/null +++ b/pipeline/preprocessors/handle_constants.py @@ -0,0 +1,44 @@ +"""Helpers for substituting documentation constants in markdown content.""" + +from __future__ import annotations + +import logging +import re + +from pipeline.preprocessors.constants_map import CONSTANTS_MAP + +logger = logging.getLogger(__name__) + +CONSTANT_PATTERN = re.compile(r"(?[^\]]+)\]") + + +def replace_constants(markdown: str, file_path: str) -> str: + """Replace $[constant] tokens with strings from CONSTANTS_MAP. + + Args: + markdown: Raw markdown/MDX content. + file_path: Source filename for logging context. + + Returns: + Markdown string with any matching constants substituted. Unknown + constants are left unchanged. + """ + + def _replace(match: re.Match[str]) -> str: + constant_name = match.group("constant") + replacement = CONSTANTS_MAP.get(constant_name) + + if replacement is None: + logger.info( + "%s: Constant '%s' not found in constants map.", + file_path, + constant_name, + ) + return match.group(0) + + return replacement + + substituted = CONSTANT_PATTERN.sub(_replace, markdown) + # Unescape literal \$[constant] sequences that were intentionally left untouched. + return re.sub(r"\\(\$\[)", r"\1", substituted) + diff --git a/pipeline/preprocessors/markdown_preprocessor.py b/pipeline/preprocessors/markdown_preprocessor.py index c44eb21da..17649a50a 100644 --- a/pipeline/preprocessors/markdown_preprocessor.py +++ b/pipeline/preprocessors/markdown_preprocessor.py @@ -11,6 +11,7 @@ from pathlib import Path from pipeline.preprocessors.handle_auto_links import replace_autolinks +from pipeline.preprocessors.handle_constants import replace_constants logger = logging.getLogger(__name__) @@ -99,6 +100,9 @@ def preprocess_markdown( if default_scope is None: default_scope = target_language + # Apply reusable constant substitution + content = replace_constants(content, str(file_path)) + # Apply cross-reference preprocessing content = replace_autolinks(content, str(file_path), default_scope=default_scope) diff --git a/tests/unit_tests/test_handle_constants.py b/tests/unit_tests/test_handle_constants.py new file mode 100644 index 000000000..1ff2f66e9 --- /dev/null +++ b/tests/unit_tests/test_handle_constants.py @@ -0,0 +1,55 @@ +"""Tests for documentation constant substitution helpers.""" + +import logging +from pathlib import Path + +import pytest + +from pipeline.preprocessors import constants_map +from pipeline.preprocessors.handle_constants import replace_constants +from pipeline.preprocessors.markdown_preprocessor import preprocess_markdown + + +def test_replace_constants_substitutes_known_constant( + monkeypatch, +) -> None: + """Verify that known constants are replaced with mapped values.""" + monkeypatch.setitem(constants_map.CONSTANTS_MAP, "example-constant", "example-value") + + result = replace_constants("Use $[example-constant] here.", "test.mdx") + + assert result == "Use example-value here." + + +def test_replace_constants_logs_when_constant_missing( + caplog: pytest.LogCaptureFixture, +) -> None: + """Ensure missing constants are left untouched and logged.""" + caplog.set_level(logging.INFO, logger="pipeline.preprocessors.handle_constants") + + result = replace_constants("Unknown $[missing-constant] value.", "missing.mdx") + + assert result == "Unknown $[missing-constant] value." + assert "missing.mdx: Constant 'missing-constant' not found" in caplog.text + + +def test_replace_constants_respects_escaped_tokens(monkeypatch) -> None: + """Ensure escaped constant tokens render literally without substitution.""" + monkeypatch.setitem(constants_map.CONSTANTS_MAP, "example", "value") + + result = replace_constants(r"Literal \$[example] token.", "literal.mdx") + + assert result == "Literal $[example] token." + + +def test_preprocess_markdown_applies_constant_substitution(monkeypatch) -> None: + """Integration smoke test that preprocess_markdown applies substitutions.""" + monkeypatch.setitem(constants_map.CONSTANTS_MAP, "integration-constant", "final-value") + + result = preprocess_markdown( + "Result: $[integration-constant]", + Path("integration.mdx"), + target_language="python", + ) + + assert result == "Result: final-value"