Skip to content

Media hrefs resolve case-sensitively: Windows-authored folders get false missing-media on Linux #34

Description

@imnasnainaec

Lexicon.missing_media() resolves <media> and <illustration> hrefs with a plain is_file(), so a folder authored on Windows reports files as missing when it is read on a case-sensitive filesystem.

Dict.lift            <illustration href="pictures\SDD.PNG"/>
pictures/sdd.png

Loads clean on Windows; on Linux sil-lift validate warns missing-media, and --strict fails the run. The image is right there.

The lookup is the two-candidate probe in missing_media() (src/sil_lift/_model.py):

candidates = [base / relative, base / subfolder[ref.kind] / relative]
if not any(candidate.is_file() for candidate in candidates):

This is the same Windows-authored/Linux-read mismatch that #19 fixed for .lift-ranges companions, in the one other place the library touches the filesystem.

Why this is not just "call _existing_file here too"

For a companion, folding the name is unambiguously right: resolve it or silently lose every range it defines. For media the opposite argument has real force. If an illustration href says SDD.PNG and the file is sdd.png, a web export, an APK build, or anything else serving that folder from a case-sensitive host will 404. A validator that silently resolves the mismatch reports a clean bill for a dictionary that is genuinely broken downstream — hiding the defect the check exists to surface.

Options:

  • A. Fold silently. ~6 source lines. Consistent with companion resolution, kills the false positives, hides genuine portability defects.
  • B. Fold, but report the mismatch as its own finding (media-case-mismatch, warning). missing-media goes back to meaning "no such file under any spelling"; the new code says "found, but only case-insensitively — this will not survive a case-sensitive host." Costs a new problem code, which is a SemVer-covered interface here.
  • C. Leave it, document it. The status quo is at least honest about the portability problem, just unhelpfully worded and noisy.

B looks right, but it is a real interface decision and wants a deliberate call rather than a drive-by.

Two things to settle either way

  • Only the final path component folds. _existing_file searches one directory listing for a name; it does not fold directory components. Media hrefs routinely include pictures/ and audio/, so Pictures\x.png against an on-disk pictures/ would still misreport. Whether to fold directory components is more pressing here than it was for companions, where hrefs are basenames or same-folder relatives.
  • Cost. missing_media() iterates every media ref in the document — thousands in a real dictionary — and media folders are large. _existing_file's listings cache has to be threaded across the whole loop so it is one directory read per folder per call, not one per reference.

Rough size

Option A: ~50 lines over 3 files. Option B: ~200-250 lines over ~10 — _model.py, _validate.py, _cli.py, docs/en/guides/{validate,cli,lift-export-interop}.md, a negative corpus fixture plus its PROVENANCE.md entry, test_validate.py, test_cli.py, and CHANGELOG.md.

Split out of review discussion on #19, which deliberately kept its scope to companion resolution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions