mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-27 04:20:09 +00:00
388 lines
13 KiB
Python
388 lines
13 KiB
Python
"""
|
|
Format Tool - Code formatting + import normalization
|
|
===================================================
|
|
|
|
Purpose: formatting + import normalization across languages.
|
|
|
|
Backends:
|
|
- go: goimports (with local_prefix support)
|
|
- ts: prettier or biome
|
|
- py: ruff format or black
|
|
- rs: cargo fmt
|
|
- cc: clang-format
|
|
- sol: forge fmt / prettier
|
|
- schema: buf format (proto)
|
|
"""
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from .dev_tools import DevResult, DevToolBase, create_dev_result
|
|
|
|
|
|
class FmtTool(DevToolBase):
|
|
"""Code formatting tool"""
|
|
|
|
def __init__(self, target: str, **kwargs):
|
|
super().__init__(target, **kwargs)
|
|
self.opts = kwargs.get("opts", {})
|
|
self.local_prefix = self.opts.get("local_prefix") # For Go imports grouping
|
|
|
|
async def execute(self) -> DevResult:
|
|
"""Execute formatting operation"""
|
|
try:
|
|
if self.language == "go":
|
|
return await self._format_go()
|
|
elif self.language == "ts":
|
|
return await self._format_typescript()
|
|
elif self.language == "py":
|
|
return await self._format_python()
|
|
elif self.language == "rs":
|
|
return await self._format_rust()
|
|
elif self.language == "cc":
|
|
return await self._format_cpp()
|
|
elif self.language == "sol":
|
|
return await self._format_solidity()
|
|
elif self.language == "schema":
|
|
return await self._format_protobuf()
|
|
else:
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.target,
|
|
errors=[f"Formatting not supported for language: {self.language}"],
|
|
)
|
|
except Exception as e:
|
|
return create_dev_result(
|
|
ok=False,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.target,
|
|
errors=[str(e)],
|
|
)
|
|
|
|
async def _format_go(self) -> DevResult:
|
|
"""Format Go code using goimports"""
|
|
files = self.resolved["files"]
|
|
go_files = [f for f in files if f.endswith(".go")]
|
|
|
|
if not go_files:
|
|
return create_dev_result(
|
|
ok=True,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="goimports",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout="No Go files to format",
|
|
)
|
|
|
|
cmd = ["goimports", "-w"]
|
|
|
|
# Add local prefix if specified
|
|
if self.local_prefix:
|
|
cmd.extend(["-local", self.local_prefix])
|
|
|
|
cmd.extend(go_files)
|
|
|
|
result = self._run_command(cmd)
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="goimports",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=go_files if result.returncode == 0 else [],
|
|
)
|
|
|
|
async def _format_typescript(self) -> DevResult:
|
|
"""Format TypeScript/JavaScript code"""
|
|
files = self.resolved["files"]
|
|
ts_files = [
|
|
f
|
|
for f in files
|
|
if any(f.endswith(ext) for ext in [".ts", ".tsx", ".js", ".jsx"])
|
|
]
|
|
|
|
if not ts_files:
|
|
return create_dev_result(
|
|
ok=True,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout="No TypeScript/JavaScript files to format",
|
|
)
|
|
|
|
# Try biome first, then prettier
|
|
if self.backend == "biome" or self._has_biome():
|
|
return await self._format_with_biome(ts_files)
|
|
else:
|
|
return await self._format_with_prettier(ts_files)
|
|
|
|
async def _format_python(self) -> DevResult:
|
|
"""Format Python code"""
|
|
files = self.resolved["files"]
|
|
py_files = [f for f in files if f.endswith(".py")]
|
|
|
|
if not py_files:
|
|
return create_dev_result(
|
|
ok=True,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout="No Python files to format",
|
|
)
|
|
|
|
# Use ruff format if available, otherwise black
|
|
if self._has_ruff():
|
|
cmd = ["ruff", "format"] + py_files
|
|
backend = "ruff"
|
|
else:
|
|
cmd = ["black"] + py_files
|
|
backend = "black"
|
|
|
|
result = self._run_command(cmd)
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=backend,
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=py_files if result.returncode == 0 else [],
|
|
)
|
|
|
|
async def _format_rust(self) -> DevResult:
|
|
"""Format Rust code using cargo fmt"""
|
|
if self.resolved["type"] == "workspace":
|
|
cmd = ["cargo", "fmt"]
|
|
else:
|
|
# Format specific files
|
|
files = [f for f in self.resolved["files"] if f.endswith(".rs")]
|
|
if not files:
|
|
return create_dev_result(
|
|
ok=True,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="cargo",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout="No Rust files to format",
|
|
)
|
|
cmd = ["rustfmt"] + files
|
|
|
|
result = self._run_command(cmd)
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="cargo",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=self.resolved["files"] if result.returncode == 0 else [],
|
|
)
|
|
|
|
async def _format_cpp(self) -> DevResult:
|
|
"""Format C/C++ code using clang-format"""
|
|
files = self.resolved["files"]
|
|
cpp_files = [
|
|
f
|
|
for f in files
|
|
if any(
|
|
f.endswith(ext) for ext in [".c", ".cpp", ".cc", ".cxx", ".h", ".hpp"]
|
|
)
|
|
]
|
|
|
|
if not cpp_files:
|
|
return create_dev_result(
|
|
ok=True,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="clang-format",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout="No C/C++ files to format",
|
|
)
|
|
|
|
cmd = ["clang-format", "-i"] + cpp_files
|
|
|
|
result = self._run_command(cmd)
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="clang-format",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=cpp_files if result.returncode == 0 else [],
|
|
)
|
|
|
|
async def _format_solidity(self) -> DevResult:
|
|
"""Format Solidity code"""
|
|
files = self.resolved["files"]
|
|
sol_files = [f for f in files if f.endswith(".sol")]
|
|
|
|
if not sol_files:
|
|
return create_dev_result(
|
|
ok=True,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=self.backend,
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout="No Solidity files to format",
|
|
)
|
|
|
|
# Try forge fmt first
|
|
if self._has_forge():
|
|
cmd = ["forge", "fmt"] + sol_files
|
|
backend = "forge"
|
|
else:
|
|
# Fallback to prettier with solidity plugin
|
|
cmd = ["prettier", "--write"] + sol_files
|
|
backend = "prettier"
|
|
|
|
result = self._run_command(cmd)
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used=backend,
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=sol_files if result.returncode == 0 else [],
|
|
)
|
|
|
|
async def _format_protobuf(self) -> DevResult:
|
|
"""Format Protocol Buffer files using buf"""
|
|
files = self.resolved["files"]
|
|
proto_files = [f for f in files if f.endswith(".proto")]
|
|
|
|
if not proto_files:
|
|
return create_dev_result(
|
|
ok=True,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="buf",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout="No protobuf files to format",
|
|
)
|
|
|
|
cmd = ["buf", "format", "-w"] + proto_files
|
|
|
|
result = self._run_command(cmd)
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="buf",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=proto_files if result.returncode == 0 else [],
|
|
)
|
|
|
|
async def _format_with_prettier(self, files: List[str]) -> DevResult:
|
|
"""Format using Prettier"""
|
|
cmd = ["prettier", "--write"] + files
|
|
|
|
result = self._run_command(cmd)
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="prettier",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=files if result.returncode == 0 else [],
|
|
)
|
|
|
|
async def _format_with_biome(self, files: List[str]) -> DevResult:
|
|
"""Format using Biome"""
|
|
cmd = ["biome", "format", "--write"] + files
|
|
|
|
result = self._run_command(cmd)
|
|
|
|
return create_dev_result(
|
|
ok=result.returncode == 0,
|
|
root=self.workspace["root"],
|
|
language_used=self.language,
|
|
backend_used="biome",
|
|
scope_resolved=self.resolved["scope"],
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
exit_code=result.returncode,
|
|
touched_files=files if result.returncode == 0 else [],
|
|
)
|
|
|
|
def _has_biome(self) -> bool:
|
|
"""Check if Biome is available"""
|
|
try:
|
|
result = self._run_command(["biome", "--version"])
|
|
return result.returncode == 0
|
|
except (OSError, FileNotFoundError):
|
|
return False
|
|
|
|
def _has_ruff(self) -> bool:
|
|
"""Check if Ruff is available"""
|
|
try:
|
|
result = self._run_command(["ruff", "--version"])
|
|
return result.returncode == 0
|
|
except (OSError, FileNotFoundError):
|
|
return False
|
|
|
|
def _has_forge(self) -> bool:
|
|
"""Check if Forge is available"""
|
|
try:
|
|
result = self._run_command(["forge", "--version"])
|
|
return result.returncode == 0
|
|
except (OSError, FileNotFoundError):
|
|
return False
|
|
|
|
|
|
# MCP tool integration
|
|
async def fmt_tool_handler(
|
|
target: str,
|
|
language: str = "auto",
|
|
backend: str = "auto",
|
|
root: Optional[str] = None,
|
|
env: Optional[Dict[str, str]] = None,
|
|
dry_run: bool = False,
|
|
opts: Optional[Dict[str, Any]] = None,
|
|
) -> Dict[str, Any]:
|
|
"""MCP handler for fmt tool"""
|
|
|
|
tool = FmtTool(
|
|
target=target,
|
|
language=language,
|
|
backend=backend,
|
|
root=root,
|
|
env=env,
|
|
dry_run=dry_run,
|
|
opts=opts or {},
|
|
)
|
|
|
|
result = await tool.execute()
|
|
return result.dict()
|