auto-code-fixer 0.3.6__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- auto_code_fixer/__init__.py +1 -1
- auto_code_fixer/approval.py +114 -0
- auto_code_fixer/cli.py +760 -60
- auto_code_fixer/reporting.py +61 -0
- auto_code_fixer/sandbox.py +35 -0
- auto_code_fixer/utils.py +42 -0
- {auto_code_fixer-0.3.6.dist-info → auto_code_fixer-0.4.0.dist-info}/METADATA +58 -6
- {auto_code_fixer-0.3.6.dist-info → auto_code_fixer-0.4.0.dist-info}/RECORD +12 -10
- {auto_code_fixer-0.3.6.dist-info → auto_code_fixer-0.4.0.dist-info}/WHEEL +0 -0
- {auto_code_fixer-0.3.6.dist-info → auto_code_fixer-0.4.0.dist-info}/entry_points.txt +0 -0
- {auto_code_fixer-0.3.6.dist-info → auto_code_fixer-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {auto_code_fixer-0.3.6.dist-info → auto_code_fixer-0.4.0.dist-info}/top_level.txt +0 -0
auto_code_fixer/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.4.0"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import difflib
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
|
+
class PlannedChange:
|
|
7
|
+
abs_path: str
|
|
8
|
+
rel_path: str
|
|
9
|
+
old_content: str
|
|
10
|
+
new_content: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UserAbort(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def unified_diff_text(rel_path: str, old: str, new: str) -> str:
|
|
18
|
+
return "".join(
|
|
19
|
+
difflib.unified_diff(
|
|
20
|
+
(old or "").splitlines(keepends=True),
|
|
21
|
+
(new or "").splitlines(keepends=True),
|
|
22
|
+
fromfile=rel_path + " (before)",
|
|
23
|
+
tofile=rel_path + " (after)",
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def guard_planned_changes(
|
|
29
|
+
planned: list[PlannedChange],
|
|
30
|
+
*,
|
|
31
|
+
max_file_bytes: int,
|
|
32
|
+
max_total_bytes: int,
|
|
33
|
+
max_diff_lines: int,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Safety guards against huge edits.
|
|
36
|
+
|
|
37
|
+
Raises ValueError when limits are exceeded.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
if max_file_bytes <= 0 or max_total_bytes <= 0 or max_diff_lines <= 0:
|
|
41
|
+
raise ValueError("Guard limits must be positive")
|
|
42
|
+
|
|
43
|
+
total = 0
|
|
44
|
+
|
|
45
|
+
for ch in planned:
|
|
46
|
+
b = len((ch.new_content or "").encode("utf-8"))
|
|
47
|
+
total += b
|
|
48
|
+
if b > max_file_bytes:
|
|
49
|
+
raise ValueError(
|
|
50
|
+
f"Proposed content for {ch.rel_path} is {b} bytes, exceeding --max-file-bytes={max_file_bytes}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
diff = unified_diff_text(ch.rel_path, ch.old_content, ch.new_content)
|
|
54
|
+
diff_lines = len(diff.splitlines())
|
|
55
|
+
if diff_lines > max_diff_lines:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"Diff for {ch.rel_path} is {diff_lines} lines, exceeding --max-diff-lines={max_diff_lines}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if total > max_total_bytes:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"Total proposed content is {total} bytes, exceeding --max-total-bytes={max_total_bytes}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def prompt_approve_file_by_file(
|
|
67
|
+
planned: list[PlannedChange],
|
|
68
|
+
*,
|
|
69
|
+
input_fn=input,
|
|
70
|
+
print_fn=print,
|
|
71
|
+
) -> list[PlannedChange]:
|
|
72
|
+
"""Interactive approval per file.
|
|
73
|
+
|
|
74
|
+
Commands:
|
|
75
|
+
y = apply this change
|
|
76
|
+
n = skip this change
|
|
77
|
+
a = apply this and all remaining
|
|
78
|
+
q = abort
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
if not planned:
|
|
82
|
+
return []
|
|
83
|
+
|
|
84
|
+
out: list[PlannedChange] = []
|
|
85
|
+
apply_all = False
|
|
86
|
+
|
|
87
|
+
for ch in planned:
|
|
88
|
+
diff = unified_diff_text(ch.rel_path, ch.old_content, ch.new_content)
|
|
89
|
+
print_fn("\nPROPOSED CHANGE:")
|
|
90
|
+
print_fn(diff)
|
|
91
|
+
|
|
92
|
+
if apply_all:
|
|
93
|
+
out.append(ch)
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
ans = (
|
|
97
|
+
input_fn(f"Apply change for {ch.rel_path}? (y/n/a/q): ")
|
|
98
|
+
.strip()
|
|
99
|
+
.lower()
|
|
100
|
+
)
|
|
101
|
+
if ans == "y":
|
|
102
|
+
out.append(ch)
|
|
103
|
+
elif ans == "n":
|
|
104
|
+
continue
|
|
105
|
+
elif ans == "a":
|
|
106
|
+
out.append(ch)
|
|
107
|
+
apply_all = True
|
|
108
|
+
elif ans == "q":
|
|
109
|
+
raise UserAbort("User aborted approvals")
|
|
110
|
+
else:
|
|
111
|
+
# default: be safe
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
return out
|