Compare commits
No commits in common. '971c1777fd7576c2297b2a5d52e0d8559de8aaa5' and 'f45fe56f94998d6f07670d541f44f1cf88953acc' have entirely different histories.
971c1777fd
...
f45fe56f94
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,153 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Love Letters — Display random historic love letters from Project Gutenberg.
|
|
||||||
|
|
||||||
Reads pre-downloaded letter collections from the letters/ directory.
|
|
||||||
Run download_letters.py first to populate the data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
LETTERS_DIR = os.path.join(SCRIPT_DIR, "letters")
|
|
||||||
|
|
||||||
|
|
||||||
def load_all_letters(source_filter: str | None = None) -> list[dict]:
|
|
||||||
"""Load all letters from JSON files in the letters/ directory."""
|
|
||||||
if not os.path.isdir(LETTERS_DIR):
|
|
||||||
return []
|
|
||||||
all_letters: list[dict] = []
|
|
||||||
for filename in sorted(os.listdir(LETTERS_DIR)):
|
|
||||||
if not filename.endswith(".json"):
|
|
||||||
continue
|
|
||||||
source_id = filename[:-5]
|
|
||||||
if source_filter and source_id != source_filter:
|
|
||||||
continue
|
|
||||||
path = os.path.join(LETTERS_DIR, filename)
|
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
|
||||||
all_letters.extend(json.load(f))
|
|
||||||
return all_letters
|
|
||||||
|
|
||||||
|
|
||||||
def available_sources() -> list[str]:
|
|
||||||
"""Return list of source IDs from the letters/ directory."""
|
|
||||||
if not os.path.isdir(LETTERS_DIR):
|
|
||||||
return []
|
|
||||||
return sorted(
|
|
||||||
f[:-5] for f in os.listdir(LETTERS_DIR) if f.endswith(".json")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_text(text: str, width: int = 78) -> str:
|
|
||||||
"""Word-wrap text while preserving paragraph breaks."""
|
|
||||||
paragraphs = re.split(r"\n\s*\n", text)
|
|
||||||
wrapped = []
|
|
||||||
for para in paragraphs:
|
|
||||||
para = " ".join(para.split())
|
|
||||||
wrapped.append(textwrap.fill(para, width=width))
|
|
||||||
return "\n\n".join(wrapped)
|
|
||||||
|
|
||||||
|
|
||||||
def display_letter(letter: dict) -> None:
|
|
||||||
"""Pretty-print a single love letter to the terminal."""
|
|
||||||
print()
|
|
||||||
print(SEPARATOR)
|
|
||||||
print(f" ✉ {letter['author']} → {letter['recipient']}")
|
|
||||||
if letter.get("heading"):
|
|
||||||
print(f" {letter['heading']}")
|
|
||||||
print(f" ({letter['period']})")
|
|
||||||
print(SEPARATOR)
|
|
||||||
print()
|
|
||||||
|
|
||||||
print(wrap_text(letter["body"]))
|
|
||||||
|
|
||||||
print()
|
|
||||||
print(SEPARATOR)
|
|
||||||
print(f" Source: {letter['source']}")
|
|
||||||
print(f" Via Project Gutenberg • gutenberg.org")
|
|
||||||
print(SEPARATOR)
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
SEPARATOR = "─" * 60
|
|
||||||
|
|
||||||
|
|
||||||
def list_sources() -> None:
|
|
||||||
"""Print available letter collections with counts."""
|
|
||||||
sources = available_sources()
|
|
||||||
if not sources:
|
|
||||||
print("\n No letter collections found. Run download_letters.py first.\n")
|
|
||||||
return
|
|
||||||
print("\n Available collections:\n")
|
|
||||||
for i, source_id in enumerate(sources, 1):
|
|
||||||
path = os.path.join(LETTERS_DIR, f"{source_id}.json")
|
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
|
||||||
letters = json.load(f)
|
|
||||||
if not letters:
|
|
||||||
continue
|
|
||||||
first = letters[0]
|
|
||||||
print(f" {i:2}. [{source_id}]")
|
|
||||||
print(f" {first['author']} → {first['recipient']} ({first['period']})")
|
|
||||||
print(f" {first['source']} ({len(letters)} letters)")
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
sources = available_sources()
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Display random historic love letters from Project Gutenberg.",
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
epilog=textwrap.dedent("""\
|
|
||||||
examples:
|
|
||||||
%(prog)s Show a random love letter
|
|
||||||
%(prog)s -n 3 Show 3 random love letters
|
|
||||||
%(prog)s --list List available collections
|
|
||||||
%(prog)s --source keats_brawne Show only Keats letters
|
|
||||||
"""),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-n", "--count", type=int, default=1, metavar="N",
|
|
||||||
help="number of letters to display (default: 1)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--list", action="store_true",
|
|
||||||
help="list available letter collections",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--source", type=str, metavar="ID",
|
|
||||||
choices=sources if sources else None,
|
|
||||||
help="only show letters from a specific source",
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.list:
|
|
||||||
list_sources()
|
|
||||||
return
|
|
||||||
|
|
||||||
all_letters = load_all_letters(source_filter=args.source)
|
|
||||||
|
|
||||||
if not all_letters:
|
|
||||||
if not sources:
|
|
||||||
print(" No letter data found. Run download_letters.py first.")
|
|
||||||
else:
|
|
||||||
print(f" No letters found for source '{args.source}'.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
count = min(args.count, len(all_letters))
|
|
||||||
chosen = random.sample(all_letters, count)
|
|
||||||
|
|
||||||
for letter in chosen:
|
|
||||||
display_letter(letter)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Loading…
Reference in New Issue