From 321103bf8c5a1d29d1a9e8856c8d608615f00604 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 23:35:10 +0000 Subject: [PATCH] Initial upload: CLI Explain Fix project with CI/CD workflow --- src/cli_explain_fix/cli.py | 271 +++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 src/cli_explain_fix/cli.py diff --git a/src/cli_explain_fix/cli.py b/src/cli_explain_fix/cli.py new file mode 100644 index 0000000..0692d30 --- /dev/null +++ b/src/cli_explain_fix/cli.py @@ -0,0 +1,271 @@ +"""Command-line interface for CLI Explain Fix.""" + +import sys +from typing import Optional, Any + +import typer +from rich.console import Console +from rich.panel import Panel +from rich.text import Text +from rich.syntax import Syntax + +from cli_explain_fix.parser import ErrorParser +from cli_explain_fix.knowledge_base import KnowledgeBase +from cli_explain_fix.explainer import Explainer +from cli_explain_fix.config import Config + +app = typer.Typer( + name="cli-explain-fix", + help="Parse command output and generate human-readable explanations with actionable fix suggestions", + add_completion=False, +) + +console = Console() +kb = KnowledgeBase() +parser = ErrorParser() +explainer = Explainer(kb) + + +def print_rich_output(explanation: dict, verbose: bool = False, no_color: bool = False) -> None: + """Print explanation using Rich formatting.""" + console = Console(force_terminal=not no_color) + + summary = explanation.get('summary', 'Error') + what = explanation.get('what_happened', '') + why = explanation.get('why_happened', '') + fixes = explanation.get('how_to_fix', []) + + parts: list[Any] = [] + + parts.append(Text(f"Summary: {summary}\n\n", style="bold")) + parts.append(Text("What happened:\n", style="bold blue")) + parts.append(Text(f"{what}\n\n", style="")) + parts.append(Text("Why it happened:\n", style="bold blue")) + parts.append(Text(f"{why}\n\n", style="")) + + if fixes: + parts.append(Text("How to fix:\n", style="bold green")) + for i, fix in enumerate(fixes, 1): + parts.append(Text(f" {i}. {fix}\n", style="")) + + if 'location' in explanation: + loc = explanation['location'] + parts.append(Text(f"\nLocation: {loc['file']}:{loc['line']}\n", style="italic")) + + if 'code_examples' in explanation: + parts.append(Text("\nCode examples:\n", style="bold blue")) + from rich.console import Group + for example in explanation['code_examples']: + if example['description']: + parts.append(Text(f"{example['description']}\n", style="italic")) + syntax = Syntax(example['code'], example['language'], theme="monokai") + parts.append(Text.assemble(("", "")) if False else syntax) + parts.append(Text("\n", style="")) + + if 'documentation' in explanation: + parts.append(Text(f"\nDocumentation: {explanation['documentation']}\n", style="link")) + + if verbose and 'raw_error' in explanation: + parts.append(Text("\n--- Raw Error ---\n", style="dim")) + parts.append(Text(explanation['raw_error'], style="dim")) + + from rich.console import Group + content = Group(*parts) + + panel = Panel( + content, + title="CLI Explain Fix", + border_style="blue", + expand=True, + ) + console.print(panel) + + +def print_json_output(explanation: dict) -> None: + """Print explanation as JSON.""" + import json + console.print(json.dumps(explanation, indent=2)) + + +def print_plain_output(explanation: dict) -> None: + """Print explanation as plain text.""" + print(f"Error: {explanation.get('error_type', 'Unknown')}") + print(f"Language: {explanation.get('language', 'unknown')}") + print() + print("What happened:") + print(f" {explanation.get('what_happened', 'Unknown')}") + print() + print("Why it happened:") + print(f" {explanation.get('why_happened', 'Unknown')}") + print() + print("How to fix:") + for i, fix in enumerate(explanation.get('how_to_fix', []), 1): + print(f" {i}. {fix}") + + +def process_input( + input_text: str, + language: Optional[str], + verbose: bool, + output_format: str, + no_color: bool, +) -> None: + """Process input and display explanation.""" + if not input_text.strip(): + console.print(Panel( + "[bold yellow]No input provided[/bold yellow]\n\n" + "Usage examples:\n" + " echo 'ImportError: No module named foo' | cli-explain-fix\n" + " cli-explain-fix --file error.txt\n" + " cli-explain-fix --lang python 'ValueError: invalid value'", + title="CLI Explain Fix", + border_style="yellow", + )) + return + + parsed = parser.parse(input_text, language) + explanation = explainer.explain(parsed, verbose) + + if output_format == "json": + print_json_output(explanation) + elif output_format == "plain": + print_plain_output(explanation) + else: + print_rich_output(explanation, verbose, no_color) + + +@app.command() +def main( + input_text: str = typer.Argument( + None, + help="Error text to explain (reads from stdin if not provided)", + ), + file: Optional[str] = typer.Option( + None, + "--file", + "-f", + help="Read error from file", + ), + language: Optional[str] = typer.Option( + None, + "--lang", + "-l", + help="Explicitly specify language (python, javascript, go, rust, etc.)", + ), + verbose: bool = typer.Option( + False, + "--verbose", + "-v", + help="Show detailed output including raw error", + ), + json_output: bool = typer.Option( + False, + "--json", + "-j", + help="Output as JSON", + ), + plain: bool = typer.Option( + False, + "--plain", + "-p", + help="Output as plain text", + ), + no_color: bool = typer.Option( + False, + "--no-color", + help="Disable colors in output", + ), + theme: Optional[str] = typer.Option( + None, + "--theme", + help="Color theme for output", + ), +) -> None: + """Explain errors and provide fix suggestions.""" + config = Config.load() + + if theme: + output_format = "rich" + elif json_output: + output_format = "json" + elif plain: + output_format = "plain" + else: + output_format = config.output_format + + input_source = "" + + if file: + try: + with open(file, 'r', encoding='utf-8') as f: + input_source = f.read() + except OSError as e: + console.print(f"[bold red]Error:[/bold red] Could not read file '{file}': {e}") + raise typer.Exit(1) + elif input_text: + input_source = input_text + else: + input_source = sys.stdin.read() + + effective_language = language or config.default_language + process_input(input_source, effective_language, verbose, output_format, no_color) + + +@app.command() +def list_languages() -> None: + """List all supported programming languages.""" + languages = kb.list_languages() + console.print(Panel( + "\n".join(f" • {lang}" for lang in languages), + title="Supported Languages", + border_style="green", + )) + + +@app.command() +def list_errors( + language: Optional[str] = typer.Option( + None, + "--lang", + "-l", + help="Filter by language", + ), +) -> None: + """List all known error types.""" + errors = kb.list_errors(language) + + if not errors: + console.print("[yellow]No errors found in knowledge base[/yellow]") + return + + content = "\n".join( + f" • [{e['language']}] {e['error_type']}: {e['description']}" + for e in errors + ) + console.print(Panel( + content, + title="Known Errors", + border_style="blue", + )) + + +@app.command() +def show_config() -> None: + """Show current configuration.""" + config = Config.load() + + content = f"""Default Language: {config.default_language} +Output Format: {config.output_format} +Verbosity: {config.verbosity} +Theme: {config.theme} +Config File: {config.config_path}""" + + console.print(Panel( + content, + title="Current Configuration", + border_style="blue", + )) + + +if __name__ == "__main__": + app()