generated from coulomb/repo-seed
160 lines
4.5 KiB
Python
160 lines
4.5 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from markitect_tool.cli import main
|
|
from markitect_tool.ops import (
|
|
IncludeError,
|
|
compose_files,
|
|
resolve_includes,
|
|
transform_markdown,
|
|
)
|
|
|
|
|
|
def test_transform_sets_frontmatter_and_shifts_headings():
|
|
markdown = """---
|
|
title: Original
|
|
---
|
|
|
|
# Intro
|
|
|
|
## Detail
|
|
"""
|
|
|
|
result = transform_markdown(
|
|
markdown,
|
|
set_frontmatter={"status": "draft", "nested": {"owner": "Docs"}},
|
|
heading_delta=1,
|
|
)
|
|
|
|
assert "title: Original" in result.markdown
|
|
assert "status: draft" in result.markdown
|
|
assert "owner: Docs" in result.markdown
|
|
assert "## Intro" in result.markdown
|
|
assert "### Detail" in result.markdown
|
|
assert result.operations == ["set_frontmatter", "shift_headings:1"]
|
|
|
|
|
|
def test_transform_extracts_selector_text():
|
|
markdown = """# Doc
|
|
|
|
## Keep
|
|
|
|
This section should remain.
|
|
|
|
## Drop
|
|
|
|
This section should not remain.
|
|
"""
|
|
|
|
result = transform_markdown(markdown, extract_selector="sections[heading=Keep]")
|
|
|
|
assert result.markdown == "## Keep\n\nThis section should remain."
|
|
assert "Drop" not in result.markdown
|
|
|
|
|
|
def test_compose_files_adds_title_and_separators(tmp_path: Path):
|
|
one = tmp_path / "one.md"
|
|
two = tmp_path / "two.md"
|
|
one.write_text("# One\n\nText one.", encoding="utf-8")
|
|
two.write_text("---\ntitle: Two\n---\n\n# Two\n\nText two.", encoding="utf-8")
|
|
|
|
result = compose_files([one, two], title="Combined", heading_delta=1)
|
|
|
|
assert result.sources == [str(one), str(two)]
|
|
assert result.markdown.startswith("# Combined")
|
|
assert "## One" in result.markdown
|
|
assert "## Two" in result.markdown
|
|
assert "title: Two" not in result.markdown
|
|
assert "\n\n---\n\n" in result.markdown
|
|
|
|
|
|
def test_resolve_includes_supports_comment_marker_selector_and_heading_shift(tmp_path: Path):
|
|
partial = tmp_path / "partial.md"
|
|
partial.write_text(
|
|
"""# Partial
|
|
|
|
## Keep
|
|
|
|
Selected text.
|
|
|
|
## Drop
|
|
|
|
Nope.
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
markdown = '<!-- mkt:include path="partial.md" selector="sections[heading=Keep]" heading_delta="1" -->'
|
|
|
|
result = resolve_includes(markdown, base_dir=tmp_path)
|
|
|
|
assert result.included_paths == [str(partial.resolve())]
|
|
assert "### Keep" in result.markdown
|
|
assert "Selected text" in result.markdown
|
|
assert "Drop" not in result.markdown
|
|
|
|
|
|
def test_resolve_includes_supports_brace_shorthand(tmp_path: Path):
|
|
partial = tmp_path / "partial.md"
|
|
partial.write_text("Included body.", encoding="utf-8")
|
|
|
|
result = resolve_includes("Before\n\n{{include:partial.md}}\n\nAfter", base_dir=tmp_path)
|
|
|
|
assert "Before" in result.markdown
|
|
assert "Included body." in result.markdown
|
|
assert "After" in result.markdown
|
|
|
|
|
|
def test_resolve_includes_rejects_cycles(tmp_path: Path):
|
|
one = tmp_path / "one.md"
|
|
two = tmp_path / "two.md"
|
|
one.write_text("{{include:two.md}}", encoding="utf-8")
|
|
two.write_text("{{include:one.md}}", encoding="utf-8")
|
|
|
|
with pytest.raises(IncludeError, match="Circular include"):
|
|
resolve_includes(one.read_text(encoding="utf-8"), base_dir=tmp_path, current_path=one)
|
|
|
|
|
|
def test_resolve_includes_rejects_paths_outside_base_dir(tmp_path: Path):
|
|
outside = tmp_path.parent / "outside.md"
|
|
outside.write_text("Nope", encoding="utf-8")
|
|
|
|
with pytest.raises(IncludeError, match="escapes base directory"):
|
|
resolve_includes("{{include:../outside.md}}", base_dir=tmp_path)
|
|
|
|
|
|
def test_mkt_transform_writes_markdown(tmp_path: Path):
|
|
source = tmp_path / "doc.md"
|
|
source.write_text("# One\n", encoding="utf-8")
|
|
|
|
result = CliRunner().invoke(
|
|
main, ["transform", str(source), "--heading-delta", "1", "--set", "status=draft"]
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "status: draft" in result.output
|
|
assert "## One" in result.output
|
|
|
|
|
|
def test_mkt_compose_writes_output_file(tmp_path: Path):
|
|
one = tmp_path / "one.md"
|
|
output = tmp_path / "out.md"
|
|
one.write_text("# One\n", encoding="utf-8")
|
|
|
|
result = CliRunner().invoke(main, ["compose", str(one), "--title", "Combined", "--output", str(output)])
|
|
|
|
assert result.exit_code == 0
|
|
assert result.output == ""
|
|
assert output.read_text(encoding="utf-8").startswith("# Combined")
|
|
|
|
|
|
def test_mkt_include_reports_errors(tmp_path: Path):
|
|
source = tmp_path / "doc.md"
|
|
source.write_text("{{include:missing.md}}", encoding="utf-8")
|
|
|
|
result = CliRunner().invoke(main, ["include", str(source)])
|
|
|
|
assert result.exit_code == 1
|
|
assert "Included file not found" in result.output
|