Resolve companion ranges across filename case differences - #19
Draft
imnasnainaec wants to merge 3 commits into
Draft
Resolve companion ranges across filename case differences#19imnasnainaec wants to merge 3 commits into
imnasnainaec wants to merge 3 commits into
Conversation
A LIFT folder written on Windows can spell its pair inconsistently — Dict.LIFT beside Dict.lift-ranges, or the reverse — and load fine there, because the filesystem folds case. On Linux the sibling candidate is built from the .lift's own suffix, so it missed, the companion was skipped without a word, and every range it defined went absent. Candidates that match no file exactly now fall back to one whose name differs only in case. The fallback is reached only after an exact miss, so a case-folding filesystem never enters it and behaves as before; a case-sensitive one gets one directory read per folder, cached across the candidate list. Where several names fold together the lexicographically first wins. The choice is arbitrary but fixed, which matters more than which file it picks: directory order varies between filesystems and runs, and a companion that loads differently on consecutive reads would be worse than one that never loads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0.1.0 has not shipped, so there is no released behavior for an Unreleased entry to be fixing — the tolerance is simply part of what companion discovery does in the first release. Fold it into that bullet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The directory listing the fallback builds called its files "entries", the word this module uses for a LIFT <entry> everywhere else — the same collision that keeps byte regions from being called spans. Name them files. Spell the surrounding prose the way the rest of the package does: a fallback that runs rather than fires, a name that matched no file rather than missed, a helper named for the filesystem it probes rather than abbreviating it, and fixture names deliberately not taken from the corpus file. Unpack the two densest clauses so each reads in one pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
imnasnainaec
force-pushed
the
ranges-companion-filename-case
branch
from
August 12, 2026 19:22
fb6bb44 to
dededeb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A LIFT folder written on Windows can spell its pair inconsistently —
Dict.LIFTbesideDict.lift-ranges, or the reverse — and load fine there, because the filesystem folds case. On Linux the sibling candidate is built from the.lift's own suffix (_model.py:408), so it missed, the companion was skipped silently, and every range it defined went absent.Not zip-specific:
sil_lift.load("/data/Foo.LIFT")on Linux hits it with no archive involved, sinceparse_documentnever inspects the extension.Approach
A candidate that matches no file exactly now falls back to one whose name differs only in case.
range/@hrefentries doesn't re-scan.The tie-break
Where several names fold together, the lexicographically first wins. Arbitrary, but fixed — and fixed is the property that matters: directory order varies between filesystems and between runs, and a companion that resolved differently on consecutive reads would be worse than one that never resolved. Documented in
_existing_fileand asserted by a test.Open to a different rule if you'd rather have one — e.g. preferring an exact case match on the stem — but I'd want it to stay deterministic.
Tests
Four in
test_ranges_folder.py: uppercase.lift, uppercase companion, the tie-break, and a negative case pinning that the fallback doesn't reach past a folder for a name that isn't in it. The first two pass on every platform (they resolve via the old path where the filesystem folds); the tie-break needs both spellings to coexist, so it skips off Linux — theubuntu-latestmatrix leg is where it bites.python scripts/check.pygreen: 380 passed, 1 skipped (that tie-break test, on Windows), 94% coverage.Draft: opening for a read on the tie-break rule before marking ready.
🤖 Generated with Claude Code
Devin review: https://app.devin.ai/review/sillsdev/python-sil-lift/pull/19
This change is