Initial commit
This commit is contained in:
126
skills/python-code-quality/tools/python_formatter.py
Executable file
126
skills/python-code-quality/tools/python_formatter.py
Executable file
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env -S uv run --script --quiet
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = []
|
||||
# ///
|
||||
"""
|
||||
Python formatter for Claude Code.
|
||||
Automatically formats and fixes Python files using ruff.
|
||||
|
||||
Usage:
|
||||
Hook mode (stdin): echo '{"tool_input":{"file_path":"test.py"}}' | python python_formatter.py
|
||||
CLI mode: python python_formatter.py test.py
|
||||
Blocking mode: python python_formatter.py --blocking test.py
|
||||
|
||||
Options:
|
||||
--blocking Exit with code 2 when changes are made (sends feedback to Claude)
|
||||
Default: Exit with code 0 (output only in transcript mode)
|
||||
|
||||
Features:
|
||||
- Dual mode: Works with Claude Code hooks (stdin) or command-line arguments
|
||||
- Auto-formats code with ruff format
|
||||
- Auto-fixes linting issues with ruff check --fix
|
||||
- Only processes .py files
|
||||
- Reports what was changed
|
||||
- Blocking mode option for immediate Claude feedback
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def format_python_file(file_path: str, blocking: bool = False) -> bool:
|
||||
"""Format and fix a Python file using ruff.
|
||||
|
||||
Args:
|
||||
file_path: Path to the Python file to format
|
||||
blocking: If True, output to stderr for blocking behavior
|
||||
|
||||
Returns:
|
||||
bool: True if changes were made, False otherwise
|
||||
"""
|
||||
changes_made = []
|
||||
|
||||
try:
|
||||
# Run ruff format
|
||||
format_result = subprocess.run(
|
||||
["ruff", "format", file_path], capture_output=True, text=True, timeout=30
|
||||
)
|
||||
|
||||
# Check if formatting made changes
|
||||
# ruff format returns 0 whether changes were made or not
|
||||
# but outputs "1 file reformatted" vs "1 file left unchanged"
|
||||
output = (format_result.stdout + format_result.stderr).lower()
|
||||
if "reformatted" in output:
|
||||
changes_made.append("formatted code style")
|
||||
|
||||
# Run ruff check --fix to auto-fix linting issues
|
||||
fix_result = subprocess.run(
|
||||
["ruff", "check", "--fix", file_path], capture_output=True, text=True, timeout=30
|
||||
)
|
||||
|
||||
# Check if fixes were applied
|
||||
if fix_result.stdout and "fixed" in fix_result.stdout.lower():
|
||||
changes_made.append("fixed linting issues")
|
||||
|
||||
# Report results
|
||||
if changes_made:
|
||||
message = f"✓ Python formatter: {', '.join(changes_made)} in {file_path}"
|
||||
if blocking:
|
||||
print(message, file=sys.stderr)
|
||||
else:
|
||||
print(message)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f"⚠ Ruff timed out for {file_path}", file=sys.stderr)
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
print("⚠ Ruff not found. Install with: uv tool install ruff", file=sys.stderr)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error running ruff: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
# Main execution
|
||||
try:
|
||||
# Parse arguments
|
||||
blocking = False
|
||||
file_path = ""
|
||||
|
||||
# Check for --blocking flag and file path
|
||||
if len(sys.argv) > 1:
|
||||
args = sys.argv[1:]
|
||||
if "--blocking" in args:
|
||||
blocking = True
|
||||
args.remove("--blocking")
|
||||
if args:
|
||||
file_path = args[0]
|
||||
else:
|
||||
# Read from stdin (hook mode)
|
||||
input_data = json.load(sys.stdin)
|
||||
file_path = input_data.get("tool_input", {}).get("file_path", "")
|
||||
|
||||
if not file_path.endswith(".py"):
|
||||
sys.exit(0) # Not a Python file
|
||||
|
||||
if os.path.exists(file_path):
|
||||
changes_made = format_python_file(file_path, blocking=blocking)
|
||||
|
||||
# In blocking mode, exit with code 2 if changes were made
|
||||
if blocking and changes_made:
|
||||
sys.exit(2)
|
||||
else:
|
||||
print(f"⚠ File not found: {file_path}", file=sys.stderr)
|
||||
|
||||
# Always exit 0 to be non-blocking (unless blocking mode with changes)
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in Python formatter: {e}", file=sys.stderr)
|
||||
sys.exit(0) # Non-blocking even on errors
|
||||
Reference in New Issue
Block a user