qalmsw / tests /test_includes.py
pebaryan
Resolve \input{}/\include{} and discover inline bibliographies
bf81e27
Raw
History Blame Contribute Delete
4.18 kB
from pathlib import Path
from qalmsw.document import Document
from qalmsw.parse import resolve_includes
def _write(path: Path, content: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
def test_no_includes_is_passthrough(tmp_path: Path):
main = tmp_path / "main.tex"
body = "\\begin{document}\nHello world.\n\\end{document}\n"
_write(main, body)
source, line_map = resolve_includes(main)
assert source == body
assert len(line_map) == body.count("\n")
assert all(entry.file == main for entry in line_map)
assert [e.line for e in line_map] == list(range(1, len(line_map) + 1))
def test_input_inlined(tmp_path: Path):
main = tmp_path / "main.tex"
intro = tmp_path / "sections" / "intro.tex"
_write(intro, "Intro line one.\nIntro line two.\n")
_write(main, "\\begin{document}\n\\input{sections/intro}\n\\end{document}\n")
source, line_map = resolve_includes(main)
assert "Intro line one." in source
assert "Intro line two." in source
# The line containing "Intro line one." should map back to intro.tex:1.
lines = source.split("\n")
idx = lines.index("Intro line one.")
assert line_map[idx].file == intro
assert line_map[idx].line == 1
idx2 = lines.index("Intro line two.")
assert line_map[idx2].file == intro
assert line_map[idx2].line == 2
def test_include_adds_tex_suffix(tmp_path: Path):
main = tmp_path / "main.tex"
sub = tmp_path / "part.tex"
_write(sub, "Sub content.\n")
_write(main, "\\input{part}\n")
source, line_map = resolve_includes(main)
assert "Sub content." in source
lines = source.split("\n")
idx = lines.index("Sub content.")
assert line_map[idx].file == sub
def test_missing_include_is_tolerated(tmp_path: Path):
main = tmp_path / "main.tex"
_write(main, "Before\n\\input{does_not_exist}\nAfter\n")
source, line_map = resolve_includes(main)
assert "Before" in source
assert "After" in source
assert "qalmsw" in source # placeholder comment emitted
def test_cycle_detected(tmp_path: Path):
a = tmp_path / "a.tex"
b = tmp_path / "b.tex"
_write(a, "A\n\\input{b}\n")
_write(b, "B\n\\input{a}\n")
source, _ = resolve_includes(a)
# Should not infinite-loop; cycle back to a is refused.
assert source.count("cycle detected") >= 1
def test_nested_includes(tmp_path: Path):
main = tmp_path / "main.tex"
mid = tmp_path / "mid.tex"
leaf = tmp_path / "leaf.tex"
_write(leaf, "Leaf content.\n")
_write(mid, "Mid content.\n\\input{leaf}\n")
_write(main, "\\input{mid}\n")
source, line_map = resolve_includes(main)
assert "Mid content." in source
assert "Leaf content." in source
lines = source.split("\n")
assert line_map[lines.index("Leaf content.")].file == leaf
assert line_map[lines.index("Mid content.")].file == mid
def test_input_in_comment_not_followed(tmp_path: Path):
main = tmp_path / "main.tex"
_write(main, "Body\n% \\input{ignored}\n")
source, _ = resolve_includes(main)
# The commented line is kept verbatim (we preserve it as-is); we just don't follow it.
assert "ignored" in source # text preserved
# But no separate file was created, so no expansion occurred; line count matches input.
assert source.count("\n") == 2
def test_document_load_attributes_paragraphs_to_origin(tmp_path: Path):
main = tmp_path / "main.tex"
intro = tmp_path / "intro.tex"
_write(intro, "Paragraph from intro with enough words.\n")
_write(
main,
"\\begin{document}\n"
"\\input{intro}\n"
"\n"
"Paragraph in main.\n"
"\\end{document}\n",
)
doc = Document.load(main)
texts = [p.text for p in doc.paragraphs]
assert "Paragraph from intro with enough words." in texts
assert "Paragraph in main." in texts
from_intro = next(p for p in doc.paragraphs if "from intro" in p.text)
from_main = next(p for p in doc.paragraphs if "in main" in p.text)
assert from_intro.file == intro
assert from_intro.start_line == 1
assert from_main.file == main