skim-python 0.0.2__py3-none-any.whl → 0.0.3__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.
- skim_py/cli.py +5 -0
- skim_py/parser.py +76 -29
- {skim_python-0.0.2.dist-info → skim_python-0.0.3.dist-info}/METADATA +1 -1
- skim_python-0.0.3.dist-info/RECORD +11 -0
- skim_python-0.0.2.dist-info/RECORD +0 -11
- {skim_python-0.0.2.dist-info → skim_python-0.0.3.dist-info}/WHEEL +0 -0
- {skim_python-0.0.2.dist-info → skim_python-0.0.3.dist-info}/entry_points.txt +0 -0
- {skim_python-0.0.2.dist-info → skim_python-0.0.3.dist-info}/licenses/LICENSE +0 -0
skim_py/cli.py
CHANGED
|
@@ -68,6 +68,11 @@ Agent Operational Workflow:
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def main(args: Optional[List[str]] = None) -> int:
|
|
71
|
+
if sys.version_info < (3, 10):
|
|
72
|
+
current_ver = f"{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}"
|
|
73
|
+
sys.stderr.write(f"Error: skim-py requires Python 3.10+ (running on Python {current_ver}).\n")
|
|
74
|
+
return 1
|
|
75
|
+
|
|
71
76
|
if args is None:
|
|
72
77
|
args = sys.argv[1:]
|
|
73
78
|
|
skim_py/parser.py
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
import ast
|
|
5
|
-
import os
|
|
6
5
|
import re
|
|
7
6
|
from pathlib import Path
|
|
8
|
-
from typing import Optional, List
|
|
7
|
+
from typing import Optional, List
|
|
9
8
|
|
|
10
9
|
from .types import OutlineItem, ParsedFile, SyntaxNotice
|
|
11
10
|
|
|
@@ -24,7 +23,11 @@ def get_indent(line: str) -> int:
|
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
def get_block_end(lines: List[str], start: int) -> int:
|
|
27
|
-
"""Find the last indented code line, ignoring blank lines and comments.
|
|
26
|
+
"""Find the last indented code line, ignoring blank lines and comments.
|
|
27
|
+
|
|
28
|
+
Known limit: a multi-line string with content at column 0, or a closing
|
|
29
|
+
``):`` at the header's indent, ends the block early.
|
|
30
|
+
"""
|
|
28
31
|
indent = get_indent(lines[start])
|
|
29
32
|
end = start
|
|
30
33
|
for idx in range(start + 1, len(lines)):
|
|
@@ -37,6 +40,49 @@ def get_block_end(lines: List[str], start: int) -> int:
|
|
|
37
40
|
return end
|
|
38
41
|
|
|
39
42
|
|
|
43
|
+
def collect_decorators(lines: List[str], header_idx: int, floor: int):
|
|
44
|
+
"""Scan upward from a header for decorators, including multi-line ones.
|
|
45
|
+
|
|
46
|
+
Returns (start_idx or None, decorator strings in source order). Multi-line
|
|
47
|
+
decorators are found by bracket balancing; brackets inside strings are not
|
|
48
|
+
handled specially, so an unbalanced one can hide the decorator.
|
|
49
|
+
"""
|
|
50
|
+
start: Optional[int] = None
|
|
51
|
+
decos: List[str] = []
|
|
52
|
+
idx = header_idx - 1
|
|
53
|
+
while idx >= floor:
|
|
54
|
+
s = lines[idx].strip()
|
|
55
|
+
if not s or s.startswith("#"):
|
|
56
|
+
idx -= 1
|
|
57
|
+
continue
|
|
58
|
+
if s.startswith("@"):
|
|
59
|
+
decos.insert(0, s)
|
|
60
|
+
start = idx
|
|
61
|
+
idx -= 1
|
|
62
|
+
continue
|
|
63
|
+
if s[-1] in ")]}":
|
|
64
|
+
depth = 0
|
|
65
|
+
found = None
|
|
66
|
+
for j in range(idx, floor - 1, -1):
|
|
67
|
+
t = lines[j].strip()
|
|
68
|
+
depth += sum(t.count(c) for c in ")]}") - sum(t.count(c) for c in "([{")
|
|
69
|
+
if depth <= 0:
|
|
70
|
+
if t.startswith("@"):
|
|
71
|
+
found = j
|
|
72
|
+
break
|
|
73
|
+
if found is not None:
|
|
74
|
+
parts = [
|
|
75
|
+
l.strip() for l in lines[found : idx + 1]
|
|
76
|
+
if l.strip() and not l.strip().startswith("#")
|
|
77
|
+
]
|
|
78
|
+
decos.insert(0, " ".join(parts))
|
|
79
|
+
start = found
|
|
80
|
+
idx = found - 1
|
|
81
|
+
continue
|
|
82
|
+
break
|
|
83
|
+
return start, decos
|
|
84
|
+
|
|
85
|
+
|
|
40
86
|
def split_source_lines(content: str, keepends: bool = False) -> List[str]:
|
|
41
87
|
"""Normalize newlines (\r\n, \r -> \n) and split into lines."""
|
|
42
88
|
norm = content.replace("\r\n", "\n").replace("\r", "\n")
|
|
@@ -142,6 +188,7 @@ def parse_source_file(file_path: str, content: str, include_tests: bool = False)
|
|
|
142
188
|
|
|
143
189
|
# Fallback: Iterative line masking with up to 100 attempts
|
|
144
190
|
working_lines = split_source_lines(content, keepends=True)
|
|
191
|
+
original_len = len(working_lines)
|
|
145
192
|
seen_states = set()
|
|
146
193
|
max_attempts = 100
|
|
147
194
|
current_err: Optional[SyntaxError] = first_err
|
|
@@ -156,7 +203,9 @@ def parse_source_file(file_path: str, content: str, include_tests: bool = False)
|
|
|
156
203
|
line_idx = lineno - 1
|
|
157
204
|
line = working_lines[line_idx]
|
|
158
205
|
indent = get_indent(line)
|
|
159
|
-
leading_ws = line[: len(line) - len(line.lstrip())]
|
|
206
|
+
leading_ws = line[: len(line) - len(line.lstrip(" \t"))]
|
|
207
|
+
if not line.strip():
|
|
208
|
+
leading_ws = ""
|
|
160
209
|
|
|
161
210
|
state_key = (line_idx, line)
|
|
162
211
|
if state_key in seen_states:
|
|
@@ -173,7 +222,10 @@ def parse_source_file(file_path: str, content: str, include_tests: bool = False)
|
|
|
173
222
|
|
|
174
223
|
if end_idx > line_idx or m_hdr:
|
|
175
224
|
# Update notice message with masked line range if not already suffixed
|
|
176
|
-
|
|
225
|
+
if end_idx > line_idx:
|
|
226
|
+
mask_suffix = f"(L{line_idx + 1}-L{end_idx + 1} masked)"
|
|
227
|
+
else:
|
|
228
|
+
mask_suffix = f"(L{line_idx + 1} masked)"
|
|
177
229
|
for n in parsed.syntax_errors:
|
|
178
230
|
if n.line == lineno and "masked)" not in n.message:
|
|
179
231
|
n.message = f"{n.message} {mask_suffix}"
|
|
@@ -185,17 +237,9 @@ def parse_source_file(file_path: str, content: str, include_tests: bool = False)
|
|
|
185
237
|
end_l = end_idx + 1
|
|
186
238
|
|
|
187
239
|
# Scan upward for leading decorators
|
|
188
|
-
decos =
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
stripped_d = prev_d.strip()
|
|
192
|
-
if stripped_d.startswith("@"):
|
|
193
|
-
start_l = d_idx + 1
|
|
194
|
-
decos.insert(0, stripped_d)
|
|
195
|
-
elif not stripped_d or stripped_d.startswith("#"):
|
|
196
|
-
continue
|
|
197
|
-
else:
|
|
198
|
-
break
|
|
240
|
+
deco_start, decos = collect_decorators(working_lines, line_idx, 0)
|
|
241
|
+
if deco_start is not None:
|
|
242
|
+
start_l = deco_start + 1
|
|
199
243
|
|
|
200
244
|
deco_prefix = (" ".join(decos) + " ") if decos else ""
|
|
201
245
|
|
|
@@ -216,17 +260,11 @@ def parse_source_file(file_path: str, content: str, include_tests: bool = False)
|
|
|
216
260
|
ch_k_raw, ch_name = m_ch.group(1), m_ch.group(2)
|
|
217
261
|
ch_start = k + 1
|
|
218
262
|
# Scan upward for method decorators inside the class
|
|
219
|
-
ch_decos =
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
ch_start = cj + 1
|
|
225
|
-
ch_decos.insert(0, str_cj)
|
|
226
|
-
elif not str_cj or str_cj.startswith("#"):
|
|
227
|
-
continue
|
|
228
|
-
else:
|
|
229
|
-
break
|
|
263
|
+
ch_deco_start, ch_decos = collect_decorators(
|
|
264
|
+
working_lines, k, line_idx + 1
|
|
265
|
+
)
|
|
266
|
+
if ch_deco_start is not None:
|
|
267
|
+
ch_start = ch_deco_start + 1
|
|
230
268
|
|
|
231
269
|
ch_deco_prefix = (" ".join(ch_decos) + " ") if ch_decos else ""
|
|
232
270
|
|
|
@@ -287,15 +325,24 @@ def parse_source_file(file_path: str, content: str, include_tests: bool = False)
|
|
|
287
325
|
working_lines[line_idx] = leading_ws + stub
|
|
288
326
|
stub_lines.add(lineno)
|
|
289
327
|
else:
|
|
290
|
-
working_lines[line_idx] = leading_ws + "
|
|
328
|
+
working_lines[line_idx] = leading_ws + "# syntax error masked\n"
|
|
291
329
|
for k in range(line_idx + 1, end_idx + 1):
|
|
292
330
|
working_lines[k] = "# syntax error masked\n"
|
|
331
|
+
assert len(working_lines) == original_len
|
|
293
332
|
|
|
294
333
|
masked_lines.update(range(line_idx + 1, end_idx + 2))
|
|
295
334
|
masked_block_ends[lineno] = end_idx + 1
|
|
296
335
|
|
|
297
336
|
try:
|
|
298
|
-
|
|
337
|
+
try:
|
|
338
|
+
tree = ast.parse("".join(working_lines), filename=file_path, type_comments=True)
|
|
339
|
+
except IndentationError as ind_err:
|
|
340
|
+
# Masking the only statement of a suite left it empty; use pass instead.
|
|
341
|
+
if m_hdr or "expected an indented block" not in (ind_err.msg or ""):
|
|
342
|
+
raise
|
|
343
|
+
working_lines[line_idx] = leading_ws + "pass\n"
|
|
344
|
+
assert len(working_lines) == original_len
|
|
345
|
+
tree = ast.parse("".join(working_lines), filename=file_path, type_comments=True)
|
|
299
346
|
break # Successful recovery!
|
|
300
347
|
except SyntaxError as next_err:
|
|
301
348
|
current_err = next_err
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: skim-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Language-aware, AST-resilient outline and verbatim code extractor for Python tailored for AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/benedictjohannes/programming-helper-utils
|
|
6
6
|
Project-URL: Repository, https://github.com/benedictjohannes/programming-helper-utils
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
skim_py/__init__.py,sha256=GCEQwh8hAZr6Vd8gbG-xpnqnulWXEAXhG-nA7sWtmoA,71
|
|
2
|
+
skim_py/block.py,sha256=1up79BSBdFxPspp9CFcJNRGMiQmAz_frCbwp8LrqvG4,4961
|
|
3
|
+
skim_py/cli.py,sha256=OmyCCL716NvFzVRyqHQo3b3xOn-1mjcJixHqrcEKJL0,5224
|
|
4
|
+
skim_py/glance.py,sha256=zMP2_VJMfdF2ZTOWBRTuC-zcF6vNc7i5R5nHO63da0c,6931
|
|
5
|
+
skim_py/parser.py,sha256=KNIuEEDRn47iC-1csRrVauFcH4llX8Xs8_vjQPt2aFE,23538
|
|
6
|
+
skim_py/types.py,sha256=k_e9kEx-jhGtJIrh9eNqe8R87JlYvMFQvGGlbzwpXm8,1478
|
|
7
|
+
skim_python-0.0.3.dist-info/METADATA,sha256=S-ziYNSMtZcvLvtkfmnK4Z86G6pGzRCzLVbxjePvUYM,22791
|
|
8
|
+
skim_python-0.0.3.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
9
|
+
skim_python-0.0.3.dist-info/entry_points.txt,sha256=nwzHJF7CgNobxT923pwxljnEuDdJe9a2k81Q91bPY8U,45
|
|
10
|
+
skim_python-0.0.3.dist-info/licenses/LICENSE,sha256=at6MgKqAJV-KpSzR2j1hUc2hTZfymTMkbvDwmypUA9I,1074
|
|
11
|
+
skim_python-0.0.3.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
skim_py/__init__.py,sha256=GCEQwh8hAZr6Vd8gbG-xpnqnulWXEAXhG-nA7sWtmoA,71
|
|
2
|
-
skim_py/block.py,sha256=1up79BSBdFxPspp9CFcJNRGMiQmAz_frCbwp8LrqvG4,4961
|
|
3
|
-
skim_py/cli.py,sha256=gcPPkHI9GCFjmhvWyGc4srC3xeA0I14e8MgAQYY643g,4977
|
|
4
|
-
skim_py/glance.py,sha256=zMP2_VJMfdF2ZTOWBRTuC-zcF6vNc7i5R5nHO63da0c,6931
|
|
5
|
-
skim_py/parser.py,sha256=3-at3xgB5y4ICSekduAjUQpFmmAkO_tmjEZzdVXwlCY,21784
|
|
6
|
-
skim_py/types.py,sha256=k_e9kEx-jhGtJIrh9eNqe8R87JlYvMFQvGGlbzwpXm8,1478
|
|
7
|
-
skim_python-0.0.2.dist-info/METADATA,sha256=3SljznEc0UcoT21BUxfiR5-pi-XXZtw69wlBZ9MwSGY,22791
|
|
8
|
-
skim_python-0.0.2.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
9
|
-
skim_python-0.0.2.dist-info/entry_points.txt,sha256=nwzHJF7CgNobxT923pwxljnEuDdJe9a2k81Q91bPY8U,45
|
|
10
|
-
skim_python-0.0.2.dist-info/licenses/LICENSE,sha256=at6MgKqAJV-KpSzR2j1hUc2hTZfymTMkbvDwmypUA9I,1074
|
|
11
|
-
skim_python-0.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|