Files
funkpower f4d936e620 recipes, examples: shelving a library by what its books printed
The suite could read a cataloguing block and could ask a register, and
nothing showed the two together — which is the only way either is worth
acting on. A title page alone gives up a running head when the scan
starts late; an ISBN alone returns a confidently wrong book when the
catalogue has been carrying the wrong one for years.

The example's title comparison folds to letters and digits rather than
comparing strings. A register and a title page differ over case, over a
curly apostrophe against a straight one, and over whether the subtitle
counts as part of the title. An example that called those disagreements
would teach the wrong lesson, and did until it was fixed.

It runs on this library and finds both of the cases worth finding: a file
called Document5 by -ducati996 that both sources agree is Russell's
Problems of Philosophy, and a Pelikan whose stored ISBN belongs to a book
about sculpture.
2026-09-21 10:21:52 -04:00

111 lines
4.2 KiB
Python

"""What a book says about itself, checked against what a register says back.
A catalogue's subjects and titles come from wherever its files came from. Two
things can correct that without a model: the cataloguing block the book prints
in its own front matter, and the register that assigned its ISBN. Neither is
trustworthy alone — a scan that starts mid-frontmatter yields a running head
where the title should be, and an ISBN a catalogue has been carrying wrongly
returns a confidently wrong book. Together they are worth acting on.
python examples/python/shelve_a_library.py converted/*.md
With no arguments it reads a book from Project Gutenberg, which prints no
cataloguing block at all — the honest common case, and worth seeing.
"""
import pathlib
import re
import sys
import urllib.request
import catena
# The closed list this library is browsed by. `Heading = term, term` gives the
# words that file a book under it; a heading on its own matches its own name.
SHELF = """
Philosophy = philosophy, philosophical
Ancient Philosophy = ancient philosophy, plato, aristotle
Political Science = political science, politics, government
Theology = theology, christianity, religion
Church History = church history, ecclesiastical history
Byzantine History = byzantium, byzantine, constantinople
History = history, historiography
Literature = literature, fiction, poetry
Psychology = psychology
Ethics
Logic
"""
GUTENBERG = "https://www.gutenberg.org/cache/epub/5827/pg5827.txt"
def sample(into: pathlib.Path) -> str:
"""Locke's *Essay*, which Gutenberg strips the front matter from."""
book = into / "locke.txt"
if not book.exists():
into.mkdir(parents=True, exist_ok=True)
print(f"fetching {GUTENBERG}", file=sys.stderr)
with urllib.request.urlopen(GUTENBERG, timeout=120) as reply: # noqa: S310
book.write_bytes(reply.read())
return book.read_text(encoding="utf-8", errors="replace")
def same(a: str, b: str) -> bool:
"""Whether two printings of one title are the same title.
Compared on letters and digits alone. A register and a title page differ
over case, over a curly apostrophe against a straight one, and over
whether the subtitle is part of the title — none of which is a
disagreement, and all of which a plain string comparison calls one.
"""
fold = lambda s: re.sub(r"[^a-z0-9]+", "", (s or "").lower())
a, b = fold(a), fold(b)
return bool(a) and bool(b) and (a in b or b in a)
def look(text: str, name: str) -> None:
printed = catena.read_colophon(text, shelf=SHELF)
title = printed.get("title")
isbns = printed.get("isbns") or []
print(f"\n{name}")
print(f" printed title {title or '— (no title page in these pages)'}")
if printed.get("authors"):
print(f" printed authors {', '.join(printed['authors'])}")
if printed.get("shelved"):
print(f" shelved under {', '.join(printed['shelved'])}")
for raw in printed.get("unshelved", []):
# Not a failure: a heading the shelf has no place for is how you find
# out the shelf is missing one.
print(f" no shelf for {raw}")
if not printed.get("subjects"):
print(" subjects — (no cataloguing block printed)")
if not isbns:
print(" register — (nothing to look up; one source only)")
return
# The only call here that opens a socket, and it blocks.
found = catena.fetch_identifier(isbns[0], polite="you@example.org")
if not found or not found.get("found"):
print(f" register {isbns[0]} is unknown to every register asked")
return
print(f" register says {found['title']} / {', '.join(found.get('authors') or [])}")
agrees = bool(title) and same(title, found["title"])
print(f" verdict {'corroborated' if agrees else 'two sources disagree — ask a person'}")
def main() -> int:
paths = [pathlib.Path(p) for p in sys.argv[1:]]
if not paths:
look(sample(pathlib.Path("/tmp/catena-example")), "Locke, from Gutenberg")
return 0
for path in paths:
look(path.read_text(encoding="utf-8", errors="replace"), path.name)
return 0
if __name__ == "__main__":
raise SystemExit(main())