crackerjack 0.20.13__tar.gz → 0.20.15__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.
Files changed (86) hide show
  1. {crackerjack-0.20.13 → crackerjack-0.20.15}/PKG-INFO +2 -1
  2. crackerjack-0.20.15/crackerjack/.ruff_cache/0.11.13/1867267426380906393 +0 -0
  3. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/__init__.py +1 -0
  4. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/__main__.py +1 -0
  5. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/crackerjack.py +49 -25
  6. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/errors.py +1 -0
  7. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/interactive.py +16 -25
  8. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/py313.py +1 -1
  9. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/pyproject.toml +2 -1
  10. {crackerjack-0.20.13 → crackerjack-0.20.15}/pyproject.toml +2 -1
  11. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/conftest.py +2 -1
  12. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_crackerjack.py +38 -17
  13. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_crackerjack_runner.py +3 -4
  14. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_errors.py +7 -12
  15. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_interactive.py +1 -0
  16. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_interactive_run.py +10 -15
  17. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_main.py +1 -0
  18. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_py313_advanced.py +1 -0
  19. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_py313_features.py +1 -0
  20. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_pytest_features.py +1 -0
  21. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/test_structured_errors.py +1 -0
  22. crackerjack-0.20.13/crackerjack/.ruff_cache/0.11.13/1867267426380906393 +0 -0
  23. {crackerjack-0.20.13 → crackerjack-0.20.15}/LICENSE +0 -0
  24. {crackerjack-0.20.13 → crackerjack-0.20.15}/README.md +0 -0
  25. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.gitignore +0 -0
  26. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.libcst.codemod.yaml +0 -0
  27. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.pdm.toml +0 -0
  28. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.pre-commit-config.yaml +0 -0
  29. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.pytest_cache/.gitignore +0 -0
  30. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.pytest_cache/CACHEDIR.TAG +0 -0
  31. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.pytest_cache/README.md +0 -0
  32. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.pytest_cache/v/cache/nodeids +0 -0
  33. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.pytest_cache/v/cache/stepwise +0 -0
  34. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/.gitignore +0 -0
  35. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
  36. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
  37. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
  38. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
  39. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
  40. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
  41. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.11/18187162184424859798 +0 -0
  42. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.12/16869036553936192448 +0 -0
  43. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.12/1867267426380906393 +0 -0
  44. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.12/4240757255861806333 +0 -0
  45. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.12/4441409093023629623 +0 -0
  46. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.13/4240757255861806333 +0 -0
  47. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
  48. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
  49. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
  50. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
  51. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
  52. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
  53. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
  54. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
  55. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
  56. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
  57. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
  58. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
  59. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
  60. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
  61. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
  62. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
  63. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
  64. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
  65. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
  66. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
  67. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
  68. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
  69. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
  70. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
  71. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
  72. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
  73. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
  74. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
  75. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
  76. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
  77. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
  78. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
  79. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
  80. {crackerjack-0.20.13 → crackerjack-0.20.15}/crackerjack/.ruff_cache/CACHEDIR.TAG +0 -0
  81. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/TESTING.md +0 -0
  82. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/__init__.py +0 -0
  83. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/data/comments_sample.txt +0 -0
  84. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/data/docstrings_sample.txt +0 -0
  85. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/data/expected_comments_sample.txt +0 -0
  86. {crackerjack-0.20.13 → crackerjack-0.20.15}/tests/data/init.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crackerjack
3
- Version: 0.20.13
3
+ Version: 0.20.15
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>
@@ -35,6 +35,7 @@ Requires-Dist: pytest-cov>=6.1.1
35
35
  Requires-Dist: pytest-mock>=3.14.1
36
36
  Requires-Dist: pytest-timeout>=2.4
37
37
  Requires-Dist: pytest-xdist>=3.7
38
+ Requires-Dist: pyyaml>=6.0.2
38
39
  Requires-Dist: rich>=14
39
40
  Requires-Dist: tomli-w>=1.2
40
41
  Requires-Dist: typer>=0.16
@@ -1,4 +1,5 @@
1
1
  import typing as t
2
+
2
3
  from .crackerjack import Crackerjack, create_crackerjack_runner
3
4
  from .errors import (
4
5
  CleaningError,
@@ -1,4 +1,5 @@
1
1
  from enum import Enum
2
+
2
3
  import typer
3
4
  from pydantic import BaseModel, field_validator
4
5
  from rich.console import Console
@@ -1,4 +1,3 @@
1
- import ast
2
1
  import re
3
2
  import subprocess
4
3
  import typing as t
@@ -7,6 +6,7 @@ from pathlib import Path
7
6
  from subprocess import CompletedProcess
8
7
  from subprocess import run as execute
9
8
  from tomllib import loads
9
+
10
10
  from pydantic import BaseModel
11
11
  from rich.console import Console
12
12
  from tomli_w import dumps
@@ -73,8 +73,8 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
73
73
  def clean_file(self, file_path: Path) -> None:
74
74
  try:
75
75
  code = file_path.read_text()
76
- code = self.remove_docstrings(code)
77
76
  code = self.remove_line_comments(code)
77
+ code = self.remove_docstrings(code)
78
78
  code = self.remove_extra_whitespace(code)
79
79
  code = self.reformat_code(code)
80
80
  file_path.write_text(code)
@@ -83,18 +83,44 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
83
83
  print(f"Error cleaning {file_path}: {e}")
84
84
 
85
85
  def remove_docstrings(self, code: str) -> str:
86
- tree = ast.parse(code)
87
- for node in ast.walk(tree):
88
- if isinstance(
89
- node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.Module
90
- ):
91
- if ast.get_docstring(node):
92
- node.body = (
93
- node.body[1:]
94
- if isinstance(node.body[0], ast.Expr)
95
- else node.body
96
- )
97
- return ast.unparse(tree)
86
+ lines = code.split("\n")
87
+ cleaned_lines = []
88
+ in_docstring = False
89
+ docstring_delimiter = None
90
+ waiting_for_docstring = False
91
+ for line in lines:
92
+ stripped = line.strip()
93
+ if stripped.startswith(("def ", "class ", "async def ")):
94
+ waiting_for_docstring = True
95
+ cleaned_lines.append(line)
96
+ continue
97
+ if waiting_for_docstring and stripped:
98
+ if stripped.startswith(('"""', "'''", '"', "'")):
99
+ if stripped.startswith(('"""', "'''")):
100
+ docstring_delimiter = stripped[:3]
101
+ else:
102
+ docstring_delimiter = stripped[0]
103
+ if stripped.endswith(docstring_delimiter) and len(stripped) > len(
104
+ docstring_delimiter
105
+ ):
106
+ waiting_for_docstring = False
107
+ continue
108
+ else:
109
+ in_docstring = True
110
+ waiting_for_docstring = False
111
+ continue
112
+ else:
113
+ waiting_for_docstring = False
114
+ if in_docstring:
115
+ if docstring_delimiter and stripped.endswith(docstring_delimiter):
116
+ in_docstring = False
117
+ docstring_delimiter = None
118
+ continue
119
+ else:
120
+ continue
121
+ cleaned_lines.append(line)
122
+
123
+ return "\n".join(cleaned_lines)
98
124
 
99
125
  def remove_line_comments(self, code: str) -> str:
100
126
  lines = code.split("\n")
@@ -119,7 +145,7 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
119
145
  elif char == "#" and in_string is None:
120
146
  comment = line[i:].strip()
121
147
  if re.match(
122
- "^#\\s*(?:type:\\s*ignore|noqa|nosec|pragma:\\s*no\\s*cover|pylint:\\s*disable|mypy:\\s*ignore)",
148
+ r"^#\s*(?:type:\s*ignore(?:\[.*?\])?|noqa|nosec|pragma:\s*no\s*cover|pylint:\s*disable|mypy:\s*ignore)",
123
149
  comment,
124
150
  ):
125
151
  result.append(line[i:])
@@ -167,16 +193,14 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
167
193
  or (
168
194
  next_line.startswith("#")
169
195
  and any(
170
- (
171
- pattern in next_line
172
- for pattern in (
173
- "type:",
174
- "noqa",
175
- "nosec",
176
- "pragma:",
177
- "pylint:",
178
- "mypy:",
179
- )
196
+ pattern in next_line
197
+ for pattern in (
198
+ "type:",
199
+ "noqa",
200
+ "nosec",
201
+ "pragma:",
202
+ "pylint:",
203
+ "mypy:",
180
204
  )
181
205
  )
182
206
  )
@@ -2,6 +2,7 @@ import sys
2
2
  import typing as t
3
3
  from enum import Enum
4
4
  from pathlib import Path
5
+
5
6
  from rich.console import Console
6
7
  from rich.panel import Panel
7
8
 
@@ -2,6 +2,7 @@ import time
2
2
  import typing as t
3
3
  from enum import Enum, auto
4
4
  from pathlib import Path
5
+
5
6
  from rich.box import ROUNDED
6
7
  from rich.console import Console
7
8
  from rich.layout import Layout
@@ -18,6 +19,7 @@ from rich.prompt import Confirm, Prompt
18
19
  from rich.table import Table
19
20
  from rich.text import Text
20
21
  from rich.tree import Tree
22
+
21
23
  from .errors import CrackerjackError, ErrorCode, handle_error
22
24
 
23
25
 
@@ -66,10 +68,8 @@ class Task:
66
68
 
67
69
  def can_run(self) -> bool:
68
70
  return all(
69
- (
70
- dep.status in (TaskStatus.SUCCESS, TaskStatus.SKIPPED)
71
- for dep in self.dependencies
72
- )
71
+ dep.status in (TaskStatus.SUCCESS, TaskStatus.SKIPPED)
72
+ for dep in self.dependencies
73
73
  )
74
74
 
75
75
  def __str__(self) -> str:
@@ -107,11 +107,8 @@ class WorkflowManager:
107
107
 
108
108
  def all_tasks_completed(self) -> bool:
109
109
  return all(
110
- (
111
- task.status
112
- in (TaskStatus.SUCCESS, TaskStatus.FAILED, TaskStatus.SKIPPED)
113
- for task in self.tasks.values()
114
- )
110
+ task.status in (TaskStatus.SUCCESS, TaskStatus.FAILED, TaskStatus.SKIPPED)
111
+ for task in self.tasks.values()
115
112
  )
116
113
 
117
114
  def run_task(self, task: Task, func: t.Callable[[], t.Any]) -> bool:
@@ -257,7 +254,7 @@ class InteractiveCLI:
257
254
  status = "[grey]⏸️ Pending[/grey]"
258
255
  duration = task.duration
259
256
  duration_text = f"{duration:.2f}s" if duration else "-"
260
- deps = ", ".join((dep.name for dep in task.dependencies)) or "-"
257
+ deps = ", ".join(dep.name for dep in task.dependencies) or "-"
261
258
  table.add_row(task.name, status, duration_text, deps)
262
259
  return table
263
260
 
@@ -317,25 +314,19 @@ class InteractiveCLI:
317
314
  layout["details"].update(self.show_task_status(next_task))
318
315
  layout["tasks"].update(self.show_task_table())
319
316
  successful = sum(
320
- (
321
- 1
322
- for task in self.workflow.tasks.values()
323
- if task.status == TaskStatus.SUCCESS
324
- )
317
+ 1
318
+ for task in self.workflow.tasks.values()
319
+ if task.status == TaskStatus.SUCCESS
325
320
  )
326
321
  failed = sum(
327
- (
328
- 1
329
- for task in self.workflow.tasks.values()
330
- if task.status == TaskStatus.FAILED
331
- )
322
+ 1
323
+ for task in self.workflow.tasks.values()
324
+ if task.status == TaskStatus.FAILED
332
325
  )
333
326
  skipped = sum(
334
- (
335
- 1
336
- for task in self.workflow.tasks.values()
337
- if task.status == TaskStatus.SKIPPED
338
- )
327
+ 1
328
+ for task in self.workflow.tasks.values()
329
+ if task.status == TaskStatus.SKIPPED
339
330
  )
340
331
  summary = Panel(
341
332
  f"Workflow completed!\n\n[green]✅ Successful tasks: {successful}[/green]\n[red]❌ Failed tasks: {failed}[/red]\n[blue]⏩ Skipped tasks: {skipped}[/blue]",
@@ -168,7 +168,7 @@ def clean_python_code(code: str) -> str:
168
168
  continue
169
169
  case s if "#" in s and (
170
170
  not any(
171
- (skip in s for skip in ("# noqa", "# type:", "# pragma", "# skip"))
171
+ skip in s for skip in ("# noqa", "# type:", "# pragma", "# skip")
172
172
  )
173
173
  ):
174
174
  code_part = line.split("#", 1)[0].rstrip()
@@ -4,7 +4,7 @@ requires = [ "pdm-backend" ]
4
4
 
5
5
  [project]
6
6
  name = "crackerjack"
7
- version = "0.20.12"
7
+ version = "0.20.14"
8
8
  description = "Crackerjack: code quality toolkit"
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -54,6 +54,7 @@ dependencies = [
54
54
  "pytest-mock>=3.14.1",
55
55
  "pytest-timeout>=2.4",
56
56
  "pytest-xdist>=3.7",
57
+ "pyyaml>=6.0.2",
57
58
  "rich>=14",
58
59
  "tomli-w>=1.2",
59
60
  "typer>=0.16",
@@ -6,7 +6,7 @@ requires = [
6
6
 
7
7
  [project]
8
8
  name = "crackerjack"
9
- version = "0.20.13"
9
+ version = "0.20.15"
10
10
  description = "Crackerjack: code quality toolkit"
11
11
  readme = "README.md"
12
12
  keywords = [
@@ -54,6 +54,7 @@ dependencies = [
54
54
  "pytest-mock>=3.14.1",
55
55
  "pytest-timeout>=2.4",
56
56
  "pytest-xdist>=3.7",
57
+ "pyyaml>=6.0.2",
57
58
  "rich>=14",
58
59
  "tomli-w>=1.2",
59
60
  "typer>=0.16",
@@ -2,6 +2,7 @@ import os
2
2
  import time
3
3
  import typing as t
4
4
  from pathlib import Path
5
+
5
6
  import pytest
6
7
  from pytest import Config, Item, Parser
7
8
 
@@ -42,7 +43,7 @@ def pytest_addoption(parser: Parser) -> None:
42
43
 
43
44
  def pytest_collection_modifyitems(config: Config, items: list[Item]) -> None:
44
45
  benchmark_mode = t.cast(bool, config.getoption("--benchmark"))
45
- has_benchmark_tests = any((item.get_closest_marker("benchmark") for item in items))
46
+ has_benchmark_tests = any(item.get_closest_marker("benchmark") for item in items)
46
47
  if benchmark_mode or has_benchmark_tests:
47
48
  has_worker = hasattr(config, "workerinput")
48
49
  try:
@@ -6,6 +6,7 @@ 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
+
9
10
  import pytest
10
11
  from rich.console import Console
11
12
  from crackerjack.crackerjack import (
@@ -229,12 +230,12 @@ class TestCrackerjackProcess:
229
230
  cj.process(options)
230
231
  mock_run_tests.assert_called_once_with(options)
231
232
  console_print_calls = [str(call) for call in mock_console_print.call_args_list]
232
- assert any(("Running tests" in call for call in console_print_calls)), (
233
+ assert any("Running tests" in call for call in console_print_calls), (
233
234
  "Expected 'Running tests' message was not printed"
234
235
  )
235
- assert any(
236
- ("Skipping config updates" in call for call in console_print_calls)
237
- ), "Expected 'Skipping config updates' message was not printed"
236
+ assert any("Skipping config updates" in call for call in console_print_calls), (
237
+ "Expected 'Skipping config updates' message was not printed"
238
+ )
238
239
 
239
240
  def test_process_with_skip_hooks_option(
240
241
  self,
@@ -264,11 +265,11 @@ class TestCrackerjackProcess:
264
265
  mock_run_pre_commit.assert_not_called()
265
266
  mock_run_tests.assert_called_once_with(options)
266
267
  console_print_calls = [str(call) for call in mock_console_print.call_args_list]
267
- assert any(("Running tests" in call for call in console_print_calls)), (
268
+ assert any("Running tests" in call for call in console_print_calls), (
268
269
  "Expected 'Running tests' message was not printed"
269
270
  )
270
271
  assert any(
271
- ("Skipping pre-commit hooks" in call for call in console_print_calls)
272
+ "Skipping pre-commit hooks" in call for call in console_print_calls
272
273
  ), "Expected 'Skipping pre-commit hooks' message was not printed"
273
274
 
274
275
  def test_process_with_bump_option(
@@ -296,9 +297,9 @@ class TestCrackerjackProcess:
296
297
  cj.process(options)
297
298
  mock_cj_execute.assert_any_call(["pdm", "bump", "minor"])
298
299
  console_print_calls = [str(call) for call in mock_console_print.call_args_list]
299
- assert any(
300
- ("Skipping config updates" in call for call in console_print_calls)
301
- ), "Expected 'Skipping config updates' message was not printed"
300
+ assert any("Skipping config updates" in call for call in console_print_calls), (
301
+ "Expected 'Skipping config updates' message was not printed"
302
+ )
302
303
 
303
304
  def test_process_with_publish_option(
304
305
  self,
@@ -704,10 +705,8 @@ class TestCrackerjackProcess:
704
705
  assert ["pdm", "build"] in actual_calls
705
706
  assert ["pdm", "publish", "--no-build"] in actual_calls
706
707
  assert any(
707
- (
708
- "build output" in str(call)
709
- for call in mock_print.mock_calls
710
- )
708
+ "build output" in str(call)
709
+ for call in mock_print.mock_calls
711
710
  )
712
711
 
713
712
  def test_process_with_commit_input(
@@ -760,10 +759,8 @@ class TestCrackerjackProcess:
760
759
  cj.process(options)
761
760
  mock_update_configs.assert_called_once()
762
761
  assert any(
763
- (
764
- "PDM installation failed" in str(call)
765
- for call in mock_console_print.mock_calls
766
- )
762
+ "PDM installation failed" in str(call)
763
+ for call in mock_console_print.mock_calls
767
764
  )
768
765
 
769
766
  def test_process_with_crackerjack_project(
@@ -1062,6 +1059,30 @@ class TestCrackerjackProcess:
1062
1059
  f"Cleaned code does not match expected.\nExpected:\n{expected_cleaned}\nGot:\n{cleaned_code}"
1063
1060
  )
1064
1061
 
1062
+ def test_code_cleaner_preserve_special_comments(self) -> None:
1063
+ from rich.console import Console
1064
+ from crackerjack.crackerjack import CodeCleaner
1065
+
1066
+ code_cleaner = CodeCleaner(console=Console())
1067
+ test_code = "def test_func():\n x = 1 # type: ignore\n y = 2 # noqa\n z = 3 # nosec\n a = 4 # type: ignore[arg-type]\n b = 5 # noqa: E501\n c = 6 # nosec: B101\n d = 7 # pragma: no cover\n e = 8 # pylint: disable=line-too-long\n f = 9 # mypy: ignore\n g = 10 # regular comment that should be removed\n h = 11 #type:ignore\n i = 12#noqa\n j = 13 #nosec\n return x + y + z"
1068
+ expected_code = "def test_func():\n x = 1 # type: ignore\n y = 2 # noqa\n z = 3 # nosec\n a = 4 # type: ignore[arg-type]\n b = 5 # noqa: E501\n c = 6 # nosec: B101\n d = 7 # pragma: no cover\n e = 8 # pylint: disable=line-too-long\n f = 9 # mypy: ignore\n g = 10\n h = 11 #type:ignore\n i = 12#noqa\n j = 13 #nosec\n return x + y + z"
1069
+ cleaned_code = code_cleaner.remove_line_comments(test_code)
1070
+ assert cleaned_code == expected_code, (
1071
+ f"Special comments not preserved correctly.\nExpected:\n{expected_code}\nGot:\n{cleaned_code}"
1072
+ )
1073
+
1074
+ def test_code_cleaner_special_comments_in_strings(self) -> None:
1075
+ from rich.console import Console
1076
+ from crackerjack.crackerjack import CodeCleaner
1077
+
1078
+ code_cleaner = CodeCleaner(console=Console())
1079
+ test_code = 'def test_func():\n s1 = "# type: ignore" # should keep comment outside string but preserve string\n s2 = \'# noqa\' # type: ignore\n s3 = "test" # regular comment should be removed\n return s1 + s2'
1080
+ expected_code = 'def test_func():\n s1 = "# type: ignore"\n s2 = \'# noqa\' # type: ignore\n s3 = "test"\n return s1 + s2'
1081
+ cleaned_code = code_cleaner.remove_line_comments(test_code)
1082
+ assert cleaned_code == expected_code, (
1083
+ f"String handling with special comments failed.\nExpected:\n{expected_code}\nGot:\n{cleaned_code}"
1084
+ )
1085
+
1065
1086
  def test_code_cleaner_remove_extra_whitespace(self) -> None:
1066
1087
  from rich.console import Console
1067
1088
  from crackerjack.crackerjack import CodeCleaner
@@ -2,6 +2,7 @@ import tempfile
2
2
  import typing as t
3
3
  from pathlib import Path
4
4
  from unittest.mock import MagicMock, patch
5
+
5
6
  import pytest
6
7
  from rich.console import Console
7
8
  from crackerjack import create_crackerjack_runner
@@ -129,8 +130,6 @@ def test_process_with_test_and_skip_hooks_options(
129
130
  mock_run_tests.assert_called_once_with(options)
130
131
  mocks["project_manager"].run_pre_commit.assert_not_called()
131
132
  assert any(
132
- (
133
- "Skipping pre-commit hooks" in str(call)
134
- for call in mocks["console"].print.call_args_list
135
- )
133
+ "Skipping pre-commit hooks" in str(call)
134
+ for call in mocks["console"].print.call_args_list
136
135
  )
@@ -1,5 +1,6 @@
1
1
  import typing as t
2
2
  from unittest.mock import MagicMock, patch
3
+
3
4
  import pytest
4
5
  from rich.console import Console
5
6
  from crackerjack.errors import (
@@ -125,19 +126,15 @@ class TestErrorCodes:
125
126
  assert "CONFIG" in code.name
126
127
  elif 2000 <= value < 3000:
127
128
  assert any(
128
- (
129
- prefix in code.name
130
- for prefix in ("COMMAND", "EXTERNAL", "PDM", "PRE_COMMIT")
131
- )
129
+ prefix in code.name
130
+ for prefix in ("COMMAND", "EXTERNAL", "PDM", "PRE_COMMIT")
132
131
  )
133
132
  elif 3000 <= value < 4000:
134
133
  assert "TEST" in code.name or "BENCHMARK" in code.name
135
134
  elif 4000 <= value < 5000:
136
135
  assert any(
137
- (
138
- prefix in code.name
139
- for prefix in ("BUILD", "PUBLISH", "VERSION", "AUTHENTICATION")
140
- )
136
+ prefix in code.name
137
+ for prefix in ("BUILD", "PUBLISH", "VERSION", "AUTHENTICATION")
141
138
  )
142
139
  elif 5000 <= value < 6000:
143
140
  assert (
@@ -151,8 +148,6 @@ class TestErrorCodes:
151
148
  assert "CODE" in code.name or "FORMATTING" in code.name
152
149
  elif 9000 <= value < 10000:
153
150
  assert any(
154
- (
155
- prefix in code.name
156
- for prefix in ("UNKNOWN", "NOT_IMPLEMENTED", "UNEXPECTED")
157
- )
151
+ prefix in code.name
152
+ for prefix in ("UNKNOWN", "NOT_IMPLEMENTED", "UNEXPECTED")
158
153
  )
@@ -2,6 +2,7 @@ import io
2
2
  import time
3
3
  from pathlib import Path
4
4
  from unittest.mock import MagicMock, patch
5
+
5
6
  import pytest
6
7
  from rich.console import Console
7
8
  from rich.panel import Panel
@@ -1,4 +1,5 @@
1
1
  from unittest.mock import MagicMock, patch
2
+
2
3
  import pytest
3
4
  from rich.console import Console
4
5
  from crackerjack.errors import ErrorCode, ExecutionError
@@ -123,31 +124,25 @@ class TestInteractiveRun:
123
124
  assert len(cli.workflow.tasks) == 3
124
125
  assert (
125
126
  sum(
126
- (
127
- 1
128
- for t in cli.workflow.tasks.values()
129
- if t.status == TaskStatus.SUCCESS
130
- )
127
+ 1
128
+ for t in cli.workflow.tasks.values()
129
+ if t.status == TaskStatus.SUCCESS
131
130
  )
132
131
  == 1
133
132
  )
134
133
  assert (
135
134
  sum(
136
- (
137
- 1
138
- for t in cli.workflow.tasks.values()
139
- if t.status == TaskStatus.FAILED
140
- )
135
+ 1
136
+ for t in cli.workflow.tasks.values()
137
+ if t.status == TaskStatus.FAILED
141
138
  )
142
139
  == 1
143
140
  )
144
141
  assert (
145
142
  sum(
146
- (
147
- 1
148
- for t in cli.workflow.tasks.values()
149
- if t.status == TaskStatus.SKIPPED
150
- )
143
+ 1
144
+ for t in cli.workflow.tasks.values()
145
+ if t.status == TaskStatus.SKIPPED
151
146
  )
152
147
  == 1
153
148
  )
@@ -1,5 +1,6 @@
1
1
  import typing as t
2
2
  from unittest.mock import MagicMock, patch
3
+
3
4
  import pytest
4
5
  from typer.testing import CliRunner
5
6
  from crackerjack.__main__ import BumpOption, Options, app
@@ -2,6 +2,7 @@ import subprocess
2
2
  from pathlib import Path
3
3
  from typing import Any
4
4
  from unittest import mock
5
+
5
6
  from crackerjack.py313 import (
6
7
  CommandResult,
7
8
  CommandRunner,
@@ -1,4 +1,5 @@
1
1
  from pathlib import Path
2
+
2
3
  from crackerjack.py313 import (
3
4
  CommandResult,
4
5
  HookResult,
@@ -1,4 +1,5 @@
1
1
  import time
2
+
2
3
  import pytest
3
4
 
4
5
 
@@ -2,6 +2,7 @@ import io
2
2
  import typing as t
3
3
  from pathlib import Path
4
4
  from unittest.mock import MagicMock, patch
5
+
5
6
  import pytest
6
7
  from rich.console import Console
7
8
 
File without changes
File without changes