Initial upload: CLI Explain Fix project with CI/CD workflow
This commit is contained in:
271
src/cli_explain_fix/cli.py
Normal file
271
src/cli_explain_fix/cli.py
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user