mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
c5484934c1
- Add commentary for remaining 63 top verses across 22 books - Create scripts/commentary_cli.py for managing commentary - Update .claude/agents/commentary-generator.md with improved instructions - Remove deprecated one-off scripts and temp files - Clean up verse_commentary.json (now using per-book files) Books updated: 1 John, 1 Peter, Daniel, Deuteronomy, Ecclesiastes, Esther, Ezekiel, Hebrews, Isaiah, Jeremiah, Job, Joshua, Luke, Malachi, Mark, Micah, Proverbs, Psalms, Ruth, Zechariah, Zephaniah Total: 12,992 verse commentaries across 66 books 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
340 lines
10 KiB
Python
Executable File
340 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Commentary CLI - Command-line tool for managing verse commentary.
|
|
|
|
Usage:
|
|
# Check coverage against top verses
|
|
uv run python scripts/commentary_cli.py coverage
|
|
|
|
# List missing verses for a book
|
|
uv run python scripts/commentary_cli.py missing Isaiah
|
|
|
|
# Get verse text (for agent use)
|
|
uv run python scripts/commentary_cli.py verse Isaiah 7 14
|
|
|
|
# Add commentary for a verse
|
|
uv run python scripts/commentary_cli.py add Isaiah 7 14 --analysis "..." --historical "..." --questions "Q1" "Q2"
|
|
|
|
# Validate commentary file
|
|
uv run python scripts/commentary_cli.py validate Isaiah
|
|
|
|
# Show stats for all books
|
|
uv run python scripts/commentary_cli.py stats
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add project root to path
|
|
PROJECT_ROOT = Path(__file__).parent.parent
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
DATA_DIR = PROJECT_ROOT / "kjvstudy_org" / "data" / "verse_commentary"
|
|
|
|
|
|
def slugify(book: str) -> str:
|
|
"""Create filesystem-friendly file name for a book."""
|
|
slug = re.sub(r"[^a-z0-9]+", book.lower(), "")
|
|
slug = re.sub(r"[^a-z0-9]+", "_", book.lower())
|
|
slug = re.sub(r"_+", "_", slug).strip("_")
|
|
return slug or "book"
|
|
|
|
|
|
def load_book_commentary(book: str) -> dict:
|
|
"""Load commentary file for a book."""
|
|
slug = slugify(book)
|
|
filepath = DATA_DIR / f"{slug}.json"
|
|
|
|
if not filepath.exists():
|
|
return {"book": book, "commentary": {}}
|
|
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
|
|
|
|
def save_book_commentary(book: str, data: dict) -> Path:
|
|
"""Save commentary file for a book."""
|
|
slug = slugify(book)
|
|
filepath = DATA_DIR / f"{slug}.json"
|
|
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
|
|
return filepath
|
|
|
|
|
|
def get_verse_text(book: str, chapter: int, verse: int) -> dict | None:
|
|
"""Get verse text from the KJV module."""
|
|
try:
|
|
from kjvstudy_org.kjv import Bible
|
|
bible = Bible()
|
|
text = bible.get_verse_text(book, chapter, verse)
|
|
if text:
|
|
return {"book": book, "chapter": chapter, "verse": verse, "text": text}
|
|
return None
|
|
except Exception as e:
|
|
print(f"Error loading verse: {e}", file=sys.stderr)
|
|
return None
|
|
|
|
|
|
def cmd_verse(args):
|
|
"""Get verse text."""
|
|
verse = get_verse_text(args.book, args.chapter, args.verse)
|
|
if verse:
|
|
print(json.dumps(verse, indent=2))
|
|
else:
|
|
print(f"Verse not found: {args.book} {args.chapter}:{args.verse}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def cmd_coverage(args):
|
|
"""Check coverage against top verses list."""
|
|
# Import check_top_verses functionality
|
|
from check_top_verses import TOP_VERSES, check_coverage
|
|
|
|
covered, missing = check_coverage()
|
|
total = len(TOP_VERSES)
|
|
|
|
print(f"Coverage: {len(covered)}/{total} ({100*len(covered)/total:.1f}%)")
|
|
print(f"Missing: {len(missing)} verses")
|
|
|
|
if args.verbose and missing:
|
|
print("\nMissing verses:")
|
|
for book, verses in sorted(missing.items()):
|
|
print(f" {book}: {', '.join(map(str, verses))}")
|
|
|
|
|
|
def cmd_missing(args):
|
|
"""List missing verses for a book."""
|
|
from check_top_verses import TOP_VERSES
|
|
|
|
book = args.book
|
|
if book not in TOP_VERSES:
|
|
print(f"Book '{book}' not in top verses list", file=sys.stderr)
|
|
print(f"Available books: {', '.join(sorted(TOP_VERSES.keys()))}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
data = load_book_commentary(book)
|
|
commentary = data.get("commentary", {})
|
|
|
|
missing = []
|
|
for chapter, verses in TOP_VERSES[book].items():
|
|
chapter_data = commentary.get(str(chapter), {})
|
|
for verse in verses:
|
|
if str(verse) not in chapter_data:
|
|
missing.append(f"{chapter}:{verse}")
|
|
|
|
if missing:
|
|
print(f"Missing verses in {book}:")
|
|
for ref in missing:
|
|
print(f" {ref}")
|
|
else:
|
|
print(f"All top verses covered in {book}")
|
|
|
|
|
|
def cmd_add(args):
|
|
"""Add commentary for a verse."""
|
|
book = args.book
|
|
chapter = str(args.chapter)
|
|
verse = str(args.verse)
|
|
|
|
# Load existing data
|
|
data = load_book_commentary(book)
|
|
commentary = data.get("commentary", {})
|
|
|
|
# Check if verse already exists
|
|
if chapter in commentary and verse in commentary[chapter]:
|
|
if not args.force:
|
|
print(f"Commentary already exists for {book} {chapter}:{verse}", file=sys.stderr)
|
|
print("Use --force to overwrite", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Build commentary entry
|
|
entry = {
|
|
"analysis": args.analysis,
|
|
"historical": args.historical,
|
|
"questions": args.questions or []
|
|
}
|
|
|
|
# Add to structure
|
|
if chapter not in commentary:
|
|
commentary[chapter] = {}
|
|
commentary[chapter][verse] = entry
|
|
data["commentary"] = commentary
|
|
|
|
# Save
|
|
filepath = save_book_commentary(book, data)
|
|
print(f"Added commentary for {book} {chapter}:{verse}")
|
|
print(f"Saved to: {filepath}")
|
|
|
|
|
|
def cmd_validate(args):
|
|
"""Validate commentary file structure."""
|
|
book = args.book
|
|
data = load_book_commentary(book)
|
|
|
|
errors = []
|
|
warnings = []
|
|
|
|
if "book" not in data:
|
|
errors.append("Missing 'book' field")
|
|
|
|
if "commentary" not in data:
|
|
errors.append("Missing 'commentary' field")
|
|
else:
|
|
for chapter, verses in data["commentary"].items():
|
|
if not isinstance(verses, dict):
|
|
errors.append(f"Chapter {chapter} is not a dict")
|
|
continue
|
|
|
|
for verse, entry in verses.items():
|
|
ref = f"{chapter}:{verse}"
|
|
|
|
if not isinstance(entry, dict):
|
|
errors.append(f"{ref}: Entry is not a dict")
|
|
continue
|
|
|
|
if "analysis" not in entry:
|
|
warnings.append(f"{ref}: Missing 'analysis'")
|
|
if "historical" not in entry:
|
|
warnings.append(f"{ref}: Missing 'historical'")
|
|
if "questions" not in entry:
|
|
warnings.append(f"{ref}: Missing 'questions'")
|
|
elif not isinstance(entry.get("questions"), list):
|
|
errors.append(f"{ref}: 'questions' is not a list")
|
|
|
|
if errors:
|
|
print(f"ERRORS in {book}:")
|
|
for e in errors:
|
|
print(f" - {e}")
|
|
|
|
if warnings:
|
|
print(f"WARNINGS in {book}:")
|
|
for w in warnings:
|
|
print(f" - {w}")
|
|
|
|
if not errors and not warnings:
|
|
print(f"{book}: Valid")
|
|
|
|
sys.exit(1 if errors else 0)
|
|
|
|
|
|
def cmd_stats(args):
|
|
"""Show stats for all commentary files."""
|
|
files = list(DATA_DIR.glob("*.json"))
|
|
|
|
print(f"{'Book':<20} {'Chapters':<10} {'Verses':<10}")
|
|
print("-" * 42)
|
|
|
|
total_chapters = 0
|
|
total_verses = 0
|
|
|
|
for filepath in sorted(files):
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
|
|
book = data.get("book", filepath.stem)
|
|
commentary = data.get("commentary", {})
|
|
|
|
chapters = len(commentary)
|
|
verses = sum(len(v) for v in commentary.values() if isinstance(v, dict))
|
|
|
|
print(f"{book:<20} {chapters:<10} {verses:<10}")
|
|
total_chapters += chapters
|
|
total_verses += verses
|
|
|
|
print("-" * 42)
|
|
print(f"{'TOTAL':<20} {total_chapters:<10} {total_verses:<10}")
|
|
|
|
|
|
def cmd_export_verse(args):
|
|
"""Export verse with commentary as JSON (for agents)."""
|
|
book = args.book
|
|
chapter = args.chapter
|
|
verse = args.verse
|
|
|
|
# Get verse text
|
|
verse_data = get_verse_text(book, chapter, verse)
|
|
|
|
# Get commentary
|
|
data = load_book_commentary(book)
|
|
commentary = data.get("commentary", {}).get(str(chapter), {}).get(str(verse))
|
|
|
|
output = {
|
|
"reference": f"{book} {chapter}:{verse}",
|
|
"text": verse_data.get("text") if verse_data else None,
|
|
"has_commentary": commentary is not None,
|
|
"commentary": commentary
|
|
}
|
|
|
|
print(json.dumps(output, indent=2))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="CLI tool for managing verse commentary",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog=__doc__
|
|
)
|
|
|
|
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
|
|
|
# verse command
|
|
p_verse = subparsers.add_parser("verse", help="Get verse text")
|
|
p_verse.add_argument("book", help="Book name")
|
|
p_verse.add_argument("chapter", type=int, help="Chapter number")
|
|
p_verse.add_argument("verse", type=int, help="Verse number")
|
|
p_verse.set_defaults(func=cmd_verse)
|
|
|
|
# coverage command
|
|
p_coverage = subparsers.add_parser("coverage", help="Check top verses coverage")
|
|
p_coverage.add_argument("-v", "--verbose", action="store_true", help="Show missing verses")
|
|
p_coverage.set_defaults(func=cmd_coverage)
|
|
|
|
# missing command
|
|
p_missing = subparsers.add_parser("missing", help="List missing verses for a book")
|
|
p_missing.add_argument("book", help="Book name")
|
|
p_missing.set_defaults(func=cmd_missing)
|
|
|
|
# add command
|
|
p_add = subparsers.add_parser("add", help="Add commentary for a verse")
|
|
p_add.add_argument("book", help="Book name")
|
|
p_add.add_argument("chapter", type=int, help="Chapter number")
|
|
p_add.add_argument("verse", type=int, help="Verse number")
|
|
p_add.add_argument("--analysis", required=True, help="Theological analysis")
|
|
p_add.add_argument("--historical", required=True, help="Historical context")
|
|
p_add.add_argument("--questions", nargs="+", help="Reflection questions")
|
|
p_add.add_argument("--force", action="store_true", help="Overwrite existing")
|
|
p_add.set_defaults(func=cmd_add)
|
|
|
|
# validate command
|
|
p_validate = subparsers.add_parser("validate", help="Validate commentary file")
|
|
p_validate.add_argument("book", help="Book name")
|
|
p_validate.set_defaults(func=cmd_validate)
|
|
|
|
# stats command
|
|
p_stats = subparsers.add_parser("stats", help="Show stats for all books")
|
|
p_stats.set_defaults(func=cmd_stats)
|
|
|
|
# export command
|
|
p_export = subparsers.add_parser("export", help="Export verse with commentary")
|
|
p_export.add_argument("book", help="Book name")
|
|
p_export.add_argument("chapter", type=int, help="Chapter number")
|
|
p_export.add_argument("verse", type=int, help="Verse number")
|
|
p_export.set_defaults(func=cmd_export_verse)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
args.func(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|