skim-python 0.0.1__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/__init__.py +5 -0
- skim_py/block.py +150 -0
- skim_py/cli.py +147 -0
- skim_py/glance.py +203 -0
- skim_py/parser.py +300 -0
- skim_py/types.py +40 -0
- skim_python-0.0.1.dist-info/METADATA +523 -0
- skim_python-0.0.1.dist-info/RECORD +11 -0
- skim_python-0.0.1.dist-info/WHEEL +4 -0
- skim_python-0.0.1.dist-info/entry_points.txt +2 -0
- skim_python-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: skim-python
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Language-aware, AST-resilient outline and verbatim code extractor for Python tailored for AI agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/benedictjohannes/programming-helper-utils
|
|
6
|
+
Project-URL: Repository, https://github.com/benedictjohannes/programming-helper-utils
|
|
7
|
+
Project-URL: Issues, https://github.com/benedictjohannes/programming-helper-utils/issues
|
|
8
|
+
Author: Benedict Johannes
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agents,ast,code-intelligence,developer-tools,extractor,outline
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# `skim-py`
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/skim-python/)
|
|
29
|
+
[](../../LICENSE)
|
|
30
|
+
|
|
31
|
+
A fast, zero-dependency, AST-resilient Python outline and verbatim code block extractor tailored for AI coding agents and terminal workflows.
|
|
32
|
+
|
|
33
|
+
> Part of the [`skim-*` family - check out the complete suite](https://github.com/benedictjohannes/programming-helper-utils). Conforms to the [`skim-*` Family Design Specification](https://github.com/benedictjohannes/programming-helper-utils/blob/master/Design.md).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Why `skim-py`?
|
|
38
|
+
|
|
39
|
+
When AI coding assistants (such as Claude, Gemini, ChatGPT, or Cursor) operate on real-world Python repositories, reading entire source files into prompt context leads to performance and cost degradation:
|
|
40
|
+
|
|
41
|
+
1. **Context Window Exhaustion & Attention Drift**: Dumping 1,000–5,000 line source files burns through token budgets rapidly. Irrelevant implementation details dilute the model's attention, leading to hallucinations and missed context.
|
|
42
|
+
2. **The Test Bloat Trap**: In modern Python codebases, test files (`test_*.py`, `*_test.py`, `tests/`) and in-file test suites (`class Test*`, `def test_*`) often make up 50–70% of a repository's volume. When exploring domain logic or architecture, test suites add immense noise.
|
|
43
|
+
3. **Repeated File Reading Loops**: Agents often read file A, find a call to a method on class B in file B, read all of file B, and repeat—quickly exhausting tool calls and context windows.
|
|
44
|
+
4. **Syntax Tolerance During Active Edits**: Codebases being actively refactored or edited often contain temporary syntax errors. Traditional strict parsers crash completely, leaving the agent unable to inspect surrounding structures.
|
|
45
|
+
|
|
46
|
+
`skim-py` solves this by giving agents (and developers) a pair of surgical tools powered by Python's built-in `ast` and `tokenize` modules (zero third-party dependencies):
|
|
47
|
+
- **`glance`**: Instant, high-density outlines of declarations, signatures, dataclass/Pydantic/enum fields, and line spans—with test files and in-file test suites filtered out by default.
|
|
48
|
+
- **`block`**: Verbatim code retrieval for an exact class, function, or qualified member (`Class.method`) with line gutters or raw code ready for editing.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Recommended Agent Workflow
|
|
53
|
+
|
|
54
|
+
```mermaid
|
|
55
|
+
flowchart TD
|
|
56
|
+
A["1. Survey Structure<br/><code>skim-py glance src/ -s</code>"] --> B{"Need specific<br/>identifier?"}
|
|
57
|
+
B -->|Yes| C["2. Locate Logic<br/><code>skim-py glance src/ -s -f keyword</code>"]
|
|
58
|
+
B -->|No| D["Inspect Overview"]
|
|
59
|
+
C --> E["3. Fetch Surgical Block<br/><code>skim-py block src/service.py UserService.get_user</code>"]
|
|
60
|
+
D --> E
|
|
61
|
+
E --> F["Form Accurate Hypotheses & Edit Safely"]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
1. **Survey (`glance -s`)**: Get the structural topography of a module or package in under 100 tokens.
|
|
65
|
+
2. **Locate (`glance -f <keyword>`)**: Narrow down matching declarations across files with case-insensitive substring filters.
|
|
66
|
+
3. **Inspect (`block <file> <target>`)**: Fetch the exact verbatim implementation block with accurate file line numbers for precision editing.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- **Zero Third-Party Dependencies**: Pure Python standard library implementation using `ast` and `tokenize`.
|
|
73
|
+
- **Fast Startup & Minimal Overhead**: Launches instantaneously without waiting for heavy virtual environments or complex toolchains.
|
|
74
|
+
- **Test-Blind by Default**:
|
|
75
|
+
- Automatically excludes `test_*.py`, `*_test.py`, `conftest.py`, and test directories (`tests/`, `test/`, `testing/`, `__tests__/`) during traversals unless `-t` / `--include-tests` is specified.
|
|
76
|
+
- Automatically detects in-file test classes (`Test*`, `unittest.TestCase`) and test functions (`test_*`) and filters them out of outlines and target lookups, reporting skipped line ranges (`[tests skipped: 8..9, 16..17, 20..25]`).
|
|
77
|
+
- **Language-Idiomatic Qualified Targets**: Target methods via `Class.method`, constructors via `Class.__init__`, dunder methods via `Class.__str__`, or standalone functions, classes, and module-level variables.
|
|
78
|
+
- **Decorator & Signature Preservation**: Includes full decorator chains (`@classmethod`, `@property`, `@field_validator`) and return annotations in both outlines and block extractions.
|
|
79
|
+
- **Exact-Case Precedence with Case-Insensitive Fallback**: Matches `basemodel.model_dump` to `BaseModel.model_dump` while prioritizing exact case if both co-exist.
|
|
80
|
+
- **Clear Ambiguity Diagnostics**: If a target matches multiple candidates, `skim-py` exits with code `3` and presents numbered candidate locations with line spans and disambiguation tips.
|
|
81
|
+
- **Syntax Error Tolerance & AST Recovery**:
|
|
82
|
+
- Reports syntax notices cleanly under the file header without crashing, pinpointing the error line and message.
|
|
83
|
+
- Automatically recovers using a bounded iterative line-masking fallback (up to 100 iterations), isolating broken lines while preserving exact 1-to-1 line offsets so surrounding valid classes, methods, and functions remain fully outlined and extractable.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Installation & Usage
|
|
88
|
+
|
|
89
|
+
### On-Demand (`uvx` or `pipx`)
|
|
90
|
+
Run directly without installing:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Via uvx (Fastest & Recommended)
|
|
94
|
+
uvx --from skim-python skim-py glance src/ -s
|
|
95
|
+
uvx --from skim-python skim-py block src/models.py BaseModel
|
|
96
|
+
|
|
97
|
+
# Via pipx
|
|
98
|
+
pipx run --spec skim-python skim-py glance src/ -s
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Global Install (`uv` / `pipx` / `pip`)
|
|
102
|
+
Install globally to add `skim-py` directly to your `PATH`:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Via uv (Recommended)
|
|
106
|
+
uv tool install skim-python
|
|
107
|
+
|
|
108
|
+
# Via pipx
|
|
109
|
+
pipx install skim-python
|
|
110
|
+
|
|
111
|
+
# Via pip
|
|
112
|
+
pip install skim-python
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### From Local Source
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/benedictjohannes/programming-helper-utils.git
|
|
119
|
+
cd programming-helper-utils/python/skim-py
|
|
120
|
+
|
|
121
|
+
# With uv:
|
|
122
|
+
uv tool install --editable .
|
|
123
|
+
|
|
124
|
+
# Or with pip:
|
|
125
|
+
pip install -e .
|
|
126
|
+
|
|
127
|
+
# Or link the standalone runner directly to your PATH:
|
|
128
|
+
mkdir -p ~/.local/bin
|
|
129
|
+
ln -sf "$(pwd)/skim-py" ~/.local/bin/skim-py
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## CLI Reference & Concrete Examples
|
|
135
|
+
|
|
136
|
+
> [!NOTE]
|
|
137
|
+
> Examples below are authentic outputs produced by running `skim-py` against the [`pydantic/pydantic`](https://github.com/pydantic/pydantic) repository and local fixtures.
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
Usage:
|
|
141
|
+
skim-py glance [options] <path>...
|
|
142
|
+
skim-py block [options] <file> <target>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### 1. `glance` Subcommand
|
|
148
|
+
|
|
149
|
+
Outlines structure, signatures, fields, and imports across files or directories.
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
Usage:
|
|
153
|
+
skim-py glance [options] <path>...
|
|
154
|
+
|
|
155
|
+
Arguments:
|
|
156
|
+
<path>... One or more source files, directories, or globs
|
|
157
|
+
|
|
158
|
+
Options:
|
|
159
|
+
-s, --signatures Show declarations & types without function bodies
|
|
160
|
+
-d, --details Show class attributes, annotated fields, and enum members
|
|
161
|
+
-u, --uses, --imports Display top-level import statements
|
|
162
|
+
-f, --filter <text> Filter items containing <text> (case-insensitive, repeatable)
|
|
163
|
+
-t, --include-tests Include test files (test_*.py, etc.) and test blocks
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Example 1: Basic Structural Outline of a File
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
skim-py glance testingRepositories/pydantic/pydantic/errors.py
|
|
170
|
+
```
|
|
171
|
+
```text
|
|
172
|
+
=== testingRepositories/pydantic/pydantic/errors.py (185 lines) ===
|
|
173
|
+
|
|
174
|
+
Outline:
|
|
175
|
+
L15-L23 var __all__
|
|
176
|
+
L28-L28 var DEV_ERROR_DOCS_URL
|
|
177
|
+
L82-L98 class PydanticErrorMixin
|
|
178
|
+
L90-L92 method __init__
|
|
179
|
+
L94-L98 method __str__
|
|
180
|
+
L101-L102 class PydanticUserError
|
|
181
|
+
L105-L127 class PydanticUndefinedAnnotation
|
|
182
|
+
L113-L115 method __init__
|
|
183
|
+
L117-L127 method from_name_error
|
|
184
|
+
L130-L138 class PydanticImportError
|
|
185
|
+
L137-L138 method __init__
|
|
186
|
+
L141-L149 class PydanticSchemaGenerationError
|
|
187
|
+
L148-L149 method __init__
|
|
188
|
+
L152-L160 class PydanticInvalidForJsonSchema
|
|
189
|
+
L159-L160 method __init__
|
|
190
|
+
L163-L182 class PydanticForbiddenQualifier
|
|
191
|
+
L175-L182 method __init__
|
|
192
|
+
L185-L185 var __getattr__
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Example 2: Signatures and Imports (`-s -u`)
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
skim-py glance testingRepositories/pydantic/pydantic/errors.py -s -u
|
|
199
|
+
```
|
|
200
|
+
```text
|
|
201
|
+
=== testingRepositories/pydantic/pydantic/errors.py (185 lines) ===
|
|
202
|
+
|
|
203
|
+
Imports:
|
|
204
|
+
from __future__ import annotations as _annotations
|
|
205
|
+
from typing import Any, ClassVar, Literal
|
|
206
|
+
from typing_extensions import Self
|
|
207
|
+
from typing_inspection.introspection import Qualifier
|
|
208
|
+
from pydantic._internal import _repr
|
|
209
|
+
from ._migration import getattr_migration
|
|
210
|
+
from .version import version_short
|
|
211
|
+
|
|
212
|
+
Outline:
|
|
213
|
+
L15-L23 __all__ = ('PydanticUserError', 'PydanticUndefinedAnnotation', 'PydanticImportError', 'PydanticSchemaGenerationError', 'PydanticInvalidForJsonSchema', 'PydanticForbiddenQualifier', 'PydanticErrorCodes')
|
|
214
|
+
L28-L28 DEV_ERROR_DOCS_URL = f'https://errors.pydantic.dev/{version_short()}/u/'
|
|
215
|
+
L82-L98 class PydanticErrorMixin
|
|
216
|
+
L90-L92 def __init__(self, message: str, *, code: PydanticErrorCodes | None) -> None
|
|
217
|
+
L94-L98 def __str__(self) -> str
|
|
218
|
+
L101-L102 class PydanticUserError(PydanticErrorMixin, RuntimeError)
|
|
219
|
+
L105-L127 class PydanticUndefinedAnnotation(PydanticErrorMixin, NameError)
|
|
220
|
+
L113-L115 def __init__(self, name: str | None, message: str) -> None
|
|
221
|
+
L117-L127 @classmethod def from_name_error(cls, name_error: NameError) -> Self
|
|
222
|
+
L130-L138 class PydanticImportError(PydanticErrorMixin, ImportError)
|
|
223
|
+
L137-L138 def __init__(self, message: str) -> None
|
|
224
|
+
L141-L149 class PydanticSchemaGenerationError(PydanticUserError)
|
|
225
|
+
L148-L149 def __init__(self, message: str) -> None
|
|
226
|
+
L152-L160 class PydanticInvalidForJsonSchema(PydanticUserError)
|
|
227
|
+
L159-L160 def __init__(self, message: str) -> None
|
|
228
|
+
L163-L182 class PydanticForbiddenQualifier(PydanticUserError)
|
|
229
|
+
L175-L182 def __init__(self, qualifier: Qualifier, annotation: Any) -> None
|
|
230
|
+
L185-L185 __getattr__ = getattr_migration(__name__)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Example 3: Expand Details and Class Attributes (`-s -d`)
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
skim-py glance testingRepositories/pydantic/pydantic/errors.py -s -d
|
|
237
|
+
```
|
|
238
|
+
```text
|
|
239
|
+
=== testingRepositories/pydantic/pydantic/errors.py (185 lines) ===
|
|
240
|
+
|
|
241
|
+
Outline:
|
|
242
|
+
L15-L23 __all__ = ('PydanticUserError', 'PydanticUndefinedAnnotation', 'PydanticImportError', 'PydanticSchemaGenerationError', 'PydanticInvalidForJsonSchema', 'PydanticForbiddenQualifier', 'PydanticErrorCodes')
|
|
243
|
+
L28-L28 DEV_ERROR_DOCS_URL = f'https://errors.pydantic.dev/{version_short()}/u/'
|
|
244
|
+
L82-L98 class PydanticErrorMixin
|
|
245
|
+
L90-L92 def __init__(self, message: str, *, code: PydanticErrorCodes | None) -> None
|
|
246
|
+
L94-L98 def __str__(self) -> str
|
|
247
|
+
L101-L102 class PydanticUserError(PydanticErrorMixin, RuntimeError)
|
|
248
|
+
L105-L127 class PydanticUndefinedAnnotation(PydanticErrorMixin, NameError)
|
|
249
|
+
L113-L115 def __init__(self, name: str | None, message: str) -> None
|
|
250
|
+
L117-L127 @classmethod def from_name_error(cls, name_error: NameError) -> Self
|
|
251
|
+
L130-L138 class PydanticImportError(PydanticErrorMixin, ImportError)
|
|
252
|
+
L137-L138 def __init__(self, message: str) -> None
|
|
253
|
+
L141-L149 class PydanticSchemaGenerationError(PydanticUserError)
|
|
254
|
+
L148-L149 def __init__(self, message: str) -> None
|
|
255
|
+
L152-L160 class PydanticInvalidForJsonSchema(PydanticUserError)
|
|
256
|
+
L159-L160 def __init__(self, message: str) -> None
|
|
257
|
+
L163-L182 class PydanticForbiddenQualifier(PydanticUserError)
|
|
258
|
+
| _qualifier_repr_map: ClassVar[dict[Qualifier, str]] = {'required': 'typing.Required', 'not_required': 'typing.NotRequired', 'read_only': 'typing.ReadOnly', 'class_var': 'typing.ClassVar', 'init_var': 'dataclasses.InitVar', 'final': 'typing.Final'}
|
|
259
|
+
L175-L182 def __init__(self, qualifier: Qualifier, annotation: Any) -> None
|
|
260
|
+
L185-L185 __getattr__ = getattr_migration(__name__)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### Example 4: Filter by Keyword across Declarations (`-f <keyword>`)
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
skim-py glance testingRepositories/pydantic/pydantic/errors.py -s -f "from_name_error"
|
|
267
|
+
```
|
|
268
|
+
```text
|
|
269
|
+
=== testingRepositories/pydantic/pydantic/errors.py (185 lines) ===
|
|
270
|
+
|
|
271
|
+
Outline:
|
|
272
|
+
L105-L127 class PydanticUndefinedAnnotation(PydanticErrorMixin, NameError)
|
|
273
|
+
L117-L127 @classmethod def from_name_error(cls, name_error: NameError) -> Self
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
#### Example 5: In-File Test Filtering & Inclusion (`-t`)
|
|
277
|
+
|
|
278
|
+
In-file test suites (`Test*`, `test_*`) are filtered out by default, with skipped ranges highlighted:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Default: in-file tests omitted
|
|
282
|
+
skim-py glance tests/fixtures/tests.py
|
|
283
|
+
```
|
|
284
|
+
```text
|
|
285
|
+
=== tests/fixtures/tests.py (29 lines) [tests skipped: 8..9, 16..17, 20..25, 28..29] ===
|
|
286
|
+
|
|
287
|
+
Outline:
|
|
288
|
+
L4-L5 function normal_helper
|
|
289
|
+
L12-L17 class NormalClass
|
|
290
|
+
L13-L14 method regular_method
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
When test inspection is desired, pass `-t`:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
skim-py glance tests/fixtures/tests.py -t
|
|
297
|
+
```
|
|
298
|
+
```text
|
|
299
|
+
=== tests/fixtures/tests.py (29 lines) [test] ===
|
|
300
|
+
|
|
301
|
+
Outline:
|
|
302
|
+
L4-L5 function normal_helper
|
|
303
|
+
L8-L9 def test_helper_not_test() -> str
|
|
304
|
+
L12-L17 class NormalClass
|
|
305
|
+
L13-L14 method regular_method
|
|
306
|
+
L16-L17 def test_like_method(self) -> None
|
|
307
|
+
L20-L25 class TestSuite(unittest.TestCase)
|
|
308
|
+
L21-L22 def test_example(self) -> None
|
|
309
|
+
L24-L25 def test_another(self) -> None
|
|
310
|
+
L28-L29 def test_standalone_func() -> None
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Similarly, test files (`test_*.py`, `tests/`) are excluded by default during directory traversals and only included when `-t` is passed:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
skim-py glance testingRepositories/pydantic/tests/test_warnings.py -t
|
|
317
|
+
```
|
|
318
|
+
```text
|
|
319
|
+
=== testingRepositories/pydantic/tests/test_warnings.py (41 lines) [test] ===
|
|
320
|
+
|
|
321
|
+
Outline:
|
|
322
|
+
L5-L10 def test_pydantic_deprecation_warning()
|
|
323
|
+
L13-L17 def test_pydantic_deprecation_warning_tailing_dot_in_message()
|
|
324
|
+
L20-L23 def test_pydantic_deprecation_warning_calculated_expected_removal()
|
|
325
|
+
L26-L32 def test_pydantic_deprecation_warning_2_0_migration_guide_link()
|
|
326
|
+
L35-L41 def test_pydantic_deprecated_since_2_0_warning()
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### Example 6: Syntax Error Tolerance & Recovery
|
|
330
|
+
|
|
331
|
+
If a file has a syntax error during active editing or refactoring, `skim-py` does not crash; it pinpoints syntax errors directly under the file header and automatically recovers to outline valid surrounding declarations:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
skim-py glance tests/fixtures/syntax_error.py -s
|
|
335
|
+
```
|
|
336
|
+
```text
|
|
337
|
+
=== tests/fixtures/syntax_error.py (12 lines) [! 1 syntax error] ===
|
|
338
|
+
Syntax Notice: L7: invalid syntax
|
|
339
|
+
|
|
340
|
+
Outline:
|
|
341
|
+
L1-L2 def valid_before()
|
|
342
|
+
L5-L8 def broken_function()
|
|
343
|
+
L11-L12 def valid_after()
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Surrounding valid functions remain fully extractable via `block`:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
skim-py block tests/fixtures/syntax_error.py valid_after
|
|
350
|
+
```
|
|
351
|
+
```text
|
|
352
|
+
=== tests/fixtures/syntax_error.py (L11-L12) ===
|
|
353
|
+
11 | def valid_after():
|
|
354
|
+
12 | return 2
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
And if an extracted block contains a syntax error within its line span, the warning is surfaced directly above the block:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
skim-py block tests/fixtures/syntax_error.py broken_function
|
|
361
|
+
```
|
|
362
|
+
```text
|
|
363
|
+
=== tests/fixtures/syntax_error.py (L5-L8) [! 1 syntax error] ===
|
|
364
|
+
Syntax Notice: L7: invalid syntax
|
|
365
|
+
5 | def broken_function():
|
|
366
|
+
6 | x = 10
|
|
367
|
+
7 | y =
|
|
368
|
+
8 | return x
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### 2. `block` Subcommand
|
|
374
|
+
|
|
375
|
+
Extracts the exact verbatim source block for a class, function, variable, or `Class.method`.
|
|
376
|
+
|
|
377
|
+
```text
|
|
378
|
+
Usage:
|
|
379
|
+
skim-py block [options] <file> <target>
|
|
380
|
+
|
|
381
|
+
Arguments:
|
|
382
|
+
<file> Path to the source file
|
|
383
|
+
<target> Name of class, method, function, or Class.method
|
|
384
|
+
|
|
385
|
+
Options:
|
|
386
|
+
-t, --include-tests Search inside test files and test blocks
|
|
387
|
+
--raw Omit line number gutter (clean code for edits/diffs)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Example 7: Extract Class Definition
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
skim-py block testingRepositories/pydantic/pydantic/warnings.py PydanticDeprecationWarning
|
|
394
|
+
```
|
|
395
|
+
```text
|
|
396
|
+
=== testingRepositories/pydantic/pydantic/warnings.py (L23-L54) ===
|
|
397
|
+
23 | class PydanticDeprecationWarning(DeprecationWarning):
|
|
398
|
+
24 | """A Pydantic specific deprecation warning.
|
|
399
|
+
25 |
|
|
400
|
+
26 | This warning is raised when using deprecated functionality in Pydantic. It provides information on when the
|
|
401
|
+
27 | deprecation was introduced and the expected version in which the corresponding functionality will be removed.
|
|
402
|
+
28 |
|
|
403
|
+
29 | Attributes:
|
|
404
|
+
30 | message: Description of the warning.
|
|
405
|
+
31 | since: Pydantic version in what the deprecation was introduced.
|
|
406
|
+
32 | expected_removal: Pydantic version in what the corresponding functionality expected to be removed.
|
|
407
|
+
33 | """
|
|
408
|
+
34 |
|
|
409
|
+
35 | message: str
|
|
410
|
+
36 | since: tuple[int, int]
|
|
411
|
+
37 | expected_removal: tuple[int, int]
|
|
412
|
+
38 |
|
|
413
|
+
39 | def __init__(
|
|
414
|
+
40 | self, message: str, *args: object, since: tuple[int, int], expected_removal: tuple[int, int] | None = None
|
|
415
|
+
41 | ) -> None:
|
|
416
|
+
42 | super().__init__(message, *args)
|
|
417
|
+
43 | self.message = message.rstrip('.')
|
|
418
|
+
44 | self.since = since
|
|
419
|
+
45 | self.expected_removal = expected_removal if expected_removal is not None else (since[0] + 1, 0)
|
|
420
|
+
46 |
|
|
421
|
+
47 | def __str__(self) -> str:
|
|
422
|
+
48 | message = (
|
|
423
|
+
49 | f'{self.message}. Deprecated in Pydantic V{self.since[0]}.{self.since[1]}'
|
|
424
|
+
50 | f' to be removed in V{self.expected_removal[0]}.{self.expected_removal[1]}.'
|
|
425
|
+
51 | )
|
|
426
|
+
52 | if self.since == (2, 0):
|
|
427
|
+
53 | message += f' See Pydantic V2 Migration Guide at https://errors.pydantic.dev/{version_short()}/migration/'
|
|
428
|
+
54 | return message
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
#### Example 8: Extract Qualified Method (`Class.method`)
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
skim-py block testingRepositories/pydantic/pydantic/errors.py PydanticUndefinedAnnotation.from_name_error
|
|
435
|
+
```
|
|
436
|
+
```text
|
|
437
|
+
=== testingRepositories/pydantic/pydantic/errors.py (L117-L127) ===
|
|
438
|
+
117 | @classmethod
|
|
439
|
+
118 | def from_name_error(cls, name_error: NameError) -> Self:
|
|
440
|
+
119 | """Convert a `NameError` to a `PydanticUndefinedAnnotation` error.
|
|
441
|
+
120 |
|
|
442
|
+
121 | Args:
|
|
443
|
+
122 | name_error: `NameError` to be converted.
|
|
444
|
+
123 |
|
|
445
|
+
124 | Returns:
|
|
446
|
+
125 | Converted `PydanticUndefinedAnnotation` error.
|
|
447
|
+
126 | """
|
|
448
|
+
127 | return cls(name=name_error.name, message=str(name_error))
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Case-insensitive target matching is also supported:
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
skim-py block testingRepositories/pydantic/pydantic/errors.py pydanticundefinedannotation.from_name_error
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### Example 9: Raw Output for Seamless Patching (`--raw`)
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
skim-py block testingRepositories/pydantic/pydantic/errors.py PydanticUndefinedAnnotation.from_name_error --raw
|
|
461
|
+
```
|
|
462
|
+
```python
|
|
463
|
+
@classmethod
|
|
464
|
+
def from_name_error(cls, name_error: NameError) -> Self:
|
|
465
|
+
"""Convert a `NameError` to a `PydanticUndefinedAnnotation` error.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
name_error: `NameError` to be converted.
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
Converted `PydanticUndefinedAnnotation` error.
|
|
472
|
+
"""
|
|
473
|
+
return cls(name=name_error.name, message=str(name_error))
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
#### Example 10: Disambiguation Handling
|
|
477
|
+
|
|
478
|
+
When multiple candidates match an ambiguous target name, `skim-py` exits with status `3` and provides candidates and resolution tips:
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
skim-py block testingRepositories/pydantic/pydantic/errors.py __init__
|
|
482
|
+
```
|
|
483
|
+
```text
|
|
484
|
+
Error: ambiguous target '__init__' matched 6 candidates in testingRepositories/pydantic/pydantic/errors.py:
|
|
485
|
+
1) L90-L92 method PydanticErrorMixin.__init__
|
|
486
|
+
2) L113-L115 method PydanticUndefinedAnnotation.__init__
|
|
487
|
+
3) L137-L138 method PydanticImportError.__init__
|
|
488
|
+
4) L148-L149 method PydanticSchemaGenerationError.__init__
|
|
489
|
+
5) L159-L160 method PydanticInvalidForJsonSchema.__init__
|
|
490
|
+
6) L175-L182 method PydanticForbiddenQualifier.__init__
|
|
491
|
+
|
|
492
|
+
Tip: Disambiguate with Class.method (e.g. BaseModel.model_dump) or read line range directly.
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Exit Codes
|
|
498
|
+
|
|
499
|
+
`skim-py` adheres to the standardized POSIX / `skim-*` family exit codes:
|
|
500
|
+
|
|
501
|
+
| Code | Meaning | Description |
|
|
502
|
+
|:---:|:---|:---|
|
|
503
|
+
| `0` | **Success** | Operation completed normally. |
|
|
504
|
+
| `1` | **Error** | Path not found, I/O failure, or invalid CLI flag. |
|
|
505
|
+
| `2` | **Not Found** | The specified target symbol was not found in the file. |
|
|
506
|
+
| `3` | **Ambiguous** | Multiple candidates match the target symbol; disambiguation required. |
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Automated Tests
|
|
511
|
+
|
|
512
|
+
Unit tests and edge-case fixtures are located in [`tests/`](tests/).
|
|
513
|
+
|
|
514
|
+
Run test suite via `pytest`:
|
|
515
|
+
```bash
|
|
516
|
+
pytest tests/
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## License
|
|
522
|
+
|
|
523
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
skim_py/__init__.py,sha256=GCEQwh8hAZr6Vd8gbG-xpnqnulWXEAXhG-nA7sWtmoA,71
|
|
2
|
+
skim_py/block.py,sha256=R6KWLB8-WtsKxFMPKJces_H30vk7ht_E-ig5dEnDwns,4818
|
|
3
|
+
skim_py/cli.py,sha256=gcPPkHI9GCFjmhvWyGc4srC3xeA0I14e8MgAQYY643g,4977
|
|
4
|
+
skim_py/glance.py,sha256=4tsJT1cxlWUCJhi3fiLI1t_57UnP9cA6nrstzSOZrqM,6811
|
|
5
|
+
skim_py/parser.py,sha256=d9GiiG34iBl5WhdjUU1hFyQ1_3l8ynBsLeDsoz0Wufw,11255
|
|
6
|
+
skim_py/types.py,sha256=5Eeif5OKjqD9hWc5JFccMttSLrwIB1KnoC4cgzG4SBk,1083
|
|
7
|
+
skim_python-0.0.1.dist-info/METADATA,sha256=qMFLXCH1kZQot_guaA2soAS-w2WwK6QHOjgMN2gYUfI,21537
|
|
8
|
+
skim_python-0.0.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
9
|
+
skim_python-0.0.1.dist-info/entry_points.txt,sha256=nwzHJF7CgNobxT923pwxljnEuDdJe9a2k81Q91bPY8U,45
|
|
10
|
+
skim_python-0.0.1.dist-info/licenses/LICENSE,sha256=at6MgKqAJV-KpSzR2j1hUc2hTZfymTMkbvDwmypUA9I,1074
|
|
11
|
+
skim_python-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Benedict Johannes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|