crackerjack 0.21.6__py3-none-any.whl → 0.21.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -86,55 +86,96 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
86
86
  except Exception as e:
87
87
  print(f"Error cleaning {file_path}: {e}")
88
88
 
89
- def remove_docstrings(self, code: str) -> str:
90
- lines = code.split("\n")
91
- cleaned_lines = []
92
- docstring_state = {
89
+ def _initialize_docstring_state(self) -> dict[str, t.Any]:
90
+ return {
93
91
  "in_docstring": False,
94
92
  "delimiter": None,
95
93
  "waiting": False,
96
94
  "function_indent": 0,
97
95
  "removed_docstring": False,
96
+ "in_multiline_def": False,
98
97
  }
98
+
99
+ def _handle_function_definition(
100
+ self, line: str, stripped: str, state: dict[str, t.Any]
101
+ ) -> bool:
102
+ if self._is_function_or_class_definition(stripped):
103
+ state["waiting"] = True
104
+ state["function_indent"] = len(line) - len(line.lstrip())
105
+ state["removed_docstring"] = False
106
+ state["in_multiline_def"] = not stripped.endswith(":")
107
+ return True
108
+ return False
109
+
110
+ def _handle_multiline_definition(
111
+ self, line: str, stripped: str, state: dict[str, t.Any]
112
+ ) -> bool:
113
+ if state["in_multiline_def"]:
114
+ if stripped.endswith(":"):
115
+ state["in_multiline_def"] = False
116
+ return True
117
+ return False
118
+
119
+ def _handle_waiting_docstring(
120
+ self, lines: list[str], i: int, stripped: str, state: dict[str, t.Any]
121
+ ) -> tuple[bool, str | None]:
122
+ if state["waiting"] and stripped:
123
+ if self._handle_docstring_start(stripped, state):
124
+ pass_line = None
125
+ if not state["in_docstring"]:
126
+ function_indent: int = state["function_indent"]
127
+ if self._needs_pass_statement(lines, i + 1, function_indent):
128
+ pass_line = " " * (function_indent + 4) + "pass"
129
+ state["removed_docstring"] = True
130
+ return True, pass_line
131
+ else:
132
+ state["waiting"] = False
133
+ return False, None
134
+
135
+ def _handle_docstring_content(
136
+ self, lines: list[str], i: int, stripped: str, state: dict[str, t.Any]
137
+ ) -> tuple[bool, str | None]:
138
+ if state["in_docstring"]:
139
+ if self._handle_docstring_end(stripped, state):
140
+ pass_line = None
141
+ function_indent: int = state["function_indent"]
142
+ if self._needs_pass_statement(lines, i + 1, function_indent):
143
+ pass_line = " " * (function_indent + 4) + "pass"
144
+ state["removed_docstring"] = False
145
+ return True, pass_line
146
+ else:
147
+ return True, None
148
+ return False, None
149
+
150
+ def _process_line(
151
+ self, lines: list[str], i: int, line: str, state: dict[str, t.Any]
152
+ ) -> tuple[bool, str | None]:
153
+ stripped = line.strip()
154
+ if self._handle_function_definition(line, stripped, state):
155
+ return True, line
156
+ if self._handle_multiline_definition(line, stripped, state):
157
+ return True, line
158
+ handled, pass_line = self._handle_waiting_docstring(lines, i, stripped, state)
159
+ if handled:
160
+ return True, pass_line
161
+ handled, pass_line = self._handle_docstring_content(lines, i, stripped, state)
162
+ if handled:
163
+ return True, pass_line
164
+ if state["removed_docstring"] and stripped:
165
+ state["removed_docstring"] = False
166
+ return False, line
167
+
168
+ def remove_docstrings(self, code: str) -> str:
169
+ lines = code.split("\n")
170
+ cleaned_lines = []
171
+ docstring_state = self._initialize_docstring_state()
99
172
  for i, line in enumerate(lines):
100
- stripped = line.strip()
101
- if self._is_function_or_class_definition(stripped):
102
- docstring_state["waiting"] = True
103
- docstring_state["function_indent"] = len(line) - len(line.lstrip())
104
- docstring_state["removed_docstring"] = False
173
+ handled, result_line = self._process_line(lines, i, line, docstring_state)
174
+ if handled:
175
+ if result_line is not None:
176
+ cleaned_lines.append(result_line)
177
+ else:
105
178
  cleaned_lines.append(line)
106
- continue
107
- if docstring_state["waiting"] and stripped:
108
- if self._handle_docstring_start(stripped, docstring_state):
109
- if not docstring_state["in_docstring"]:
110
- if self._needs_pass_statement(
111
- lines, i + 1, docstring_state["function_indent"]
112
- ):
113
- pass_line = (
114
- " " * (docstring_state["function_indent"] + 4) + "pass"
115
- )
116
- cleaned_lines.append(pass_line)
117
- docstring_state["removed_docstring"] = True
118
- continue
119
- else:
120
- docstring_state["waiting"] = False
121
- if docstring_state["in_docstring"]:
122
- if self._handle_docstring_end(stripped, docstring_state):
123
- if self._needs_pass_statement(
124
- lines, i + 1, docstring_state["function_indent"]
125
- ):
126
- pass_line = (
127
- " " * (docstring_state["function_indent"] + 4) + "pass"
128
- )
129
- cleaned_lines.append(pass_line)
130
- docstring_state["removed_docstring"] = False
131
- continue
132
- else:
133
- continue
134
- if docstring_state["removed_docstring"] and stripped:
135
- docstring_state["removed_docstring"] = False
136
- cleaned_lines.append(line)
137
-
138
179
  return "\n".join(cleaned_lines)
139
180
 
140
181
  def _is_function_or_class_definition(self, stripped_line: str) -> bool:
@@ -177,9 +218,8 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
177
218
  line_indent = len(line) - len(line.lstrip())
178
219
  if line_indent <= function_indent:
179
220
  return True
180
- if line_indent == function_indent + 4:
221
+ if line_indent > function_indent:
181
222
  return False
182
-
183
223
  return True
184
224
 
185
225
  def remove_line_comments(self, code: str) -> str:
@@ -192,7 +232,6 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
192
232
  cleaned_line = self._process_line_for_comments(line)
193
233
  if cleaned_line or not line.strip():
194
234
  cleaned_lines.append(cleaned_line or line)
195
-
196
235
  return "\n".join(cleaned_lines)
197
236
 
198
237
  def _process_line_for_comments(self, line: str) -> str:
@@ -205,7 +244,6 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
205
244
  break
206
245
  else:
207
246
  result.append(char)
208
-
209
247
  return "".join(result).rstrip()
210
248
 
211
249
  def _handle_string_character(
@@ -216,18 +254,14 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
216
254
  string_state: dict[str, t.Any],
217
255
  result: list[str],
218
256
  ) -> bool:
219
- """Handle string quote characters. Returns True if character was handled."""
220
257
  if char not in ("'", '"'):
221
258
  return False
222
-
223
259
  if index > 0 and line[index - 1] == "\\":
224
260
  return False
225
-
226
261
  if string_state["in_string"] is None:
227
262
  string_state["in_string"] = char
228
263
  elif string_state["in_string"] == char:
229
264
  string_state["in_string"] = None
230
-
231
265
  result.append(char)
232
266
  return True
233
267
 
@@ -239,14 +273,11 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
239
273
  string_state: dict[str, t.Any],
240
274
  result: list[str],
241
275
  ) -> bool:
242
- """Handle comment character. Returns True if comment was found."""
243
276
  if char != "#" or string_state["in_string"] is not None:
244
277
  return False
245
-
246
278
  comment = line[index:].strip()
247
279
  if self._is_special_comment_line(comment):
248
280
  result.append(line[index:])
249
-
250
281
  return True
251
282
 
252
283
  def _is_special_comment_line(self, comment: str) -> bool:
@@ -260,23 +291,23 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
260
291
  lines = code.split("\n")
261
292
  cleaned_lines = []
262
293
  function_tracker = {"in_function": False, "function_indent": 0}
294
+ import_tracker = {"in_imports": False, "last_import_type": None}
263
295
  for i, line in enumerate(lines):
264
296
  line = line.rstrip()
265
297
  stripped_line = line.lstrip()
266
298
  self._update_function_state(line, stripped_line, function_tracker)
299
+ self._update_import_state(line, stripped_line, import_tracker)
267
300
  if not line:
268
301
  if self._should_skip_empty_line(
269
- i, lines, cleaned_lines, function_tracker
302
+ i, lines, cleaned_lines, function_tracker, import_tracker
270
303
  ):
271
304
  continue
272
305
  cleaned_lines.append(line)
273
-
274
306
  return "\n".join(self._remove_trailing_empty_lines(cleaned_lines))
275
307
 
276
308
  def _update_function_state(
277
309
  self, line: str, stripped_line: str, function_tracker: dict[str, t.Any]
278
310
  ) -> None:
279
- """Update function tracking state based on current line."""
280
311
  if stripped_line.startswith(("def ", "async def ")):
281
312
  function_tracker["in_function"] = True
282
313
  function_tracker["function_indent"] = len(line) - len(stripped_line)
@@ -284,10 +315,116 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
284
315
  function_tracker["in_function"] = False
285
316
  function_tracker["function_indent"] = 0
286
317
 
318
+ def _update_import_state(
319
+ self, line: str, stripped_line: str, import_tracker: dict[str, t.Any]
320
+ ) -> None:
321
+ if stripped_line.startswith(("import ", "from ")):
322
+ import_tracker["in_imports"] = True
323
+ if self._is_stdlib_import(stripped_line):
324
+ current_type = "stdlib"
325
+ elif self._is_local_import(stripped_line):
326
+ current_type = "local"
327
+ else:
328
+ current_type = "third_party"
329
+ import_tracker["last_import_type"] = current_type
330
+ elif stripped_line and not stripped_line.startswith("#"):
331
+ import_tracker["in_imports"] = False
332
+ import_tracker["last_import_type"] = None
333
+
334
+ def _is_stdlib_import(self, stripped_line: str) -> bool:
335
+ try:
336
+ if stripped_line.startswith("from "):
337
+ module = stripped_line.split()[1].split(".")[0]
338
+ else:
339
+ module = stripped_line.split()[1].split(".")[0]
340
+ except IndexError:
341
+ return False
342
+ stdlib_modules = {
343
+ "os",
344
+ "sys",
345
+ "re",
346
+ "json",
347
+ "datetime",
348
+ "time",
349
+ "pathlib",
350
+ "typing",
351
+ "collections",
352
+ "itertools",
353
+ "functools",
354
+ "operator",
355
+ "math",
356
+ "random",
357
+ "uuid",
358
+ "urllib",
359
+ "http",
360
+ "html",
361
+ "xml",
362
+ "email",
363
+ "csv",
364
+ "sqlite3",
365
+ "subprocess",
366
+ "threading",
367
+ "multiprocessing",
368
+ "asyncio",
369
+ "contextlib",
370
+ "dataclasses",
371
+ "enum",
372
+ "abc",
373
+ "io",
374
+ "tempfile",
375
+ "shutil",
376
+ "glob",
377
+ "pickle",
378
+ "copy",
379
+ "heapq",
380
+ "bisect",
381
+ "array",
382
+ "struct",
383
+ "zlib",
384
+ "hashlib",
385
+ "hmac",
386
+ "secrets",
387
+ "base64",
388
+ "binascii",
389
+ "codecs",
390
+ "locale",
391
+ "platform",
392
+ "socket",
393
+ "ssl",
394
+ "ipaddress",
395
+ "logging",
396
+ "warnings",
397
+ "inspect",
398
+ "ast",
399
+ "dis",
400
+ "tokenize",
401
+ "keyword",
402
+ "linecache",
403
+ "traceback",
404
+ "weakref",
405
+ "gc",
406
+ "ctypes",
407
+ "unittest",
408
+ "doctest",
409
+ "pdb",
410
+ "profile",
411
+ "cProfile",
412
+ "timeit",
413
+ "trace",
414
+ "calendar",
415
+ "decimal",
416
+ "fractions",
417
+ "statistics",
418
+ "tomllib",
419
+ }
420
+ return module in stdlib_modules
421
+
422
+ def _is_local_import(self, stripped_line: str) -> bool:
423
+ return stripped_line.startswith("from .") or " . " in stripped_line
424
+
287
425
  def _is_function_end(
288
426
  self, line: str, stripped_line: str, function_tracker: dict[str, t.Any]
289
427
  ) -> bool:
290
- """Check if current line marks the end of a function."""
291
428
  return (
292
429
  function_tracker["in_function"]
293
430
  and bool(line)
@@ -301,16 +438,44 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
301
438
  lines: list[str],
302
439
  cleaned_lines: list[str],
303
440
  function_tracker: dict[str, t.Any],
441
+ import_tracker: dict[str, t.Any],
304
442
  ) -> bool:
305
- """Determine if an empty line should be skipped."""
306
443
  if line_idx > 0 and cleaned_lines and (not cleaned_lines[-1]):
307
444
  return True
308
445
 
446
+ if self._is_import_section_separator(line_idx, lines, import_tracker):
447
+ return False
448
+
309
449
  if function_tracker["in_function"]:
310
450
  return self._should_skip_function_empty_line(line_idx, lines)
311
-
312
451
  return False
313
452
 
453
+ def _is_import_section_separator(
454
+ self, line_idx: int, lines: list[str], import_tracker: dict[str, t.Any]
455
+ ) -> bool:
456
+ if not import_tracker["in_imports"]:
457
+ return False
458
+
459
+ next_line_idx = line_idx + 1
460
+ while next_line_idx < len(lines) and not lines[next_line_idx].strip():
461
+ next_line_idx += 1
462
+
463
+ if next_line_idx >= len(lines):
464
+ return False
465
+
466
+ next_line = lines[next_line_idx].strip()
467
+ if not next_line.startswith(("import ", "from ")):
468
+ return False
469
+
470
+ if self._is_stdlib_import(next_line):
471
+ next_type = "stdlib"
472
+ elif self._is_local_import(next_line):
473
+ next_type = "local"
474
+ else:
475
+ next_type = "third_party"
476
+
477
+ return import_tracker["last_import_type"] != next_type
478
+
314
479
  def _should_skip_function_empty_line(self, line_idx: int, lines: list[str]) -> bool:
315
480
  next_line_idx = line_idx + 1
316
481
  if next_line_idx >= len(lines):
@@ -323,7 +488,6 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
323
488
  return True
324
489
  if next_line in ("pass", "break", "continue", "raise"):
325
490
  return True
326
-
327
491
  return self._is_special_comment(next_line)
328
492
 
329
493
  def _is_special_comment(self, line: str) -> bool:
@@ -463,15 +627,12 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
463
627
  for tool, settings in our_toml_config.get("tool", {}).items():
464
628
  if tool not in pkg_toml_config["tool"]:
465
629
  pkg_toml_config["tool"][tool] = {}
466
-
467
630
  pkg_tool_config = pkg_toml_config["tool"][tool]
468
-
469
631
  self._merge_tool_config(settings, pkg_tool_config, tool)
470
632
 
471
633
  def _merge_tool_config(
472
634
  self, our_config: dict[str, t.Any], pkg_config: dict[str, t.Any], tool: str
473
635
  ) -> None:
474
- """Recursively merge tool configuration, preserving existing project settings."""
475
636
  for setting, value in our_config.items():
476
637
  if isinstance(value, dict):
477
638
  self._merge_nested_config(setting, value, pkg_config)
@@ -481,21 +642,17 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
481
642
  def _merge_nested_config(
482
643
  self, setting: str, value: dict[str, t.Any], pkg_config: dict[str, t.Any]
483
644
  ) -> None:
484
- """Handle nested configuration merging."""
485
645
  if setting not in pkg_config:
486
646
  pkg_config[setting] = {}
487
647
  elif not isinstance(pkg_config[setting], dict):
488
648
  pkg_config[setting] = {}
489
-
490
649
  self._merge_tool_config(value, pkg_config[setting], "")
491
-
492
650
  for k, v in value.items():
493
651
  self._merge_nested_value(k, v, pkg_config[setting])
494
652
 
495
653
  def _merge_nested_value(
496
654
  self, key: str, value: t.Any, nested_config: dict[str, t.Any]
497
655
  ) -> None:
498
- """Merge individual nested values."""
499
656
  if isinstance(value, str | list) and "crackerjack" in str(value):
500
657
  nested_config[key] = self.swap_package_name(value)
501
658
  elif self._is_mergeable_list(key, value):
@@ -510,7 +667,6 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
510
667
  def _merge_direct_config(
511
668
  self, setting: str, value: t.Any, pkg_config: dict[str, t.Any]
512
669
  ) -> None:
513
- """Handle direct configuration merging."""
514
670
  if isinstance(value, str | list) and "crackerjack" in str(value):
515
671
  pkg_config[setting] = self.swap_package_name(value)
516
672
  elif self._is_mergeable_list(setting, value):
@@ -773,7 +929,6 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
773
929
  self._add_benchmark_flags(test, options)
774
930
  else:
775
931
  self._add_worker_flags(test, options, project_size)
776
-
777
932
  return test
778
933
 
779
934
  def _detect_project_size(self) -> str:
@@ -289,7 +289,6 @@ class InteractiveCLI:
289
289
  )
290
290
  total_tasks = len(self.workflow.tasks)
291
291
  progress_task = progress.add_task("Running workflow", total=total_tasks)
292
-
293
292
  return {
294
293
  "progress": progress,
295
294
  "progress_task": progress_task,
@@ -299,14 +298,11 @@ class InteractiveCLI:
299
298
  def _execute_workflow_loop(
300
299
  self, layout: Layout, progress_tracker: dict[str, t.Any], live: Live
301
300
  ) -> None:
302
- """Execute the main workflow loop."""
303
301
  while not self.workflow.all_tasks_completed():
304
302
  layout["tasks"].update(self.show_task_table())
305
303
  next_task = self.workflow.get_next_task()
306
-
307
304
  if not next_task:
308
305
  break
309
-
310
306
  if self._should_execute_task(layout, next_task, live):
311
307
  self._execute_task(layout, next_task, progress_tracker)
312
308
  else:
@@ -322,20 +318,16 @@ class InteractiveCLI:
322
318
  def _execute_task(
323
319
  self, layout: Layout, task: Task, progress_tracker: dict[str, t.Any]
324
320
  ) -> None:
325
- """Execute a single task and update progress."""
326
321
  task.start()
327
322
  layout["details"].update(self.show_task_status(task))
328
323
  time.sleep(1)
329
-
330
324
  success = self._simulate_task_execution()
331
-
332
325
  if success:
333
326
  task.complete()
334
327
  progress_tracker["completed_tasks"] += 1
335
328
  else:
336
329
  error = self._create_task_error(task.name)
337
330
  task.fail(error)
338
-
339
331
  progress_tracker["progress"].update(
340
332
  progress_tracker["progress_task"],
341
333
  completed=progress_tracker["completed_tasks"],
@@ -4,7 +4,7 @@ requires = [ "pdm-backend" ]
4
4
 
5
5
  [project]
6
6
  name = "crackerjack"
7
- version = "0.21.5"
7
+ version = "0.21.7"
8
8
  description = "Crackerjack: code quality toolkit"
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crackerjack
3
- Version: 0.21.6
3
+ Version: 0.21.8
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 --rich-ui
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 --rich-ui
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
- - `--rich-ui`: Enable the interactive Rich UI for a more user-friendly experience with visual progress tracking and interactive prompts.
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 --rich-ui
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 `--rich-ui` flag:
503
+ To use the Rich UI, run Crackerjack with the `--interactive` flag:
504
504
 
505
505
  ```bash
506
- python -m crackerjack --rich-ui
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 --rich-ui -v
545
+ python -m crackerjack --interactive -v
546
546
  ```
547
547
 
548
548
  ## Python 3.13+ Features
@@ -1,7 +1,7 @@
1
- crackerjack-0.21.6.dist-info/METADATA,sha256=YcmoMu7VODbnvoQf63uApMapVEOpKbHjdeFs0Qutza4,26414
2
- crackerjack-0.21.6.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- crackerjack-0.21.6.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- crackerjack-0.21.6.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
1
+ crackerjack-0.21.8.dist-info/METADATA,sha256=IPe32XzA-hFDy247TIpvfAOWuWZwr64gbhZCUbsP4jQ,26442
2
+ crackerjack-0.21.8.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
+ crackerjack-0.21.8.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ crackerjack-0.21.8.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
5
5
  crackerjack/.gitignore,sha256=n8cD6U16L3XZn__PvhYm_F7-YeFHFucHCyxWj2NZCGs,259
6
6
  crackerjack/.libcst.codemod.yaml,sha256=a8DlErRAIPV1nE6QlyXPAzTOgkB24_spl2E9hphuf5s,772
7
7
  crackerjack/.pdm.toml,sha256=dZe44HRcuxxCFESGG8SZIjmc-cGzSoyK3Hs6t4NYA8w,23
@@ -34,7 +34,7 @@ crackerjack/.ruff_cache/0.11.7/10386934055395314831,sha256=lBNwN5zAgM4OzbkXIOzCc
34
34
  crackerjack/.ruff_cache/0.11.7/3557596832929915217,sha256=fKlwUbsvT3YIKV6UR-aA_i64lLignWeVfVu-MMmVbU0,207
35
35
  crackerjack/.ruff_cache/0.11.8/530407680854991027,sha256=xAMAL3Vu_HR6M-h5ojCTaak0By5ii8u-14pXULLgLqw,224
36
36
  crackerjack/.ruff_cache/0.12.0/5056746222905752453,sha256=MqrIT5qymJcgAOBZyn-TvYoGCFfDFCgN9IwSULq8n14,256
37
- crackerjack/.ruff_cache/0.12.1/5056746222905752453,sha256=ksDcvUi9t_w2SUbEhH8Yk-ArZbLaWKIkwnKqJ0Q9ceA,256
37
+ crackerjack/.ruff_cache/0.12.1/5056746222905752453,sha256=g9fAkLYZwfeJKgDNXhdsOwHnHBgTG5KKHfMoy5Lut1s,256
38
38
  crackerjack/.ruff_cache/0.2.0/10047773857155985907,sha256=j9LNa_RQ4Plor7go1uTYgz17cEENKvZQ-dP6b9MX0ik,248
39
39
  crackerjack/.ruff_cache/0.2.1/8522267973936635051,sha256=u_aPBMibtAp_iYvLwR88GMAECMcIgHezxMyuapmU2P4,248
40
40
  crackerjack/.ruff_cache/0.2.2/18053836298936336950,sha256=Xb_ebP0pVuUfSqPEZKlhQ70so_vqkEfMYpuHQ06iR5U,248
@@ -64,9 +64,9 @@ crackerjack/.ruff_cache/0.9.9/8843823720003377982,sha256=e4ymkXfQsUg5e_mtO34xTsa
64
64
  crackerjack/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
65
65
  crackerjack/__init__.py,sha256=8tBSPAru_YDuPpjz05cL7pNbZjYFoRT_agGd_FWa3gY,839
66
66
  crackerjack/__main__.py,sha256=AknITUlFjq3YUK9s2xeL62dM0GN82JBQyDkPzQ_hCUg,6561
67
- crackerjack/crackerjack.py,sha256=p5079e4I_pMHKiTFm0ckiuKJ1jvYCq8Na_0Vk-R12xA,35155
67
+ crackerjack/crackerjack.py,sha256=Vzt-oT989OJcw0He40BnnTUlmV8-siiZy5-a5fRjL1E,40046
68
68
  crackerjack/errors.py,sha256=QEPtVuMtKtQHuawgr1ToMaN1KbUg5h9-4mS33YB5Znk,4062
69
- crackerjack/interactive.py,sha256=5KXKSvWKttLvHcI1L4VEDc3Rb-ZpHBOl_Qr7lhD-O4Q,16262
69
+ crackerjack/interactive.py,sha256=y5QbyR2Wp8WkC_iC89ZqETm-wjAN9X5DK1L3yetpjN4,16153
70
70
  crackerjack/py313.py,sha256=buYE7LO11Q64ffowEhTZRFQoAGj_8sg3DTlZuv8M9eo,5890
71
- crackerjack/pyproject.toml,sha256=w4ssNTiRx9zQqQuywBGMIISalKLwg59DPSza9eGXuGk,4988
72
- crackerjack-0.21.6.dist-info/RECORD,,
71
+ crackerjack/pyproject.toml,sha256=CT9hhv56Ax8h1vljzxcEAn4s2wlQh1oZ0Iq3kiA6-m0,4988
72
+ crackerjack-0.21.8.dist-info/RECORD,,