diff --git a/src/codealmanac/services/wiki/paths.py b/src/codealmanac/services/wiki/paths.py index 4fe21863..444ba2ce 100644 --- a/src/codealmanac/services/wiki/paths.py +++ b/src/codealmanac/services/wiki/paths.py @@ -52,6 +52,13 @@ def iter_page_paths(almanac_path: Path) -> Iterator[Path]: if not almanac_path.is_dir(): return for path in sorted(almanac_path.rglob("*.md")): + # `rglob` is case-insensitive on Windows and on default macOS volumes, so + # it also matches ".MD" and ".Md". `page_id_for_path` compares the suffix + # exactly, so yielding one of those raises and takes down every command + # that reindexes. Linux never matched them at all, so skipping here is + # what makes the three platforms agree on the same wiki tree. + if path.suffix != ".md": + continue if is_reserved_page_path(almanac_path, path): continue yield path diff --git a/tests/test_wiki_parsing.py b/tests/test_wiki_parsing.py index 097f20a3..4bc114f4 100644 --- a/tests/test_wiki_parsing.py +++ b/tests/test_wiki_parsing.py @@ -1,9 +1,12 @@ +from pathlib import Path + from codealmanac.services.wiki.frontmatter import parse_frontmatter from codealmanac.services.wiki.links import extract_page_links, resolve_page_href from codealmanac.services.wiki.paths import ( escape_glob_meta, iter_page_paths, normalize_reference_path, + page_id_for_path, ) @@ -19,6 +22,37 @@ def test_page_iteration_excludes_repository_manuals(tmp_path): assert tuple(iter_page_paths(almanac_path)) == (page,) +def test_page_iteration_excludes_uppercase_markdown_suffixes(tmp_path): + almanac_path = tmp_path / "almanac" + almanac_path.mkdir(parents=True) + page = almanac_path / "wiki.md" + page.write_text("# Wiki\n", encoding="utf-8") + (almanac_path / "NOTES.MD").write_text("# Notes\n", encoding="utf-8") + (almanac_path / "Mixed.Md").write_text("# Mixed\n", encoding="utf-8") + + assert tuple(iter_page_paths(almanac_path)) == (page,) + + +def test_page_iteration_filters_case_insensitive_glob_matches(tmp_path, monkeypatch): + # `rglob` only returns uppercase suffixes on a case-insensitive filesystem, + # so on Linux the test above cannot reach the filter at all. Feeding the + # match in directly pins the behaviour on every runner, and keeps + # `iter_page_paths` and `page_id_for_path` provably in agreement. + almanac_path = tmp_path / "almanac" + almanac_path.mkdir(parents=True) + page = almanac_path / "wiki.md" + page.write_text("# Wiki\n", encoding="utf-8") + upper = almanac_path / "NOTES.MD" + upper.write_text("# Notes\n", encoding="utf-8") + monkeypatch.setattr(Path, "rglob", lambda self, pattern: iter((page, upper))) + + iterated = tuple(iter_page_paths(almanac_path)) + + assert iterated == (page,) + for path in iterated: + assert page_id_for_path(almanac_path, path) + + def test_frontmatter_uses_pydantic_validated_shape(): parsed = parse_frontmatter( """---