Private
Public Access
0
0

add indentation scripts

This commit is contained in:
2026-05-16 03:01:25 -04:00
parent 3e642d7c7d
commit 79c5035d1c
4 changed files with 665 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
import ast
import sys
from pathlib import Path
ROOT_DIR = Path(__file__).parent.parent
class IndentationAnalyzer(ast.NodeVisitor):
def __init__(self, source_lines: list[str]):
self.source_lines = source_lines
self.violations: list[tuple[int, int, int, str]] = []
def visit_Module(self, node: ast.Module):
for child in node.body:
self._walk_node(child, 0)
self.generic_visit(node)
def _walk_node(self, node: ast.AST, depth: int):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
line_no = node.lineno
expected_indent = depth
actual_indent = self._get_indent(line_no)
if actual_indent != expected_indent:
self.violations.append((line_no, actual_indent, expected_indent,
f"{node.__class__.__name__} {node.name}"))
body_depth = depth + 1
for child in node.body:
self._walk_node(child, body_depth)
elif isinstance(node, ast.If):
line_no = node.lineno
expected_indent = depth
actual_indent = self._get_indent(line_no)
if actual_indent != expected_indent:
self.violations.append((line_no, actual_indent, expected_indent, "if statement"))
for child in node.body:
self._walk_node(child, depth + 1)
if node.orelse:
self._walk_node(node.orelse, depth + 1)
elif isinstance(node, (ast.For, ast.While, ast.With)):
line_no = node.lineno
expected_indent = depth
actual_indent = self._get_indent(line_no)
if actual_indent != expected_indent:
self.violations.append((line_no, actual_indent, expected_indent,
node.__class__.__name__))
for child in node.body:
self._walk_node(child, depth + 1)
if isinstance(node, ast.For) and node.orelse:
self._walk_node(node.orelse, depth + 1)
elif isinstance(node, ast.Try):
for child in node.body:
self._walk_node(child, depth + 1)
for handler in node.handlers:
self._walk_node(handler, depth + 1)
if node.orelse:
self._walk_node(node.orelse, depth + 1)
if node.finalbody:
self._walk_node(node.finalbody, depth + 1)
elif isinstance(node, (ast.Assign, ast.Expr, ast.Return, ast.Pass, ast.Raise, ast.Assert)):
pass
else:
self.generic_visit(node)
def _get_indent(self, lineno: int) -> int:
if lineno <= 0 or lineno > len(self.source_lines):
return 0
line = self.source_lines[lineno - 1]
stripped = line.lstrip()
return len(line) - len(stripped)
def audit_file(filepath: Path) -> list[tuple[int, int, int, str]]:
try:
with open(filepath, "r", encoding="utf-8", newline="") as f:
source = f.read()
source_lines = source.splitlines()
tree = ast.parse(source, filename=str(filepath))
analyzer = IndentationAnalyzer(source_lines)
analyzer.visit(tree)
return analyzer.violations
except SyntaxError:
return []
except Exception:
return []
def main():
dirs = ["src", "tests", "scripts", "conductor"]
total_files = 0
files_with_violations = 0
total_violations = 0
results: dict[str, list[tuple]] = {}
for dir_name in dirs:
dir_path = ROOT_DIR / dir_name
if not dir_path.exists():
continue
py_files = list(dir_path.rglob("*.py"))
for py_file in sorted(py_files):
total_files += 1
violations = audit_file(py_file)
if violations:
files_with_violations += 1
total_violations += len(violations)
rel = str(py_file.relative_to(dir_path))
results[f"{dir_name}/{rel}"] = violations
print(f"Total files scanned: {total_files}")
print(f"Files with violations: {files_with_violations}")
print(f"Total violations: {total_violations}")
print()
for path, violations in sorted(results.items()):
print(f"{path}: {len(violations)} violations")
for line_no, actual, expected, desc in violations[:5]:
print(f" Line {line_no}: actual={actual}, expected={expected} for {desc}")
if len(violations) > 5:
print(f" ... and {len(violations) - 5} more")
if __name__ == "__main__":
main()
+167
View File
@@ -0,0 +1,167 @@
import ast
import sys
from pathlib import Path
ROOT_DIR = Path(__file__).parent.parent
class IndentationCorrector(ast.NodeVisitor):
def __init__(self, source_lines: list[str]):
self.source_lines = source_lines
self.new_lines: list[str] = []
self._indentation_stack: list[int] = [0]
def visit_Module(self, node: ast.Module):
self._process_lines(0, 0)
for child in node.body:
self._walk_node(child, 0)
self.generic_visit(node)
def _walk_node(self, node: ast.AST, depth: int):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
line_no = node.lineno - 1
actual_indent = self._get_indent(line_no)
expected_indent = depth
self._process_lines(line_no, expected_indent)
self._indentation_stack.append(depth + 1)
for child in node.body:
self._walk_node(child, depth + 1)
self._indentation_stack.pop()
elif isinstance(node, ast.If):
line_no = node.lineno - 1
self._process_lines(line_no, depth)
for child in node.body:
self._walk_node(child, depth + 1)
if node.orelse:
self._walk_node(node.orelse, depth + 1)
elif isinstance(node, (ast.For, ast.While, ast.With)):
line_no = node.lineno - 1
self._process_lines(line_no, depth)
for child in node.body:
self._walk_node(child, depth + 1)
if isinstance(node, ast.For) and node.orelse:
self._walk_node(node.orelse, depth + 1)
elif isinstance(node, ast.Try):
for child in node.body:
self._walk_node(child, depth + 1)
for handler in node.handlers:
self._walk_node(handler, depth + 1)
if node.orelse:
self._walk_node(node.orelse, depth + 1)
if node.finalbody:
self._walk_node(node.finalbody, depth + 1)
else:
self.generic_visit(node)
def _get_indent(self, lineno: int) -> int:
if lineno < 0 or lineno >= len(self.source_lines):
return 0
line = self.source_lines[lineno]
stripped = line.lstrip()
return len(line) - len(stripped)
def _process_lines(self, end_line: int, expected_indent: int):
pass
def correct_file(filepath: Path) -> tuple[bool, str]:
try:
with open(filepath, "r", encoding="utf-8", newline="") as f:
source = f.read()
source_lines = source.splitlines()
tree = ast.parse(source, filename=str(filepath))
corrector = IndentationCorrector(source_lines)
corrector.visit(tree)
return False, "Not implemented yet"
except Exception as e:
return False, str(e)
def fix_indentation_simple(filepath: Path, base_indent: int = 4, target_indent: int = 1) -> tuple[bool, str]:
try:
with open(filepath, "r", encoding="utf-8", newline="") as f:
lines = f.readlines()
new_lines = []
changed = False
for line in lines:
stripped = line.lstrip()
if not stripped:
new_lines.append(line)
continue
leading = len(line) - len(stripped)
if leading > 0:
old_level = leading // base_indent
new_leading = old_level * target_indent
if leading != new_leading:
new_lines.append(" " * new_leading + stripped + "\n" if not line.endswith("\n") else " " * new_leading + stripped)
changed = True
else:
new_lines.append(line)
else:
new_lines.append(line)
if changed:
with open(filepath, "w", encoding="utf-8", newline="") as f:
f.writelines(new_lines)
return True, "Fixed"
return False, "No changes needed"
except Exception as e:
return False, str(e)
def main():
if len(sys.argv) > 2:
filepath = Path(sys.argv[2])
base_indent = int(sys.argv[3]) if len(sys.argv) > 3 else 4
target_indent = int(sys.argv[4]) if len(sys.argv) > 4 else 1
changed, msg = fix_indentation_simple(filepath, base_indent, target_indent)
print(f"{filepath}: {msg}")
return
files_to_fix = [
("src/fuzzy_anchor.py", 4, 1),
("src/patch_modal.py", 2, 1),
("scripts/extract_symbols.py", 4, 1),
("scripts/tasks/download_fonts.py", 4, 1),
]
test_files = [
("tests/test_arch_boundary_phase1.py", 2, 1),
("tests/test_arch_boundary_phase2.py", 2, 1),
("tests/test_arch_boundary_phase3.py", 2, 1),
("tests/test_external_editor.py", 4, 1),
("tests/test_headless_service.py", 4, 1),
("tests/test_history_manager.py", 4, 1),
("tests/test_fuzzy_anchor.py", 4, 1),
("tests/test_gemini_cli_adapter.py", 4, 1),
("tests/test_ai_client_cli.py", 4, 1),
("tests/test_api_events.py", 4, 1),
("tests/test_context_composition_decoupled.py", 4, 1),
("tests/test_context_composition_phase3.py", 4, 1),
("tests/test_context_composition_phase4.py", 4, 1),
("tests/test_diff_viewer.py", 2, 1),
("tests/test_discussion_takes_gui.py", 4, 1),
("tests/test_external_mcp_hitl.py", 4, 1),
("tests/test_gui_discussion_tabs.py", 4, 1),
("tests/test_gui_stress_performance.py", 4, 1),
("tests/test_gui_updates.py", 2, 1),
("tests/test_hot_reloader.py", 4, 1),
("tests/test_mma_dashboard_refresh.py", 4, 1),
("tests/test_mma_node_editor.py", 4, 1),
("tests/test_mma_orchestration_gui.py", 4, 1),
("tests/test_py_struct_tools.py", 4, 1),
("tests/test_thinking_persistence.py", 4, 1),
("tests/test_tier4_interceptor.py", 2, 1),
("tests/test_tiered_aggregation.py", 4, 1),
("tests/test_visual_orchestration.py", 4, 1),
]
all_files = files_to_fix + test_files
for rel_path, base, target in all_files:
filepath = ROOT_DIR / rel_path
if filepath.exists():
changed, msg = fix_indentation_simple(filepath, base, target)
print(f"{rel_path}: {msg}")
if __name__ == "__main__":
main()
+274
View File
@@ -0,0 +1,274 @@
import ast
import sys
from pathlib import Path
ROOT_DIR = Path(__file__).parent.parent
class IndentationFixer(ast.NodeVisitor):
def __init__(self, source_lines: list[str]):
self.source_lines = source_lines
self.result_lines: list[str] = []
self._pending_lines: list[tuple[int, str]] = []
self._current_depth = 0
def fix(self) -> list[str]:
self._process_pending(0)
self.visit_Module(ast.parse("".join(self.source_lines)))
return self.result_lines
def _get_indent(self, lineno: int) -> int:
if lineno <= 0 or lineno > len(self.source_lines):
return 0
line = self.source_lines[lineno - 1]
stripped = line.lstrip()
return len(line) - len(stripped)
def _is_docstring_or_comment(self, line: str) -> bool:
stripped = line.lstrip()
if stripped.startswith('#'):
return True
if stripped.startswith('"""') or stripped.startswith("'''"):
return True
return False
def _process_pending(self, target_depth: int):
while self._pending_lines:
line_no, line = self._pending_lines[0]
stripped = line.lstrip()
actual = len(line) - len(stripped)
expected = target_depth
if actual == expected:
self.result_lines.append(line)
self._pending_lines.pop(0)
elif actual < expected:
break
elif actual > expected:
self.result_lines.append(" " * expected + stripped)
self._pending_lines.pop(0)
def visit_Module(self, node: ast.Module):
for child in node.body:
self._walk_node(child)
self.generic_visit(node)
def _walk_node(self, node: ast.AST):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
lineno = node.lineno
for i, (old_no, old_line) in enumerate(self._pending_lines):
if old_no == lineno:
self._pending_lines.pop(i)
break
actual = self._get_indent(lineno)
expected = self._current_depth
line = self.source_lines[lineno - 1]
stripped = line.lstrip()
if actual != expected:
self.result_lines.append(" " * expected + stripped)
else:
self.result_lines.append(line)
self._current_depth += 1
for child in node.body:
self._walk_node(child)
self._current_depth -= 1
elif isinstance(node, ast.If):
lineno = node.lineno
line = self.source_lines[lineno - 1]
stripped = line.lstrip()
actual = self._get_indent(lineno)
expected = self._current_depth
if actual != expected:
self.result_lines.append(" " * expected + stripped)
else:
self.result_lines.append(line)
self._current_depth += 1
for child in node.body:
self._walk_node(child)
self._current_depth -= 1
if node.orelse:
self._current_depth += 1
self._walk_node(node.orelse)
self._current_depth -= 1
elif isinstance(node, (ast.For, ast.While, ast.With)):
lineno = node.lineno
line = self.source_lines[lineno - 1]
stripped = line.lstrip()
actual = self._get_indent(lineno)
expected = self._current_depth
if actual != expected:
self.result_lines.append(" " * expected + stripped)
else:
self.result_lines.append(line)
self._current_depth += 1
for child in node.body:
self._walk_node(child)
self._current_depth -= 1
if isinstance(node, ast.For) and node.orelse:
self._current_depth += 1
for child in node.orelse:
self._walk_node(child)
self._current_depth -= 1
elif isinstance(node, ast.Try):
for child in node.body:
self._walk_node(child)
for handler in node.handlers:
self._current_depth += 1
for child in handler.body:
self._walk_node(child)
self._current_depth -= 1
if node.orelse:
self._current_depth += 1
for child in node.orelse:
self._walk_node(child)
self._current_depth -= 1
if node.finalbody:
self._current_depth += 1
for child in node.finalbody:
self._walk_node(child)
self._current_depth -= 1
else:
self.generic_visit(node)
def fix_file_ast(filepath: Path) -> tuple[bool, str]:
try:
with open(filepath, "r", encoding="utf-8", newline="") as f:
source = f.read()
source_lines = source.splitlines()
tree = ast.parse(source, filename=str(filepath))
fixer = IndentationFixer(source_lines)
new_lines = fixer.fix()
new_source = "\n".join(new_lines)
if new_source == source:
return False, "No changes"
with open(filepath, "w", encoding="utf-8", newline="") as f:
f.write(new_source)
ast.parse(new_source)
return True, "Fixed"
except SyntaxError as e:
return False, f"Syntax error: {e}"
except Exception as e:
return False, str(e)
def fix_file_simple(filepath: Path, base_indent: int = 4) -> tuple[bool, str]:
try:
with open(filepath, "r", encoding="utf-8", newline="") as f:
lines = f.readlines()
in_docstring = False
new_lines = []
changed = False
for line in lines:
stripped = line.lstrip()
if not stripped:
new_lines.append(line)
continue
if not in_docstring:
if stripped.startswith('#'):
new_lines.append(line)
continue
if '"""' in stripped or "'''" in stripped:
triple_pos = max(stripped.find('"""') if '"""' in stripped else 999,
stripped.find("'''") if "'''" in stripped else 999)
if triple_pos == 0:
in_docstring = True
new_lines.append(line)
continue
if in_docstring:
if '"""' in stripped or "'''" in stripped:
in_docstring = False
new_lines.append(line)
continue
leading = len(line) - len(stripped)
if leading > 0:
level = leading // base_indent
new_leading = level
if leading != new_leading:
new_lines.append(" " * new_leading + stripped + ("\n" if not line.endswith("\n") else ""))
changed = True
else:
new_lines.append(line)
else:
new_lines.append(line)
if changed:
with open(filepath, "w", encoding="utf-8", newline="") as f:
f.writelines(new_lines)
return True, "Fixed"
return False, "No changes needed"
except Exception as e:
return False, str(e)
def main():
if len(sys.argv) > 1:
filepath = Path(sys.argv[1])
changed, msg = fix_file_simple(filepath)
print(f"{filepath}: {msg}")
return
files_to_fix = [
("src/fuzzy_anchor.py", 4),
("src/patch_modal.py", 2),
("scripts/extract_symbols.py", 4),
("scripts/tasks/download_fonts.py", 4),
("tests/test_arch_boundary_phase1.py", 2),
("tests/test_arch_boundary_phase2.py", 2),
("tests/test_arch_boundary_phase3.py", 2),
("tests/test_external_editor.py", 4),
("tests/test_headless_service.py", 4),
("tests/test_history_manager.py", 4),
("tests/test_fuzzy_anchor.py", 4),
("tests/test_gemini_cli_adapter.py", 4),
("tests/test_ai_client_cli.py", 4),
("tests/test_api_events.py", 4),
("tests/test_context_composition_decoupled.py", 4),
("tests/test_context_composition_phase3.py", 4),
("tests/test_context_composition_phase4.py", 4),
("tests/test_diff_viewer.py", 2),
("tests/test_discussion_takes_gui.py", 4),
("tests/test_external_mcp_hitl.py", 4),
("tests/test_gui_discussion_tabs.py", 4),
("tests/test_gui_stress_performance.py", 4),
("tests/test_gui_updates.py", 2),
("tests/test_hot_reloader.py", 4),
("tests/test_mma_dashboard_refresh.py", 4),
("tests/test_mma_node_editor.py", 4),
("tests/test_mma_orchestration_gui.py", 4),
("tests/test_py_struct_tools.py", 4),
("tests/test_thinking_persistence.py", 4),
("tests/test_tier4_interceptor.py", 2),
("tests/test_tiered_aggregation.py", 4),
("tests/test_visual_orchestration.py", 4),
]
for rel_path, base_indent in files_to_fix:
filepath = ROOT_DIR / rel_path
if filepath.exists():
changed, msg = fix_file_simple(filepath, base_indent)
print(f"{rel_path}: {msg}")
if __name__ == "__main__":
main()
+104
View File
@@ -0,0 +1,104 @@
import ast
import sys
from pathlib import Path
ROOT_DIR = Path(__file__).parent.parent
class PythonIndentationFixer(ast.NodeVisitor):
def __init__(self, source_lines: list[str]):
self.source_lines = source_lines
self.result: list[str] = []
self._depth = 0
self._pending: list[tuple[int, str]] = []
def fix(self) -> str:
tree = ast.parse("".join(self.source_lines))
self._walk_module(tree)
return "\n".join(self.result)
def _get_line(self, lineno: int) -> str:
if 0 < lineno <= len(self.source_lines):
return self.source_lines[lineno - 1]
return ""
def _walk_module(self, node: ast.Module):
for item in node.body:
self._process_item(item, 0)
self.generic_visit(node)
def _process_item(self, node: ast.AST, base_depth: int):
lineno = node.lineno
line = self._get_line(lineno)
stripped = line.lstrip()
leading = len(line) - len(stripped)
expected = base_depth
if leading != expected:
self.result.append(" " * expected + stripped)
else:
self.result.append(line.rstrip("\n"))
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
body_depth = base_depth + 1
for child in node.body:
self._process_item(child, body_depth)
elif isinstance(node, (ast.If, ast.For, ast.While, ast.With, ast.Try)):
body_depth = base_depth + 1
for child in node.body:
self._process_item(child, body_depth)
if isinstance(node, ast.If) and node.orelse:
self._process_item(node.orelse, base_depth + 1)
if isinstance(node, ast.For) and node.orelse:
for child in node.orelse:
self._process_item(child, body_depth)
elif isinstance(node, ast.Try):
for handler in node.handlers:
for child in handler.body:
self._process_item(child, body_depth)
if node.orelse:
for child in node.orelse:
self._process_item(child, body_depth)
if node.finalbody:
for child in node.finalbody:
self._process_item(child, body_depth)
else:
self.generic_visit(node)
def generic_visit(self, node: ast.AST):
pass
def fix_file_ast(filepath: Path) -> tuple[bool, str]:
try:
with open(filepath, "r", encoding="utf-8", newline="") as f:
source = f.read()
lines = source.splitlines()
fixer = PythonIndentationFixer(lines)
new_source = fixer.fix()
ast.parse(new_source)
if new_source == source:
return False, "No changes needed"
with open(filepath, "w", encoding="utf-8", newline="") as f:
f.write(new_source)
return True, "Fixed"
except SyntaxError as e:
return False, f"SyntaxError: {e}"
except Exception as e:
return False, str(e)
def main():
if len(sys.argv) > 1:
filepath = Path(sys.argv[1])
changed, msg = fix_file_ast(filepath)
print(f"{filepath}: {msg}")
return
print("AST-based Python indentation fixer")
print("Usage: python fix_indent_ast.py <filepath>")
if __name__ == "__main__":
main()