bindery-cli 0.43.0__tar.gz → 0.44.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bindery_cli-0.44.1/.github/FUNDING.yml +2 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/.gitignore +1 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/CLAUDE.md +8 -2
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/PKG-INFO +1 -1
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/patchnotes.md +17 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/pyproject.toml +1 -1
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/roadmap.md +90 -9
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/spec.md +25 -7
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/__init__.py +1 -1
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/audit.py +301 -22
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/cli.py +16 -1
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/epub.py +244 -16
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/pagination.py +8 -4
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/transforms.py +7 -2
- bindery_cli-0.44.1/tests/test_cover_tocdrift.py +207 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_epub.py +17 -7
- bindery_cli-0.44.1/tests/test_space_renames.py +263 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/uv.lock +1 -1
- bindery_cli-0.43.0/testing_facility/books_to_fix/.gitkeep +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/.github/workflows/ci.yml +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/.github/workflows/publish.yml +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/AGENTS.md +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/LICENSE +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/README.md +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/docs/screenshots/library-sweep.png +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/logo.svg +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/plugin/__init__.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/project.done +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/run_tests.sh +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/scripts/FastSweep.java +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/scripts/build_plugin.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/scripts/fast_sweep.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/__main__.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/library.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/reserialize.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/validate.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/src/bindery/watermark.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_audit.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_cli.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_library.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_matcher_hardening.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_pagination.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_plugin.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_reserialize.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_transforms.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_validate.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_version.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_watermark.py +0 -0
- {bindery_cli-0.43.0 → bindery_cli-0.44.1}/tests/test_xml_hardening.py +0 -0
|
@@ -37,7 +37,13 @@ Born from the 2026 library audit (see the user memory `calibre-library-epubcheck
|
|
|
37
37
|
`--unwrap-block-in-inline`, `--strip-invalid-value`, `--unwrap-illegal-tags`,
|
|
38
38
|
`--prune-missing-resources`, `--strip-broken-anchors`, `--encode-url-spaces`,
|
|
39
39
|
`--fix-container`, `--fix-media-types`, `--fix-cover`; transforms.py, threaded
|
|
40
|
-
through epub.py)
|
|
40
|
+
through epub.py). Since v0.44.0 `--encode-url-spaces` also RENAMES the
|
|
41
|
+
archive entries whose names carry raw spaces to their underscore spellings
|
|
42
|
+
and rewrites every reference (OPF, NCX, content, CSS `url()`); underscore,
|
|
43
|
+
never percent-encoding (epubcheck decodes references before entry lookup,
|
|
44
|
+
verified against 5.3), ambiguous renames refused per entry (`space_rename_map`),
|
|
45
|
+
accepted under `no_worse` because PKG-010 sits on the warning axis the gate
|
|
46
|
+
does not measure:
|
|
41
47
|
they alter markup structure or fabricate minimal content. v0.14–v0.16 ran these unconditionally, which broke this rule;
|
|
42
48
|
v0.17.0 restored it. `--unwrap-illegal-tags` additionally protects any illegal-tag
|
|
43
49
|
name that an EPUB stylesheet styles as an element selector (`css_protected_tags`,
|
|
@@ -86,7 +92,7 @@ Born from the 2026 library audit (see the user memory `calibre-library-epubcheck
|
|
|
86
92
|
- `src/bindery/pagination.py`: the opt-in lossy page-number strip (runhead detection, page-layer decision, block-centric removal/merge, safety nets).
|
|
87
93
|
- `src/bindery/watermark.py`: the opt-in lossy watermark strip (anchored and anchorless signature removal).
|
|
88
94
|
- `src/bindery/reserialize.py`: structural repair via `html5lib`.
|
|
89
|
-
- `src/bindery/audit.py`: read-only body-text audits (`content`, `pagenumbers`, `emptytext`, `ocr`, `monolithic` since v0.21.0 with `--max-doc-chars N`, and `completeness` since v0.36.0) behind the `audit` subcommand (v0.15.0). Since v0.18.0 library mode resolves EPUB paths through `cquarry.get_format_path()` and can apply a tag to flagged books via `cquarry.write.WritableCalibreDB` (only with explicit `--tag`; the only sanctioned write path). Since v0.19.0 `audit --id BOOK_ID` audits a single library book through cquarry's single-entity `get_book()` fetch (no library-wide cache; supports `--tag`; incompatible with the directory argument; comma lists since v0.23.0). Since v0.22.0 every archive entry is fully read (CRC + decompression) before analysis (a damaged entry reports its own CORRUPT verdict instead of feeding emptytext) and manifest/NCX references to absent files classify as `convention` (bloated ToC, consecutive span) or `fragment` (broken span). Hazard: the scan loop reuses `tag` as its per-book display column, so the `--tag` argument is captured as `audit_tag` at function entry; do not collapse them again. Since v0.29.0 `audit --json FILE` writes per-file analyzer verdicts in the `library --json` shape (directory, library, and single-book modes; with `--id`, exactly one id; emptytext omitted from a record when the archive verdict owns the body-text story). Since v0.36.0 the completeness analyzer (Phase 15) reports the phase-1 spot-check per book: spine/prose-doc counts (prose = >= 400 visible chars), first/middle/last prose-doc opening/closing excerpts, trailing-ToC classification (`_trailing_toc`: short link lines dominate, no paragraph-length blocks), and the unreadable fraction. It is ADVISORY by contract (problem always False; ADVISORY on a trailing ToC or unreadable fraction >= 10%), never moves the exit code, and is skipped like emptytext when the archive verdict owns the book. Since v0.38.0 the archive verdict distinguishes font obfuscation from DRM (`OBFUSCATION_ALGOS`: IDPF 2008/embedding + both Adobe URIs; readable obfuscated entries are the benign OBFUSCATED advisory, an unreadable one is CORRUPT, and only non-obfuscation algorithms give ENCRYPTED). Since v0.43.0 untrusted metadata XML is parsed through `_safe_xml_parse` (DOCTYPE/ENTITY declarations refused before xml.etree sees them; the stdlib shape of defusedxml's entity protection) and single entries above `_MAX_ENTRY_BYTES` (32 MB) are refused before decompression; a refused metadata file returns the shell book. Since v0.40.0 the vir-tui console renderer loads through the module-level `_LazyUI` proxy (a plain `import vir_tui as ui` at module level would make 3.12/3.13 installs unimportable now that the stack is marker-gated; LOAD_GLOBAL never consults module `__getattr__`, so a proxy object is the lazy shape that works).
|
|
95
|
+
- `src/bindery/audit.py`: read-only body-text audits (`content`, `pagenumbers`, `emptytext`, `ocr`, `monolithic` since v0.21.0 with `--max-doc-chars N`, and `completeness` since v0.36.0) behind the `audit` subcommand (v0.15.0). Since v0.18.0 library mode resolves EPUB paths through `cquarry.get_format_path()` and can apply a tag to flagged books via `cquarry.write.WritableCalibreDB` (only with explicit `--tag`; the only sanctioned write path). Since v0.19.0 `audit --id BOOK_ID` audits a single library book through cquarry's single-entity `get_book()` fetch (no library-wide cache; supports `--tag`; incompatible with the directory argument; comma lists since v0.23.0). Since v0.22.0 every archive entry is fully read (CRC + decompression) before analysis (a damaged entry reports its own CORRUPT verdict instead of feeding emptytext) and manifest/NCX references to absent files classify as `convention` (bloated ToC, consecutive span) or `fragment` (broken span). Hazard: the scan loop reuses `tag` as its per-book display column, so the `--tag` argument is captured as `audit_tag` at function entry; do not collapse them again. Since v0.29.0 `audit --json FILE` writes per-file analyzer verdicts in the `library --json` shape (directory, library, and single-book modes; with `--id`, exactly one id; emptytext omitted from a record when the archive verdict owns the body-text story). Since v0.44.0 two more advisory analyzers ride the single pass: `cover` (the EPUB3 `properties~="cover-image"` slice of the ruled hybrid: dangling EPUB2 meta, EPUB3 declaration, cover-file presence) and `tocdrift` (the NCX navMap vs EPUB3 nav toc structural diff, fragments included; audit-only, ToC synthesis out of charter permanently); the loader now exposes the parsed OPF, the NCX text, and the nav HTML on `Book`. Since v0.36.0 the completeness analyzer (Phase 15) reports the phase-1 spot-check per book: spine/prose-doc counts (prose = >= 400 visible chars), first/middle/last prose-doc opening/closing excerpts, trailing-ToC classification (`_trailing_toc`: short link lines dominate, no paragraph-length blocks), and the unreadable fraction. It is ADVISORY by contract (problem always False; ADVISORY on a trailing ToC or unreadable fraction >= 10%), never moves the exit code, and is skipped like emptytext when the archive verdict owns the book. Since v0.38.0 the archive verdict distinguishes font obfuscation from DRM (`OBFUSCATION_ALGOS`: IDPF 2008/embedding + both Adobe URIs; readable obfuscated entries are the benign OBFUSCATED advisory, an unreadable one is CORRUPT, and only non-obfuscation algorithms give ENCRYPTED). Since v0.43.0 untrusted metadata XML is parsed through `_safe_xml_parse` (DOCTYPE/ENTITY declarations refused before xml.etree sees them; the stdlib shape of defusedxml's entity protection) and single entries above `_MAX_ENTRY_BYTES` (32 MB) are refused before decompression; a refused metadata file returns the shell book. Since v0.40.0 the vir-tui console renderer loads through the module-level `_LazyUI` proxy (a plain `import vir_tui as ui` at module level would make 3.12/3.13 installs unimportable now that the stack is marker-gated; LOAD_GLOBAL never consults module `__getattr__`, so a proxy object is the lazy shape that works).
|
|
90
96
|
- `src/bindery/epub.py`: archive rewrite, NCX uid sync, RepairReport, mismatch detection, and the opt-in structural-repair plumbing (including the CSS precondition scan). Since v0.38.0/v0.39.0 (Phase 16): `generate_container` + the `--fix-container` gateway (insert after mimetype when missing, replace in-loop when stale; constant-epoch timestamp keeps bytes deterministic), `prune_dangling_edges` (prune_missing_manifest_items returns the pruned ids and the edges that pointed at them (spine@toc, media-overlay, fallback, EPUB2 cover meta) are rewritten so pruning cannot manufacture the regression the gate rejects), and `fix_manifest_media_types` (extension map + magic-byte confirmation via a `peek` callable over the open source zip; jpg/png/gif only), and `fix_cover_meta` (the ruling's deterministic half: dangling EPUB2 cover meta re-pointed from the guide's cover reference when exactly one manifest item carries that file, removed otherwise; accepted under no_worse like the lossy strips because cover wiring is invisible to epubcheck).
|
|
91
97
|
- `src/bindery/validate.py`: epubcheck wrapper, the `gate` (improvement) and `no_worse` (no-regression, for the lossy strips) acceptance bars.
|
|
92
98
|
- `src/bindery/library.py`: Calibre walk, atomic replace, backups, and native format installation. Since v0.19.0 `CalibreIdResolver` resolves the book id from `metadata.db` through cquarry (one lazy path→id map per run; since v0.20.0 the map comes from `CalibreDB.format_path_index()`, re-normalized with `resolve().lower()` for the resolver's symlinked-directory and case-insensitive matching); the `(id)` directory-name regex is only the no-catalog fallback. Since v0.24.0 `install_format()` places the repaired file atomically and updates the `data` row through `cquarry.write.WritableCalibreDB` (since v0.31.0 via `set_format`, cquarry 1.17's sanctioned remove+add in one transaction); the external `calibredb` CLI is gone. Since v0.32.0 a guessed id drives a row update only when metadata.db corroborates it (`_verify_guess`: books row exists, file in the book's own directory, catalogued `data.name` when a row exists); anything else saves in place and leaves the catalog untouched: never reintroduce directory-name guessing as the primary source: renamed/mismatched directories would replace the wrong book. Since 2026-09-15 `make_backup` takes the opt-in `--backup-keep N` ring: at most N backup files per book, the author original `.bak` never deleted, newest content at the highest name; default remains an unbounded rotation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: bindery-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.44.1
|
|
4
4
|
Summary: Audit EPUB body text and repair broken EPUBs with deterministic, epubcheck-gated fixes; atomic in-place replacement in a Calibre library.
|
|
5
5
|
Project-URL: Homepage, https://github.com/VirInvictus/bindery-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/VirInvictus/bindery-cli
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
# bindery-cli Patch Notes
|
|
2
|
+
## v0.44.1 (2026-09-18)
|
|
3
|
+
|
|
4
|
+
### Doctor names its interpreter
|
|
5
|
+
|
|
6
|
+
* **`bindery doctor` prints `sys.executable` on its python line**, in the epubcheck line's `version [path]` shape. The version-only line hid which environment was probed, so an html5lib "not installed" read as wrong when the module sat in the system interpreter: a uv tool venv (`include-system-site-packages = false`) genuinely cannot see system site-packages, and `find_spec` only ever consults the running interpreter. The path makes the install shape self-diagnosing.
|
|
7
|
+
* Repo housekeeping with no runtime effect: `FUNDING.yml` (GitHub Sponsors + Liberapay).
|
|
8
|
+
## v0.44.0 (2026-09-17)
|
|
9
|
+
|
|
10
|
+
### The wave-2 batch: PKG-010 entry renames, the cover and ToC-drift audit slices, and the roman grammar unified
|
|
11
|
+
|
|
12
|
+
* **`--encode-url-spaces` gained its rename half**, the biggest real-prevalence repair class left (PKG-010: 8,237 occurrences in 284 books at the 2026-09-17 refresh). Archive entries whose names carry raw spaces are renamed to their underscore spellings and every reference is rewritten (OPF, NCX, content `src`/`href`, stylesheet `url()`). The design was decided by experiment, not by the roadmap box's plan: epubcheck 5.3 DECODES references before entry lookup, so percent-encoded entries are unfindable by the encoded references pointing at them (all four raw/percent/underscore combinations built and checked); underscore on both sides resolves and clears PKG-010. Ambiguous renames are refused per entry (a target that already exists, two names converging, a duplicated archive name), a book with any non-UTF-8 document refuses all of its renames, a renamed OPF regenerates a stale container, and the acceptance rides `no_worse` because the gain sits on the warning axis the improvement gate does not measure (same bar as `--fix-cover`).
|
|
13
|
+
* **The matcher fix that explains Theaetetus** (the 2026-09-16 hand-rescue): the encode pass's value class `[^"']` stopped at apostrophes inside double-quoted hrefs, so the 270 raw-space OPF hrefs never matched and the fatal survived. The matcher now takes the tempered-quote idiom (possessive, house style), and an apostrophe in a double-quoted filename is ordinary.
|
|
14
|
+
* **`bindery audit cover`** (advisory, the EPUB3 half of the ruled hybrid): dangling EPUB2 `<meta name="cover">` (the `--fix-cover` class), the EPUB3 `properties~="cover-image"` declaration, and whether the named cover file exists. **`bindery audit tocdrift`**: the structural diff between the NCX navMap and the EPUB3 nav toc, keyed on the full target including fragments -- entries each side is missing plus label mismatches. Both never flag a book and never move the exit code; ToC synthesis stays out of the repair charter permanently. The loader exposes the parsed OPF, the NCX text, and the nav HTML on `Book`.
|
|
15
|
+
* **The audit-refresh deep-dive ran** (read-only FastSweep extract over the full 5,228-book library; raw report in the facility): RSC-005 164,302 -> 13,782 occurrences, RSC-007 3,219 -> 14,162 in 604 books (the classics-wave imports), RSC-020 9,953 in 337 books, PKG-010 8,237 in 284 books (population flat: the rename class was still live, hence this ship).
|
|
16
|
+
* **The roman grammar is one copy again**: pagination.py is the canonical home (it vendors into the plugin; audit does not), audit.py imports `ROMAN_RE`/`ROMAN_MAX`/`roman_value`, and the two byte-identical copies cannot drift. The RepairFlags dataclass refactor stays boxed with its reason (pure churn on the signatures this release touched).
|
|
17
|
+
* Suite: 497 -> 521 tests (tests/test_space_renames.py, tests/test_cover_tocdrift.py, and the updated `--encode-url-spaces` end-to-end pins).
|
|
18
|
+
|
|
2
19
|
## v0.43.0 (2026-09-16)
|
|
3
20
|
|
|
4
21
|
### The stdlib XML hardening: untrusted metadata is bounded and entity-refused, no dependency
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bindery-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.44.1"
|
|
8
8
|
description = "Audit EPUB body text and repair broken EPUBs with deterministic, epubcheck-gated fixes; atomic in-place replacement in a Calibre library."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
classifiers = [
|
|
@@ -1812,25 +1812,72 @@ four interpreter families.
|
|
|
1812
1812
|
|
|
1813
1813
|
### Reopened / boxed by the final blitz (2026-09-15)
|
|
1814
1814
|
|
|
1815
|
-
- [
|
|
1815
|
+
- [x] **--encode-url-spaces entry renames** (PKG-010, top-6 warning
|
|
1816
1816
|
class, 280 books): rename archive entries whose filenames carry
|
|
1817
1817
|
raw spaces, rewriting every reference; the reference half
|
|
1818
1818
|
shipped v0.28.0. Biggest real-prevalence repair class left; the
|
|
1819
|
-
gate must re-measure the whole book.
|
|
1820
|
-
|
|
1819
|
+
gate must re-measure the whole book. *(SHIPPED 2026-09-17,
|
|
1820
|
+
v0.44.0 -- with the plan CHANGED by experiment. epubcheck 5.3
|
|
1821
|
+
DECODES references before entry lookup, so percent-encoded entry
|
|
1822
|
+
names are unfindable by the encoded refs pointing at them
|
|
1823
|
+
(variants built and checked: raw/raw = PKG-010 only,
|
|
1824
|
+
pct/pct = broken lookups, pct-raw = broken, underscore/underscore
|
|
1825
|
+
= clean); renames therefore use the underscore spelling, and
|
|
1826
|
+
references are rewritten to the identical form. Ambiguous renames
|
|
1827
|
+
are refused per entry (target exists, two names converging, a
|
|
1828
|
+
duplicated archive name); a book with any non-UTF-8 document
|
|
1829
|
+
refuses all renames. The rename rides the existing
|
|
1830
|
+
--encode-url-spaces flag and is accepted under no_worse (its gain
|
|
1831
|
+
sits on the warning axis the gate does not measure; same bar as
|
|
1832
|
+
fix-cover). The refresh also found the real reason Theaetetus's
|
|
1833
|
+
OPF fatal survived the 0.28 reference half: the value class
|
|
1834
|
+
[^"'] stopped at apostrophes inside double-quoted hrefs; the
|
|
1835
|
+
matcher now takes the tempered-quote idiom. Refresh counts:
|
|
1836
|
+
PKG-010 at 8,237 occurrences in 284 books (was 4,112 in ~280;
|
|
1837
|
+
the class persisted). Pinned by tests/test_space_renames.py
|
|
1838
|
+
plus the updated test_epub pins.)*
|
|
1839
|
+
- [x] **Cover advisory analyzer**: the EPUB3 `properties="cover-image"`
|
|
1821
1840
|
audit slice completing the ruled hybrid (21 dangling EPUB2 metas
|
|
1822
|
-
and 3 absent cover files measured behind it).
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1841
|
+
and 3 absent cover files measured behind it). *(SHIPPED 2026-09-17,
|
|
1842
|
+
v0.44.0: `bindery audit cover` reads the package's own wiring --
|
|
1843
|
+
dangling EPUB2 meta, EPUB3 property declaration, cover-file
|
|
1844
|
+
presence -- advisory by contract like completeness; the loader
|
|
1845
|
+
now exposes the parsed OPF.)*
|
|
1846
|
+
- [x] **Audit-refresh deep-dive**: per-class deltas vs the 2026-09-12
|
|
1847
|
+
baselines; sizes the two boxes above for free. *(DONE 2026-09-17:
|
|
1848
|
+
read-only FastSweep extract over the full 5,228-book library;
|
|
1849
|
+
raw report at testing_facility/top500candidates/
|
|
1850
|
+
extract-2026-09-17.txt. RSC-005 164,302 -> 13,782 occurrences
|
|
1851
|
+
(the repair waves did land); RSC-007 3,219 -> 14,162 in 604
|
|
1852
|
+
books (up: the classics-wave imports); RSC-020 8,431 -> 9,953
|
|
1853
|
+
in 337 books; PKG-010 4,112 -> 8,237 in 284 books (population
|
|
1854
|
+
flat -- the rename class stayed live, hence the ship). 2,910
|
|
1855
|
+
books carry findings.)*
|
|
1856
|
+
- [x] **NCX<->nav drift detector** (audit-only): the remaining
|
|
1857
|
+
unticked Phase 16 C box; size it after the deep-dive. *(SHIPPED
|
|
1858
|
+
2026-09-17, v0.44.0: `bindery audit tocdrift` diffs the NCX
|
|
1859
|
+
navMap against the EPUB3 nav toc -- entries each side is missing
|
|
1860
|
+
plus label mismatches, keyed on the full target including the
|
|
1861
|
+
fragment. Advisory by contract; the loader exposes the NCX text
|
|
1862
|
+
and the nav document's HTML. ToC synthesis stays out of charter
|
|
1863
|
+
permanently.)*
|
|
1827
1864
|
- [ ] **Internal refactors (polish, no behavior change)**: a
|
|
1828
1865
|
RepairFlags dataclass for the triplicated ~25-kwarg
|
|
1829
1866
|
process_book/repair_epub signature; shared roman/arabic number
|
|
1830
1867
|
helpers for pagination.py and audit.py (their year-range
|
|
1831
1868
|
grammars differ by design: document or unify, never a third
|
|
1832
1869
|
copy). Deliberately not refactored mid-blitz: pure churn risk
|
|
1833
|
-
around five releases of work.
|
|
1870
|
+
around five releases of work. *(Roman/arabic half DONE
|
|
1871
|
+
2026-09-17, v0.44.0: the two copies were byte-identical, so the
|
|
1872
|
+
box resolves to UNIFY -- pagination.py is the canonical home
|
|
1873
|
+
(it vendors into the plugin; audit does not), audit.py imports
|
|
1874
|
+
ROMAN_RE/ROMAN_MAX/roman_value from it, and the comment records
|
|
1875
|
+
that any future divergence is a decision at pagination, not a
|
|
1876
|
+
fork. RepairFlags dataclass DEFERRED with reason: pure churn on
|
|
1877
|
+
a ~25-kwarg surface immediately after the v0.44 batch touched
|
|
1878
|
+
exactly those signatures; do it as a standalone lane with the
|
|
1879
|
+
full suite and the plugin byte-compat job green at every step,
|
|
1880
|
+
not as a rider.)*
|
|
1834
1881
|
- [ ] **MobileRead listing for the Bindery Repair plugin** (Brandon's
|
|
1835
1882
|
manual step): post the listing with name/identity, the
|
|
1836
1883
|
release-attached zip, minimum Calibre 7.0, Linux platform note.
|
|
@@ -1902,3 +1949,37 @@ four interpreter families.
|
|
|
1902
1949
|
15 tests (spin shapes alarm-guarded per greedy site, unterminated-
|
|
1903
1950
|
quote skips, well-formed byte-identity, prune spin over the
|
|
1904
1951
|
truncated-OPF shape). Never a third grammar copy.)*
|
|
1952
|
+
|
|
1953
|
+
- [x] **`--encode-url-spaces` left an OPF-manifest RSC-020 FATAL standing
|
|
1954
|
+
on the Theaetetus fixture** (observed 2026-09-16, classics wave
|
|
1955
|
+
phase 1): the Hackett Aristotle *Theaetetus* EPUB (McDowell) arrived
|
|
1956
|
+
with 270 raw-space zip entry names AND 270 raw-space hrefs in
|
|
1957
|
+
content.opf — epubcheck fatal RSC-020 at content.opf(33,110). The
|
|
1958
|
+
`--all` sweep's encode pass recorded `url_spaces_encoded:351` and
|
|
1959
|
+
dropped errors 356→6, but the fatal survived it (1f before → 1f
|
|
1960
|
+
after). A hand rebuild (percent-encode every reference in
|
|
1961
|
+
.opf/.ncx/.xhtml while KEEPING the raw-space entry names — epubcheck
|
|
1962
|
+
decodes hrefs to find entries, so renaming entries to encoded form
|
|
1963
|
+
breaks lookups the other way) cleared the fatal entirely: 0f.
|
|
1964
|
+
Suspect the encode pass covers content docs and NCX but not the OPF
|
|
1965
|
+
manifest's own href attributes, or a reserialize round-trip writes
|
|
1966
|
+
them back decoded. Fixture: the imported book (id 9135, the file was
|
|
1967
|
+
hand-fixed in place) — the defects and the fix script's operations
|
|
1968
|
+
are documented in the library's phase-1 record for 2026-09-16.
|
|
1969
|
+
*(RESOLVED 2026-09-17, v0.44.0: neither suspicion was right. The
|
|
1970
|
+
real cause was the value class [^"'] in the encode matcher, which
|
|
1971
|
+
stopped at the apostrophes in the OPF's filenames -- the OPF
|
|
1972
|
+
branch DID run but matched nothing. The tempered-quote idiom
|
|
1973
|
+
fixes the matcher, and the entry-rename half clears the class
|
|
1974
|
+
outright.)*
|
|
1975
|
+
|
|
1976
|
+
- [ ] **Candidate transform: `--` inside XML comments (RSC-016 fatal)**.
|
|
1977
|
+
Same book: `OEBPS/...split_269.html` carried a `--` inside an XML
|
|
1978
|
+
comment (illegal XML, fatal RSC-016), costing the file its only
|
|
1979
|
+
fatal after the OPF fix. Hand fix applied: replace `--` with an
|
|
1980
|
+
en-dash inside comment nodes (2 sites, prose preserved). A small
|
|
1981
|
+
`fix_comment_double_hyphen` transform (comment nodes only, `--` ->
|
|
1982
|
+
en-dash, byte-counted) would make the always-on core able to parse
|
|
1983
|
+
such books at all — until then the book parses in lenient readers
|
|
1984
|
+
but never passes epubcheck. Low frequency (one book in the wave),
|
|
1985
|
+
noted with the fixture reference above.
|
|
@@ -184,11 +184,20 @@ every EPUB3 book in an `--all` sweep taking exactly that damage).
|
|
|
184
184
|
reader resolves (`kindle:embed:`, `file:`, ...) are stripped under the same flag; the
|
|
185
185
|
resolvable set is fixed (`http`, `https`, `mailto`) and extends only with a named
|
|
186
186
|
finding.
|
|
187
|
-
- **`--encode-url-spaces`**:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
- **`--encode-url-spaces`**: repairs raw-space URLs two ways. The reference half
|
|
188
|
+
percent-encodes raw spaces in `src`/`href` attribute values across the package
|
|
189
|
+
(OPF manifest, NCX `content src`, content documents): a URL with a literal space is
|
|
190
|
+
not a valid URL (RSC-020 "not a valid URL") and unresolvable on strict readers; the
|
|
191
|
+
encoded form denotes the same file and renders identically. The rename half (v0.44.0)
|
|
192
|
+
renames archive entries whose names carry raw spaces to their underscore spellings
|
|
193
|
+
and rewrites every reference to them (OPF, NCX, content `src`/`href`, stylesheet
|
|
194
|
+
`url()`), clearing PKG-010: underscore, never percent-encoding, because epubcheck
|
|
195
|
+
decodes references before entry lookup (both-sides-%20 breaks lookups; verified
|
|
196
|
+
against epubcheck 5.3). Ambiguous renames (a target that already exists, two names
|
|
197
|
+
converging, a duplicated entry name) are refused per entry, and a book with any
|
|
198
|
+
non-UTF-8 document refuses all of its renames (those entries pass through
|
|
199
|
+
byte-for-byte with no chance to rewrite their references). Scope stays fixed to the
|
|
200
|
+
space character: extend only with a named epubcheck finding.
|
|
192
201
|
|
|
193
202
|
- **`--fix-container`**: generate `META-INF/container.xml` at the located OPF when the
|
|
194
203
|
container is missing, unparseable, or names a file the archive does not contain. This is the
|
|
@@ -400,8 +409,8 @@ crashing the sweep.
|
|
|
400
409
|
|
|
401
410
|
## Audit subcommand (read-only)
|
|
402
411
|
|
|
403
|
-
`bindery audit {content,pagenumbers,emptytext,ocr,monolithic,completeness,all} [PATH] [--max-doc-chars N]
|
|
404
|
-
[--tag TAG] [--id IDs]` (v0.15.0, `audit.py`; `--tag` since v0.18.0; `monolithic` since v0.21.0, `--max-doc-chars N`; `--id` since v0.19.0, comma-lists in v0.23.0; `completeness` since v0.36.0) inspects
|
|
412
|
+
`bindery audit {content,pagenumbers,emptytext,ocr,monolithic,completeness,cover,tocdrift,all} [PATH] [--max-doc-chars N]
|
|
413
|
+
[--tag TAG] [--id IDs]` (v0.15.0, `audit.py`; `--tag` since v0.18.0; `monolithic` since v0.21.0, `--max-doc-chars N`; `--id` since v0.19.0, comma-lists in v0.23.0; `completeness` since v0.36.0; `cover` and `tocdrift` since v0.44.0) inspects
|
|
405
414
|
EPUB body text for flaws epubcheck cannot see: non-English script blocks, baked-in page-number
|
|
406
415
|
layers (sliding-window density heuristics), empty or thin books, systemic OCR damage, and
|
|
407
416
|
single oversized content documents (one spine doc at or above 300k characters: readers refuse
|
|
@@ -417,6 +426,15 @@ read fine are the OBFUSCATED advisory (publisher embedding, benign, never a fail
|
|
|
417
426
|
unreadable obfuscation entry is CORRUPT (a broken font, not a business model), and only
|
|
418
427
|
non-obfuscation algorithms give the ENCRYPTED verdict with its DRM skip advice.
|
|
419
428
|
|
|
429
|
+
Two more advisory analyzers ship in v0.44.0. `cover` reads the package's own cover
|
|
430
|
+
wiring (the EPUB3 half of the ruled hybrid): a dangling EPUB2 `<meta name="cover">`
|
|
431
|
+
(the `--fix-cover` repair class), an EPUB3 `properties~="cover-image"` declaration,
|
|
432
|
+
and whether the named cover file exists. `tocdrift` diffs the two tables of contents
|
|
433
|
+
an EPUB 3 book may carry -- the NCX navMap against the nav document -- reporting
|
|
434
|
+
entries each side is missing and labels that disagree; it feeds decisions, never a
|
|
435
|
+
rewrite (ToC synthesis is out of the repair charter permanently). Both never flag a
|
|
436
|
+
book and never move the exit code.
|
|
437
|
+
|
|
420
438
|
The completeness analyzer is the phase-1 spot-check and is advisory by contract: it never
|
|
421
439
|
flags a book and never moves the exit code. Per book it reports the spine doc count, the
|
|
422
440
|
prose-doc count (spine docs carrying at least 400 visible characters), the opening and closing
|
|
@@ -11,7 +11,7 @@ Calibre plugin built from this core.
|
|
|
11
11
|
Single source of truth for the version. pyproject.toml must match VERSION below.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "0.
|
|
14
|
+
VERSION = "0.44.1"
|
|
15
15
|
__version__ = VERSION
|
|
16
16
|
|
|
17
17
|
from .epub import RepairReport, repair_epub # noqa: E402
|