crackerjack 0.21.7__py3-none-any.whl → 0.22.0__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.
@@ -14,11 +14,6 @@ repos:
14
14
  - id: check-added-large-files
15
15
  name: check-added-large-files
16
16
 
17
- - repo: https://github.com/abravalheri/validate-pyproject
18
- rev: v0.24.1
19
- hooks:
20
- - id: validate-pyproject
21
-
22
17
  - repo: https://github.com/tox-dev/pyproject-fmt
23
18
  rev: "v2.6.0"
24
19
  hooks:
@@ -34,7 +29,7 @@ repos:
34
29
  - keyring
35
30
 
36
31
  - repo: https://github.com/astral-sh/uv-pre-commit
37
- rev: 0.7.15
32
+ rev: 0.7.16
38
33
  hooks:
39
34
  - id: uv-lock
40
35
  files: ^pyproject\.toml$
@@ -110,14 +105,3 @@ repos:
110
105
  rev: v1.1.402
111
106
  hooks:
112
107
  - id: pyright
113
-
114
- # - repo: https://github.com/pdoc3/pdoc
115
- # rev: master
116
- # hooks:
117
- # - id: pdoc
118
- # name: pdoc
119
- # entry: pdoc --html -f -o docs module1 module2 module3
120
- # language_version: python3.11
121
- # require_serial: true
122
- # types: [ python ]
123
- # always_run: true
crackerjack/__init__.py CHANGED
@@ -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,
crackerjack/__main__.py CHANGED
@@ -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
@@ -22,7 +23,6 @@ class BumpOption(str, Enum):
22
23
  class Options(BaseModel):
23
24
  commit: bool = False
24
25
  interactive: bool = False
25
- doc: bool = False
26
26
  no_config_updates: bool = False
27
27
  publish: BumpOption | None = None
28
28
  bump: BumpOption | None = None
@@ -62,7 +62,6 @@ cli_options = {
62
62
  "--interactive",
63
63
  help="Use the interactive Rich UI for a better experience.",
64
64
  ),
65
- "doc": typer.Option(False, "-d", "--doc", help="Generate documentation."),
66
65
  "no_config_updates": typer.Option(
67
66
  False, "-n", "--no-config-updates", help="Do not update configuration files."
68
67
  ),
@@ -145,7 +144,6 @@ cli_options = {
145
144
  def main(
146
145
  commit: bool = cli_options["commit"],
147
146
  interactive: bool = cli_options["interactive"],
148
- doc: bool = cli_options["doc"],
149
147
  no_config_updates: bool = cli_options["no_config_updates"],
150
148
  update_precommit: bool = cli_options["update_precommit"],
151
149
  verbose: bool = cli_options["verbose"],
@@ -168,7 +166,6 @@ def main(
168
166
  options = Options(
169
167
  commit=commit,
170
168
  interactive=interactive,
171
- doc=doc,
172
169
  no_config_updates=no_config_updates,
173
170
  update_precommit=update_precommit,
174
171
  verbose=verbose,
@@ -6,6 +6,7 @@ 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
+
9
10
  from pydantic import BaseModel
10
11
  from rich.console import Console
11
12
  from tomli_w import dumps
@@ -31,7 +32,6 @@ class CommandRunner(t.Protocol):
31
32
  class OptionsProtocol(t.Protocol):
32
33
  commit: bool
33
34
  interactive: bool
34
- doc: bool
35
35
  no_config_updates: bool
36
36
  verbose: bool
37
37
  update_precommit: bool
@@ -169,9 +169,12 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
169
169
  cleaned_lines = []
170
170
  docstring_state = self._initialize_docstring_state()
171
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)
172
+ handled, result_line = self._process_line(lines, i, line, docstring_state)
173
+ if handled:
174
+ if result_line is not None:
175
+ cleaned_lines.append(result_line)
176
+ else:
177
+ cleaned_lines.append(line)
175
178
  return "\n".join(cleaned_lines)
176
179
 
177
180
  def _is_function_or_class_definition(self, stripped_line: str) -> bool:
@@ -287,13 +290,15 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
287
290
  lines = code.split("\n")
288
291
  cleaned_lines = []
289
292
  function_tracker = {"in_function": False, "function_indent": 0}
293
+ import_tracker = {"in_imports": False, "last_import_type": None}
290
294
  for i, line in enumerate(lines):
291
295
  line = line.rstrip()
292
296
  stripped_line = line.lstrip()
293
297
  self._update_function_state(line, stripped_line, function_tracker)
298
+ self._update_import_state(line, stripped_line, import_tracker)
294
299
  if not line:
295
300
  if self._should_skip_empty_line(
296
- i, lines, cleaned_lines, function_tracker
301
+ i, lines, cleaned_lines, function_tracker, import_tracker
297
302
  ):
298
303
  continue
299
304
  cleaned_lines.append(line)
@@ -309,6 +314,113 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
309
314
  function_tracker["in_function"] = False
310
315
  function_tracker["function_indent"] = 0
311
316
 
317
+ def _update_import_state(
318
+ self, line: str, stripped_line: str, import_tracker: dict[str, t.Any]
319
+ ) -> None:
320
+ if stripped_line.startswith(("import ", "from ")):
321
+ import_tracker["in_imports"] = True
322
+ if self._is_stdlib_import(stripped_line):
323
+ current_type = "stdlib"
324
+ elif self._is_local_import(stripped_line):
325
+ current_type = "local"
326
+ else:
327
+ current_type = "third_party"
328
+ import_tracker["last_import_type"] = current_type
329
+ elif stripped_line and not stripped_line.startswith("#"):
330
+ import_tracker["in_imports"] = False
331
+ import_tracker["last_import_type"] = None
332
+
333
+ def _is_stdlib_import(self, stripped_line: str) -> bool:
334
+ try:
335
+ if stripped_line.startswith("from "):
336
+ module = stripped_line.split()[1].split(".")[0]
337
+ else:
338
+ module = stripped_line.split()[1].split(".")[0]
339
+ except IndexError:
340
+ return False
341
+ stdlib_modules = {
342
+ "os",
343
+ "sys",
344
+ "re",
345
+ "json",
346
+ "datetime",
347
+ "time",
348
+ "pathlib",
349
+ "typing",
350
+ "collections",
351
+ "itertools",
352
+ "functools",
353
+ "operator",
354
+ "math",
355
+ "random",
356
+ "uuid",
357
+ "urllib",
358
+ "http",
359
+ "html",
360
+ "xml",
361
+ "email",
362
+ "csv",
363
+ "sqlite3",
364
+ "subprocess",
365
+ "threading",
366
+ "multiprocessing",
367
+ "asyncio",
368
+ "contextlib",
369
+ "dataclasses",
370
+ "enum",
371
+ "abc",
372
+ "io",
373
+ "tempfile",
374
+ "shutil",
375
+ "glob",
376
+ "pickle",
377
+ "copy",
378
+ "heapq",
379
+ "bisect",
380
+ "array",
381
+ "struct",
382
+ "zlib",
383
+ "hashlib",
384
+ "hmac",
385
+ "secrets",
386
+ "base64",
387
+ "binascii",
388
+ "codecs",
389
+ "locale",
390
+ "platform",
391
+ "socket",
392
+ "ssl",
393
+ "ipaddress",
394
+ "logging",
395
+ "warnings",
396
+ "inspect",
397
+ "ast",
398
+ "dis",
399
+ "tokenize",
400
+ "keyword",
401
+ "linecache",
402
+ "traceback",
403
+ "weakref",
404
+ "gc",
405
+ "ctypes",
406
+ "unittest",
407
+ "doctest",
408
+ "pdb",
409
+ "profile",
410
+ "cProfile",
411
+ "timeit",
412
+ "trace",
413
+ "calendar",
414
+ "decimal",
415
+ "fractions",
416
+ "statistics",
417
+ "tomllib",
418
+ }
419
+ return module in stdlib_modules
420
+
421
+ def _is_local_import(self, stripped_line: str) -> bool:
422
+ return stripped_line.startswith("from .") or " . " in stripped_line
423
+
312
424
  def _is_function_end(
313
425
  self, line: str, stripped_line: str, function_tracker: dict[str, t.Any]
314
426
  ) -> bool:
@@ -325,13 +437,44 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
325
437
  lines: list[str],
326
438
  cleaned_lines: list[str],
327
439
  function_tracker: dict[str, t.Any],
440
+ import_tracker: dict[str, t.Any],
328
441
  ) -> bool:
329
442
  if line_idx > 0 and cleaned_lines and (not cleaned_lines[-1]):
330
443
  return True
444
+
445
+ if self._is_import_section_separator(line_idx, lines, import_tracker):
446
+ return False
447
+
331
448
  if function_tracker["in_function"]:
332
449
  return self._should_skip_function_empty_line(line_idx, lines)
333
450
  return False
334
451
 
452
+ def _is_import_section_separator(
453
+ self, line_idx: int, lines: list[str], import_tracker: dict[str, t.Any]
454
+ ) -> bool:
455
+ if not import_tracker["in_imports"]:
456
+ return False
457
+
458
+ next_line_idx = line_idx + 1
459
+ while next_line_idx < len(lines) and not lines[next_line_idx].strip():
460
+ next_line_idx += 1
461
+
462
+ if next_line_idx >= len(lines):
463
+ return False
464
+
465
+ next_line = lines[next_line_idx].strip()
466
+ if not next_line.startswith(("import ", "from ")):
467
+ return False
468
+
469
+ if self._is_stdlib_import(next_line):
470
+ next_type = "stdlib"
471
+ elif self._is_local_import(next_line):
472
+ next_type = "local"
473
+ else:
474
+ next_type = "third_party"
475
+
476
+ return import_tracker["last_import_type"] != next_type
477
+
335
478
  def _should_skip_function_empty_line(self, line_idx: int, lines: list[str]) -> bool:
336
479
  next_line_idx = line_idx + 1
337
480
  if next_line_idx >= len(lines):
crackerjack/errors.py CHANGED
@@ -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
 
@@ -4,7 +4,7 @@ requires = [ "pdm-backend" ]
4
4
 
5
5
  [project]
6
6
  name = "crackerjack"
7
- version = "0.21.6"
7
+ version = "0.21.8"
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.7
3
+ Version: 0.22.0
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 --interactive
105
+ python -m crackerjack -i
106
106
  ```
107
107
 
108
108
  ---
@@ -160,7 +160,7 @@ Crackerjack automatically installs and manages these pre-commit hooks:
160
160
  3. **Ruff:** [Ruff](https://github.com/astral-sh/ruff) for linting, code formatting, and general code style enforcement.
161
161
  4. **Vulture:** [Vulture](https://github.com/jendrikseipp/vulture) to identify dead code.
162
162
  5. **Creosote:** [Creosote](https://github.com/fredrikaverpil/creosote) to detect unused dependencies.
163
- 6. **Flynt:** [Flynt](https://github.com/ikamensh/flynt/) for converting string formatting to f-strings.
163
+ 6. **Complexipy:** [Complexipy](https://github.com/rohaquinlop/complexipy-pre-commit) for analyzing code complexity.
164
164
  7. **Codespell:** [Codespell](https://github.com/codespell-project/codespell) for correcting typos in the code.
165
165
  8. **Autotyping:** [Autotyping](https://github.com/JelleZijlstra/autotyping) for adding type hints.
166
166
  9. **Refurb:** [Refurb](https://github.com/dosisod/refurb) to suggest code improvements.
@@ -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 --interactive
272
+ python -m crackerjack -i
273
273
  ```
274
274
 
275
275
  ## Usage
@@ -301,7 +301,6 @@ class MyOptions:
301
301
  # Configuration options
302
302
  self.no_config_updates = False # Skip updating config files
303
303
  self.update_precommit = False # Update pre-commit hooks
304
- self.doc = False # Generate documentation (not implemented yet)
305
304
 
306
305
  # Process options
307
306
  self.clean = True # Clean code (remove docstrings, comments, etc.)
@@ -347,7 +346,6 @@ runner.process(MyOptions())
347
346
  - `-i`, `--interactive`: Run pre-commit hooks interactively when possible.
348
347
  - `-n`, `--no-config-updates`: Skip updating configuration files (e.g., `pyproject.toml`).
349
348
  - `-u`, `--update-precommit`: Update pre-commit hooks to the latest versions.
350
- - `-d`, `--doc`: Generate documentation. (not yet implemented)
351
349
  - `-v`, `--verbose`: Enable verbose output.
352
350
  - `-p`, `--publish <micro|minor|major>`: Bump the project version and publish to PyPI using PDM.
353
351
  - `-b`, `--bump <micro|minor|major>`: Bump the project version without publishing.
@@ -361,7 +359,7 @@ runner.process(MyOptions())
361
359
  - `--benchmark-regression`: Fail tests if benchmarks regress beyond threshold.
362
360
  - `--benchmark-regression-threshold`: Set threshold percentage for benchmark regression (default 5.0%).
363
361
  - `-a`, `--all`: Run with `-x -t -p <micro|minor|major> -c` development options.
364
- - `--interactive`: Enable the interactive Rich UI for a more user-friendly experience with visual progress tracking and interactive prompts.
362
+ - `-i`, `--interactive`: Enable the interactive Rich UI for a more user-friendly experience with visual progress tracking and interactive prompts.
365
363
  - `--ai-agent`: Enable AI agent mode with structured output (see [AI Agent Integration](#ai-agent-integration)).
366
364
  - `--help`: Display help.
367
365
 
@@ -461,7 +459,7 @@ runner.process(MyOptions())
461
459
 
462
460
  - **Rich Interactive Mode** - Run with the interactive Rich UI:
463
461
  ```bash
464
- python -m crackerjack --interactive
462
+ python -m crackerjack -i
465
463
  ```
466
464
 
467
465
  - **AI Integration** - Run with structured output for AI tools:
@@ -500,10 +498,10 @@ Crackerjack now offers an enhanced interactive experience through its Rich UI:
500
498
  - **Error Visualization:** Errors are presented in a structured, easy-to-understand format with recovery suggestions
501
499
  - **File Selection:** Interactive file browser for operations that require selecting files
502
500
 
503
- To use the Rich UI, run Crackerjack with the `--interactive` flag:
501
+ To use the Rich UI, run Crackerjack with the `-i` flag:
504
502
 
505
503
  ```bash
506
- python -m crackerjack --interactive
504
+ python -m crackerjack -i
507
505
  ```
508
506
 
509
507
  This launches an interactive terminal interface where you can:
@@ -542,7 +540,7 @@ python -m crackerjack -v
542
540
  For the most comprehensive error details with visual formatting, combine verbose mode with the Rich UI:
543
541
 
544
542
  ```bash
545
- python -m crackerjack --interactive -v
543
+ python -m crackerjack -i -v
546
544
  ```
547
545
 
548
546
  ## Python 3.13+ Features
@@ -606,7 +604,7 @@ Crackerjack is designed with modern Python principles in mind:
606
604
  - **bandit:** For finding security vulnerabilities.
607
605
  - **vulture:** For dead code detection.
608
606
  - **creosote:** For unused dependency detection.
609
- - **flynt:** For f-string conversion.
607
+ - **complexipy:** For code complexity analysis.
610
608
  - **codespell:** For spelling correction.
611
609
  - **autotyping:** For automatically adding type hints.
612
610
  - **refurb:** For code improvement suggestions.
@@ -1,12 +1,12 @@
1
- crackerjack-0.21.7.dist-info/METADATA,sha256=suf-qK6MQQTYL6v8ZXcTUT70x6ADX6u1Dd8YvhQSV60,26442
2
- crackerjack-0.21.7.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- crackerjack-0.21.7.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- crackerjack-0.21.7.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
1
+ crackerjack-0.22.0.dist-info/METADATA,sha256=nqIxAtAdYiUHDn2lfX8PAIeSO8ufSkPQ6qcVNEmXqpo,26251
2
+ crackerjack-0.22.0.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
+ crackerjack-0.22.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ crackerjack-0.22.0.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
8
8
  crackerjack/.pre-commit-config-ai.yaml,sha256=K8xXKMJcdhfXOk24L4XpK7H8YlvnZfOh4NVA6qvOz8I,3319
9
- crackerjack/.pre-commit-config.yaml,sha256=glBcQ4nwC28t4Ier2E69bUg8pNQ9YyCtHmD-fV6bRm0,3076
9
+ crackerjack/.pre-commit-config.yaml,sha256=fLYBe846QI2Kgfjut0biMx4sjbZkJwc0Z9nvcNhGXD0,2643
10
10
  crackerjack/.pytest_cache/.gitignore,sha256=Ptcxtl0GFQwTji2tsL4Gl1UIiKa0frjEXsya26i46b0,37
11
11
  crackerjack/.pytest_cache/CACHEDIR.TAG,sha256=N9yI75oKvt2-gQU6bdj9-xOvthMEXqHrSlyBWnSjveQ,191
12
12
  crackerjack/.pytest_cache/README.md,sha256=c_1vzN2ALEGaay2YPWwxc7fal1WKxLWJ7ewt_kQ9ua0,302
@@ -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=CHy2IV23GK0EauutmCmddO7Ck-fHMzbvpvZ1ZO9Cp8Y,256
37
+ crackerjack/.ruff_cache/0.12.1/5056746222905752453,sha256=38wx4Qzd6e1GIHXu-oCJpPPNMXpUhTAGS3pPKW85138,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
@@ -62,11 +62,11 @@ crackerjack/.ruff_cache/0.9.3/13948373885254993391,sha256=kGhtIkzPUtKAgvlKs3D8j4
62
62
  crackerjack/.ruff_cache/0.9.9/12813592349865671909,sha256=tmr8_vhRD2OxsVuMfbJPdT9fDFX-d5tfC5U9jgziyho,224
63
63
  crackerjack/.ruff_cache/0.9.9/8843823720003377982,sha256=e4ymkXfQsUg5e_mtO34xTsaTvs1uA3_fI216Qq9qCAM,136
64
64
  crackerjack/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
65
- crackerjack/__init__.py,sha256=LyIPEfjBcqWp7UVuJ-b7cc8oiFxrQqyL8RJ7g6c17Qs,838
66
- crackerjack/__main__.py,sha256=GP5GBbWK5lDTQanCHKVEyS3hDWvshwlrhWagzQlRuD4,6560
67
- crackerjack/crackerjack.py,sha256=jlepiHgMkvTyl83O8HUqC4mCsQbZIejerOHNQD15JcU,35854
68
- crackerjack/errors.py,sha256=xvXO3bk0VrxQz0lysIuVOju6SxxDgf04Wne1BedLFB8,4061
69
- crackerjack/interactive.py,sha256=ierA2jeUsigTJhKOcJJsjhBZm2WwsQpvow3Y5ppF7IE,16151
65
+ crackerjack/__init__.py,sha256=8tBSPAru_YDuPpjz05cL7pNbZjYFoRT_agGd_FWa3gY,839
66
+ crackerjack/__main__.py,sha256=lEyi83ChuehqEJgUQ6Ib4ByOofvmhi_0M7oFamMC_1I,6407
67
+ crackerjack/crackerjack.py,sha256=lWA98K2uY4i4TnXN24pselsiJWSlQgQ6ztTXGn7n27Q,40032
68
+ crackerjack/errors.py,sha256=QEPtVuMtKtQHuawgr1ToMaN1KbUg5h9-4mS33YB5Znk,4062
69
+ crackerjack/interactive.py,sha256=y5QbyR2Wp8WkC_iC89ZqETm-wjAN9X5DK1L3yetpjN4,16153
70
70
  crackerjack/py313.py,sha256=buYE7LO11Q64ffowEhTZRFQoAGj_8sg3DTlZuv8M9eo,5890
71
- crackerjack/pyproject.toml,sha256=OIoNmIktC1abFPgePb95Zg9BDTGfRxXftK-KccWgalc,4988
72
- crackerjack-0.21.7.dist-info/RECORD,,
71
+ crackerjack/pyproject.toml,sha256=UaUtLOyXoOvjesOY4lRj-hT4L5WjVbCH0C5fGoAMBXQ,4988
72
+ crackerjack-0.22.0.dist-info/RECORD,,