Files
kdp_generator/main.py
T
2026-03-24 19:36:04 +02:00

111 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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()