Files
errorfix-cli/errorfix/cli.py
2026-02-01 03:56:20 +00:00

150 lines
5.0 KiB
Python

import sys
import os
from typing import Optional, List, Tuple
import click
from errorfix.rules import RuleLoader
from errorfix.patterns import PatternMatcher
from errorfix.formatters import TextFormatter, JSONFormatter, StructuredFormatter
from errorfix.plugins import PluginLoader
def get_rule_loader() -> RuleLoader:
return RuleLoader()
def get_pattern_matcher() -> PatternMatcher:
return PatternMatcher()
def get_plugin_loader() -> PluginLoader:
return PluginLoader()
@click.group()
@click.option('--rules-path', '-r', multiple=True, help='Path to rule files or directories')
@click.option('--plugin', '-p', multiple=True, help='Plugin modules to load')
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
@click.pass_context
def cli(ctx: click.Context, rules_path: Tuple[str], plugin: Tuple[str], verbose: bool):
ctx.ensure_object(dict)
ctx.obj['rules_path'] = list(rules_path)
ctx.obj['plugins'] = list(plugin)
ctx.obj['verbose'] = verbose
@cli.command()
@click.option('--input', '-i', 'error_input', type=click.File('r'), default='-', help='Input file or stdin')
@click.option('--output-format', '-f', type=click.Choice(['text', 'json', 'structured']), default='text', help='Output format')
@click.option('--language', '-l', help='Filter rules by language')
@click.option('--tool', '-t', help='Filter rules by tool')
@click.option('--limit', type=int, default=None, help='Limit number of matches')
@click.option('--no-color', is_flag=True, help='Disable colored output')
@click.option('--rules', '-r', multiple=True, help='Additional rule paths')
@click.pass_context
def fix(
ctx: click.Context,
error_input: click.File,
output_format: str,
language: Optional[str],
tool: Optional[str],
limit: Optional[int],
no_color: bool,
rules: Tuple[str]
):
if error_input == sys.stdin and hasattr(sys.stdin, 'closed') and sys.stdin.closed:
error_text = ''
else:
error_text = error_input.read() if error_input and error_input != '-' else ''
rule_loader = get_rule_loader()
matcher = get_pattern_matcher()
plugin_loader = get_plugin_loader()
all_rules_paths = list(ctx.obj.get('rules_path', [])) + list(rules)
rules_list = []
for path in all_rules_paths:
try:
if os.path.exists(path):
rules_list.extend(rule_loader.load_multiple([path]))
except Exception as e:
if ctx.obj.get('verbose'):
click.echo(f"Warning: Failed to load rules from {path}: {e}", err=True)
for plugin_name in ctx.obj.get('plugins', []):
try:
plugin_loader.load_plugin_module(plugin_name)
plugin_rules = plugin_loader.get_registry().get_all_rules()
from errorfix.rules import Rule
rules_list.extend([Rule.from_dict(r) for r in plugin_rules])
except Exception as e:
click.echo(f"Warning: Failed to load plugin {plugin_name}: {e}", err=True)
if not rules_list:
default_rules_path = os.environ.get('ERRORFIX_RULES_PATH', 'rules')
if os.path.exists(default_rules_path):
try:
rules_list = rule_loader.load_directory(default_rules_path)
except Exception:
pass
if language:
rules_list = rule_loader.filter_rules(rules_list, language=language)
if tool:
rules_list = rule_loader.filter_rules(rules_list, tool=tool)
matches = matcher.match_all(error_text, rules_list, limit=limit)
if output_format == 'json':
formatter = JSONFormatter(pretty=not no_color)
elif output_format == 'structured':
formatter = StructuredFormatter()
else:
formatter = TextFormatter(use_colors=not no_color)
output = formatter.format(matches, error_text)
click.echo(output)
@cli.command()
@click.option('--dry-run', is_flag=True, help='Show what would be fixed without applying')
@click.pass_context
def interactive(ctx: click.Context, dry_run: bool):
click.echo("Interactive mode not yet implemented. Use --dry-run for preview.")
@cli.command()
def plugins():
plugin_loader = get_plugin_loader()
plugin_list = plugin_loader.discover_and_load()
for plugin in plugin_list:
click.echo(f"{plugin.name} v{plugin.version}: {plugin.description}")
@cli.command()
@click.option('--rules', '-r', multiple=True, help='Rule paths to check')
@click.pass_context
def check(ctx: click.Context, rules: Tuple[str]):
rule_loader = get_rule_loader()
all_rules_paths = list(ctx.obj.get('rules_path', [])) + list(rules)
for path in all_rules_paths:
try:
if os.path.exists(path):
rules_list = rule_loader.load_multiple([path])
click.echo(f"Loaded {len(rules_list)} rules from {path}")
else:
click.echo(f"Path not found: {path}")
except Exception as e:
click.echo(f"Error loading {path}: {e}", err=True)
def main():
cli()
if __name__ == '__main__':
main()