Files
errorfix-cli/errorfix/cli.py
7000pctAUTO a78a6dbfd6
Some checks failed
CI / test (push) Has been cancelled
ErrorFix CLI CI / test (push) Has been cancelled
fix: resolve CI linting failures by adding ruff configuration
2026-02-01 04:48:03 +00:00

155 lines
5.1 KiB
Python

import sys
import os
from typing import Optional, Tuple, IO
import click
from errorfix.rules import RuleLoader
from errorfix.patterns import PatternMatcher
from errorfix.formatters import TextFormatter, JSONFormatter, StructuredFormatter, Formatter
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: IO[str],
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 = ''
if error_input and error_input != '-':
try:
error_text = error_input.read()
except Exception:
pass
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: 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()