diff --git a/great_docs/assets/post-render.py b/great_docs/assets/post-render.py index d3a7ceb..865569a 100644 --- a/great_docs/assets/post-render.py +++ b/great_docs/assets/post-render.py @@ -4380,11 +4380,6 @@ def generate_markdown_pages(): if not html_file.endswith(".html"): continue - # Skip skill.html as the raw skill.md is served directly as a resource - # and should not be overwritten by a generated .md from rendered HTML - if rel == "skill.html" or rel == os.path.join("skill.html"): - continue - try: with open(html_file, "r", encoding="utf-8") as f: content = f.read() @@ -4735,28 +4730,6 @@ def _param_dl_to_html(m): print("##GD:PASS:Markdown pages generated", flush=True) -# ── Clean up skill.html ────────────────────────────────────────────────────── -# Quarto renders skill.md → skill.html despite the !skill.md exclusion in -# project.render when skill.md is also in project.resources. The raw skill.md -# is served as-is; the rendered skills.html page is the intended viewer. -# Delete the spurious skill.html so it doesn't confuse users or agents. -_skill_html = os.path.join("_site", "skill.html") -if os.path.exists(_skill_html): - os.remove(_skill_html) - print("Removed spurious _site/skill.html (raw skill.md is served directly)") - -# Fix links in skills.html that Quarto rewrote from skill.md → skill.html -_skills_page = os.path.join("_site", "skills.html") -if os.path.exists(_skills_page): - with open(_skills_page, "r", encoding="utf-8") as f: - _skills_content = f.read() - _fixed = _skills_content.replace('href="./skill.html"', 'href="skill.md"') - if _fixed != _skills_content: - with open(_skills_page, "w", encoding="utf-8") as f: - f.write(_fixed) - print("Fixed skill.md link in skills.html") - - # ══════════════════════════════════════════════════════════════════════════════ # STRIP COLGROUP TAGS FROM TABLES # ══════════════════════════════════════════════════════════════════════════════ diff --git a/great_docs/core.py b/great_docs/core.py index 93e2e58..1949052 100644 --- a/great_docs/core.py +++ b/great_docs/core.py @@ -11,6 +11,21 @@ from .config import Config from ._subprocess import TEXT_MODE_KWARGS +# Quarto's default input file types, enumerated as render globs. Used to seed +# `project.render` whenever we also add `!` exclusions. A recursive `**` glob +# overpowers any negation that follows it (so `!skill.md` would be ignored), +# whereas these per-extension globs still match recursively while letting the +# exclusions take effect. +_QUARTO_RENDER_GLOBS = [ + "*.qmd", + "*.md", + "*.markdown", + "*.rmd", + "*.Rmd", + "*.rmarkdown", + "*.ipynb", +] + def _patch_griffe(): """Ensure griffe has CyclicAliasError and AliasResolutionError at top level. @@ -3602,7 +3617,7 @@ def _add_project_resources( if render_excludes: if "render" not in project: - project["render"] = ["**"] + project["render"] = list(_QUARTO_RENDER_GLOBS) render = project["render"] if isinstance(render, str): render = [render] # pragma: no cover @@ -11164,10 +11179,12 @@ def _update_quarto_config(self) -> None: if "skill.md" not in config["project"]["resources"]: config["project"]["resources"].append("skill.md") - # Exclude skill.md from rendering (Quarto renders .md by default) - # The render list needs "**" first (render everything), then exclusions + # Exclude skill.md from rendering (Quarto renders .md by default). + # The render list enumerates Quarto's default input globs before the + # exclusions; a recursive `**` would overpower the `!skill.md` + # negation and render skill.md anyway (see issue #228). if "render" not in config["project"]: - config["project"]["render"] = ["**"] + config["project"]["render"] = list(_QUARTO_RENDER_GLOBS) if "!skill.md" not in config["project"]["render"]: config["project"]["render"].append("!skill.md") diff --git a/tests/test_great_docs.py b/tests/test_great_docs.py index 713aa80..6a6fa0f 100644 --- a/tests/test_great_docs.py +++ b/tests/test_great_docs.py @@ -3256,6 +3256,34 @@ def test_assets_config_update_only_when_copied(): assert "assets/**" not in config["project"]["resources"] +def test_skill_render_exclusion_uses_enumerated_globs(): + """_update_quarto_config() excludes skill.md without a recursive ** glob. + + A recursive "**" in the render list overpowers any "!" negation, so Quarto + would render skill.md → skill.html anyway (see issue #228). The render list + must enumerate Quarto's default input globs instead. + """ + with tempfile.TemporaryDirectory() as tmp_dir: + project_path = Path(tmp_dir) + + pyproject = project_path / "pyproject.toml" + pyproject.write_text('[project]\nname = "test"\nversion = "0.1.0"') + + docs = GreatDocs(project_path=tmp_dir) + docs.project_path.mkdir(parents=True, exist_ok=True) + docs._update_quarto_config() + + quarto_yml = docs.project_path / "_quarto.yml" + with open(quarto_yml, "r") as f: + config = read_yaml(f) + + render = config["project"]["render"] + assert "!skill.md" in render + assert "**" not in render + assert "*.qmd" in render + assert "*.md" in render + + def test_user_guide_config_option_overrides_default_dir(): """Test that user_guide config option takes precedence over user_guide/ directory.""" with tempfile.TemporaryDirectory() as tmp_dir: @@ -9416,6 +9444,10 @@ def test_process_custom_pages_passthrough_and_raw(): assert "custom/app.js" in resources assert "custom/widget.html" in resources assert "!custom/widget.html" in render + # The render list must enumerate Quarto's default input globs rather + # than a recursive "**", which would overpower the "!" exclusions. + assert "**" not in render + assert "*.qmd" in render def test_process_custom_pages_missing_dir():