feature: support more macros settings

Signed-off-by: Timothée Mazzucotelli <dev@pawamoy.fr>
This commit is contained in:
Timothée Mazzucotelli committed 2026-09-24 17:50:05 +02:00
1 parent acee89a5f8
commit 0223fc9b64
8 files changed
+425 -19

No files matched your search

+129 -2
View File
@@ -23,6 +23,7 @@
from __future__ import annotations
import logging
import os
from io import StringIO
from typing import TYPE_CHECKING
@@ -34,11 +35,13 @@ from jinja2.exceptions import (
TemplateSyntaxError,
UndefinedError,
)
from markdown import Markdown
from tests.unit.extensions.conftest import soup
from zensical.extensions.context import ContextPreprocessor
from zensical.extensions.macros import (
MacroEnv,
MacrosExtension,
_fix_url,
_load_module,
_load_one_yaml,
@@ -55,7 +58,6 @@ from zensical.extensions.table_reader import (
if TYPE_CHECKING:
from pathlib import Path
from markdown import Markdown
from pandas import DataFrame
@@ -126,6 +128,18 @@ class TestFilters:
class TestMacroEnv:
@pytest.mark.parametrize("verbose", [False, True])
def test_chatter_respects_verbose(
self, caplog: pytest.LogCaptureFixture, verbose: bool
) -> None:
env = MacroEnv(verbose=verbose)
chatter = env.start_chatting("Example", color="cyan")
with caplog.at_level(logging.INFO, logger="zensical.extensions.macros"):
chatter("Count:", 2)
assert caplog.messages == (
["[macros - Example] - Count: 2"] if verbose else []
)
def test_conf_is_stored(self) -> None:
conf = {"site_name": "My Site", "docs_dir": "/docs"}
env = MacroEnv(conf=conf)
@@ -268,6 +282,118 @@ class TestLoadModule:
class TestPreprocessor:
@pytest.mark.parametrize(
"md",
[
{
"config": {
"markdown_extensions": {
"zensical.extensions.macros": {
"render_by_default": False,
"force_render_paths": (
"# Selected pages\n\n"
"guides/\n!guides/drafts/\nguides/drafts/keep.md\n"
"/only.md\nrender_*.md\n\\#literal.md\n\\!literal.md"
),
}
}
}
}
],
indirect=True,
)
@pytest.mark.parametrize(
("path", "override", "rendered"),
[
("guides/index.md", None, True),
(os.path.join("guides", "nested", "page.md"), None, True),
("guides/café.md", None, True),
("guides/drafts/page.md", None, False),
("guides/drafts/keep.md", None, True),
("guides/disabled.md", False, False),
("guides/drafts/keep.md", False, False),
("outside.md", True, True),
("outside.md", None, False),
("guides.md", None, False),
("only.md", None, True),
("nested/only.md", None, False),
("nested/render_example.md", None, True),
("#literal.md", None, True),
("!literal.md", None, True),
],
)
def test_force_render_paths_respects_page_overrides(
self, md: Markdown, path: str, override: bool | None, rendered: bool
) -> None:
context = ContextPreprocessor.from_markdown(md)
assert context is not None
context.page.path = path
if override is not None:
context.page.meta["render_macros"] = override
html = soup(md.convert("Value: {{ 1 + 1 }}"))
assert html.get_text() == (
"Value: 2" if rendered else "Value: {{ 1 + 1 }}"
)
def test_force_render_paths_does_not_require_page_context(self) -> None:
md = Markdown(
extensions=[
MacrosExtension(
render_by_default=False, force_render_paths="**"
)
]
)
assert md.convert("Value: {{ 1 + 1 }}") == "<p>Value: {{ 1 + 1 }}</p>"
@pytest.mark.parametrize("verbose", [False, True])
@pytest.mark.parametrize(
"md",
[
{
"config": {
"markdown_extensions": {
"zensical.extensions.macros": {"on_error_fail": True}
}
}
}
],
indirect=True,
)
def test_verbose_traces_module_loading_and_rendering(
self,
md: Markdown,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
verbose: bool,
) -> None:
md.preprocessors["macros"].config.verbose = verbose
(tmp_path / "main.py").write_text(
"def define_env(env):\n"
' chatter = env.start_chatting("Example")\n'
' chatter("Registered macros")\n'
" @env.macro\n"
" def greet(name):\n"
' chatter("Greeting:", name)\n'
' return "Hello " + name\n',
encoding="utf-8",
)
with caplog.at_level(logging.INFO, logger="zensical.extensions.macros"):
html = md.convert('{{ greet("World") }}')
assert "Hello World" in html
if verbose:
assert (
"[macros - render] - Rendering page: index.md"
in caplog.messages
)
assert any(
"Loading local module:" in line for line in caplog.messages
)
assert "[macros - Example] - Registered macros" in caplog.messages
assert "[macros - Example] - Greeting: World" in caplog.messages
else:
assert caplog.messages == []
@pytest.mark.parametrize(
("md", "expected_text"),
[
@@ -289,7 +415,8 @@ class TestPreprocessor:
"config": {
"markdown_extensions": {
"zensical.extensions.macros": {
"render_by_default": True
"render_by_default": True,
"force_render_paths": "!**",
},
},
}
+17 -2
View File
@@ -281,7 +281,6 @@ class TestPluginShimming:
plugins: dict[str, dict[str, Any]] = {
"callouts": {},
"glightbox": {"auto": False},
"macros": {"render_by_default": False},
"mike": {"version_selector": False},
"mkdocstrings": {"enabled": False},
"search": {"separator": r"\s+"},
@@ -291,7 +290,6 @@ class TestPluginShimming:
for name, options in {
"callouts": {"aliases": False, "breakless_lists": False},
"glightbox": {"slide_effect": "fade"},
"macros": {"force_render_paths": "guides/**"},
"mike": {"javascript_dir": "scripts"},
"mkdocstrings": {"enable_inventory": False, "watch": ["src"]},
"search": {"lang": ["en", "fr"]},
@@ -634,6 +632,23 @@ class TestPluginShimming:
config = self._parse_yaml(tmp_path, plugins={"macros": {}})
assert MacrosExtension.name in config["markdown_extensions"]
@pytest.mark.parametrize(
("option", "value"),
[("force_render_paths", "guides/\n!guides/drafts/"), ("verbose", True)],
)
def test_macros_settings_forwarded_and_hashed(
self, tmp_path: Path, option: str, value: str | bool
) -> None:
baseline = self._parse_yaml(tmp_path, plugins={"macros": {}})
config_file = tmp_path / "mkdocs.yml"
config_file.write_text(
_minimal_yaml(plugins={"macros": {option: value}})
)
configured = parse_config(str(config_file))
assert configured["mdx_configs"][MacrosExtension.name][option] == value
assert configured["plugins_hash"] != baseline["plugins_hash"]
def test_table_reader_plugin_shimmed(self, tmp_path: Path) -> None:
config = self._parse_yaml(tmp_path, plugins={"table-reader": {}})
assert TableReaderExtension.name in config["markdown_extensions"]
+40 -2
View File
@@ -287,8 +287,6 @@ def test_rejects_invalid_blog_configuration(name: str, data: Any) -> None:
("glightbox", "draggable"),
("glightbox", "background"),
("glightbox", "shadow"),
("macros", "force_render_paths"),
("macros", "verbose"),
("mike", "css_dir"),
("mike", "javascript_dir"),
("mkdocstrings", "enable_inventory"),
@@ -449,6 +447,8 @@ def test_normalizes_null_shim_configuration(name: str) -> None:
"include_yaml": {"data": "data.yml"},
"include_dir": "includes",
"render_by_default": False,
"force_render_paths": "guides/\n!guides/drafts/",
"verbose": True,
"on_error_fail": True,
"on_undefined": "strict",
"j2_block_start_string": "<%",
@@ -480,6 +480,44 @@ def test_accepts_supported_shim_options(
assert plugins[name]["config"] == config
@pytest.mark.parametrize("plugin", ["macros", "material/macros"])
@pytest.mark.parametrize(
("option", "value"),
[
("force_render_paths", ""),
("force_render_paths", "# Pages to render\nguides/\n!guides/drafts/"),
("verbose", True),
("verbose", False),
],
)
def test_preserves_macros_settings(
plugin: str, option: str, value: Any
) -> None:
data = {option: value}
plugins = _convert_plugins({plugin: data})
assert plugins["macros"]["config"] == data
assert data == {option: value}
@pytest.mark.parametrize(
("option", "value"),
[
("force_render_paths", True),
("force_render_paths", ["guides/"]),
("force_render_paths", {}),
("force_render_paths", 1),
("force_render_paths", None),
("verbose", "true"),
("verbose", 1),
("verbose", []),
("verbose", None),
],
)
def test_rejects_invalid_macros_settings(option: str, value: Any) -> None:
with pytest.raises(ConfigurationError, match=f"macros {option} must be"):
_convert_plugins({"macros": {option: value}})
@pytest.mark.parametrize("plugin", ["autorefs", "material/autorefs"])
@pytest.mark.parametrize(
("option", "value"),