415 lines
18 KiB
Python
Executable File
415 lines
18 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""
|
|
Code Roast Bot - AI-powered sarcastic code review with professional implementation
|
|
This script provides humorous code feedback while demonstrating real code analysis concepts
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import re
|
|
import json
|
|
import random
|
|
import argparse
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
|
|
class CodeRoastBot:
|
|
"""Professional code analysis with humorous feedback delivery"""
|
|
|
|
def __init__(self, roast_intensity: int = 7):
|
|
self.roast_intensity = min(10, max(1, roast_intensity))
|
|
self.roast_history = []
|
|
|
|
# Professional code analysis patterns
|
|
self.code_patterns = {
|
|
'long_function': {'regex': r'def\s+\w+\([^)]*\):.*\n(?:\s+.*\n){20,}', 'severity': 'medium'},
|
|
'deep_nesting': {'regex': r'(if|for|while|try).*\n(\s+){8}', 'severity': 'high'},
|
|
'long_line': {'regex': r'.{100,}', 'severity': 'low'},
|
|
'magic_numbers': {'regex': r'\b\d{2,}\b(?!\s*#.*magic)', 'severity': 'medium'},
|
|
'todo_comments': {'regex': r'#\s*TODO|FIXME|HACK', 'severity': 'low'},
|
|
'complex_regex': {'regex': r're\.compile\([^)]{100,}\)', 'severity': 'high'},
|
|
'multiple_returns': {'regex': r'return.*\n.*return', 'severity': 'medium'},
|
|
'bare_except': {'regex': r'except\s*:', 'severity': 'high'},
|
|
'global_variables': {'regex': r'^\s*[A-Z_]+\s*=', 'severity': 'medium'},
|
|
'long_imports': {'regex': r'from\s+\w+(\.\w+)*\s+import\s+[^,]+,', 'severity': 'low'}
|
|
}
|
|
|
|
# Professional feedback templates
|
|
self.professional_feedback = {
|
|
'long_function': [
|
|
"Consider breaking this function into smaller, more focused units",
|
|
"This function might benefit from the Single Responsibility Principle",
|
|
"Function complexity could be reduced by extracting helper methods"
|
|
],
|
|
'deep_nesting': [
|
|
"Deep nesting can make code difficult to read and maintain",
|
|
"Consider extracting nested logic into separate functions",
|
|
"Guard clauses or early returns might simplify this structure"
|
|
],
|
|
'long_line': [
|
|
"Line length exceeds typical style guide recommendations",
|
|
"Consider breaking long lines for better readability",
|
|
"PEP 8 suggests limiting lines to 79 characters"
|
|
],
|
|
'magic_numbers': [
|
|
"Magic numbers should be replaced with named constants",
|
|
"Consider defining these values as named constants for clarity",
|
|
"Magic numbers reduce code maintainability"
|
|
],
|
|
'todo_comments': [
|
|
"TODO comments should be addressed before production deployment",
|
|
"Consider creating proper tickets for TODO items",
|
|
"FIXME comments indicate technical debt that should be resolved"
|
|
],
|
|
'complex_regex': [
|
|
"Complex regular expressions should be documented",
|
|
"Consider breaking complex regex into smaller, named components",
|
|
"Regex complexity makes maintenance difficult"
|
|
],
|
|
'multiple_returns': [
|
|
"Multiple return points can make function flow harder to follow",
|
|
"Consider restructuring to have a single exit point",
|
|
"Multiple returns are acceptable but should be used judiciously"
|
|
],
|
|
'bare_except': [
|
|
"Bare except clauses can hide important errors",
|
|
"Specify the exceptions you want to catch",
|
|
"Bare except makes debugging more difficult"
|
|
],
|
|
'global_variables': [
|
|
"Global variables can make code harder to test and maintain",
|
|
"Consider dependency injection instead of global state",
|
|
"Global variables reduce code modularity"
|
|
],
|
|
'long_imports': [
|
|
"Long import lines can be hard to read",
|
|
"Consider using multiple import statements",
|
|
"Import organization improves code readability"
|
|
]
|
|
}
|
|
|
|
# Humorous roast templates (professional but entertaining)
|
|
self.roast_templates = {
|
|
'low': [
|
|
"This code is {issue}, but we've all been there. No judgment!",
|
|
"Found {issue} - minor issue, but worth noting for future reference.",
|
|
"Code has {issue}. Consider fixing it when you have a spare moment.",
|
|
"Detecting {issue} - not the end of the world, but room for improvement."
|
|
],
|
|
'medium': [
|
|
"Well hello there, {issue}! Your code decided to be 'creative' today.",
|
|
"This code has {issue}. It's like the code equivalent of wearing socks with sandals.",
|
|
"Found {issue} in your code. It's not broken, but it's definitely... interesting.",
|
|
"Your code exhibits {issue}. The compiler is giving you the side-eye right now."
|
|
],
|
|
'high': [
|
|
"WOW! This code has {issue}. That's... one way to solve the problem!",
|
|
"I'm not angry, I'm just disappointed that I found {issue} in your code.",
|
|
"This code has {issue}. It's so bold it makes me respect it, then fear it.",
|
|
"Congratulations! Your code achieved {issue}. That's not something you see every day."
|
|
]
|
|
}
|
|
|
|
# Code quality assessments
|
|
self.quality_assessments = [
|
|
"This code has more issues than a comic book store.",
|
|
"Your code is like a box of chocolates - full of surprises.",
|
|
"This code writes itself - unfortunately, it didn't study programming first.",
|
|
"Your code is so unique, it probably has its own programming paradigm.",
|
|
"This code is breaking new ground - unfortunately, it's the ground of common sense.",
|
|
"Your code is like modern art - some people get it, most people don't.",
|
|
"This code has more personality than a sitcom character.",
|
|
"Your code is the reason why 'code review' was invented.",
|
|
"This code is so creative, it makes abstract art look straightforward.",
|
|
"Your code is like a puzzle - the main puzzle is figuring out what it does."
|
|
]
|
|
|
|
def analyze_file(self, file_path: str) -> Dict:
|
|
"""Analyze a single file for code issues"""
|
|
issues = []
|
|
total_lines = 0
|
|
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
lines = content.split('\n')
|
|
total_lines = len(lines)
|
|
|
|
for pattern_name, pattern_info in self.code_patterns.items():
|
|
matches = re.finditer(pattern_info['regex'], content, re.MULTILINE | re.DOTALL)
|
|
for match in matches:
|
|
# Find line number
|
|
line_number = content[:match.start()].count('\n') + 1
|
|
line_content = lines[line_number - 1] if line_number <= len(lines) else ""
|
|
|
|
issues.append({
|
|
'type': pattern_name,
|
|
'line': line_number,
|
|
'content': line_content.strip(),
|
|
'severity': pattern_info['severity'],
|
|
'match_text': match.group()
|
|
})
|
|
|
|
except Exception as e:
|
|
issues.append({
|
|
'type': 'file_error',
|
|
'line': 0,
|
|
'content': f"Could not read file: {str(e)}",
|
|
'severity': 'high',
|
|
'match_text': ''
|
|
})
|
|
|
|
return {
|
|
'file_path': file_path,
|
|
'total_lines': total_lines,
|
|
'issues': issues,
|
|
'issue_count': len(issues)
|
|
}
|
|
|
|
def generate_roast(self, analysis_result: Dict) -> str:
|
|
"""Generate humorous but professional feedback"""
|
|
issues = analysis_result['issues']
|
|
file_path = analysis_result['file_path']
|
|
|
|
if not issues:
|
|
return f"🎉 {file_path} is surprisingly clean! Either you're a coding genius or this file is empty. Either way, well done!"
|
|
|
|
# Generate professional summary
|
|
summary_lines = []
|
|
summary_lines.append(f"📋 Code Analysis for {file_path}")
|
|
summary_lines.append(f" Total lines: {analysis_result['total_lines']}")
|
|
summary_lines.append(f" Issues found: {len(issues)}")
|
|
summary_lines.append("")
|
|
|
|
# Group issues by severity
|
|
severity_counts = {'low': 0, 'medium': 0, 'high': 0}
|
|
for issue in issues:
|
|
if issue['severity'] in severity_counts:
|
|
severity_counts[issue['severity']] += 1
|
|
|
|
summary_lines.append("📊 Issue Breakdown:")
|
|
for severity, count in severity_counts.items():
|
|
if count > 0:
|
|
summary_lines.append(f" {severity.title()}: {count}")
|
|
|
|
summary_lines.append("")
|
|
|
|
# Add specific feedback for each issue
|
|
summary_lines.append("🔍 Detailed Feedback:")
|
|
for issue in issues[:5]: # Limit to top 5 issues for readability
|
|
if issue['type'] in self.professional_feedback:
|
|
professional = random.choice(self.professional_feedback[issue['type']])
|
|
roast = self._generate_roast_for_issue(issue)
|
|
|
|
summary_lines.append(f"Line {issue['line']}: {issue['type']}")
|
|
summary_lines.append(f" 💡 {professional}")
|
|
if self.roast_intensity >= 6:
|
|
summary_lines.append(f" 😄 {roast}")
|
|
summary_lines.append("")
|
|
|
|
# Add overall assessment
|
|
if len(issues) > 10:
|
|
assessment = random.choice(self.quality_assessments)
|
|
summary_lines.append(f"🎭 Overall Assessment: {assessment}")
|
|
|
|
# Add encouragement
|
|
summary_lines.append("")
|
|
summary_lines.append("💪 Remember: Every great developer was once a beginner. Keep coding, keep learning!")
|
|
summary_lines.append("🚀 Professional tip: Use linters and formatters to catch these issues automatically.")
|
|
|
|
return "\n".join(summary_lines)
|
|
|
|
def _generate_roast_for_issue(self, issue: Dict) -> str:
|
|
"""Generate a roast for a specific issue"""
|
|
severity = issue['severity']
|
|
issue_type = issue['type']
|
|
|
|
if severity not in self.roast_templates:
|
|
severity = 'medium'
|
|
|
|
template = random.choice(self.roast_templates[severity])
|
|
|
|
# Convert issue type to readable format
|
|
readable_issue = issue_type.replace('_', ' ').title()
|
|
|
|
return template.format(issue=readable_issue)
|
|
|
|
def analyze_directory(self, directory: str) -> Dict:
|
|
"""Analyze all files in a directory"""
|
|
results = {}
|
|
total_issues = 0
|
|
|
|
# Supported file extensions
|
|
extensions = ['.py', '.js', '.ts', '.java', '.cpp', '.c', '.go', '.rs', '.rb']
|
|
|
|
for root, dirs, files in os.walk(directory):
|
|
# Skip common directories to ignore
|
|
dirs[:] = [d for d in dirs if d not in ['.git', '__pycache__', 'node_modules', '.venv']]
|
|
|
|
for file in files:
|
|
if any(file.endswith(ext) for ext in extensions):
|
|
file_path = os.path.join(root, file)
|
|
result = self.analyze_file(file_path)
|
|
results[file_path] = result
|
|
total_issues += result['issue_count']
|
|
|
|
return {
|
|
'directory': directory,
|
|
'files_analyzed': len(results),
|
|
'total_issues': total_issues,
|
|
'results': results
|
|
}
|
|
|
|
def generate_directory_report(self, analysis: Dict) -> str:
|
|
"""Generate a comprehensive report for directory analysis"""
|
|
report_lines = []
|
|
|
|
report_lines.append("🎪 CI/CD Chaos Code Roast Report")
|
|
report_lines.append("=" * 50)
|
|
report_lines.append(f"📁 Directory: {analysis['directory']}")
|
|
report_lines.append(f"📄 Files analyzed: {analysis['files_analyzed']}")
|
|
report_lines.append(f"🐛 Total issues found: {analysis['total_issues']}")
|
|
report_lines.append("")
|
|
|
|
# Top problematic files
|
|
sorted_files = sorted(analysis['results'].items(),
|
|
key=lambda x: x[1]['issue_count'], reverse=True)
|
|
|
|
report_lines.append("🔥 Most Problematic Files:")
|
|
for file_path, result in sorted_files[:5]:
|
|
if result['issue_count'] > 0:
|
|
report_lines.append(f" {file_path}: {result['issue_count']} issues")
|
|
|
|
report_lines.append("")
|
|
|
|
# Individual file summaries
|
|
report_lines.append("📋 Individual File Analysis:")
|
|
for file_path, result in sorted_files:
|
|
if result['issue_count'] > 0:
|
|
report_lines.append("-" * 40)
|
|
report_lines.append(f"File: {file_path}")
|
|
report_lines.append(f"Issues: {result['issue_count']}")
|
|
|
|
# Show top issues
|
|
issues_by_type = {}
|
|
for issue in result['issues']:
|
|
issue_type = issue['type']
|
|
if issue_type not in issues_by_type:
|
|
issues_by_type[issue_type] = []
|
|
issues_by_type[issue_type].append(issue['line'])
|
|
|
|
for issue_type, lines in issues_by_type.items():
|
|
report_lines.append(f" {issue_type}: lines {', '.join(map(str, lines[:3]))}")
|
|
|
|
report_lines.append("")
|
|
|
|
# Add humorous summary
|
|
if analysis['total_issues'] > 50:
|
|
report_lines.append("🎭 Professional Assessment:")
|
|
report_lines.append("This codebase has more personality flaws than a reality TV star.")
|
|
report_lines.append("But don't worry - even the best developers write imperfect code.")
|
|
report_lines.append("The important thing is that you're seeking to improve!")
|
|
elif analysis['total_issues'] > 20:
|
|
report_lines.append("🎭 Professional Assessment:")
|
|
report_lines.append("Your code is like a diamond in the rough - valuable but needs polishing.")
|
|
report_lines.append("Keep up the good work and continue refining your craft!")
|
|
else:
|
|
report_lines.append("🎭 Professional Assessment:")
|
|
report_lines.append("Your code is surprisingly clean! You must be using good practices.")
|
|
report_lines.append("Maintain this quality and you'll be a coding superstar!")
|
|
|
|
report_lines.append("")
|
|
report_lines.append("🚀 Remember: Code reviews and analysis are tools for growth, not criticism.")
|
|
report_lines.append("Every issue found is an opportunity to become a better developer.")
|
|
|
|
return "\n".join(report_lines)
|
|
|
|
def roast_commit_message(self, commit_message: str) -> str:
|
|
"""Roast a commit message"""
|
|
roasts = []
|
|
|
|
# Check commit message length
|
|
if len(commit_message) < 10:
|
|
roasts.append("This commit message is shorter than a developer's attention span during a 9 AM meeting.")
|
|
elif len(commit_message) > 72:
|
|
roasts.append("This commit message is longer than the actual changes. Someone's being thorough!")
|
|
|
|
# Check for common commit message patterns
|
|
if "fix" in commit_message.lower() and "bug" in commit_message.lower():
|
|
roasts.append("Fixing a bug with a commit message that mentions 'fix' and 'bug' - how meta!")
|
|
|
|
if "update" in commit_message.lower() and "readme" in commit_message.lower():
|
|
roasts.append("Updating the README because the code was too confusing to understand on its own.")
|
|
|
|
if "wip" in commit_message.lower():
|
|
roasts.append("Work In Progress - or as I call it, 'I broke something and I'll fix it later'.")
|
|
|
|
if "lol" in commit_message.lower() or "haha" in commit_message.lower():
|
|
roasts.append("This commit message contains laughter. Let's hope the code is funnier than the joke!")
|
|
|
|
# Check for imperative mood
|
|
if not commit_message.split()[0].endswith('ed') and not commit_message.split()[0].endswith('s'):
|
|
roasts.append("Your commit message isn't in imperative mood. The git police are coming!")
|
|
|
|
if not roasts:
|
|
roasts.append("This is actually a pretty good commit message. I'm genuinely impressed!")
|
|
roasts.append("Professional, clear, and concise. Are you sure you're a real developer?")
|
|
|
|
return random.choice(roasts)
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Code Roast Bot - Professional code analysis with humor')
|
|
parser.add_argument('path', nargs='?', help='File or directory to analyze')
|
|
parser.add_argument('--intensity', '-i', type=int, default=7,
|
|
help='Roast intensity (1-10, default: 7)')
|
|
parser.add_argument('--commit', '-c', help='Roast a commit message')
|
|
parser.add_argument('--output', '-o', help='Output file for report')
|
|
|
|
args = parser.parse_args()
|
|
|
|
bot = CodeRoastBot(args.intensity)
|
|
|
|
if args.commit:
|
|
# Roast commit message
|
|
roast = bot.roast_commit_message(args.commit)
|
|
print(f"🎪 Commit Message Roast:")
|
|
print(f"Message: {args.commit}")
|
|
print(f"Roast: {roast}")
|
|
return
|
|
|
|
if not args.path:
|
|
print("Please provide a file or directory path, or use --commit for commit message roasting")
|
|
parser.print_help()
|
|
return
|
|
|
|
path = args.path
|
|
|
|
if os.path.isfile(path):
|
|
# Analyze single file
|
|
result = bot.analyze_file(path)
|
|
roast = bot.generate_roast(result)
|
|
print(roast)
|
|
|
|
elif os.path.isdir(path):
|
|
# Analyze directory
|
|
analysis = bot.analyze_directory(path)
|
|
report = bot.generate_directory_report(analysis)
|
|
print(report)
|
|
|
|
else:
|
|
print(f"Path not found: {path}")
|
|
return
|
|
|
|
# Save to file if requested
|
|
if args.output:
|
|
with open(args.output, 'w', encoding='utf-8') as f:
|
|
if os.path.isfile(path):
|
|
f.write(bot.generate_roast(bot.analyze_file(path)))
|
|
else:
|
|
f.write(bot.generate_directory_report(bot.analyze_directory(path)))
|
|
print(f"\n📄 Report saved to: {args.output}")
|
|
|
|
if __name__ == "__main__":
|
|
main() |