diff --git a/README.md b/README.md index 131c045..8753985 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ python -m src.custom_bingo --input-file --output-file - `-i, --input-file PATH`: Input spreadsheet file (Excel/CSV) with B, I, N, G, O columns [required] - `-o, --output-file PATH`: Output PDF file for the BINGO cards [required] - `-n, --number-of-cards INTEGER`: Number of BINGO cards to generate (default: 1) +- `-t, --title TEXT`: Title text to display at the top of the card (default: BINGO) +- `-s, --sub-headers TEXT`: Comma-separated sub-headers for each column (e.g., "Category1,Category2,Category3,Category4,Category5") ### Example @@ -53,6 +55,9 @@ python -m src.custom_bingo -i data/ChristmasSongsBingo.csv -o output.pdf # Generate multiple BINGO cards python -m src.custom_bingo -i data/ChristmasSongsBingo.csv -o output.pdf -n 5 + +# Generate a BINGO card with custom title and sub-headers +python -m src.custom_bingo -i data/ChristmasSongsBingo.csv -o output.pdf -t "Holiday Music Bingo" -s "Traditional,Upbeat,Holy,Classic,Festive" ``` ## Input Format diff --git a/src/custom_bingo/main.py b/src/custom_bingo/main.py index ae7395c..c0ad95b 100644 --- a/src/custom_bingo/main.py +++ b/src/custom_bingo/main.py @@ -12,20 +12,32 @@ from custom_bingo.pdf_generator import export_cards_to_pdf help='Output PDF file for the BINGO cards') @click.option('--number-of-cards', '-n', default=1, type=int, help='Number of BINGO cards to generate (default: 1)') -def main(input_file, output_file, number_of_cards): +@click.option('--title', '-t', default='BINGO', type=str, + help='Title text to display at the top of the card (default: BINGO)') +@click.option('--sub-headers', '-s', default=None, type=str, + help='Comma-separated sub-headers for each column (e.g., "Category1,Category2,Category3,Category4,Category5")') +def main(input_file, output_file, number_of_cards, title, sub_headers): """Generate custom BINGO cards from a spreadsheet.""" try: # Read the spreadsheet data click.echo(f"Reading data from {input_file}...") data = read_spreadsheet(input_file) + # Parse sub-headers if provided + sub_headers_list = None + if sub_headers: + sub_headers_list = [h.strip() for h in sub_headers.split(',')] + if len(sub_headers_list) != 5: + click.echo("Error: Sub-headers must contain exactly 5 comma-separated values for the 5 columns.", err=True) + return + # Generate the bingo cards click.echo(f"Generating {number_of_cards} BINGO card(s)...") cards = generate_bingo_cards(data, number_of_cards) - # Export to PDF + # Export to PDF with custom title and sub-headers click.echo(f"Exporting cards to {output_file}...") - export_cards_to_pdf(cards, output_file) + export_cards_to_pdf(cards, output_file, title=title, sub_headers=sub_headers_list) click.echo(f"Successfully generated {number_of_cards} BINGO card(s) in {output_file}") except Exception as e: diff --git a/src/custom_bingo/pdf_generator.py b/src/custom_bingo/pdf_generator.py index 4e66e5b..d3c2f66 100644 --- a/src/custom_bingo/pdf_generator.py +++ b/src/custom_bingo/pdf_generator.py @@ -94,13 +94,15 @@ def draw_multiline_text(c: canvas.Canvas, text: str, x: float, y: float, width: c.drawString(centered_x, y_position, line) -def export_cards_to_pdf(cards: List[List[List[str]]], output_file: str): +def export_cards_to_pdf(cards: List[List[List[str]]], output_file: str, title: str = "BINGO", sub_headers: List[str] = None): """ Export the generated BINGO cards to a PDF file. Args: cards: List of 5x5 matrices representing BINGO cards output_file: Path for the output PDF file + title: Custom title to display at the top of the card + sub_headers: Optional list of sub-headers for each column (B, I, N, G, O) """ # Create a new PDF document c = canvas.Canvas(output_file, pagesize=letter) @@ -116,30 +118,41 @@ def export_cards_to_pdf(cards: List[List[List[str]]], output_file: str): # Starting position for the first card (centered horizontally) start_x = (width - card_width) / 2 - # Position the card lower on the page to accommodate title and headers better - # Increase distance between title and grid - title_height = 0.5 * inch # Space for title - header_height = cell_height # Space for headers - space_between_title_and_headers = 0.3 * inch # Space to prevent overlap - start_y = height - margin - title_height - space_between_title_and_headers - header_height - card_height + # Position the card lower on the page to accommodate title, sub-headers and headers + title_height = 0.5 * inch # Space for main title + sub_header_height = cell_height * 0.5 if sub_headers else 0 # Space for sub-headers if present + header_height = cell_height # Space for column headers + space_between_elements = 0.2 * inch # Space between elements + + start_y = height - margin - title_height - space_between_elements - sub_header_height - space_between_elements - header_height - card_height for idx, card in enumerate(cards): # Add a new page for each card after the first if idx > 0: c.showPage() - start_y = height - margin - title_height - space_between_title_and_headers - header_height - card_height + start_y = height - margin - title_height - space_between_elements - sub_header_height - space_between_elements - header_height - card_height - # Draw title "BINGO" + # Draw custom title c.setFont("Helvetica", 24) - title_width = c.stringWidth("BINGO", "Helvetica", 24) - title_y = height - margin # Position title at the top with margin - c.drawString((width - title_width) / 2, title_y, "BINGO") + title_width = c.stringWidth(title, "Helvetica", 24) + title_y = height - margin # Position main title at the top with margin + c.drawString((width - title_width) / 2, title_y, title) - # Draw column headers (B, I, N, G, O) below the title with more spacing + # Draw sub-headers if provided + if sub_headers: + for col, sub_header in enumerate(sub_headers): + cell_x = start_x + col * cell_width + # Position sub-headers between the main column headers and the grid + sub_header_y = start_y + card_height + header_height + space_between_elements/2 + + # Draw sub-header text centered in the sub-header area + draw_multiline_text(c, sub_header, cell_x, sub_header_y, cell_width, sub_header_height, max_font_size=12) + + # Draw column headers (B, I, N, G, O) below the sub-headers (or title if no sub-headers) headers = ['B', 'I', 'N', 'G', 'O'] for col, header in enumerate(headers): cell_x = start_x + col * cell_width - # Position headers above the grid with adequate spacing from title + # Position headers above the grid with adequate spacing header_y = start_y + card_height # Headers positioned right above the grid # Draw header text centered in the header cell diff --git a/tests/test_main.py b/tests/test_main.py index 7e082eb..d234caa 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -8,7 +8,7 @@ from custom_bingo.main import main def test_main_command(): """Test the main CLI command with a temporary CSV file.""" runner = CliRunner() - + # Create a temporary CSV file csv_content = """B,I,N,G,O Apple,Book,Car,Door,Elephant @@ -16,18 +16,18 @@ Banana,Clock,Desk,Engine,Fish Orange,Pencil,Tree,Flower,Guitar Grape,Eraser,River,Grass,Hat Lemon,Notebook,Moon,Leaf,Jacket""" - + with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as csv_file: csv_file.write(csv_content) csv_path = csv_file.name - + # Create a temporary output PDF file with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as pdf_file: pdf_path = pdf_file.name - + # Remove the PDF file so the command can create it os.remove(pdf_path) - + try: # Run the CLI command result = runner.invoke(main, [ @@ -35,15 +35,111 @@ Lemon,Notebook,Moon,Leaf,Jacket""" '--output-file', pdf_path, '--number-of-cards', '1' ]) - + # Check that the command completed successfully assert result.exit_code == 0 assert "Successfully generated" in result.output - + # Check that the output PDF file was created assert os.path.exists(pdf_path) assert os.path.getsize(pdf_path) > 0 - + + finally: + # Clean up temporary files + if os.path.exists(csv_path): + os.remove(csv_path) + if os.path.exists(pdf_path): + os.remove(pdf_path) + + +def test_main_command_with_custom_title(): + """Test the main CLI command with custom title.""" + runner = CliRunner() + + # Create a temporary CSV file + csv_content = """B,I,N,G,O +Apple,Book,Car,Door,Elephant +Banana,Clock,Desk,Engine,Fish +Orange,Pencil,Tree,Flower,Guitar +Grape,Eraser,River,Grass,Hat +Lemon,Notebook,Moon,Leaf,Jacket""" + + with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as csv_file: + csv_file.write(csv_content) + csv_path = csv_file.name + + # Create a temporary output PDF file + with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as pdf_file: + pdf_path = pdf_file.name + + # Remove the PDF file so the command can create it + os.remove(pdf_path) + + try: + # Run the CLI command with custom title + result = runner.invoke(main, [ + '--input-file', csv_path, + '--output-file', pdf_path, + '--number-of-cards', '1', + '--title', 'Custom Title' + ]) + + # Check that the command completed successfully + assert result.exit_code == 0 + assert "Successfully generated" in result.output + + # Check that the output PDF file was created + assert os.path.exists(pdf_path) + assert os.path.getsize(pdf_path) > 0 + + finally: + # Clean up temporary files + if os.path.exists(csv_path): + os.remove(csv_path) + if os.path.exists(pdf_path): + os.remove(pdf_path) + + +def test_main_command_with_sub_headers(): + """Test the main CLI command with sub-headers.""" + runner = CliRunner() + + # Create a temporary CSV file + csv_content = """B,I,N,G,O +Apple,Book,Car,Door,Elephant +Banana,Clock,Desk,Engine,Fish +Orange,Pencil,Tree,Flower,Guitar +Grape,Eraser,River,Grass,Hat +Lemon,Notebook,Moon,Leaf,Jacket""" + + with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as csv_file: + csv_file.write(csv_content) + csv_path = csv_file.name + + # Create a temporary output PDF file + with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as pdf_file: + pdf_path = pdf_file.name + + # Remove the PDF file so the command can create it + os.remove(pdf_path) + + try: + # Run the CLI command with sub-headers + result = runner.invoke(main, [ + '--input-file', csv_path, + '--output-file', pdf_path, + '--number-of-cards', '1', + '--sub-headers', 'Fruits,Objects,Nouns,Actions,Accessories' + ]) + + # Check that the command completed successfully + assert result.exit_code == 0 + assert "Successfully generated" in result.output + + # Check that the output PDF file was created + assert os.path.exists(pdf_path) + assert os.path.getsize(pdf_path) > 0 + finally: # Clean up temporary files if os.path.exists(csv_path):