Source code for sabbath_school_reproducer.main

#!/usr/bin/env python3
"""
Sabbath School Lesson Downloader and Generator

This script downloads lesson content from GitHub and generates a PDF.

Usage:
python3 main.py config.yaml [--debug] [--debug-html-only]
"""

import sys
import os
import argparse
import logging
from .config import Config
from .downloader import GitHubDownloader
from .aggregator import ContentAggregator
from .processor import MarkdownProcessor
from .generator.html_generator import HtmlGenerator
from .generator.pdf_generator import PdfGenerator
from .generator.svg_updater import SvgUpdater
from .utils.debug_tools import DebugTools


[docs] def main(): """ Main function that orchestrates the entire process This function handles command-line arguments, loads configuration, downloads lesson content, and generates PDF output. It also supports reproduction mode for adapting historical lessons to new dates. """ # Create the main parser parser = argparse.ArgumentParser(description='Download and process Sabbath School lessons') # Create subparsers subparsers = parser.add_subparsers(dest='command', help='Commands') # Add 'init' subcommand init_parser = subparsers.add_parser('init', help='Initialize the environment with default settings') # Add 'run' subcommand for the main functionality run_parser = subparsers.add_parser('run', help='Run a lesson configuration') run_parser.add_argument('config_file', help='Path to YAML configuration file') run_parser.add_argument('--debug', action='store_true', help='Enable debug mode with verbose logging') run_parser.add_argument('--debug-html-only', action='store_true', help='Only generate debug HTML without PDF') run_parser.add_argument('--generate-config', action='store_true', help='Generate a sample config file and exit') run_parser.add_argument('--quiet-deps', action='store_true', help='Silence debug messages from dependencies') run_parser.add_argument('-y', '--yes', action='store_true', help='Answer yes to all prompts (force overwrite)') # Add shared arguments to the main parser for backward compatibility parser.add_argument('--debug', action='store_true', help='Enable debug mode with verbose logging') parser.add_argument('--debug-html-only', action='store_true', help='Only generate debug HTML without PDF') parser.add_argument('--generate-config', action='store_true', help='Generate a sample config file and exit') parser.add_argument('--quiet-deps', action='store_true', help='Silence debug messages from dependencies') parser.add_argument('-y', '--yes', action='store_true', help='Answer yes to all prompts (force overwrite)') # Parse the arguments args = parser.parse_args() # Handle generate-config option if args.generate_config: from .bin.generate_config import generate_template_config, generate_default_theme, generate_language_template config_path = generate_template_config() generate_default_theme() os.makedirs("languages", exist_ok=True) for lang in ["en", "swa", "luo"]: generate_language_template(lang, "languages") print(f"Sample configuration file generated at: {config_path}") return 0 # Handle init command if args.command == 'init': from .bin.generate_config import generate_template_config, generate_default_theme, generate_language_template config_path = generate_template_config() generate_default_theme() os.makedirs("languages", exist_ok=True) for lang in ["en", "swa", "luo"]: generate_language_template(lang, "languages") print(f"Sample configuration file generated at: {config_path}") return 0 # Check if a valid command or config file is provided if args.command != 'run' and not hasattr(args, 'config_file'): parser.print_help() print("\nError: Either a command or config file is required") return 1 # For backward compatibility, if no command is specified but a config file is given as positional arg if not args.command and len(sys.argv) > 1 and not sys.argv[1].startswith('-'): args.config_file = sys.argv[1] args.command = 'run' # Configure logging based on debug flag log_level = logging.DEBUG if args.debug else logging.INFO logging.basicConfig( level=log_level, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) if args.quiet_deps: logging.getLogger('fontTools').setLevel(logging.WARNING) logging.getLogger('weasyprint').setLevel(logging.WARNING) logging.getLogger('fontTools.subset').setLevel(logging.WARNING) logging.getLogger('fontTools.ttLib.ttFont').setLevel(logging.WARNING) logging.getLogger('fontTools.subset.timer').setLevel(logging.WARNING) try: # Get config filename from args config_file = args.config_file if hasattr(args, 'config_file') else None if not config_file and args.command != 'run': parser.print_help() print("\nError: Config file is required when using the 'run' command") return 1 print(f"Loading configuration from {config_file}...") config = Config(config_file) # Generate input filename with lesson range information range_filename = GitHubDownloader.get_lesson_range_filename(config) # Check if file path is absolute or relative if not os.path.isabs(range_filename): # Make sure the filename is in the same directory as the config file config_dir = os.path.dirname(args.config_file) range_filename = os.path.join(config_dir, range_filename) year = config.get("year") quarter = config.get("quarter") language = config.get("language") config.config['output_file'] = f"./output/sabbath_school_lesson_{year}_{quarter}_{language}.pdf" # Update config with the new filename config.config['input_file'] = range_filename # Print reproduction settings if configured if 'reproduce' in config.config and config.config['reproduce'].get('year'): reproduction_year = config.config['reproduce']['year'] reproduction_quarter = config.config['reproduce']['quarter'] target_year = config.config['year'] target_quarter = config.config['quarter'] print(f"Reproduction mode: Converting {reproduction_year} {reproduction_quarter} to {target_year} {target_quarter}") if config.config['reproduce'].get('start_lesson'): start_lesson = config.config['reproduce']['start_lesson'] stop_lesson = config.config['reproduce'].get('stop_lesson', 'end') print(f"Lesson range: {start_lesson} to {stop_lesson}") if config.config['reproduce'].get('quarter_start_date'): print(f"New quarter start date: {config.config['reproduce']['quarter_start_date']}") # Generate GitHub paths github_paths = config.get_github_paths() print(f"Processing source: year {github_paths['base_url'].split('/')[-3]}, quarter {github_paths['base_url'].split('/')[-2]}, language {config['language']}") # Check if we should download the file should_download = GitHubDownloader.check_existing_file(range_filename, args.yes) if should_download: # Download lesson data print("Downloading lesson content from GitHub...") downloader = GitHubDownloader(github_paths, config) lesson_data = downloader.download_lesson_data() # Combine into a single markdown file print("Combining lesson content...") markdown_path = ContentAggregator.combine_lesson_content(lesson_data, range_filename) else: # Use existing file print(f"Using existing file: {range_filename}") markdown_path = range_filename with open(range_filename, 'r') as file: lesson_data = file.read() # Generate debug HTML if requested if args.debug_html_only: debug_html_path = config['output_file'].replace('.pdf', '_debug.html') debug_html_path = DebugTools.generate_debug_html(markdown_path, debug_html_path) print(f"Debug HTML created at: {debug_html_path}") return 0 # Process the markdown file to extract structured content print("Processing markdown content...") content_data = MarkdownProcessor.process_markdown_file(markdown_path, config.config) # Update SVG files with dynamic content if available front_cover_path = config.get('front_cover_svg') back_cover_path = config.get('back_cover_svg') if front_cover_path: updated_front_cover = SvgUpdater.update_svg_with_config(front_cover_path, config.config, lesson_data, is_temporary=True) if updated_front_cover: front_cover_path = updated_front_cover print(f"Updated front cover SVG with dynamic content") if back_cover_path: updated_back_cover = SvgUpdater.update_svg_with_config(back_cover_path, config.config, lesson_data, is_temporary=True) if updated_back_cover: back_cover_path = updated_back_cover print(f"Updated back cover SVG with dynamic content") # Generate HTML print("Generating HTML...") html_content = HtmlGenerator.generate_html( content_data, front_cover_svg_path=front_cover_path, back_cover_svg_path=back_cover_path, config=config.config ) # Save debug HTML debug_html_path = config['output_file'].replace('.pdf', '_debug.html') with open(debug_html_path, 'w', encoding='utf-8') as f: f.write(html_content) print(f"Debug HTML saved to: {debug_html_path}") # Generate PDF print("Generating PDF...") PdfGenerator.generate_pdf(html_content, config['output_file'], config.config) print(f"PDF generation complete: {config['output_file']}") except Exception as e: print(f"Error: {str(e)}") if args.debug: import traceback traceback.print_exc() return 1 return 0
if __name__ == "__main__": sys.exit(main())