Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lectern/recon.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from lectern.recon_autograde import (fetch_autograde, fetch_autograde_artifact,
scrape_autograde, AutogradeResult)
from lectern.recon_git import recon_git
from lectern.recon_docs import recon_doc
from lectern.recon_docs import recon_doc, resolve_doc_path
from lectern.recon_links import repo_links
from lectern.recon_record import RepoRecord
from lectern.recon_bundle import write_bundle
Expand Down Expand Up @@ -57,7 +57,7 @@ def _default_auto(ref: RepoRef) -> AutogradeResult | None:
if cloned and repo.exists():
ag = do_auto(ref)
git = recon_git(repo, profile=m.git_profile)
docs = {d.label: recon_doc(repo / d.file, label=d.label) for d in m.docs}
docs = {d.label: recon_doc(resolve_doc_path(repo, d.file), label=d.label) for d in m.docs}
else:
ag, git, docs = None, None, {d.label: recon_doc(repo / d.file, label=d.label) for d in m.docs}
commit = ag.commit if ag else None
Expand Down
33 changes: 33 additions & 0 deletions lectern/recon_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,39 @@ class DocRecon:

_FM = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL)


def resolve_doc_path(repo_root: Path, rel_file: str) -> Path:
"""Resolve a manifest deliverable path tolerantly within a repo.

Students name the writeup inconsistently — ``WRITEUP.md``, ``Writeup.md``,
``submission/writeup.md``, ``CECS 378 Lab Writeup.md``. The strict
``repo/<rel_file>`` check marked all of those not-present (a false doc-✗) and
dropped them from the writeup snapshot. Resolve in priority order:

1. exact ``repo/<rel_file>``
2. case-insensitive basename match anywhere in the tree (shallowest wins)
3. a *single* ``.md`` whose name contains the configured stem (e.g. ``…Writeup.md``)

Ambiguous (2+ fuzzy candidates) or not-found returns the canonical path, so
the caller records ``present=False`` exactly as before. ``.git`` is never searched.
"""
repo_root = Path(repo_root)
canonical = repo_root / rel_file
if canonical.exists():
return canonical
target = Path(rel_file).name.lower()
stem = Path(rel_file).stem.lower()
files = [p for p in repo_root.rglob("*")
if p.is_file() and ".git" not in p.relative_to(repo_root).parts]
ci = sorted((p for p in files if p.name.lower() == target),
key=lambda p: len(p.relative_to(repo_root).parts))
if ci:
return ci[0]
fuzzy = [p for p in files if p.suffix.lower() == ".md" and stem in p.name.lower()]
if len(fuzzy) == 1:
return fuzzy[0]
return canonical

def recon_doc(path: Path, *, label: str) -> DocRecon:
p = Path(path)
if not p.exists():
Expand Down
49 changes: 48 additions & 1 deletion tests/test_recon_docs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from lectern.recon_docs import recon_doc, DocRecon
from lectern.recon_docs import recon_doc, DocRecon, resolve_doc_path

WRITEUP = """---
honor: SOLDIER-abc123
Expand Down Expand Up @@ -28,3 +28,50 @@ def test_recon_doc_missing(tmp_path):
d = recon_doc(tmp_path / "nope.md", label="grimoire")
assert d.present is False
assert d.word_count == 0


# --- resolve_doc_path: tolerant deliverable lookup (case/path variants) ---
# Students name the writeup inconsistently; the strict repo/<file> check marked
# real submissions not-present (false doc-✗) and dropped them from the snapshot.

def test_resolve_exact_path_wins(tmp_path):
(tmp_path / "writeup.md").write_text("x")
assert resolve_doc_path(tmp_path, "writeup.md") == tmp_path / "writeup.md"

def test_resolve_case_variant_at_root(tmp_path):
(tmp_path / "WRITEUP.md").write_text("x")
assert resolve_doc_path(tmp_path, "writeup.md") == tmp_path / "WRITEUP.md"

def test_resolve_basename_in_subdir(tmp_path):
(tmp_path / "submission").mkdir()
(tmp_path / "submission" / "writeup.md").write_text("x")
assert resolve_doc_path(tmp_path, "writeup.md") == tmp_path / "submission" / "writeup.md"

def test_resolve_fuzzy_md_containing_stem(tmp_path):
f = tmp_path / "CECS 378 Lab Writeup.md"; f.write_text("x")
assert resolve_doc_path(tmp_path, "writeup.md") == f

def test_resolve_prefers_root_over_subdir(tmp_path):
(tmp_path / "WRITEUP.md").write_text("root")
(tmp_path / "sub").mkdir(); (tmp_path / "sub" / "writeup.md").write_text("deep")
assert resolve_doc_path(tmp_path, "writeup.md") == tmp_path / "WRITEUP.md"

def test_resolve_ignores_git_dir(tmp_path):
g = tmp_path / ".git"; g.mkdir(); (g / "writeup.md").write_text("x")
assert resolve_doc_path(tmp_path, "writeup.md") == tmp_path / "writeup.md" # canonical (not found)

def test_resolve_ambiguous_fuzzy_returns_canonical(tmp_path):
(tmp_path / "my writeup draft.md").write_text("a")
(tmp_path / "final writeup notes.md").write_text("b")
assert resolve_doc_path(tmp_path, "writeup.md") == tmp_path / "writeup.md" # ambiguous -> canonical

def test_resolve_not_found_returns_canonical(tmp_path):
(tmp_path / "README.md").write_text("x")
assert resolve_doc_path(tmp_path, "writeup.md") == tmp_path / "writeup.md"

def test_resolve_then_recon_doc_reads_variant_body(tmp_path):
# the integration the bug was about: a variant-named writeup is discovered
# and its body is available to snapshot.
(tmp_path / "WRITEUP.md").write_text("# Title\nreal content here\n")
d = recon_doc(resolve_doc_path(tmp_path, "writeup.md"), label="writeup")
assert d.present is True and "real content here" in d.body
Loading