crackerjack 0.21.5__tar.gz → 0.21.7__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.
- {crackerjack-0.21.5 → crackerjack-0.21.7}/PKG-INFO +8 -8
- {crackerjack-0.21.5 → crackerjack-0.21.7}/README.md +7 -7
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.gitignore +4 -1
- crackerjack-0.21.7/crackerjack/.ruff_cache/0.12.1/5056746222905752453 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/__init__.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/__main__.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/crackerjack.py +100 -46
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/errors.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/interactive.py +0 -10
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/pyproject.toml +1 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/pyproject.toml +1 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/conftest.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_crackerjack.py +38 -25
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_crackerjack_runner.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_errors.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_interactive.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_interactive_run.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_main.py +0 -3
- crackerjack-0.21.7/tests/test_multiline_functions.py +163 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_py313_advanced.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_py313_features.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_pytest_features.py +0 -1
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/test_structured_errors.py +0 -1
- crackerjack-0.21.5/crackerjack/.ruff_cache/0.12.1/5056746222905752453 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/LICENSE +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.libcst.codemod.yaml +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.pdm.toml +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.pre-commit-config-ai.yaml +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.pre-commit-config.yaml +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.pytest_cache/.gitignore +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.pytest_cache/CACHEDIR.TAG +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.pytest_cache/README.md +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.pytest_cache/v/cache/nodeids +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.pytest_cache/v/cache/stepwise +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/.gitignore +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.11/18187162184424859798 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.12/16869036553936192448 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.12/1867267426380906393 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.12/4240757255861806333 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.12/4441409093023629623 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.13/1867267426380906393 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.13/4240757255861806333 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.12.0/5056746222905752453 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/CACHEDIR.TAG +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/py313.py +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/TESTING.md +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/__init__.py +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/data/comments_sample.txt +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/data/docstrings_sample.txt +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/data/expected_comments_sample.txt +0 -0
- {crackerjack-0.21.5 → crackerjack-0.21.7}/tests/data/init.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: crackerjack
|
3
|
-
Version: 0.21.
|
3
|
+
Version: 0.21.7
|
4
4
|
Summary: Crackerjack: code quality toolkit
|
5
5
|
Keywords: bandit,black,creosote,mypy,pyright,pytest,refurb,ruff
|
6
6
|
Author-Email: lesleslie <les@wedgwoodwebworks.com>
|
@@ -102,7 +102,7 @@ If you're new to Crackerjack, follow these steps:
|
|
102
102
|
|
103
103
|
Or use the interactive Rich UI:
|
104
104
|
```
|
105
|
-
python -m crackerjack --
|
105
|
+
python -m crackerjack --interactive
|
106
106
|
```
|
107
107
|
|
108
108
|
---
|
@@ -269,7 +269,7 @@ python -m crackerjack -t --benchmark-regression --benchmark-regression-threshold
|
|
269
269
|
|
270
270
|
Or with the interactive Rich UI:
|
271
271
|
```
|
272
|
-
python -m crackerjack --
|
272
|
+
python -m crackerjack --interactive
|
273
273
|
```
|
274
274
|
|
275
275
|
## Usage
|
@@ -361,7 +361,7 @@ runner.process(MyOptions())
|
|
361
361
|
- `--benchmark-regression`: Fail tests if benchmarks regress beyond threshold.
|
362
362
|
- `--benchmark-regression-threshold`: Set threshold percentage for benchmark regression (default 5.0%).
|
363
363
|
- `-a`, `--all`: Run with `-x -t -p <micro|minor|major> -c` development options.
|
364
|
-
- `--
|
364
|
+
- `--interactive`: Enable the interactive Rich UI for a more user-friendly experience with visual progress tracking and interactive prompts.
|
365
365
|
- `--ai-agent`: Enable AI agent mode with structured output (see [AI Agent Integration](#ai-agent-integration)).
|
366
366
|
- `--help`: Display help.
|
367
367
|
|
@@ -461,7 +461,7 @@ runner.process(MyOptions())
|
|
461
461
|
|
462
462
|
- **Rich Interactive Mode** - Run with the interactive Rich UI:
|
463
463
|
```bash
|
464
|
-
python -m crackerjack --
|
464
|
+
python -m crackerjack --interactive
|
465
465
|
```
|
466
466
|
|
467
467
|
- **AI Integration** - Run with structured output for AI tools:
|
@@ -500,10 +500,10 @@ Crackerjack now offers an enhanced interactive experience through its Rich UI:
|
|
500
500
|
- **Error Visualization:** Errors are presented in a structured, easy-to-understand format with recovery suggestions
|
501
501
|
- **File Selection:** Interactive file browser for operations that require selecting files
|
502
502
|
|
503
|
-
To use the Rich UI, run Crackerjack with the `--
|
503
|
+
To use the Rich UI, run Crackerjack with the `--interactive` flag:
|
504
504
|
|
505
505
|
```bash
|
506
|
-
python -m crackerjack --
|
506
|
+
python -m crackerjack --interactive
|
507
507
|
```
|
508
508
|
|
509
509
|
This launches an interactive terminal interface where you can:
|
@@ -542,7 +542,7 @@ python -m crackerjack -v
|
|
542
542
|
For the most comprehensive error details with visual formatting, combine verbose mode with the Rich UI:
|
543
543
|
|
544
544
|
```bash
|
545
|
-
python -m crackerjack --
|
545
|
+
python -m crackerjack --interactive -v
|
546
546
|
```
|
547
547
|
|
548
548
|
## Python 3.13+ Features
|
@@ -58,7 +58,7 @@ If you're new to Crackerjack, follow these steps:
|
|
58
58
|
|
59
59
|
Or use the interactive Rich UI:
|
60
60
|
```
|
61
|
-
python -m crackerjack --
|
61
|
+
python -m crackerjack --interactive
|
62
62
|
```
|
63
63
|
|
64
64
|
---
|
@@ -225,7 +225,7 @@ python -m crackerjack -t --benchmark-regression --benchmark-regression-threshold
|
|
225
225
|
|
226
226
|
Or with the interactive Rich UI:
|
227
227
|
```
|
228
|
-
python -m crackerjack --
|
228
|
+
python -m crackerjack --interactive
|
229
229
|
```
|
230
230
|
|
231
231
|
## Usage
|
@@ -317,7 +317,7 @@ runner.process(MyOptions())
|
|
317
317
|
- `--benchmark-regression`: Fail tests if benchmarks regress beyond threshold.
|
318
318
|
- `--benchmark-regression-threshold`: Set threshold percentage for benchmark regression (default 5.0%).
|
319
319
|
- `-a`, `--all`: Run with `-x -t -p <micro|minor|major> -c` development options.
|
320
|
-
- `--
|
320
|
+
- `--interactive`: Enable the interactive Rich UI for a more user-friendly experience with visual progress tracking and interactive prompts.
|
321
321
|
- `--ai-agent`: Enable AI agent mode with structured output (see [AI Agent Integration](#ai-agent-integration)).
|
322
322
|
- `--help`: Display help.
|
323
323
|
|
@@ -417,7 +417,7 @@ runner.process(MyOptions())
|
|
417
417
|
|
418
418
|
- **Rich Interactive Mode** - Run with the interactive Rich UI:
|
419
419
|
```bash
|
420
|
-
python -m crackerjack --
|
420
|
+
python -m crackerjack --interactive
|
421
421
|
```
|
422
422
|
|
423
423
|
- **AI Integration** - Run with structured output for AI tools:
|
@@ -456,10 +456,10 @@ Crackerjack now offers an enhanced interactive experience through its Rich UI:
|
|
456
456
|
- **Error Visualization:** Errors are presented in a structured, easy-to-understand format with recovery suggestions
|
457
457
|
- **File Selection:** Interactive file browser for operations that require selecting files
|
458
458
|
|
459
|
-
To use the Rich UI, run Crackerjack with the `--
|
459
|
+
To use the Rich UI, run Crackerjack with the `--interactive` flag:
|
460
460
|
|
461
461
|
```bash
|
462
|
-
python -m crackerjack --
|
462
|
+
python -m crackerjack --interactive
|
463
463
|
```
|
464
464
|
|
465
465
|
This launches an interactive terminal interface where you can:
|
@@ -498,7 +498,7 @@ python -m crackerjack -v
|
|
498
498
|
For the most comprehensive error details with visual formatting, combine verbose mode with the Rich UI:
|
499
499
|
|
500
500
|
```bash
|
501
|
-
python -m crackerjack --
|
501
|
+
python -m crackerjack --interactive -v
|
502
502
|
```
|
503
503
|
|
504
504
|
## Python 3.13+ Features
|
Binary file
|
@@ -6,7 +6,6 @@ from pathlib import Path
|
|
6
6
|
from subprocess import CompletedProcess
|
7
7
|
from subprocess import run as execute
|
8
8
|
from tomllib import loads
|
9
|
-
|
10
9
|
from pydantic import BaseModel
|
11
10
|
from rich.console import Console
|
12
11
|
from tomli_w import dumps
|
@@ -86,28 +85,93 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
86
85
|
except Exception as e:
|
87
86
|
print(f"Error cleaning {file_path}: {e}")
|
88
87
|
|
88
|
+
def _initialize_docstring_state(self) -> dict[str, t.Any]:
|
89
|
+
return {
|
90
|
+
"in_docstring": False,
|
91
|
+
"delimiter": None,
|
92
|
+
"waiting": False,
|
93
|
+
"function_indent": 0,
|
94
|
+
"removed_docstring": False,
|
95
|
+
"in_multiline_def": False,
|
96
|
+
}
|
97
|
+
|
98
|
+
def _handle_function_definition(
|
99
|
+
self, line: str, stripped: str, state: dict[str, t.Any]
|
100
|
+
) -> bool:
|
101
|
+
if self._is_function_or_class_definition(stripped):
|
102
|
+
state["waiting"] = True
|
103
|
+
state["function_indent"] = len(line) - len(line.lstrip())
|
104
|
+
state["removed_docstring"] = False
|
105
|
+
state["in_multiline_def"] = not stripped.endswith(":")
|
106
|
+
return True
|
107
|
+
return False
|
108
|
+
|
109
|
+
def _handle_multiline_definition(
|
110
|
+
self, line: str, stripped: str, state: dict[str, t.Any]
|
111
|
+
) -> bool:
|
112
|
+
if state["in_multiline_def"]:
|
113
|
+
if stripped.endswith(":"):
|
114
|
+
state["in_multiline_def"] = False
|
115
|
+
return True
|
116
|
+
return False
|
117
|
+
|
118
|
+
def _handle_waiting_docstring(
|
119
|
+
self, lines: list[str], i: int, stripped: str, state: dict[str, t.Any]
|
120
|
+
) -> tuple[bool, str | None]:
|
121
|
+
if state["waiting"] and stripped:
|
122
|
+
if self._handle_docstring_start(stripped, state):
|
123
|
+
pass_line = None
|
124
|
+
if not state["in_docstring"]:
|
125
|
+
function_indent: int = state["function_indent"]
|
126
|
+
if self._needs_pass_statement(lines, i + 1, function_indent):
|
127
|
+
pass_line = " " * (function_indent + 4) + "pass"
|
128
|
+
state["removed_docstring"] = True
|
129
|
+
return True, pass_line
|
130
|
+
else:
|
131
|
+
state["waiting"] = False
|
132
|
+
return False, None
|
133
|
+
|
134
|
+
def _handle_docstring_content(
|
135
|
+
self, lines: list[str], i: int, stripped: str, state: dict[str, t.Any]
|
136
|
+
) -> tuple[bool, str | None]:
|
137
|
+
if state["in_docstring"]:
|
138
|
+
if self._handle_docstring_end(stripped, state):
|
139
|
+
pass_line = None
|
140
|
+
function_indent: int = state["function_indent"]
|
141
|
+
if self._needs_pass_statement(lines, i + 1, function_indent):
|
142
|
+
pass_line = " " * (function_indent + 4) + "pass"
|
143
|
+
state["removed_docstring"] = False
|
144
|
+
return True, pass_line
|
145
|
+
else:
|
146
|
+
return True, None
|
147
|
+
return False, None
|
148
|
+
|
149
|
+
def _process_line(
|
150
|
+
self, lines: list[str], i: int, line: str, state: dict[str, t.Any]
|
151
|
+
) -> tuple[bool, str | None]:
|
152
|
+
stripped = line.strip()
|
153
|
+
if self._handle_function_definition(line, stripped, state):
|
154
|
+
return True, line
|
155
|
+
if self._handle_multiline_definition(line, stripped, state):
|
156
|
+
return True, line
|
157
|
+
handled, pass_line = self._handle_waiting_docstring(lines, i, stripped, state)
|
158
|
+
if handled:
|
159
|
+
return True, pass_line
|
160
|
+
handled, pass_line = self._handle_docstring_content(lines, i, stripped, state)
|
161
|
+
if handled:
|
162
|
+
return True, pass_line
|
163
|
+
if state["removed_docstring"] and stripped:
|
164
|
+
state["removed_docstring"] = False
|
165
|
+
return False, line
|
166
|
+
|
89
167
|
def remove_docstrings(self, code: str) -> str:
|
90
168
|
lines = code.split("\n")
|
91
169
|
cleaned_lines = []
|
92
|
-
docstring_state =
|
93
|
-
for line in lines:
|
94
|
-
|
95
|
-
if
|
96
|
-
|
97
|
-
cleaned_lines.append(line)
|
98
|
-
continue
|
99
|
-
if docstring_state["waiting"] and stripped:
|
100
|
-
if self._handle_docstring_start(stripped, docstring_state):
|
101
|
-
continue
|
102
|
-
else:
|
103
|
-
docstring_state["waiting"] = False
|
104
|
-
if docstring_state["in_docstring"]:
|
105
|
-
if self._handle_docstring_end(stripped, docstring_state):
|
106
|
-
continue
|
107
|
-
else:
|
108
|
-
continue
|
109
|
-
cleaned_lines.append(line)
|
110
|
-
|
170
|
+
docstring_state = self._initialize_docstring_state()
|
171
|
+
for i, line in enumerate(lines):
|
172
|
+
_, result_line = self._process_line(lines, i, line, docstring_state)
|
173
|
+
if result_line:
|
174
|
+
cleaned_lines.append(result_line)
|
111
175
|
return "\n".join(cleaned_lines)
|
112
176
|
|
113
177
|
def _is_function_or_class_definition(self, stripped_line: str) -> bool:
|
@@ -137,6 +201,21 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
137
201
|
state["in_docstring"] = False
|
138
202
|
state["delimiter"] = None
|
139
203
|
return True
|
204
|
+
return False
|
205
|
+
|
206
|
+
def _needs_pass_statement(
|
207
|
+
self, lines: list[str], start_index: int, function_indent: int
|
208
|
+
) -> bool:
|
209
|
+
for i in range(start_index, len(lines)):
|
210
|
+
line = lines[i]
|
211
|
+
stripped = line.strip()
|
212
|
+
if not stripped:
|
213
|
+
continue
|
214
|
+
line_indent = len(line) - len(line.lstrip())
|
215
|
+
if line_indent <= function_indent:
|
216
|
+
return True
|
217
|
+
if line_indent > function_indent:
|
218
|
+
return False
|
140
219
|
return True
|
141
220
|
|
142
221
|
def remove_line_comments(self, code: str) -> str:
|
@@ -149,7 +228,6 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
149
228
|
cleaned_line = self._process_line_for_comments(line)
|
150
229
|
if cleaned_line or not line.strip():
|
151
230
|
cleaned_lines.append(cleaned_line or line)
|
152
|
-
|
153
231
|
return "\n".join(cleaned_lines)
|
154
232
|
|
155
233
|
def _process_line_for_comments(self, line: str) -> str:
|
@@ -162,7 +240,6 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
162
240
|
break
|
163
241
|
else:
|
164
242
|
result.append(char)
|
165
|
-
|
166
243
|
return "".join(result).rstrip()
|
167
244
|
|
168
245
|
def _handle_string_character(
|
@@ -173,18 +250,14 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
173
250
|
string_state: dict[str, t.Any],
|
174
251
|
result: list[str],
|
175
252
|
) -> bool:
|
176
|
-
"""Handle string quote characters. Returns True if character was handled."""
|
177
253
|
if char not in ("'", '"'):
|
178
254
|
return False
|
179
|
-
|
180
255
|
if index > 0 and line[index - 1] == "\\":
|
181
256
|
return False
|
182
|
-
|
183
257
|
if string_state["in_string"] is None:
|
184
258
|
string_state["in_string"] = char
|
185
259
|
elif string_state["in_string"] == char:
|
186
260
|
string_state["in_string"] = None
|
187
|
-
|
188
261
|
result.append(char)
|
189
262
|
return True
|
190
263
|
|
@@ -196,14 +269,11 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
196
269
|
string_state: dict[str, t.Any],
|
197
270
|
result: list[str],
|
198
271
|
) -> bool:
|
199
|
-
"""Handle comment character. Returns True if comment was found."""
|
200
272
|
if char != "#" or string_state["in_string"] is not None:
|
201
273
|
return False
|
202
|
-
|
203
274
|
comment = line[index:].strip()
|
204
275
|
if self._is_special_comment_line(comment):
|
205
276
|
result.append(line[index:])
|
206
|
-
|
207
277
|
return True
|
208
278
|
|
209
279
|
def _is_special_comment_line(self, comment: str) -> bool:
|
@@ -227,13 +297,11 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
227
297
|
):
|
228
298
|
continue
|
229
299
|
cleaned_lines.append(line)
|
230
|
-
|
231
300
|
return "\n".join(self._remove_trailing_empty_lines(cleaned_lines))
|
232
301
|
|
233
302
|
def _update_function_state(
|
234
303
|
self, line: str, stripped_line: str, function_tracker: dict[str, t.Any]
|
235
304
|
) -> None:
|
236
|
-
"""Update function tracking state based on current line."""
|
237
305
|
if stripped_line.startswith(("def ", "async def ")):
|
238
306
|
function_tracker["in_function"] = True
|
239
307
|
function_tracker["function_indent"] = len(line) - len(stripped_line)
|
@@ -244,7 +312,6 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
244
312
|
def _is_function_end(
|
245
313
|
self, line: str, stripped_line: str, function_tracker: dict[str, t.Any]
|
246
314
|
) -> bool:
|
247
|
-
"""Check if current line marks the end of a function."""
|
248
315
|
return (
|
249
316
|
function_tracker["in_function"]
|
250
317
|
and bool(line)
|
@@ -259,13 +326,10 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
259
326
|
cleaned_lines: list[str],
|
260
327
|
function_tracker: dict[str, t.Any],
|
261
328
|
) -> bool:
|
262
|
-
"""Determine if an empty line should be skipped."""
|
263
329
|
if line_idx > 0 and cleaned_lines and (not cleaned_lines[-1]):
|
264
330
|
return True
|
265
|
-
|
266
331
|
if function_tracker["in_function"]:
|
267
332
|
return self._should_skip_function_empty_line(line_idx, lines)
|
268
|
-
|
269
333
|
return False
|
270
334
|
|
271
335
|
def _should_skip_function_empty_line(self, line_idx: int, lines: list[str]) -> bool:
|
@@ -280,7 +344,6 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
|
|
280
344
|
return True
|
281
345
|
if next_line in ("pass", "break", "continue", "raise"):
|
282
346
|
return True
|
283
|
-
|
284
347
|
return self._is_special_comment(next_line)
|
285
348
|
|
286
349
|
def _is_special_comment(self, line: str) -> bool:
|
@@ -420,15 +483,12 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
|
|
420
483
|
for tool, settings in our_toml_config.get("tool", {}).items():
|
421
484
|
if tool not in pkg_toml_config["tool"]:
|
422
485
|
pkg_toml_config["tool"][tool] = {}
|
423
|
-
|
424
486
|
pkg_tool_config = pkg_toml_config["tool"][tool]
|
425
|
-
|
426
487
|
self._merge_tool_config(settings, pkg_tool_config, tool)
|
427
488
|
|
428
489
|
def _merge_tool_config(
|
429
490
|
self, our_config: dict[str, t.Any], pkg_config: dict[str, t.Any], tool: str
|
430
491
|
) -> None:
|
431
|
-
"""Recursively merge tool configuration, preserving existing project settings."""
|
432
492
|
for setting, value in our_config.items():
|
433
493
|
if isinstance(value, dict):
|
434
494
|
self._merge_nested_config(setting, value, pkg_config)
|
@@ -438,21 +498,17 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
|
|
438
498
|
def _merge_nested_config(
|
439
499
|
self, setting: str, value: dict[str, t.Any], pkg_config: dict[str, t.Any]
|
440
500
|
) -> None:
|
441
|
-
"""Handle nested configuration merging."""
|
442
501
|
if setting not in pkg_config:
|
443
502
|
pkg_config[setting] = {}
|
444
503
|
elif not isinstance(pkg_config[setting], dict):
|
445
504
|
pkg_config[setting] = {}
|
446
|
-
|
447
505
|
self._merge_tool_config(value, pkg_config[setting], "")
|
448
|
-
|
449
506
|
for k, v in value.items():
|
450
507
|
self._merge_nested_value(k, v, pkg_config[setting])
|
451
508
|
|
452
509
|
def _merge_nested_value(
|
453
510
|
self, key: str, value: t.Any, nested_config: dict[str, t.Any]
|
454
511
|
) -> None:
|
455
|
-
"""Merge individual nested values."""
|
456
512
|
if isinstance(value, str | list) and "crackerjack" in str(value):
|
457
513
|
nested_config[key] = self.swap_package_name(value)
|
458
514
|
elif self._is_mergeable_list(key, value):
|
@@ -467,7 +523,6 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
|
|
467
523
|
def _merge_direct_config(
|
468
524
|
self, setting: str, value: t.Any, pkg_config: dict[str, t.Any]
|
469
525
|
) -> None:
|
470
|
-
"""Handle direct configuration merging."""
|
471
526
|
if isinstance(value, str | list) and "crackerjack" in str(value):
|
472
527
|
pkg_config[setting] = self.swap_package_name(value)
|
473
528
|
elif self._is_mergeable_list(setting, value):
|
@@ -633,7 +688,7 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
|
|
633
688
|
)
|
634
689
|
if result.returncode == 0:
|
635
690
|
self.console.print("PDM installed: ✅\n")
|
636
|
-
self.execute_command(["pdm", "
|
691
|
+
self.execute_command(["pdm", "lock"])
|
637
692
|
self.console.print("Lock file updated: ✅\n")
|
638
693
|
else:
|
639
694
|
self.console.print(
|
@@ -730,7 +785,6 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
|
|
730
785
|
self._add_benchmark_flags(test, options)
|
731
786
|
else:
|
732
787
|
self._add_worker_flags(test, options, project_size)
|
733
|
-
|
734
788
|
return test
|
735
789
|
|
736
790
|
def _detect_project_size(self) -> str:
|
@@ -2,7 +2,6 @@ import time
|
|
2
2
|
import typing as t
|
3
3
|
from enum import Enum, auto
|
4
4
|
from pathlib import Path
|
5
|
-
|
6
5
|
from rich.box import ROUNDED
|
7
6
|
from rich.console import Console
|
8
7
|
from rich.layout import Layout
|
@@ -19,7 +18,6 @@ from rich.prompt import Confirm, Prompt
|
|
19
18
|
from rich.table import Table
|
20
19
|
from rich.text import Text
|
21
20
|
from rich.tree import Tree
|
22
|
-
|
23
21
|
from .errors import CrackerjackError, ErrorCode, handle_error
|
24
22
|
|
25
23
|
|
@@ -289,7 +287,6 @@ class InteractiveCLI:
|
|
289
287
|
)
|
290
288
|
total_tasks = len(self.workflow.tasks)
|
291
289
|
progress_task = progress.add_task("Running workflow", total=total_tasks)
|
292
|
-
|
293
290
|
return {
|
294
291
|
"progress": progress,
|
295
292
|
"progress_task": progress_task,
|
@@ -299,14 +296,11 @@ class InteractiveCLI:
|
|
299
296
|
def _execute_workflow_loop(
|
300
297
|
self, layout: Layout, progress_tracker: dict[str, t.Any], live: Live
|
301
298
|
) -> None:
|
302
|
-
"""Execute the main workflow loop."""
|
303
299
|
while not self.workflow.all_tasks_completed():
|
304
300
|
layout["tasks"].update(self.show_task_table())
|
305
301
|
next_task = self.workflow.get_next_task()
|
306
|
-
|
307
302
|
if not next_task:
|
308
303
|
break
|
309
|
-
|
310
304
|
if self._should_execute_task(layout, next_task, live):
|
311
305
|
self._execute_task(layout, next_task, progress_tracker)
|
312
306
|
else:
|
@@ -322,20 +316,16 @@ class InteractiveCLI:
|
|
322
316
|
def _execute_task(
|
323
317
|
self, layout: Layout, task: Task, progress_tracker: dict[str, t.Any]
|
324
318
|
) -> None:
|
325
|
-
"""Execute a single task and update progress."""
|
326
319
|
task.start()
|
327
320
|
layout["details"].update(self.show_task_status(task))
|
328
321
|
time.sleep(1)
|
329
|
-
|
330
322
|
success = self._simulate_task_execution()
|
331
|
-
|
332
323
|
if success:
|
333
324
|
task.complete()
|
334
325
|
progress_tracker["completed_tasks"] += 1
|
335
326
|
else:
|
336
327
|
error = self._create_task_error(task.name)
|
337
328
|
task.fail(error)
|
338
|
-
|
339
329
|
progress_tracker["progress"].update(
|
340
330
|
progress_tracker["progress_task"],
|
341
331
|
completed=progress_tracker["completed_tasks"],
|
@@ -6,7 +6,6 @@ from dataclasses import dataclass
|
|
6
6
|
from enum import Enum
|
7
7
|
from pathlib import Path
|
8
8
|
from unittest.mock import MagicMock, patch
|
9
|
-
|
10
9
|
import pytest
|
11
10
|
from rich.console import Console
|
12
11
|
from crackerjack.crackerjack import (
|
@@ -317,12 +316,10 @@ class TestCrackerjackProcess:
|
|
317
316
|
) -> None:
|
318
317
|
options = options_factory(bump="minor", no_config_updates=True)
|
319
318
|
cj = Crackerjack(dry_run=True)
|
320
|
-
|
321
319
|
with patch("rich.prompt.Confirm.ask", return_value=True) as mock_confirm:
|
322
320
|
with patch.object(Crackerjack, "execute_command") as mock_exec:
|
323
321
|
mock_exec.return_value = MagicMock(returncode=0)
|
324
322
|
cj._bump_version(options)
|
325
|
-
|
326
323
|
mock_confirm.assert_called_once_with(
|
327
324
|
"Are you sure you want to bump the minor version?", default=False
|
328
325
|
)
|
@@ -339,12 +336,10 @@ class TestCrackerjackProcess:
|
|
339
336
|
) -> None:
|
340
337
|
options = options_factory(bump="minor", no_config_updates=True)
|
341
338
|
cj = Crackerjack(dry_run=True)
|
342
|
-
|
343
339
|
with patch("rich.prompt.Confirm.ask", return_value=False) as mock_confirm:
|
344
340
|
with patch.object(Crackerjack, "execute_command") as mock_exec:
|
345
341
|
mock_exec.return_value = MagicMock(returncode=0)
|
346
342
|
cj._bump_version(options)
|
347
|
-
|
348
343
|
mock_confirm.assert_called_once_with(
|
349
344
|
"Are you sure you want to bump the minor version?", default=False
|
350
345
|
)
|
@@ -361,12 +356,10 @@ class TestCrackerjackProcess:
|
|
361
356
|
) -> None:
|
362
357
|
options = options_factory(bump="major", no_config_updates=True)
|
363
358
|
cj = Crackerjack(dry_run=True)
|
364
|
-
|
365
359
|
with patch("rich.prompt.Confirm.ask", return_value=True) as mock_confirm:
|
366
360
|
with patch.object(Crackerjack, "execute_command") as mock_exec:
|
367
361
|
mock_exec.return_value = MagicMock(returncode=0)
|
368
362
|
cj._bump_version(options)
|
369
|
-
|
370
363
|
mock_confirm.assert_called_once_with(
|
371
364
|
"Are you sure you want to bump the major version?", default=False
|
372
365
|
)
|
@@ -383,12 +376,10 @@ class TestCrackerjackProcess:
|
|
383
376
|
) -> None:
|
384
377
|
options = options_factory(bump="micro", no_config_updates=True)
|
385
378
|
cj = Crackerjack(dry_run=True)
|
386
|
-
|
387
379
|
with patch("rich.prompt.Confirm.ask") as mock_confirm:
|
388
380
|
with patch.object(Crackerjack, "execute_command") as mock_exec:
|
389
381
|
mock_exec.return_value = MagicMock(returncode=0)
|
390
382
|
cj._bump_version(options)
|
391
|
-
|
392
383
|
mock_confirm.assert_not_called()
|
393
384
|
mock_exec.assert_called_once_with(["pdm", "bump", "micro"])
|
394
385
|
|
@@ -405,7 +396,6 @@ class TestCrackerjackProcess:
|
|
405
396
|
pytest_command = (_cj := Crackerjack(dry_run=True))._prepare_pytest_command(
|
406
397
|
options
|
407
398
|
)
|
408
|
-
|
409
399
|
assert "--junitxml=test-results.xml" in pytest_command
|
410
400
|
assert "--cov-report=json:coverage.json" in pytest_command
|
411
401
|
assert "--quiet" in pytest_command
|
@@ -427,7 +417,6 @@ class TestCrackerjackProcess:
|
|
427
417
|
pytest_command = (_cj := Crackerjack(dry_run=True))._prepare_pytest_command(
|
428
418
|
options
|
429
419
|
)
|
430
|
-
|
431
420
|
assert "--junitxml=test-results.xml" in pytest_command
|
432
421
|
assert "--cov-report=json:coverage.json" in pytest_command
|
433
422
|
assert "--benchmark-json=benchmark.json" in pytest_command
|
@@ -447,12 +436,10 @@ class TestCrackerjackProcess:
|
|
447
436
|
pytest_command = (_cj := Crackerjack(dry_run=True))._prepare_pytest_command(
|
448
437
|
options
|
449
438
|
)
|
450
|
-
|
451
439
|
assert "--junitxml=test-results.xml" not in pytest_command
|
452
440
|
assert "--cov-report=json:coverage.json" not in pytest_command
|
453
441
|
assert "--benchmark-json=benchmark.json" not in pytest_command
|
454
442
|
assert "--quiet" not in pytest_command
|
455
|
-
|
456
443
|
assert "--capture=fd" in pytest_command
|
457
444
|
assert "--disable-warnings" in pytest_command
|
458
445
|
assert "--durations=0" in pytest_command
|
@@ -943,16 +930,11 @@ class TestCrackerjackProcess:
|
|
943
930
|
options_factory: t.Callable[..., OptionsForTesting],
|
944
931
|
) -> None:
|
945
932
|
mock_project = MagicMock()
|
946
|
-
|
947
933
|
options = options_factory(ai_agent=True)
|
948
934
|
mock_project.options = options
|
949
|
-
|
950
935
|
mock_project.console = MagicMock()
|
951
|
-
|
952
936
|
mock_project.execute_command.return_value = MagicMock(returncode=0)
|
953
|
-
|
954
937
|
ProjectManager.run_pre_commit(mock_project)
|
955
|
-
|
956
938
|
mock_project.execute_command.assert_called_with(
|
957
939
|
["pre-commit", "run", "--all-files", "-c", ".pre-commit-config-ai.yaml"]
|
958
940
|
)
|
@@ -964,24 +946,17 @@ class TestCrackerjackProcess:
|
|
964
946
|
options_factory: t.Callable[..., OptionsForTesting],
|
965
947
|
) -> None:
|
966
948
|
mock_project = MagicMock(spec=ProjectManager)
|
967
|
-
|
968
949
|
options = options_factory(ai_agent=True)
|
969
950
|
mock_project.options = options
|
970
|
-
|
971
951
|
mock_project.console = MagicMock()
|
972
|
-
|
973
952
|
mock_project.config_manager = MagicMock()
|
974
|
-
|
975
953
|
with patch.object(mock_project, "execute_command") as mock_execute:
|
976
954
|
mock_execute.return_value = MagicMock(
|
977
955
|
returncode=0,
|
978
956
|
stdout="package1\npackage2\n",
|
979
957
|
)
|
980
|
-
|
981
958
|
original_method = ProjectManager.update_pkg_configs
|
982
|
-
|
983
959
|
original_method(mock_project)
|
984
|
-
|
985
960
|
mock_execute.assert_any_call(
|
986
961
|
["pre-commit", "install", "-c", ".pre-commit-config-ai.yaml"]
|
987
962
|
)
|
@@ -1091,6 +1066,7 @@ class TestCrackerjackProcess:
|
|
1091
1066
|
mock_clean_file.assert_called_once_with(py_file)
|
1092
1067
|
|
1093
1068
|
def test_code_cleaner_remove_docstrings(self) -> None:
|
1069
|
+
import ast
|
1094
1070
|
from rich.console import Console
|
1095
1071
|
from crackerjack.crackerjack import CodeCleaner
|
1096
1072
|
|
@@ -1108,6 +1084,43 @@ class TestCrackerjackProcess:
|
|
1108
1084
|
assert "This is a multi-line docstring." not in cleaned_code, (
|
1109
1085
|
f"Got: {cleaned_code!r}"
|
1110
1086
|
)
|
1087
|
+
try:
|
1088
|
+
ast.parse(cleaned_code)
|
1089
|
+
except SyntaxError as e:
|
1090
|
+
raise AssertionError(
|
1091
|
+
f"Cleaned code is not valid Python syntax: {e}\nCode: {cleaned_code!r}"
|
1092
|
+
)
|
1093
|
+
|
1094
|
+
def test_code_cleaner_remove_docstrings_empty_functions(self) -> None:
|
1095
|
+
import ast
|
1096
|
+
from rich.console import Console
|
1097
|
+
from crackerjack.crackerjack import CodeCleaner
|
1098
|
+
|
1099
|
+
code_cleaner = CodeCleaner(console=Console())
|
1100
|
+
test_code = """
|
1101
|
+
def empty_function():
|
1102
|
+
pass
|
1103
|
+
class TestClass:
|
1104
|
+
def method_with_docstring_only(self):
|
1105
|
+
pass
|
1106
|
+
def method_with_code(self):
|
1107
|
+
return True
|
1108
|
+
"""
|
1109
|
+
cleaned_code = code_cleaner.remove_docstrings(test_code)
|
1110
|
+
print(f"Cleaned code: {cleaned_code!r}")
|
1111
|
+
assert '"""This function has only a docstring."""' not in cleaned_code
|
1112
|
+
assert '"""Class docstring."""' not in cleaned_code
|
1113
|
+
assert '"""Method with only docstring."""' not in cleaned_code
|
1114
|
+
assert '"""This method has code after docstring."""' not in cleaned_code
|
1115
|
+
assert "def empty_function():\n pass" in cleaned_code
|
1116
|
+
assert "def method_with_docstring_only(self):\n pass" in cleaned_code
|
1117
|
+
assert "def method_with_code(self):\n return True" in cleaned_code
|
1118
|
+
try:
|
1119
|
+
ast.parse(cleaned_code)
|
1120
|
+
except SyntaxError as e:
|
1121
|
+
raise AssertionError(
|
1122
|
+
f"Cleaned code is not valid Python syntax: {e}\nCode: {cleaned_code!r}"
|
1123
|
+
)
|
1111
1124
|
|
1112
1125
|
def test_code_cleaner_remove_line_comments(self) -> None:
|
1113
1126
|
from pathlib import Path
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import typing as t
|
2
2
|
from unittest.mock import MagicMock, patch
|
3
|
-
|
4
3
|
import pytest
|
5
4
|
from typer.testing import CliRunner
|
6
5
|
from crackerjack.__main__ import BumpOption, Options, app
|
@@ -59,7 +58,6 @@ def test_interactive_option(
|
|
59
58
|
assert result.exit_code == 0
|
60
59
|
mock_interactive.assert_called_once()
|
61
60
|
mock_crackerjack_process.process.assert_not_called()
|
62
|
-
|
63
61
|
mock_interactive.reset_mock()
|
64
62
|
result = runner.invoke(app, ["--interactive"])
|
65
63
|
assert result.exit_code == 0
|
@@ -203,7 +201,6 @@ def test_multiple_options(
|
|
203
201
|
assert result.exit_code == 0
|
204
202
|
mock_interactive.assert_called_once()
|
205
203
|
mock_crackerjack_process.process.assert_not_called()
|
206
|
-
|
207
204
|
result = runner.invoke(app, ["-c", "-d", "-t", "-x"])
|
208
205
|
assert result.exit_code == 0
|
209
206
|
mock_crackerjack_process.process.assert_called_once()
|
@@ -0,0 +1,163 @@
|
|
1
|
+
"""Test cases for multiline function definitions in docstring removal."""
|
2
|
+
|
3
|
+
import ast
|
4
|
+
from rich.console import Console
|
5
|
+
from crackerjack.crackerjack import CodeCleaner
|
6
|
+
|
7
|
+
|
8
|
+
class TestMultilineFunctions:
|
9
|
+
def setup_method(self) -> None:
|
10
|
+
self.console = Console()
|
11
|
+
self.cleaner = CodeCleaner(console=self.console)
|
12
|
+
|
13
|
+
def test_multiline_async_function_with_docstring(self) -> None:
|
14
|
+
code = """class BackgroundTaskManager:
|
15
|
+
async def __aexit__(
|
16
|
+
self,
|
17
|
+
exc_type: type[BaseException] | None,
|
18
|
+
exc_val: BaseException | None,
|
19
|
+
exc_tb: t.Any | None,
|
20
|
+
) -> None:
|
21
|
+
del exc_type, exc_val, exc_tb
|
22
|
+
self.logger.debug("Background task manager shutting down")
|
23
|
+
await wait_for_background_tasks(self.cleanup_timeout)
|
24
|
+
"""
|
25
|
+
result = self.cleaner.remove_docstrings(code)
|
26
|
+
ast.parse(result)
|
27
|
+
assert '"""Clean up background tasks on exit."""' not in result
|
28
|
+
assert "del exc_type, exc_val, exc_tb" in result
|
29
|
+
assert "self.logger.debug" in result
|
30
|
+
|
31
|
+
def test_multiline_function_with_docstring(self) -> None:
|
32
|
+
code = '''def complex_function(
|
33
|
+
param1: str,
|
34
|
+
param2: int,
|
35
|
+
param3: dict[str, Any],
|
36
|
+
) -> bool:
|
37
|
+
"""This is a complex function with multiple parameters."""
|
38
|
+
return True
|
39
|
+
'''
|
40
|
+
result = self.cleaner.remove_docstrings(code)
|
41
|
+
ast.parse(result)
|
42
|
+
assert (
|
43
|
+
'"""This is a complex function with multiple parameters."""' not in result
|
44
|
+
)
|
45
|
+
assert "return True" in result
|
46
|
+
|
47
|
+
def test_multiline_function_no_docstring(self) -> None:
|
48
|
+
code = """def complex_function(
|
49
|
+
param1: str,
|
50
|
+
param2: int,
|
51
|
+
param3: dict[str, Any],
|
52
|
+
) -> bool:
|
53
|
+
return True
|
54
|
+
"""
|
55
|
+
result = self.cleaner.remove_docstrings(code)
|
56
|
+
ast.parse(result)
|
57
|
+
assert "return True" in result
|
58
|
+
assert "def complex_function(" in result
|
59
|
+
|
60
|
+
def test_nested_multiline_functions(self) -> None:
|
61
|
+
code = """class TestClass:
|
62
|
+
def outer_method(
|
63
|
+
self,
|
64
|
+
param: str,
|
65
|
+
) -> None:
|
66
|
+
def inner_function(
|
67
|
+
x: int,
|
68
|
+
y: int,
|
69
|
+
) -> int:
|
70
|
+
return x + y
|
71
|
+
return inner_function(1, 2)
|
72
|
+
"""
|
73
|
+
result = self.cleaner.remove_docstrings(code)
|
74
|
+
ast.parse(result)
|
75
|
+
assert '"""Outer method docstring."""' not in result
|
76
|
+
assert '"""Inner function docstring."""' not in result
|
77
|
+
assert "return x + y" in result
|
78
|
+
assert "return inner_function(1, 2)" in result
|
79
|
+
|
80
|
+
def test_multiline_function_with_decorators(self) -> None:
|
81
|
+
code = """class TestClass:
|
82
|
+
@property
|
83
|
+
@some_decorator(
|
84
|
+
param1="value1",
|
85
|
+
param2="value2"
|
86
|
+
)
|
87
|
+
def complex_property(
|
88
|
+
self,
|
89
|
+
) -> str:
|
90
|
+
return "test"
|
91
|
+
"""
|
92
|
+
result = self.cleaner.remove_docstrings(code)
|
93
|
+
ast.parse(result)
|
94
|
+
assert (
|
95
|
+
'"""Property with complex decorator and multiline signature."""'
|
96
|
+
not in result
|
97
|
+
)
|
98
|
+
assert "@property" in result
|
99
|
+
assert "@some_decorator" in result
|
100
|
+
assert 'return "test"' in result
|
101
|
+
|
102
|
+
def test_class_with_multiline_init(self) -> None:
|
103
|
+
code = '''class ComplexClass:
|
104
|
+
"""Class docstring."""
|
105
|
+
def __init__(
|
106
|
+
self,
|
107
|
+
param1: str,
|
108
|
+
param2: int = 42,
|
109
|
+
param3: Optional[dict] = None,
|
110
|
+
) -> None:
|
111
|
+
self.param1 = param1
|
112
|
+
self.param2 = param2
|
113
|
+
self.param3 = param3 or {}
|
114
|
+
'''
|
115
|
+
result = self.cleaner.remove_docstrings(code)
|
116
|
+
ast.parse(result)
|
117
|
+
assert '"""Class docstring."""' not in result
|
118
|
+
assert '"""Initialize the complex class."""' not in result
|
119
|
+
assert "self.param1 = param1" in result
|
120
|
+
assert "self.param2 = param2" in result
|
121
|
+
assert "self.param3 = param3 or {}" in result
|
122
|
+
|
123
|
+
def test_multiline_function_empty_body_gets_pass(self) -> None:
|
124
|
+
code = '''def empty_function(
|
125
|
+
param1: str,
|
126
|
+
param2: int,
|
127
|
+
) -> None:
|
128
|
+
"""This function only has a docstring."""
|
129
|
+
'''
|
130
|
+
result = self.cleaner.remove_docstrings(code)
|
131
|
+
ast.parse(result)
|
132
|
+
assert '"""This function only has a docstring."""' not in result
|
133
|
+
assert "pass" in result
|
134
|
+
|
135
|
+
def test_complex_real_world_example(self) -> None:
|
136
|
+
code = '''class BackgroundTaskManager:
|
137
|
+
"""Manage background tasks."""
|
138
|
+
def __init__(self, cleanup_timeout: float = 30.0) -> None:
|
139
|
+
self.cleanup_timeout = cleanup_timeout
|
140
|
+
self.logger = depends.get(Logger)
|
141
|
+
async def __aenter__(self) -> "BackgroundTaskManager":
|
142
|
+
self.logger.debug("Background task manager started")
|
143
|
+
return self
|
144
|
+
async def __aexit__(
|
145
|
+
self,
|
146
|
+
exc_type: type[BaseException] | None,
|
147
|
+
exc_val: BaseException | None,
|
148
|
+
exc_tb: t.Any | None,
|
149
|
+
) -> None:
|
150
|
+
del exc_type, exc_val, exc_tb
|
151
|
+
self.logger.debug("Background task manager shutting down")
|
152
|
+
await wait_for_background_tasks(self.cleanup_timeout)
|
153
|
+
'''
|
154
|
+
result = self.cleaner.remove_docstrings(code)
|
155
|
+
ast.parse(result)
|
156
|
+
assert '"""Manage background tasks."""' not in result
|
157
|
+
assert '"""Initialize the background task manager."""' not in result
|
158
|
+
assert '"""Enter async context."""' not in result
|
159
|
+
assert '"""Clean up background tasks on exit."""' not in result
|
160
|
+
assert "self.cleanup_timeout = cleanup_timeout" in result
|
161
|
+
assert 'self.logger.debug("Background task manager started")' in result
|
162
|
+
assert "del exc_type, exc_val, exc_tb" in result
|
163
|
+
assert "await wait_for_background_tasks" in result
|
Binary file
|
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
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.11/3256171999636029978
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.4/10355199064880463147
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.6/15140459877605758699
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.1.9/17041001205004563469
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.11/18187162184424859798
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.12/16869036553936192448
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.12/1867267426380906393
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.12/4240757255861806333
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.12/4441409093023629623
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.13/1867267426380906393
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.13/4240757255861806333
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.2/4070660268492669020
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.3/9818742842212983150
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.4/9818742842212983150
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.6/3557596832929915217
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.7/10386934055395314831
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.11.7/3557596832929915217
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.12.0/5056746222905752453
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.2.0/10047773857155985907
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.2.2/18053836298936336950
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.3.0/12548816621480535786
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.3.3/11081883392474770722
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.3.5/16311176246009842383
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.6.0/11982804814124138945
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.6.0/12055761203849489982
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.7.3/16061516852537040135
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.8.4/16354268377385700367
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.9.10/12813592349865671909
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.9.3/13948373885254993391
RENAMED
File without changes
|
{crackerjack-0.21.5 → crackerjack-0.21.7}/crackerjack/.ruff_cache/0.9.9/12813592349865671909
RENAMED
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
|