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