Files
kdp_generator/main.py
T

111 lines
4.1 KiB
Python
Raw Normal View History

2026-03-24 19:36:04 +02:00
"""CLI entry point for the Crossword KDP Book Generator."""
import logging
import sys
import click
from csv_reader import CSVValidationError, read_csv
from generator import generate_batch
from kdp_format import render_kdp_book
from models import TRIM_SIZES, PuzzleBook
@click.command()
@click.option("--input", "input_path", required=True,
type=click.Path(exists=True, dir_okay=False),
help="Path to a CSV file with word,clue columns.")
@click.option("--output", "output_path", default="output.pdf",
show_default=True,
help="Output PDF file path.")
@click.option("--puzzles", default=20, show_default=True,
help="Number of crossword puzzles to generate.")
@click.option("--grid-size", default=13, show_default=True,
help="Grid dimensions (e.g. 15 for a 15×15 grid).")
@click.option("--trim-size", default="8.5x11", show_default=True,
type=click.Choice(list(TRIM_SIZES.keys())),
help="KDP page trim size.")
@click.option("--title", default="Crossword Puzzles", show_default=True,
help="Book title for the cover page.")
@click.option("--author", default="", help="Author name.")
@click.option("--words-per-puzzle", default=20, show_default=True,
help="Target number of words per puzzle.")
@click.option("--solutions-at-end", is_flag=True, default=False,
help="Group all solution pages at the back of the book.")
@click.option("--seed", default=None, type=int,
help="Random seed for reproducible output.")
@click.option("--no-toc", is_flag=True, default=False,
help="Omit the table of contents page.")
@click.option("--no-instructions", is_flag=True, default=False,
help="Omit the how-to-solve instructions page.")
@click.option("-v", "--verbose", is_flag=True, default=False,
help="Enable verbose logging.")
def main(input_path: str, output_path: str, puzzles: int, grid_size: int,
trim_size: str, title: str, author: str, words_per_puzzle: int,
solutions_at_end: bool, seed: int | None, no_toc: bool,
no_instructions: bool, verbose: bool) -> None:
"""Generate a KDP-ready crossword puzzle book from a CSV word list."""
logging.basicConfig(
level=logging.DEBUG if verbose else logging.INFO,
format="%(levelname)s: %(message)s",
)
# -- read & validate CSV -------------------------------------------------
try:
word_clues = read_csv(input_path, min_words=3,
max_word_length=grid_size)
except (CSVValidationError, FileNotFoundError) as exc:
click.echo(f"Error: {exc}", err=True)
sys.exit(1)
click.echo(f"Loaded {len(word_clues)} words from {input_path}.")
# -- generate puzzles ----------------------------------------------------
click.echo(f"Generating {puzzles} puzzle(s) "
f"({words_per_puzzle} words each, {grid_size}×{grid_size} grid)…")
generated = generate_batch(
word_clues,
count=puzzles,
words_per_puzzle=words_per_puzzle,
grid_size=grid_size,
seed=seed,
)
if not generated:
click.echo("Error: could not generate any puzzles. "
"Try a larger word list or smaller grid.", err=True)
sys.exit(1)
if len(generated) < puzzles:
click.echo(f"Warning: only {len(generated)}/{puzzles} puzzles met "
f"quality thresholds.", err=True)
click.echo(f"Generated {len(generated)} puzzle(s).")
# -- assemble KDP book ---------------------------------------------------
book = PuzzleBook(
title=title,
author=author,
puzzles=generated,
trim_size=TRIM_SIZES[trim_size],
solutions_at_end=solutions_at_end,
)
try:
total_pages = render_kdp_book(
book, output_path,
include_toc=not no_toc,
include_instructions=not no_instructions,
)
except ValueError as exc:
click.echo(f"Error: {exc}", err=True)
sys.exit(1)
click.echo(f"Wrote {total_pages}-page KDP book to {output_path}.")
if __name__ == "__main__":
main()