fix: correct CI workflow configuration for git-diff-explainer-cli
This commit is contained in:
@@ -1,291 +1 @@
|
||||
"""CLI interface for git diff explainer."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from gdiffer import __version__
|
||||
from gdiffer.code_analyzer import CodeAnalyzer
|
||||
from gdiffer.issue_detector import IssueDetector
|
||||
from gdiffer.language_detector import LanguageDetector
|
||||
from gdiffer.models import DiffAnalysis, DiffFile
|
||||
from gdiffer.output import OutputFormatter, OutputFormat
|
||||
from gdiffer.parser import parse_diff
|
||||
|
||||
|
||||
def create_analysis(files: list[DiffFile], verbose: bool = False) -> DiffAnalysis:
|
||||
analysis = DiffAnalysis()
|
||||
language_detector = LanguageDetector()
|
||||
code_analyzer = CodeAnalyzer()
|
||||
issue_detector = IssueDetector()
|
||||
|
||||
for file_obj in files:
|
||||
analysis.files.append(file_obj)
|
||||
|
||||
if file_obj.change_type == "add":
|
||||
analysis.files_added += 1
|
||||
elif file_obj.change_type == "delete":
|
||||
analysis.files_deleted += 1
|
||||
elif file_obj.change_type == "rename":
|
||||
analysis.files_renamed += 1
|
||||
else:
|
||||
analysis.files_modified += 1
|
||||
|
||||
lang = language_detector.detect(file_obj.filename)
|
||||
if lang != "text":
|
||||
analysis.language_breakdown[lang] = analysis.language_breakdown.get(lang, 0) + 1
|
||||
|
||||
for hunk in file_obj.hunks:
|
||||
old_code = '\n'.join(hunk.old_lines_content)
|
||||
new_code = '\n'.join(hunk.new_lines_content)
|
||||
|
||||
summary = code_analyzer.summarize_change(old_code, new_code, lang)
|
||||
|
||||
issues = issue_detector.detect_diff_issues(old_code, new_code, lang)
|
||||
for issue in issues:
|
||||
issue_dict = {
|
||||
'type': issue.type,
|
||||
'severity': issue.severity,
|
||||
'title': issue.title,
|
||||
'description': issue.description,
|
||||
'line': issue.line,
|
||||
'suggestion': issue.suggestion,
|
||||
'file': file_obj.filename,
|
||||
}
|
||||
analysis.all_issues.append(issue_dict)
|
||||
|
||||
suggestions = issue_detector.suggest_improvements(new_code, lang)
|
||||
analysis.all_suggestions.extend(suggestions)
|
||||
|
||||
analysis.total_changes += hunk.new_lines
|
||||
|
||||
analysis.total_files = len(files)
|
||||
|
||||
return analysis
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version=__version__)
|
||||
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
|
||||
@click.option('--output', '-o', type=click.Choice(['terminal', 'json', 'plain']),
|
||||
default='terminal', help='Output format')
|
||||
@click.pass_context
|
||||
def main(ctx: click.Context, verbose: bool, output: str):
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['verbose'] = verbose
|
||||
ctx.obj['output'] = output
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('diff_input', type=click.STRING, required=False)
|
||||
@click.option('--file', '-f', type=click.Path(exists=True), help='Read diff from file')
|
||||
@click.option('--stdin', '-s', is_flag=True, help='Read diff from stdin')
|
||||
@click.pass_context
|
||||
def explain(ctx: click.Context, diff_input: Optional[str], file: Optional[str], stdin: bool):
|
||||
verbose = ctx.obj.get('verbose', False)
|
||||
output_format = ctx.obj.get('output', 'terminal')
|
||||
|
||||
diff_content = ""
|
||||
|
||||
if stdin:
|
||||
diff_content = sys.stdin.read()
|
||||
elif file:
|
||||
with open(file, 'r') as f:
|
||||
diff_content = f.read()
|
||||
elif diff_input:
|
||||
diff_content = diff_input
|
||||
else:
|
||||
click.echo("No diff provided. Use --stdin, --file, or pass diff as argument.", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
files = parse_diff(diff_content)
|
||||
|
||||
if not files:
|
||||
click.echo("No valid diff files found. Please provide a valid git diff.", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
analysis = create_analysis(files, verbose)
|
||||
|
||||
if output_format == 'json':
|
||||
result = format_analysis_json(analysis)
|
||||
click.echo(result)
|
||||
else:
|
||||
formatter = OutputFormatter(OutputFormat(output_format))
|
||||
formatter.print_analysis(analysis)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error analyzing diff: {e}", err=True)
|
||||
if verbose:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option('--file', '-f', type=click.Path(exists=True), help='Read diff from file')
|
||||
@click.option('--stdin', '-s', is_flag=True, help='Read diff from stdin')
|
||||
@click.pass_context
|
||||
def issues(ctx: click.Context, file: Optional[str], stdin: bool):
|
||||
diff_content = ""
|
||||
|
||||
if stdin:
|
||||
diff_content = sys.stdin.read()
|
||||
elif file:
|
||||
with open(file, 'r') as f:
|
||||
diff_content = f.read()
|
||||
else:
|
||||
diff_content = sys.stdin.read()
|
||||
|
||||
if not diff_content.strip():
|
||||
click.echo("No diff provided.", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
files = parse_diff(diff_content)
|
||||
|
||||
issue_detector = IssueDetector()
|
||||
all_issues = []
|
||||
|
||||
for file_obj in files:
|
||||
for hunk in file_obj.hunks:
|
||||
old_code = '\n'.join(hunk.old_lines_content)
|
||||
new_code = '\n'.join(hunk.new_lines_content)
|
||||
lang = LanguageDetector().detect(file_obj.filename)
|
||||
|
||||
issues = issue_detector.detect_diff_issues(old_code, new_code, lang)
|
||||
for issue in issues:
|
||||
all_issues.append({
|
||||
'file': file_obj.filename,
|
||||
'line': issue.line,
|
||||
'severity': issue.severity,
|
||||
'title': issue.title,
|
||||
'description': issue.description,
|
||||
'suggestion': issue.suggestion,
|
||||
})
|
||||
|
||||
if all_issues:
|
||||
severity_priority = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3}
|
||||
all_issues.sort(key=lambda x: severity_priority.get(x.get('severity', ''), 4))
|
||||
|
||||
if ctx.obj.get('output') == 'json':
|
||||
click.echo(json.dumps(all_issues, indent=2))
|
||||
else:
|
||||
for issue in all_issues:
|
||||
color = {'critical': 'red', 'high': 'orange3', 'medium': 'yellow', 'low': 'cyan'}.get(
|
||||
issue['severity'], 'white'
|
||||
)
|
||||
click.echo(f"[{color}][{issue['severity'].upper()}][/] {issue['title']}")
|
||||
click.echo(f" File: {issue['file']}:{issue['line']}")
|
||||
click.echo(f" {issue['description']}")
|
||||
click.echo(f" Suggestion: {issue['suggestion']}")
|
||||
click.echo()
|
||||
else:
|
||||
click.echo("No issues detected in the diff.")
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error analyzing issues: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option('--file', '-f', type=click.Path(exists=True), help='Read diff from file')
|
||||
@click.option('--stdin', '-s', is_flag=True, help='Read diff from stdin')
|
||||
@click.pass_context
|
||||
def summarize(ctx: click.Context, file: Optional[str], stdin: bool):
|
||||
diff_content = ""
|
||||
|
||||
if stdin:
|
||||
diff_content = sys.stdin.read()
|
||||
elif file:
|
||||
with open(file, 'r') as f:
|
||||
diff_content = f.read()
|
||||
else:
|
||||
diff_content = sys.stdin.read()
|
||||
|
||||
if not diff_content.strip():
|
||||
click.echo("No diff provided.", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
files = parse_diff(diff_content)
|
||||
|
||||
if not files:
|
||||
click.echo("No valid diff files found.")
|
||||
sys.exit(1)
|
||||
|
||||
analysis = create_analysis(files)
|
||||
|
||||
click.echo(f"Files changed: {analysis.total_files}")
|
||||
click.echo(f" Added: {analysis.files_added}")
|
||||
click.echo(f" Deleted: {analysis.files_deleted}")
|
||||
click.echo(f" Modified: {analysis.files_modified}")
|
||||
click.echo(f" Renamed: {analysis.files_renamed}")
|
||||
click.echo(f"Total changes: {analysis.total_changes}")
|
||||
|
||||
if analysis.language_breakdown:
|
||||
click.echo("\nLanguages:")
|
||||
for lang, count in sorted(analysis.language_breakdown.items()):
|
||||
click.echo(f" - {lang}: {count} files")
|
||||
|
||||
if analysis.all_issues:
|
||||
critical = sum(1 for i in analysis.all_issues if i.get('severity') == 'critical')
|
||||
high = sum(1 for i in analysis.all_issues if i.get('severity') == 'high')
|
||||
medium = sum(1 for i in analysis.all_issues if i.get('severity') == 'medium')
|
||||
low = sum(1 for i in analysis.all_issues if i.get('severity') == 'low')
|
||||
|
||||
click.echo(f"\nIssues found: {len(analysis.all_issues)}")
|
||||
if critical:
|
||||
click.echo(f" Critical: {critical}")
|
||||
if high:
|
||||
click.echo(f" High: {high}")
|
||||
if medium:
|
||||
click.echo(f" Medium: {medium}")
|
||||
if low:
|
||||
click.echo(f" Low: {low}")
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error summarizing diff: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def format_analysis_json(analysis: DiffAnalysis) -> str:
|
||||
result = {
|
||||
'summary': {
|
||||
'total_files': analysis.total_files,
|
||||
'files_added': analysis.files_added,
|
||||
'files_deleted': analysis.files_deleted,
|
||||
'files_modified': analysis.files_modified,
|
||||
'files_renamed': analysis.files_renamed,
|
||||
'total_changes': analysis.total_changes,
|
||||
'language_breakdown': analysis.language_breakdown,
|
||||
},
|
||||
'files': [],
|
||||
'issues': analysis.all_issues,
|
||||
'suggestions': analysis.all_suggestions,
|
||||
}
|
||||
|
||||
for file_obj in analysis.files:
|
||||
file_data = {
|
||||
'filename': file_obj.filename,
|
||||
'change_type': file_obj.change_type,
|
||||
'language': file_obj.extension,
|
||||
'hunks': [],
|
||||
}
|
||||
|
||||
for hunk in file_obj.hunks:
|
||||
hunk_data = {
|
||||
'old_start': hunk.old_start,
|
||||
'new_start': hunk.new_start,
|
||||
'changes': {
|
||||
'added': hunk.get_added_lines(),
|
||||
'removed': hunk.get_removed_lines(),
|
||||
},
|
||||
}
|
||||
file_data['hunks'].append(hunk_data)
|
||||
|
||||
result['files'].append(file_data)
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
# src/gdiffer/cli.py
|
||||
|
||||
Reference in New Issue
Block a user