Initial upload: ErrorFix CLI with rule engine and pattern matching
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
149
errorfix/cli.py
Normal file
149
errorfix/cli.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
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()
|
||||||
Reference in New Issue
Block a user