232 lines
6.8 KiB
Python
232 lines
6.8 KiB
Python
"""
|
|
Linting and formatting tools for Clover - A terminal assistant for AI-powered project management
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
import tempfile
|
|
|
|
def lint_code(file_paths: List[str], linter: str = "flake8") -> Dict[str, any]:
|
|
"""
|
|
Run linter on specified file(s).
|
|
|
|
Args:
|
|
file_paths (List[str]): List of file paths to lint
|
|
linter (str): Linter to use (default: flake8)
|
|
|
|
Returns:
|
|
Dict containing linting results and errors
|
|
"""
|
|
try:
|
|
# Validate linter
|
|
if linter not in ["flake8", "pylint"]:
|
|
return {"error": f"Unsupported linter: {linter}"}
|
|
|
|
# Check if linter is available
|
|
try:
|
|
subprocess.run([linter, "--version"],
|
|
capture_output=True, check=True)
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
return {"error": f"{linter} not found. Please install it."}
|
|
|
|
# Build command
|
|
cmd = [linter]
|
|
if linter == "flake8":
|
|
cmd.extend(["--show-source", "--statistics"])
|
|
elif linter == "pylint":
|
|
cmd.extend(["--output-format=json"])
|
|
|
|
cmd.extend(file_paths)
|
|
|
|
# Run linter
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
cwd="."
|
|
)
|
|
|
|
return {
|
|
"linter": linter,
|
|
"files_linted": file_paths,
|
|
"return_code": result.returncode,
|
|
"stdout": result.stdout,
|
|
"stderr": result.stderr,
|
|
"success": result.returncode == 0
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error running linter: {str(e)}"}
|
|
|
|
def format_code(file_paths: List[str], formatter: str = "black") -> Dict[str, any]:
|
|
"""
|
|
Run formatter on specified files.
|
|
|
|
Args:
|
|
file_paths (List[str]): List of file paths to format
|
|
formatter (str): Formatter to use (default: black)
|
|
|
|
Returns:
|
|
Dict containing formatting results and errors
|
|
"""
|
|
try:
|
|
# Validate formatter
|
|
if formatter not in ["black", "isort"]:
|
|
return {"error": f"Unsupported formatter: {formatter}"}
|
|
|
|
# Check if formatter is available
|
|
try:
|
|
subprocess.run([formatter, "--version"],
|
|
capture_output=True, check=True)
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
return {"error": f"{formatter} not found. Please install it."}
|
|
|
|
# Run formatter
|
|
cmd = [formatter]
|
|
|
|
if formatter == "black":
|
|
cmd.extend(["--check", "--diff"]) # Just check for differences
|
|
elif formatter == "isort":
|
|
cmd.append("--check-only") # Just check, don't modify
|
|
|
|
cmd.extend(file_paths)
|
|
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
cwd="."
|
|
)
|
|
|
|
return {
|
|
"formatter": formatter,
|
|
"files_formatted": file_paths,
|
|
"return_code": result.returncode,
|
|
"stdout": result.stdout,
|
|
"stderr": result.stderr,
|
|
"success": result.returncode == 0
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error running formatter: {str(e)}"}
|
|
|
|
def lint_format_report(file_paths: List[str]) -> Dict[str, any]:
|
|
"""
|
|
Return structured results of lint/format operations.
|
|
|
|
Args:
|
|
file_paths (List[str]): List of file paths to analyze
|
|
|
|
Returns:
|
|
Dict containing comprehensive analysis of files
|
|
"""
|
|
try:
|
|
# Run both linters for comprehensive analysis
|
|
flake8_result = lint_code(file_paths, "flake8")
|
|
pylint_result = lint_code(file_paths, "pylint") # This might fail gracefully
|
|
black_result = format_code(file_paths, "black")
|
|
isort_result = format_code(file_paths, "isort")
|
|
|
|
# Collect results
|
|
results = {
|
|
"files_analyzed": file_paths,
|
|
"flake8": flake8_result,
|
|
"black": black_result,
|
|
"isort": isort_result,
|
|
"pylint": pylint_result # May be error if not available
|
|
}
|
|
|
|
return results
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error generating lint/format report: {str(e)}"}
|
|
|
|
def check_python_dependencies() -> Dict[str, bool]:
|
|
"""
|
|
Check which Python development tools are available.
|
|
|
|
Returns:
|
|
Dict indicating availability of each tool
|
|
"""
|
|
tools = ["flake8", "pylint", "black", "isort"]
|
|
results = {}
|
|
|
|
for tool in tools:
|
|
try:
|
|
subprocess.run([tool, "--version"],
|
|
capture_output=True, check=True)
|
|
results[tool] = True
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
results[tool] = False
|
|
|
|
return results
|
|
|
|
def auto_format_python(file_path: str) -> Dict[str, any]:
|
|
"""
|
|
Automatically format a Python file using black and isort.
|
|
|
|
Args:
|
|
file_path (str): Path to the Python file to format
|
|
|
|
Returns:
|
|
Dict containing formatting results
|
|
"""
|
|
try:
|
|
if not file_path.endswith('.py'):
|
|
return {"error": "Only Python files can be auto-formatted"}
|
|
|
|
# Check if required tools are available
|
|
deps = check_python_dependencies()
|
|
if not deps["black"] or not deps["isort"]:
|
|
return {"error": "Required formatting tools (black, isort) not found"}
|
|
|
|
# Format with black (just check for changes first)
|
|
black_cmd = ["black", "--check", "--diff", file_path]
|
|
black_result = subprocess.run(
|
|
black_cmd,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Run isort
|
|
isort_cmd = ["isort", "--check-only", file_path]
|
|
isort_result = subprocess.run(
|
|
isort_cmd,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
# Apply formatting if checks pass (simplified implementation)
|
|
# In a real implementation, we'd run the commands without --check flag
|
|
|
|
return {
|
|
"file": file_path,
|
|
"black_check_passed": black_result.returncode == 0,
|
|
"isort_check_passed": isort_result.returncode == 0,
|
|
"formatted": black_result.returncode == 0 and isort_result.returncode == 0
|
|
}
|
|
|
|
except Exception as e:
|
|
return {"error": f"Error formatting file: {str(e)}"}
|
|
|
|
# Example usage function
|
|
def example_usage():
|
|
"""
|
|
Example of how to use the linting and formatting tools.
|
|
"""
|
|
print("Linting & Formatting Tools Examples:")
|
|
|
|
# Example 1: Lint a file
|
|
result = lint_code(["main.py"])
|
|
print(f"Flake8 result: {result}")
|
|
|
|
# Example 2: Format files
|
|
result = format_code(["main.py"])
|
|
print(f"Black result: {result}")
|
|
|
|
if __name__ == "__main__":
|
|
example_usage()
|