import argparse from dataclasses import dataclass from typing import List, Optional from pathlib import Path import sys @dataclass(frozen=True) class OllamaConfig: """Configuration for Ollama client.""" model: str server_url: str timeout: int max_retries: int @dataclass(frozen=True) class ReviewConfig: """Complete configuration for ReviewLlama.""" paths: List[Path] ollama: OllamaConfig def normalize_server_url(url: str) -> str: """Normalize Ollama server URL to ensure proper format.""" if not url.startswith(("http://", "https://")): return f"http://{url}" return url.rstrip("/") def create_ollama_config( model: str, server_url: str, timeout: int, max_retries: int ) -> OllamaConfig: """Create OllamaConfig with validated parameters.""" return OllamaConfig( model=model, server_url=normalize_server_url(server_url), timeout=timeout, max_retries=max_retries, ) def create_review_config( paths: List[Path], ollama_config: OllamaConfig ) -> ReviewConfig: """Create complete ReviewConfig from validated components.""" return ReviewConfig(paths=paths, ollama=ollama_config) def create_argument_parser() -> argparse.ArgumentParser: """Create and configure the argument parser.""" parser = argparse.ArgumentParser( prog="reviewllama", description="AI-powered code review assistant", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: reviewllama . --model gemma3:27b --server localhost:11434 reviewllama src/ tests/ --model llama3.2:7b --timeout 60 """, ) parser.add_argument( "paths", nargs="+", metavar="PATH", help="One or more file paths or git directories to review", ) parser.add_argument( "--model", default="llama3.2:3b", help="Ollama model to use for code review (default: %(default)s)", ) parser.add_argument( "--server", dest="server_url", default="localhost:11434", help="Ollama server URL (default: %(default)s)", ) parser.add_argument( "--timeout", type=int, default=30, help="Request timeout in seconds (default: %(default)s)", ) parser.add_argument( "--max-retries", dest="max_retries", type=int, default=3, help="Maximum number of retry attempts (default: %(default)s)", ) return parser def parse_raw_arguments(args: Optional[List[str]] = None) -> argparse.Namespace: """Parse command line arguments into raw namespace.""" parser = create_argument_parser() return parser.parse_args(args) def transform_namespace_to_config(namespace: argparse.Namespace) -> ReviewConfig: """Transform argparse namespace into ReviewConfig.""" paths = [Path(path_str) for path_str in namespace.paths] ollama_config = create_ollama_config( model=namespace.model, server_url=namespace.server_url, timeout=namespace.timeout, max_retries=namespace.max_retries, ) return create_review_config(paths=paths, ollama_config=ollama_config) def parse_arguments(args: Optional[List[str]] = None) -> ReviewConfig: """Parse command line arguments and return validated configuration.""" raw_namespace = parse_raw_arguments(args) return transform_namespace_to_config(raw_namespace) def cli() -> None: """Main entry point for the CLI.""" try: config = parse_arguments() # TODO: Pass config to review engine print(f"Reviewing {len(config.paths)} path(s) with model {config.ollama.model}") for path in config.paths: print(f" - {path}") except SystemExit: # argparse calls sys.exit on error, let it propagate raise except Exception as e: print(f"Error: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()