Files
kjvstudy.org/kjvstudy_org/utils/books.py
T
kennethreitz 1279fde038 Fix case-insensitive book name redirects (301)
URLs like /book/gen/chapter/1/verse/3 now properly redirect
to /book/Genesis/chapter/1/verse/3 with a 301 status.

The normalize_book_name function now does case-insensitive lookup
for both abbreviations and canonical book names.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 19:14:05 -05:00

80 lines
2.4 KiB
Python

"""Book name normalization and categorization utilities."""
import json
from pathlib import Path
from functools import lru_cache
from typing import Optional
# Path to bible metadata JSON file
_METADATA_PATH = Path(__file__).parent.parent / "data" / "bible_metadata.json"
@lru_cache(maxsize=1)
def _load_bible_metadata() -> dict:
"""
Load bible metadata from JSON file.
Cached since this data never changes and is accessed frequently.
"""
with open(_METADATA_PATH, "r", encoding="utf-8") as f:
return json.load(f)
# Load data from JSON
_metadata = _load_bible_metadata()
OT_BOOKS = _metadata["old_testament_books"]
NT_BOOKS = _metadata["new_testament_books"]
BOOK_ABBREVIATIONS = _metadata["book_abbreviations"]
@lru_cache(maxsize=1)
def _get_lowercase_abbreviations() -> dict:
"""Create a lowercase mapping of abbreviations for case-insensitive lookup."""
return {k.lower(): v for k, v in BOOK_ABBREVIATIONS.items()}
@lru_cache(maxsize=1)
def _get_lowercase_canonical_names() -> dict:
"""Create a lowercase mapping of canonical book names."""
all_books = OT_BOOKS + NT_BOOKS
return {b.lower(): b for b in all_books}
def normalize_book_name(book: str) -> Optional[str]:
"""
Normalize book name variations to canonical form.
Returns the canonical book name if a variation is detected, None otherwise.
Case-insensitive lookup.
"""
# First try exact match in abbreviations
if book in BOOK_ABBREVIATIONS:
return BOOK_ABBREVIATIONS[book]
# Then try case-insensitive match in abbreviations
lowercase_abbrevs = _get_lowercase_abbreviations()
result = lowercase_abbrevs.get(book.lower())
if result:
return result
# Finally, check if it's a case variation of a canonical name
lowercase_canonical = _get_lowercase_canonical_names()
canonical = lowercase_canonical.get(book.lower())
if canonical and canonical != book:
return canonical
return None
def is_old_testament(book: str) -> bool:
"""Check if a book is in the Old Testament."""
return book in OT_BOOKS
def is_new_testament(book: str) -> bool:
"""Check if a book is in the New Testament."""
return book in NT_BOOKS
def get_testament(book: str) -> str:
"""Return the testament name for a book."""
if is_old_testament(book):
return "Old Testament"
elif is_new_testament(book):
return "New Testament"
return "Unknown"