"""Main entry point for rss2newsletter.""" import sys import argparse import logging from typing import List, Dict from .config import get_config_from_env, setup_logging from .rss_fetcher import get_todays_articles from .ollama_client import create_ollama_client, summarize_articles from .html_generator import generate_newsletter_html, save_newsletter_html logger = logging.getLogger(__name__) def create_argument_parser() -> argparse.ArgumentParser: """Create command line argument parser.""" parser = argparse.ArgumentParser( description="Generate HTML newsletter from RSS feed using Ollama AI summaries", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python -m rss2newsletter https://feeds.example.com/rss python -m rss2newsletter https://blog.example.com/feed.xml --output my_newsletter.html python -m rss2newsletter https://news.example.com/rss --model llama3.1 """, ) parser.add_argument("rss_url", help="RSS feed URL to process") parser.add_argument( "--output", "-o", default=None, help="Output HTML filename (default: newsletter.html)", ) parser.add_argument( "--model", "-m", default=None, help="Ollama model to use for summaries (default: llama3.2)", ) parser.add_argument( "--ollama-url", default=None, help="Ollama server URL (default: http://localhost:11434)", ) parser.add_argument( "--title", "-t", default=None, help="Newsletter title (default: RSS Newsletter)" ) parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose logging" ) parser.add_argument( "--dry-run", action="store_true", help="Fetch articles but don't generate summaries or save HTML", ) return parser def merge_config_with_args(config: Dict, args: argparse.Namespace) -> Dict: """Merge configuration with command line arguments.""" if args.output: config["output"]["filename"] = args.output if args.model: config["ollama"]["model"] = args.model if args.ollama_url: config["ollama"]["base_url"] = args.ollama_url if args.title: config["output"]["feed_title"] = args.title if args.verbose: config["logging"]["level"] = "DEBUG" return config def process_rss_to_newsletter(rss_url: str, config: Dict, dry_run: bool = False) -> str: """Main processing pipeline for RSS to newsletter conversion.""" logger.info(f"Starting RSS newsletter generation for: {rss_url}") # Step 1: Fetch today's articles logger.info("Fetching articles from RSS feed...") articles = get_todays_articles(rss_url) if not articles: logger.warning("No articles found for today") return generate_newsletter_html([], config["output"]["feed_title"]) logger.info(f"Found {len(articles)} articles from today") if dry_run: logger.info("Dry run mode - stopping before summarization") for i, article in enumerate(articles, 1): logger.info(f"Article {i}: {article['title']}") return "" # Step 2: Create Ollama client and summarize articles logger.info("Generating AI summaries...") ollama_client = create_ollama_client(config["ollama"]["base_url"]) summarized_articles = summarize_articles( ollama_client, articles, config["ollama"]["model"] ) # Step 3: Generate HTML newsletter logger.info("Generating HTML newsletter...") html_content = generate_newsletter_html( summarized_articles, config["output"]["feed_title"] ) return html_content def main() -> int: """Main entry point.""" parser = create_argument_parser() args = parser.parse_args() # Load and merge configuration config = get_config_from_env() config = merge_config_with_args(config, args) # Setup logging setup_logging(config) try: # Process RSS feed to newsletter html_content = process_rss_to_newsletter(args.rss_url, config, args.dry_run) if not args.dry_run and html_content: # Save HTML file output_filename = save_newsletter_html( html_content, config["output"]["filename"] ) print(f"Newsletter saved to: {output_filename}") return 0 except KeyboardInterrupt: logger.info("Process interrupted by user") return 1 except Exception as e: logger.error(f"Error generating newsletter: {e}") if config["logging"]["level"] == "DEBUG": logger.exception("Full error details:") return 1 if __name__ == "__main__": sys.exit(main())