crackerjack 0.13.1__tar.gz → 0.14.0__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 (60) hide show
  1. {crackerjack-0.13.1 → crackerjack-0.14.0}/PKG-INFO +2 -2
  2. {crackerjack-0.13.1 → crackerjack-0.14.0}/README.md +1 -1
  3. crackerjack-0.14.0/crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
  4. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/__init__.py +1 -0
  5. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/__main__.py +1 -0
  6. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/crackerjack.py +55 -30
  7. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/pyproject.toml +1 -1
  8. {crackerjack-0.13.1 → crackerjack-0.14.0}/pyproject.toml +1 -1
  9. crackerjack-0.14.0/tests/data/comments_sample.txt +57 -0
  10. crackerjack-0.14.0/tests/data/expected_comments_sample.txt +54 -0
  11. {crackerjack-0.13.1 → crackerjack-0.14.0}/tests/test_crackerjack.py +13 -12
  12. {crackerjack-0.13.1 → crackerjack-0.14.0}/tests/test_main.py +1 -0
  13. crackerjack-0.13.1/crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
  14. crackerjack-0.13.1/tests/data/comments_sample.txt +0 -7
  15. {crackerjack-0.13.1 → crackerjack-0.14.0}/LICENSE +0 -0
  16. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.coverage +0 -0
  17. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.gitignore +0 -0
  18. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.libcst.codemod.yaml +0 -0
  19. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.pdm.toml +0 -0
  20. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.pre-commit-config.yaml +0 -0
  21. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.pytest_cache/.gitignore +0 -0
  22. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.pytest_cache/CACHEDIR.TAG +0 -0
  23. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.pytest_cache/README.md +0 -0
  24. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.pytest_cache/v/cache/nodeids +0 -0
  25. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.pytest_cache/v/cache/stepwise +0 -0
  26. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/.gitignore +0 -0
  27. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
  28. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
  29. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
  30. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
  31. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
  32. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
  33. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
  34. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
  35. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
  36. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
  37. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
  38. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
  39. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
  40. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
  41. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
  42. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
  43. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
  44. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
  45. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
  46. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
  47. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
  48. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
  49. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
  50. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
  51. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
  52. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
  53. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
  54. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
  55. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
  56. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
  57. {crackerjack-0.13.1 → crackerjack-0.14.0}/crackerjack/.ruff_cache/CACHEDIR.TAG +0 -0
  58. {crackerjack-0.13.1 → crackerjack-0.14.0}/tests/__init__.py +0 -0
  59. {crackerjack-0.13.1 → crackerjack-0.14.0}/tests/data/docstrings_sample.txt +0 -0
  60. {crackerjack-0.13.1 → crackerjack-0.14.0}/tests/data/init.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crackerjack
3
- Version: 0.13.1
3
+ Version: 0.14.0
4
4
  Summary: Default template for PDM package
5
5
  Keywords: black,ruff,mypy,creosote,refurb
6
6
  Author-Email: lesleslie <les@wedgwoodwebworks.com>
@@ -167,7 +167,7 @@ Run Crackerjack from the root of your Python project using:
167
167
  - `-n`, `--no-config-updates`: Skip updating configuration files (e.g., `pyproject.toml`).
168
168
  - `-u`, `--update-precommit`: Update pre-commit hooks to the latest versions.
169
169
  - `-d`, `--doc`: Generate documentation. (not yet implemented)
170
- - `-v`, `--verbose`: Enable verbose output. (not yet implemented)
170
+ - `-v`, `--verbose`: Enable verbose output.
171
171
  - `-p`, `--publish <micro|minor|major>`: Bump the project version and publish to PyPI using PDM.
172
172
  - `-b`, `--bump <micro|minor|major>`: Bump the project version without publishing.
173
173
  - `-x`, `--clean`: Clean code by removing docstrings, line comments, and extra whitespace.
@@ -128,7 +128,7 @@ Run Crackerjack from the root of your Python project using:
128
128
  - `-n`, `--no-config-updates`: Skip updating configuration files (e.g., `pyproject.toml`).
129
129
  - `-u`, `--update-precommit`: Update pre-commit hooks to the latest versions.
130
130
  - `-d`, `--doc`: Generate documentation. (not yet implemented)
131
- - `-v`, `--verbose`: Enable verbose output. (not yet implemented)
131
+ - `-v`, `--verbose`: Enable verbose output.
132
132
  - `-p`, `--publish <micro|minor|major>`: Bump the project version and publish to PyPI using PDM.
133
133
  - `-b`, `--bump <micro|minor|major>`: Bump the project version without publishing.
134
134
  - `-x`, `--clean`: Clean code by removing docstrings, line comments, and extra whitespace.
@@ -1,4 +1,5 @@
1
1
  from typing import Sequence
2
+
2
3
  from .crackerjack import Crackerjack, crackerjack_it
3
4
 
4
5
  __all__: Sequence[str] = ["crackerjack_it", "Crackerjack"]
@@ -1,5 +1,6 @@
1
1
  import typing as t
2
2
  from enum import Enum
3
+
3
4
  import typer
4
5
  from pydantic import BaseModel, field_validator
5
6
  from rich.console import Console
@@ -1,13 +1,16 @@
1
- import ast
1
+ import io
2
2
  import platform
3
3
  import re
4
4
  import subprocess
5
+ import tokenize
5
6
  import typing as t
6
7
  from contextlib import suppress
7
8
  from pathlib import Path
8
9
  from subprocess import CompletedProcess
9
10
  from subprocess import run as execute
11
+ from token import STRING
10
12
  from tomllib import loads
13
+
11
14
  from pydantic import BaseModel
12
15
  from rich.console import Console
13
16
  from tomli_w import dumps
@@ -30,6 +33,12 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
30
33
  pkg_dir.parent.joinpath("__pycache__").rmdir()
31
34
 
32
35
  def clean_file(self, file_path: Path) -> None:
36
+ try:
37
+ if file_path.resolve() == Path(__file__).resolve():
38
+ print(f"Skipping cleaning of {file_path} (self file).")
39
+ return
40
+ except Exception as e:
41
+ print(f"Error comparing file paths: {e}")
33
42
  try:
34
43
  code = file_path.read_text()
35
44
  code = self.remove_docstrings(code)
@@ -41,37 +50,50 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
41
50
  except Exception as e:
42
51
  print(f"Error cleaning {file_path}: {e}")
43
52
 
44
- def remove_docstrings(self, code: str) -> str:
45
- tree = ast.parse(code)
46
- for node in ast.walk(tree):
47
- if isinstance(
48
- node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Module)
49
- ):
50
- if ast.get_docstring(node):
51
- node.body = (
52
- node.body[1:]
53
- if isinstance(node.body[0], ast.Expr)
54
- else node.body
55
- )
56
- return ast.unparse(tree)
57
-
58
53
  def remove_line_comments(self, code: str) -> str:
59
- lines = code.split("\n")
60
- cleaned_lines = []
61
- for line in lines:
62
- comment_match = re.search("(?<!\\S)#(.*)", line)
63
- if comment_match is None:
64
- cleaned_lines.append(line)
54
+ new_lines = []
55
+ for line in code.splitlines():
56
+ if "#" not in line:
57
+ new_lines.append(line)
65
58
  continue
66
- comment_start = comment_match.start()
67
- code_part = line[:comment_start].rstrip()
68
- comment = line[comment_start:].strip()
69
- if code_part:
70
- if re.match("^#(?: type: ignore| noqa)(.*)?$", comment):
71
- cleaned_lines.append(line)
59
+ idx = line.find("#")
60
+ code_part = line[:idx].rstrip()
61
+ comment_part = line[idx:]
62
+ if "type: ignore" in comment_part or "noqa" in comment_part:
63
+ new_lines.append(line)
64
+ else:
65
+ if code_part:
66
+ new_lines.append(code_part)
67
+ return "\n".join(new_lines)
68
+
69
+ def remove_docstrings(self, source: str) -> str:
70
+ try:
71
+ io_obj = io.StringIO(source)
72
+ output_tokens = []
73
+ first_token_stack = [True]
74
+ tokens = list(tokenize.generate_tokens(io_obj.readline))
75
+ for tok in tokens:
76
+ token_type = tok.type
77
+ if token_type == tokenize.INDENT:
78
+ first_token_stack.append(True)
79
+ elif token_type == tokenize.DEDENT:
80
+ if len(first_token_stack) > 1:
81
+ first_token_stack.pop()
82
+ if token_type == STRING and first_token_stack[-1]:
83
+ first_token_stack[-1] = False
84
+ continue
72
85
  else:
73
- cleaned_lines.append(code_part)
74
- return "\n".join(cleaned_lines)
86
+ if token_type not in (
87
+ tokenize.NEWLINE,
88
+ tokenize.NL,
89
+ tokenize.COMMENT,
90
+ ):
91
+ first_token_stack[-1] = False
92
+ output_tokens.append(tok)
93
+ return tokenize.untokenize(output_tokens)
94
+ except Exception as e:
95
+ self.console.print(f"Error removing docstrings: {e}")
96
+ return source
75
97
 
76
98
  def remove_extra_whitespace(self, code: str) -> str:
77
99
  lines = code.split("\n")
@@ -376,7 +398,10 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
376
398
  def _run_tests(self, options: t.Any) -> None:
377
399
  if options.test:
378
400
  self.console.print("\n\nRunning tests...\n")
379
- result = self.execute_command(["pytest"], capture_output=True, text=True)
401
+ test = ["pytest"]
402
+ if options.verbose:
403
+ test.append("-v")
404
+ result = self.execute_command(test, capture_output=True, text=True)
380
405
  if result.stdout:
381
406
  self.console.print(result.stdout)
382
407
  if result.returncode > 0:
@@ -120,7 +120,7 @@ pythonPlatform = "Darwin"
120
120
 
121
121
  [project]
122
122
  name = "crackerjack"
123
- version = "0.13.0"
123
+ version = "0.13.2"
124
124
  description = "Default template for PDM package"
125
125
  requires-python = ">=3.13"
126
126
  readme = "README.md"
@@ -124,7 +124,7 @@ pythonPlatform = "Darwin"
124
124
 
125
125
  [project]
126
126
  name = "crackerjack"
127
- version = "0.13.1"
127
+ version = "0.14.0"
128
128
  description = "Default template for PDM package"
129
129
  requires-python = ">=3.13"
130
130
  readme = "README.md"
@@ -0,0 +1,57 @@
1
+ def test_func():
2
+ # This comment should be removed
3
+ return True # remove this inline comment
4
+
5
+ # Full line comment
6
+ x = 1 # type: ignore
7
+ y = 2 # noqa
8
+
9
+
10
+ class AsyncRedisBytecodeCache(AsyncBytecodeCache):
11
+ prefix: str | None
12
+ client: Redis | RedisCluster # type: ignore
13
+ configs: MappingProxyType[str, t.Any]
14
+
15
+ def __init__(
16
+ self,
17
+ prefix: str | None = None,
18
+ client: Redis | RedisCluster | None = None, # type: ignore
19
+ **configs: t.Any,
20
+ ) -> None:
21
+ self.prefix = prefix
22
+ self.client = client or Redis(**configs)
23
+ self.configs = MappingProxyType(configs)
24
+
25
+ def get_cache_key(self, name: str, filename: str | None = None) -> str:
26
+ return filename or name
27
+
28
+ def get_source_checksum(self, source: str) -> str:
29
+ return str(hash(source))
30
+
31
+ def get_bucket_name(self, key: str) -> str:
32
+ return ":".join([self.prefix, key]) if self.prefix else key
33
+
34
+ async def load_bytecode(self, bucket: Bucket) -> t.Optional[bytes]: # type: ignore[override]
35
+ code = await self.client.get(self.get_bucket_name(bucket.key)) # type: ignore
36
+ if code:
37
+ bucket.bytecode_from_string(code)
38
+ # Note: bucket.bytecode_from_string returns None but modifies bucket.code internally.
39
+ return code
40
+ return None
41
+
42
+ async def dump_bytecode(self, bucket: Bucket) -> None: # type: ignore[override]
43
+ await self.client.set( # type: ignore
44
+ self.get_bucket_name(bucket.key), bucket.bytecode_to_string()
45
+ )
46
+
47
+ async def get_bucket( # type: ignore[override]
48
+ self, environment: Environment, name: str, filename: str | None, source: str
49
+ ) -> Bucket:
50
+ key = self.get_cache_key(name, filename or name)
51
+ checksum = self.get_source_checksum(source)
52
+ bucket = Bucket(environment, key, checksum)
53
+ await self.load_bytecode(bucket)
54
+ return bucket
55
+
56
+ async def set_bucket(self, bucket: Bucket) -> None: # type: ignore[override]
57
+ await self.dump_bytecode(bucket)
@@ -0,0 +1,54 @@
1
+ def test_func():
2
+ return True
3
+
4
+ x = 1 # type: ignore
5
+ y = 2 # noqa
6
+
7
+
8
+ class AsyncRedisBytecodeCache(AsyncBytecodeCache):
9
+ prefix: str | None
10
+ client: Redis | RedisCluster # type: ignore
11
+ configs: MappingProxyType[str, t.Any]
12
+
13
+ def __init__(
14
+ self,
15
+ prefix: str | None = None,
16
+ client: Redis | RedisCluster | None = None, # type: ignore
17
+ **configs: t.Any,
18
+ ) -> None:
19
+ self.prefix = prefix
20
+ self.client = client or Redis(**configs)
21
+ self.configs = MappingProxyType(configs)
22
+
23
+ def get_cache_key(self, name: str, filename: str | None = None) -> str:
24
+ return filename or name
25
+
26
+ def get_source_checksum(self, source: str) -> str:
27
+ return str(hash(source))
28
+
29
+ def get_bucket_name(self, key: str) -> str:
30
+ return ":".join([self.prefix, key]) if self.prefix else key
31
+
32
+ async def load_bytecode(self, bucket: Bucket) -> t.Optional[bytes]: # type: ignore[override]
33
+ code = await self.client.get(self.get_bucket_name(bucket.key)) # type: ignore
34
+ if code:
35
+ bucket.bytecode_from_string(code)
36
+ return code
37
+ return None
38
+
39
+ async def dump_bytecode(self, bucket: Bucket) -> None: # type: ignore[override]
40
+ await self.client.set( # type: ignore
41
+ self.get_bucket_name(bucket.key), bucket.bytecode_to_string()
42
+ )
43
+
44
+ async def get_bucket( # type: ignore[override]
45
+ self, environment: Environment, name: str, filename: str | None, source: str
46
+ ) -> Bucket:
47
+ key = self.get_cache_key(name, filename or name)
48
+ checksum = self.get_source_checksum(source)
49
+ bucket = Bucket(environment, key, checksum)
50
+ await self.load_bytecode(bucket)
51
+ return bucket
52
+
53
+ async def set_bucket(self, bucket: Bucket) -> None: # type: ignore[override]
54
+ await self.dump_bytecode(bucket)
@@ -3,6 +3,7 @@ import typing as t
3
3
  from enum import Enum
4
4
  from pathlib import Path
5
5
  from unittest.mock import MagicMock, patch
6
+
6
7
  import pytest
7
8
  from pydantic import BaseModel
8
9
  from rich.console import Console
@@ -905,26 +906,26 @@ class TestCrackerjackProcess:
905
906
  assert '"""Method docstring."""' not in cleaned_code, f"Got: {cleaned_code!r}"
906
907
 
907
908
  def test_code_cleaner_remove_line_comments(self) -> None:
909
+ from pathlib import Path
910
+
908
911
  from rich.console import Console
909
912
  from crackerjack.crackerjack import CodeCleaner
910
913
 
911
914
  code_cleaner = CodeCleaner(console=Console())
912
915
  code_with_comments = (
913
- Path(__file__).parent / "data" / "comments_sample.txt"
914
- ).read_text()
916
+ (Path(__file__).parent / "data" / "comments_sample.txt")
917
+ .read_text()
918
+ .rstrip()
919
+ )
915
920
  cleaned_code = code_cleaner.remove_line_comments(code_with_comments)
916
- assert "x = 1 # type: ignore" in cleaned_code, f"Got: {cleaned_code!r}"
917
- assert "y = 2 # noqa" in cleaned_code, f"Got: {cleaned_code!r}"
918
- assert "# This comment should be removed" not in cleaned_code, (
919
- f"Got: {cleaned_code!r}"
921
+ expected_cleaned = (
922
+ (Path(__file__).parent / "data" / "expected_comments_sample.txt")
923
+ .read_text()
924
+ .rstrip()
920
925
  )
921
- assert "# remove this inline comment" not in cleaned_code, (
922
- f"Got: {cleaned_code!r}"
926
+ assert cleaned_code == expected_cleaned, (
927
+ f"Cleaned code does not match expected.\nExpected:\n{expected_cleaned}\nGot:\n{cleaned_code}"
923
928
  )
924
- assert (
925
- "def test_func():\n return True\n\nx = 1 # type: ignore\ny = 2 # noqa\n"
926
- in cleaned_code
927
- ), f"Got: {cleaned_code!r}"
928
929
 
929
930
  def test_code_cleaner_remove_extra_whitespace(self) -> None:
930
931
  from rich.console import Console
@@ -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, create_options
@@ -1,7 +0,0 @@
1
- def test_func():
2
- # This comment should be removed
3
- return True # remove this inline comment
4
-
5
- # Full line comment
6
- x = 1 # type: ignore
7
- y = 2 # noqa
File without changes