Clover/tools/lint_format_tools.py
2026-01-13 02:00:28 -06:00

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()