undersort 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {undersort-0.1.1 → undersort-0.1.2}/PKG-INFO +27 -22
- {undersort-0.1.1 → undersort-0.1.2}/README.md +26 -21
- {undersort-0.1.1 → undersort-0.1.2}/pyproject.toml +3 -3
- {undersort-0.1.1 → undersort-0.1.2}/tests/test_sorter.py +17 -24
- {undersort-0.1.1 → undersort-0.1.2}/undersort/__init__.py +1 -1
- {undersort-0.1.1 → undersort-0.1.2}/undersort/sorter.py +47 -9
- {undersort-0.1.1 → undersort-0.1.2}/uv.lock +1 -1
- {undersort-0.1.1 → undersort-0.1.2}/.github/workflows/ci.yml +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/.gitignore +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/.pre-commit-config.yaml +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/.pre-commit-hooks.yaml +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/.python-version +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/.vscode/settings.json +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/LICENSE +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/Makefile +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/tests/__init__.py +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/tests/test_config.py +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/tests/test_main.py +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/undersort/config.py +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/undersort/logger.py +0 -0
- {undersort-0.1.1 → undersort-0.1.2}/undersort/main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: undersort
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A tool to sort class methods by visibility (public, protected, private)
|
|
5
5
|
Project-URL: Homepage, https://github.com/kivicode/undersort
|
|
6
6
|
Project-URL: Repository, https://github.com/kivicode/undersort
|
|
@@ -61,8 +61,8 @@ order = ["public", "protected", "private"]
|
|
|
61
61
|
|
|
62
62
|
# Method type ordering within each visibility level (secondary sort, optional)
|
|
63
63
|
# Options: "class" (classmethod), "static" (staticmethod), "instance" (regular methods)
|
|
64
|
-
# Default: ["
|
|
65
|
-
method_type_order = ["
|
|
64
|
+
# Default: ["instance", "class", "static"]
|
|
65
|
+
method_type_order = ["instance", "class", "static"]
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
### Method Visibility Rules
|
|
@@ -82,19 +82,24 @@ method_type_order = ["class", "static", "instance"]
|
|
|
82
82
|
Methods are sorted in two levels:
|
|
83
83
|
|
|
84
84
|
1. **Primary**: By visibility (public → protected → private)
|
|
85
|
-
2. **Secondary**: Within each visibility level, by method type (
|
|
85
|
+
2. **Secondary**: Within each visibility level, by method type (instance → class → static by default)
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
The sorting algorithm **minimizes movement** to preserve the original order as much as possible:
|
|
88
|
+
- Methods that need to move DOWN (to a later section) are placed at the **beginning** of their target section
|
|
89
|
+
- Methods that need to move UP (to an earlier section) are placed at the **end** of their target section
|
|
90
|
+
- Methods already in the correct section maintain their relative order
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
Example order with default configuration:
|
|
93
|
+
|
|
94
|
+
1. Public instance methods
|
|
95
|
+
2. Public class methods
|
|
96
|
+
3. Public static methods
|
|
97
|
+
4. Protected instance methods
|
|
98
|
+
5. Protected class methods
|
|
99
|
+
6. Protected static methods
|
|
100
|
+
7. Private instance methods
|
|
101
|
+
8. Private class methods
|
|
102
|
+
9. Private static methods
|
|
98
103
|
|
|
99
104
|
## Usage
|
|
100
105
|
|
|
@@ -190,6 +195,12 @@ class Example:
|
|
|
190
195
|
|
|
191
196
|
```python
|
|
192
197
|
class Example:
|
|
198
|
+
def __init__(self):
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
def public_instance(self):
|
|
202
|
+
pass
|
|
203
|
+
|
|
193
204
|
@classmethod
|
|
194
205
|
def public_class(cls):
|
|
195
206
|
pass
|
|
@@ -198,19 +209,13 @@ class Example:
|
|
|
198
209
|
def public_static():
|
|
199
210
|
pass
|
|
200
211
|
|
|
201
|
-
def
|
|
202
|
-
pass
|
|
203
|
-
|
|
204
|
-
def public_instance(self):
|
|
212
|
+
def _protected_instance(self):
|
|
205
213
|
pass
|
|
206
214
|
|
|
207
215
|
@classmethod
|
|
208
216
|
def _protected_class(cls):
|
|
209
217
|
pass
|
|
210
218
|
|
|
211
|
-
def _protected_instance(self):
|
|
212
|
-
pass
|
|
213
|
-
|
|
214
219
|
def __private_method(self):
|
|
215
220
|
pass
|
|
216
221
|
```
|
|
@@ -218,7 +223,7 @@ class Example:
|
|
|
218
223
|
The methods are now organized by:
|
|
219
224
|
|
|
220
225
|
1. **Visibility**: public (including `__init__`) → protected → private
|
|
221
|
-
2. **Type** (within each visibility):
|
|
226
|
+
2. **Type** (within each visibility): instance → class → static
|
|
222
227
|
|
|
223
228
|
## Development
|
|
224
229
|
|
|
@@ -34,8 +34,8 @@ order = ["public", "protected", "private"]
|
|
|
34
34
|
|
|
35
35
|
# Method type ordering within each visibility level (secondary sort, optional)
|
|
36
36
|
# Options: "class" (classmethod), "static" (staticmethod), "instance" (regular methods)
|
|
37
|
-
# Default: ["
|
|
38
|
-
method_type_order = ["
|
|
37
|
+
# Default: ["instance", "class", "static"]
|
|
38
|
+
method_type_order = ["instance", "class", "static"]
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
### Method Visibility Rules
|
|
@@ -55,19 +55,24 @@ method_type_order = ["class", "static", "instance"]
|
|
|
55
55
|
Methods are sorted in two levels:
|
|
56
56
|
|
|
57
57
|
1. **Primary**: By visibility (public → protected → private)
|
|
58
|
-
2. **Secondary**: Within each visibility level, by method type (
|
|
58
|
+
2. **Secondary**: Within each visibility level, by method type (instance → class → static by default)
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
The sorting algorithm **minimizes movement** to preserve the original order as much as possible:
|
|
61
|
+
- Methods that need to move DOWN (to a later section) are placed at the **beginning** of their target section
|
|
62
|
+
- Methods that need to move UP (to an earlier section) are placed at the **end** of their target section
|
|
63
|
+
- Methods already in the correct section maintain their relative order
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
Example order with default configuration:
|
|
66
|
+
|
|
67
|
+
1. Public instance methods
|
|
68
|
+
2. Public class methods
|
|
69
|
+
3. Public static methods
|
|
70
|
+
4. Protected instance methods
|
|
71
|
+
5. Protected class methods
|
|
72
|
+
6. Protected static methods
|
|
73
|
+
7. Private instance methods
|
|
74
|
+
8. Private class methods
|
|
75
|
+
9. Private static methods
|
|
71
76
|
|
|
72
77
|
## Usage
|
|
73
78
|
|
|
@@ -163,6 +168,12 @@ class Example:
|
|
|
163
168
|
|
|
164
169
|
```python
|
|
165
170
|
class Example:
|
|
171
|
+
def __init__(self):
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
def public_instance(self):
|
|
175
|
+
pass
|
|
176
|
+
|
|
166
177
|
@classmethod
|
|
167
178
|
def public_class(cls):
|
|
168
179
|
pass
|
|
@@ -171,19 +182,13 @@ class Example:
|
|
|
171
182
|
def public_static():
|
|
172
183
|
pass
|
|
173
184
|
|
|
174
|
-
def
|
|
175
|
-
pass
|
|
176
|
-
|
|
177
|
-
def public_instance(self):
|
|
185
|
+
def _protected_instance(self):
|
|
178
186
|
pass
|
|
179
187
|
|
|
180
188
|
@classmethod
|
|
181
189
|
def _protected_class(cls):
|
|
182
190
|
pass
|
|
183
191
|
|
|
184
|
-
def _protected_instance(self):
|
|
185
|
-
pass
|
|
186
|
-
|
|
187
192
|
def __private_method(self):
|
|
188
193
|
pass
|
|
189
194
|
```
|
|
@@ -191,7 +196,7 @@ class Example:
|
|
|
191
196
|
The methods are now organized by:
|
|
192
197
|
|
|
193
198
|
1. **Visibility**: public (including `__init__`) → protected → private
|
|
194
|
-
2. **Type** (within each visibility):
|
|
199
|
+
2. **Type** (within each visibility): instance → class → static
|
|
195
200
|
|
|
196
201
|
## Development
|
|
197
202
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "undersort"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "A tool to sort class methods by visibility (public, protected, private)"
|
|
9
9
|
authors = [{ name = "KiviCode", email = "kivicode.dev@gmail.com" }]
|
|
10
10
|
readme = "README.md"
|
|
@@ -47,8 +47,8 @@ order = ["public", "protected", "private"]
|
|
|
47
47
|
|
|
48
48
|
# Method type ordering within each visibility level (optional)
|
|
49
49
|
# Options: "class" (classmethod), "static" (staticmethod), "instance" (regular methods)
|
|
50
|
-
# Default order if not specified: ["
|
|
51
|
-
method_type_order = ["
|
|
50
|
+
# Default order if not specified: ["instance", "class", "static"]
|
|
51
|
+
method_type_order = ["instance", "class", "static"]
|
|
52
52
|
|
|
53
53
|
[tool.ruff]
|
|
54
54
|
unsafe-fixes = true
|
|
@@ -382,7 +382,7 @@ class Example:
|
|
|
382
382
|
|
|
383
383
|
try:
|
|
384
384
|
order = ["public", "protected", "private"]
|
|
385
|
-
method_type_order = ["
|
|
385
|
+
method_type_order = ["instance", "class", "static"]
|
|
386
386
|
was_modified = sort_file(temp_path, order, method_type_order)
|
|
387
387
|
|
|
388
388
|
assert was_modified is True
|
|
@@ -390,19 +390,21 @@ class Example:
|
|
|
390
390
|
with open(temp_path) as f:
|
|
391
391
|
result = f.read()
|
|
392
392
|
|
|
393
|
-
|
|
393
|
+
instance_idx = result.find("def instance_method")
|
|
394
394
|
class_idx = result.find("def class_method")
|
|
395
395
|
static_idx = result.find("def static_method")
|
|
396
|
-
instance_idx = result.find("def instance_method")
|
|
397
396
|
|
|
398
|
-
assert
|
|
397
|
+
assert instance_idx < class_idx < static_idx
|
|
399
398
|
finally:
|
|
400
399
|
temp_path.unlink()
|
|
401
400
|
|
|
402
401
|
def test_method_type_sorting_custom_order(self) -> None:
|
|
403
|
-
"""Test custom method type ordering (
|
|
402
|
+
"""Test custom method type ordering (class first)."""
|
|
404
403
|
source = """
|
|
405
404
|
class Example:
|
|
405
|
+
def instance_method(self):
|
|
406
|
+
pass
|
|
407
|
+
|
|
406
408
|
@classmethod
|
|
407
409
|
def class_method(cls):
|
|
408
410
|
pass
|
|
@@ -410,9 +412,6 @@ class Example:
|
|
|
410
412
|
@staticmethod
|
|
411
413
|
def static_method():
|
|
412
414
|
pass
|
|
413
|
-
|
|
414
|
-
def instance_method(self):
|
|
415
|
-
pass
|
|
416
415
|
"""
|
|
417
416
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
|
418
417
|
f.write(source)
|
|
@@ -420,7 +419,7 @@ class Example:
|
|
|
420
419
|
|
|
421
420
|
try:
|
|
422
421
|
order = ["public", "protected", "private"]
|
|
423
|
-
method_type_order = ["
|
|
422
|
+
method_type_order = ["class", "static", "instance"]
|
|
424
423
|
was_modified = sort_file(temp_path, order, method_type_order)
|
|
425
424
|
|
|
426
425
|
assert was_modified is True
|
|
@@ -428,12 +427,11 @@ class Example:
|
|
|
428
427
|
with open(temp_path) as f:
|
|
429
428
|
result = f.read()
|
|
430
429
|
|
|
431
|
-
# Check order: instance -> static -> class
|
|
432
|
-
instance_idx = result.find("def instance_method")
|
|
433
|
-
static_idx = result.find("def static_method")
|
|
434
430
|
class_idx = result.find("def class_method")
|
|
431
|
+
static_idx = result.find("def static_method")
|
|
432
|
+
instance_idx = result.find("def instance_method")
|
|
435
433
|
|
|
436
|
-
assert
|
|
434
|
+
assert class_idx < static_idx < instance_idx
|
|
437
435
|
finally:
|
|
438
436
|
temp_path.unlink()
|
|
439
437
|
|
|
@@ -465,7 +463,7 @@ class Example:
|
|
|
465
463
|
|
|
466
464
|
try:
|
|
467
465
|
order = ["public", "protected", "private"]
|
|
468
|
-
method_type_order = ["
|
|
466
|
+
method_type_order = ["instance", "class", "static"]
|
|
469
467
|
was_modified = sort_file(temp_path, order, method_type_order)
|
|
470
468
|
|
|
471
469
|
assert was_modified is True
|
|
@@ -473,23 +471,18 @@ class Example:
|
|
|
473
471
|
with open(temp_path) as f:
|
|
474
472
|
result = f.read()
|
|
475
473
|
|
|
476
|
-
# Public methods should come first
|
|
477
|
-
public_class_idx = result.find("def public_class")
|
|
478
474
|
public_instance_idx = result.find("def public_instance")
|
|
475
|
+
public_class_idx = result.find("def public_class")
|
|
479
476
|
|
|
480
|
-
|
|
477
|
+
protected_instance_idx = result.find("def _protected_instance")
|
|
481
478
|
protected_class_idx = result.find("def _protected_class")
|
|
482
479
|
protected_static_idx = result.find("def _protected_static")
|
|
483
|
-
protected_instance_idx = result.find("def _protected_instance")
|
|
484
480
|
|
|
485
|
-
# Check visibility ordering
|
|
486
|
-
assert public_class_idx < protected_class_idx
|
|
487
481
|
assert public_instance_idx < protected_instance_idx
|
|
482
|
+
assert public_class_idx < protected_class_idx
|
|
488
483
|
|
|
489
|
-
|
|
490
|
-
assert public_class_idx < public_instance_idx
|
|
484
|
+
assert public_instance_idx < public_class_idx
|
|
491
485
|
|
|
492
|
-
|
|
493
|
-
assert protected_class_idx < protected_static_idx < protected_instance_idx
|
|
486
|
+
assert protected_instance_idx < protected_class_idx < protected_static_idx
|
|
494
487
|
finally:
|
|
495
488
|
temp_path.unlink()
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import difflib
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Literal
|
|
5
6
|
|
|
6
7
|
import libcst as cst
|
|
7
8
|
|
|
8
9
|
from undersort import logger
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def get_method_visibility(method_name: str) ->
|
|
12
|
+
def get_method_visibility(method_name: str) -> Literal["public", "private", "protected"]:
|
|
12
13
|
"""Determine method visibility based on naming convention.
|
|
13
14
|
|
|
14
15
|
Args:
|
|
@@ -63,13 +64,15 @@ class MethodSorter(cst.CSTTransformer):
|
|
|
63
64
|
(e.g., ["public", "protected", "private"])
|
|
64
65
|
method_type_order: Optional list specifying the order of method types
|
|
65
66
|
within each visibility level
|
|
66
|
-
(e.g., ["
|
|
67
|
+
(e.g., ["instance", "class", "static"])
|
|
67
68
|
"""
|
|
68
69
|
self.order = order
|
|
69
|
-
self.method_type_order = method_type_order or ["
|
|
70
|
+
self.method_type_order = method_type_order or ["instance", "class", "static"]
|
|
70
71
|
self.modified = False
|
|
71
72
|
|
|
72
|
-
def leave_ClassDef(
|
|
73
|
+
def leave_ClassDef( # noqa: PLR0912, PLR0915
|
|
74
|
+
self, original_node: cst.ClassDef, updated_node: cst.ClassDef
|
|
75
|
+
) -> cst.ClassDef:
|
|
73
76
|
"""Sort methods within a class definition.
|
|
74
77
|
|
|
75
78
|
Args:
|
|
@@ -91,21 +94,56 @@ class MethodSorter(cst.CSTTransformer):
|
|
|
91
94
|
if not methods:
|
|
92
95
|
return updated_node
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
method_with_index = [(i, method) for i, method in enumerate(methods)]
|
|
98
|
+
|
|
99
|
+
method_groups: dict[str, dict[str, list[tuple[int, cst.FunctionDef]]]] = {
|
|
95
100
|
"public": {"class": [], "static": [], "instance": []},
|
|
96
101
|
"protected": {"class": [], "static": [], "instance": []},
|
|
97
102
|
"private": {"class": [], "static": [], "instance": []},
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
for method in
|
|
105
|
+
for idx, method in method_with_index:
|
|
101
106
|
visibility = get_method_visibility(method.name.value)
|
|
102
107
|
method_type = get_method_type(method)
|
|
103
|
-
method_groups[visibility][method_type].append(method)
|
|
108
|
+
method_groups[visibility][method_type].append((idx, method))
|
|
104
109
|
|
|
105
|
-
|
|
110
|
+
group_order = []
|
|
106
111
|
for visibility in self.order:
|
|
107
112
|
for method_type in self.method_type_order:
|
|
108
|
-
|
|
113
|
+
group_order.append((visibility, method_type))
|
|
114
|
+
|
|
115
|
+
sorted_methods = []
|
|
116
|
+
current_position = 0
|
|
117
|
+
|
|
118
|
+
for visibility, method_type in group_order:
|
|
119
|
+
group = method_groups[visibility][method_type]
|
|
120
|
+
if not group:
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
group_indices = [idx for idx, _ in group]
|
|
124
|
+
min_original_idx = min(group_indices)
|
|
125
|
+
max_original_idx = max(group_indices)
|
|
126
|
+
|
|
127
|
+
moved_down = []
|
|
128
|
+
in_place = []
|
|
129
|
+
moved_up = []
|
|
130
|
+
|
|
131
|
+
for idx, method in group:
|
|
132
|
+
if idx < min_original_idx or (idx < current_position and current_position > 0):
|
|
133
|
+
moved_down.append((idx, method))
|
|
134
|
+
elif idx > max_original_idx:
|
|
135
|
+
moved_up.append((idx, method))
|
|
136
|
+
else:
|
|
137
|
+
in_place.append((idx, method))
|
|
138
|
+
|
|
139
|
+
moved_down.sort(key=lambda x: x[0])
|
|
140
|
+
in_place.sort(key=lambda x: x[0])
|
|
141
|
+
moved_up.sort(key=lambda x: x[0])
|
|
142
|
+
|
|
143
|
+
group_sorted = moved_down + in_place + moved_up
|
|
144
|
+
sorted_methods.extend([method for _, method in group_sorted])
|
|
145
|
+
|
|
146
|
+
current_position += len(group)
|
|
109
147
|
|
|
110
148
|
if methods != sorted_methods:
|
|
111
149
|
self.modified = True
|
|
@@ -905,7 +905,7 @@ wheels = [
|
|
|
905
905
|
|
|
906
906
|
[[package]]
|
|
907
907
|
name = "undersort"
|
|
908
|
-
version = "0.1.
|
|
908
|
+
version = "0.1.2"
|
|
909
909
|
source = { editable = "." }
|
|
910
910
|
dependencies = [
|
|
911
911
|
{ name = "libcst", version = "1.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|