crackerjack 0.11.4__py3-none-any.whl → 0.12.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.
crackerjack/.coverage ADDED
Binary file
crackerjack/.gitignore CHANGED
@@ -13,3 +13,5 @@
13
13
  /*.pyc
14
14
  /.crackerjack.yaml
15
15
  /scratch/
16
+
17
+ .qodo
@@ -1,6 +1,6 @@
1
1
  repos:
2
2
  - repo: https://github.com/pdm-project/pdm
3
- rev: 2.22.3 # a PDM release exposing the hook
3
+ rev: 2.22.4 # a PDM release exposing the hook
4
4
  hooks:
5
5
  - id: pdm-lock-check
6
6
  - repo: https://github.com/pre-commit/pre-commit-hooks
@@ -17,7 +17,7 @@ repos:
17
17
  - id: check-added-large-files
18
18
  name: check-added-large-files
19
19
  - repo: https://github.com/astral-sh/ruff-pre-commit
20
- rev: v0.9.9
20
+ rev: v0.9.10
21
21
  hooks:
22
22
  - id: ruff-format
23
23
  - id: ruff
@@ -69,7 +69,7 @@ repos:
69
69
  hooks:
70
70
  - id: pyright
71
71
  - repo: https://github.com/astral-sh/ruff-pre-commit
72
- rev: v0.9.9
72
+ rev: v0.9.10
73
73
  hooks:
74
74
  - id: ruff
75
75
  - id: ruff-format
@@ -0,0 +1,2 @@
1
+ # Created by pytest automatically.
2
+ *
@@ -0,0 +1,4 @@
1
+ Signature: 8a477f597d28d172789f06886806bc55
2
+ # This file is a cache directory tag created by pytest.
3
+ # For information about cache directory tags, see:
4
+ # https://bford.info/cachedir/spec.html
@@ -0,0 +1,8 @@
1
+ # pytest cache directory #
2
+
3
+ This directory contains data from the pytest's cache plugin,
4
+ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
5
+
6
+ **Do not** commit this to version control.
7
+
8
+ See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.
@@ -0,0 +1 @@
1
+ []
@@ -0,0 +1 @@
1
+ []
crackerjack/__init__.py CHANGED
@@ -1,7 +1,4 @@
1
1
  from typing import Sequence
2
+ from .crackerjack import Crackerjack, crackerjack_it
2
3
 
3
- from .crackerjack import crackerjack_it
4
-
5
- __all__: Sequence[str] = [
6
- "crackerjack_it",
7
- ]
4
+ __all__: Sequence[str] = ["crackerjack_it", "Crackerjack"]
crackerjack/__main__.py CHANGED
@@ -1,64 +1,119 @@
1
1
  import typing as t
2
-
3
- from click import command, help_option, option
4
- from pydantic import BaseModel
2
+ from enum import Enum
3
+ import typer
4
+ from pydantic import BaseModel, field_validator
5
+ from rich.console import Console
5
6
  from crackerjack import crackerjack_it
6
7
 
8
+ console = Console(force_terminal=True)
9
+ app = typer.Typer(
10
+ help="Crackerjack: Your Python project setup and style enforcement tool."
11
+ )
12
+
13
+
14
+ class BumpOption(str, Enum):
15
+ micro = "micro"
16
+ minor = "minor"
17
+ major = "major"
18
+
19
+ def __str__(self) -> str:
20
+ return self.value
21
+
7
22
 
8
23
  class Options(BaseModel):
9
24
  commit: bool = False
10
25
  interactive: bool = False
11
26
  doc: bool = False
12
- do_not_update_configs: bool = False
13
- publish: t.Literal["micro", "minor", "major"] | bool = False
14
- bump: t.Literal["micro", "minor", "major"] | bool = False
27
+ no_config_updates: bool = False
28
+ publish: t.Optional[BumpOption] = None
29
+ bump: t.Optional[BumpOption] = None
15
30
  verbose: bool = False
16
31
  update_precommit: bool = False
32
+ clean: bool = False
33
+ test: bool = False
34
+
35
+ @classmethod
36
+ @field_validator("publish", "bump", mode="before")
37
+ def validate_bump_options(cls, value: t.Optional[str]) -> t.Optional[BumpOption]:
38
+ if value is None:
39
+ return None
40
+ try:
41
+ return BumpOption(value.lower())
42
+ except ValueError:
43
+ valid_options = ", ".join([o.value for o in BumpOption])
44
+ raise ValueError(
45
+ f"Invalid bump option: {value}. Must be one of: {valid_options}"
46
+ )
47
+
48
+
49
+ cli_options = {
50
+ "commit": typer.Option(False, "-c", "--commit", help="Commit changes to Git."),
51
+ "interactive": typer.Option(
52
+ False, "-i", "--interactive", help="Run pre-commit hooks interactively."
53
+ ),
54
+ "doc": typer.Option(False, "-d", "--doc", help="Generate documentation."),
55
+ "no_config_updates": typer.Option(
56
+ False, "-n", "--no-config-updates", help="Do not update configuration files."
57
+ ),
58
+ "update_precommit": typer.Option(
59
+ False, "-u", "--update-precommit", help="Update pre-commit hooks."
60
+ ),
61
+ "verbose": typer.Option(False, "-v", "--verbose", help="Enable verbose output."),
62
+ "publish": typer.Option(
63
+ None,
64
+ "-p",
65
+ "--publish",
66
+ help="Bump version and publish to PyPI (micro, minor, major).",
67
+ case_sensitive=False,
68
+ ),
69
+ "bump": typer.Option(
70
+ None,
71
+ "-b",
72
+ "--bump",
73
+ help="Bump version (micro, minor, major).",
74
+ case_sensitive=False,
75
+ ),
76
+ "clean": typer.Option(
77
+ False,
78
+ "-x",
79
+ "--clean",
80
+ help="Remove docstrings, line comments, and unnecessary whitespace.",
81
+ ),
82
+ "test": typer.Option(False, "-t", "--test", help="Run tests."),
83
+ }
17
84
 
18
85
 
19
- options = Options()
86
+ def create_options(**kwargs: t.Any) -> Options:
87
+ return Options(**kwargs)
20
88
 
21
89
 
22
- @command()
23
- @help_option("-h", is_flag=True, help="help")
24
- @option("-c", is_flag=True, help="commit")
25
- @option("-i", is_flag=True, help="interactive")
26
- @option("-d", is_flag=True, help="doc")
27
- @option("-x", is_flag=True, help="do not update configs")
28
- @option("-u", is_flag=True, help="update pre-commit")
29
- @option("-v", is_flag=True, help="verbose")
30
- @option("-p", help="bump version and publish: -p [micro, minor, major]")
31
- @option("-b", help="bump version: -b [micro, minor, major]")
32
- # @option("-f", help="format: -f [module]")
33
- def crackerjack(
34
- c: bool = False,
35
- i: bool = False,
36
- d: bool = False,
37
- u: bool = False,
38
- v: bool = False,
39
- x: bool = False,
40
- p: str | bool = False,
41
- b: str | bool = False,
90
+ @app.command()
91
+ def main(
92
+ commit: bool = cli_options["commit"],
93
+ interactive: bool = cli_options["interactive"],
94
+ doc: bool = cli_options["doc"],
95
+ no_config_updates: bool = cli_options["no_config_updates"],
96
+ update_precommit: bool = cli_options["update_precommit"],
97
+ verbose: bool = cli_options["verbose"],
98
+ publish: t.Optional[BumpOption] = cli_options["publish"],
99
+ bump: t.Optional[BumpOption] = cli_options["bump"],
100
+ clean: bool = cli_options["clean"],
101
+ test: bool = cli_options["test"],
42
102
  ) -> None:
43
- if c:
44
- options.commit = c
45
- if i:
46
- options.interactive = i
47
- if d:
48
- options.doc = d
49
- if u:
50
- options.update_precommit = u
51
- if x:
52
- options.do_not_update_configs = x
53
- if p in ("micro", "minor", "major"):
54
- options.publish = p
55
- if b in ("micro", "minor", "major"):
56
- options.bump = b
57
- if v:
58
- print("-v not currently implemented")
59
- options.verbose = v
60
- crackerjack_it(options=options)
103
+ options = create_options(
104
+ commit=commit,
105
+ interactive=interactive,
106
+ doc=doc,
107
+ no_config_updates=no_config_updates,
108
+ update_precommit=update_precommit,
109
+ verbose=verbose,
110
+ publish=publish,
111
+ bump=bump,
112
+ clean=clean,
113
+ test=test,
114
+ )
115
+ crackerjack_it(options)
61
116
 
62
117
 
63
118
  if __name__ == "__main__":
64
- crackerjack()
119
+ app()
@@ -1,43 +1,190 @@
1
+ import ast
1
2
  import re
3
+ import subprocess
2
4
  import typing as t
5
+ from contextlib import suppress
3
6
  from pathlib import Path
7
+ from subprocess import CompletedProcess
4
8
  from subprocess import run as execute
5
9
  from tomllib import loads
6
-
7
10
  from pydantic import BaseModel
11
+ from rich.console import Console
8
12
  from tomli_w import dumps
9
13
 
14
+ config_files = (".gitignore", ".pre-commit-config.yaml", ".libcst.codemod.yaml")
15
+ interactive_hooks = ("refurb", "bandit", "pyright")
16
+ default_python_version = "3.13"
10
17
 
11
- class Crackerjack(BaseModel, arbitrary_types_allowed=True):
12
- our_path: Path = Path(__file__).parent
13
- pkg_path: Path = Path(Path.cwd())
14
- pkg_dir: t.Optional[Path] = None
15
- pkg_name: str = "crackerjack"
16
- our_toml: t.Optional[dict[str, t.Any]] = None
17
- pkg_toml: t.Optional[dict[str, t.Any]] = None
18
- our_toml_path: t.Optional[Path] = None
19
- pkg_toml_path: t.Optional[Path] = None
20
- python_version: str = "3.13"
18
+
19
+ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
20
+ console: Console
21
+
22
+ def clean_files(self, pkg_dir: Path | None) -> None:
23
+ if pkg_dir is None:
24
+ return
25
+ for file_path in pkg_dir.rglob("*.py"):
26
+ if not str(file_path.parent).startswith("__"):
27
+ self.clean_file(file_path)
28
+ if pkg_dir.parent.joinpath("__pycache__").exists():
29
+ pkg_dir.parent.joinpath("__pycache__").rmdir()
30
+
31
+ def clean_file(self, file_path: Path) -> None:
32
+ try:
33
+ code = file_path.read_text()
34
+ code = self.remove_docstrings(code)
35
+ code = self.remove_line_comments(code)
36
+ code = self.remove_extra_whitespace(code)
37
+ code = self.reformat_code(code)
38
+ file_path.write_text(code)
39
+ print(f"Cleaned: {file_path}")
40
+ except Exception as e:
41
+ print(f"Error cleaning {file_path}: {e}")
42
+
43
+ def remove_docstrings(self, code: str) -> str:
44
+ tree = ast.parse(code)
45
+ for node in ast.walk(tree):
46
+ if isinstance(
47
+ node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Module)
48
+ ):
49
+ if ast.get_docstring(node):
50
+ node.body = (
51
+ node.body[1:]
52
+ if isinstance(node.body[0], ast.Expr)
53
+ else node.body
54
+ )
55
+ return ast.unparse(tree)
56
+
57
+ def remove_line_comments(self, code: str) -> str:
58
+ lines = code.split("\n")
59
+ cleaned_lines = []
60
+ for line in lines:
61
+ comment_match = re.search("(?<!\\S)#(.*)", line)
62
+ if comment_match is None:
63
+ cleaned_lines.append(line)
64
+ continue
65
+ comment_start = comment_match.start()
66
+ code_part = line[:comment_start].rstrip()
67
+ comment = line[comment_start:].strip()
68
+ if code_part:
69
+ if re.match("^#(?: type: ignore| noqa)(.*)?$", comment):
70
+ cleaned_lines.append(line)
71
+ else:
72
+ cleaned_lines.append(code_part)
73
+ return "\n".join(cleaned_lines)
74
+
75
+ def remove_extra_whitespace(self, code: str) -> str:
76
+ lines = code.split("\n")
77
+ cleaned_lines = []
78
+ for i, line in enumerate(lines):
79
+ line = line.rstrip()
80
+ if i > 0 and (not line) and (not cleaned_lines[-1]):
81
+ continue
82
+ cleaned_lines.append(line)
83
+ return "\n".join(cleaned_lines)
84
+
85
+ def reformat_code(self, code: str) -> str:
86
+ try:
87
+ import tempfile
88
+
89
+ with tempfile.NamedTemporaryFile(
90
+ suffix=".py", mode="w+", delete=False
91
+ ) as temp:
92
+ temp_path = Path(temp.name)
93
+ temp_path.write_text(code)
94
+ try:
95
+ result = subprocess.run(
96
+ ["ruff", "format", str(temp_path)],
97
+ check=False,
98
+ capture_output=True,
99
+ text=True,
100
+ )
101
+ if result.returncode == 0:
102
+ formatted_code = temp_path.read_text()
103
+ else:
104
+ print(f"Ruff formatting failed: {result.stderr}")
105
+ formatted_code = code
106
+ except Exception as e:
107
+ print(f"Error running Ruff: {e}")
108
+ formatted_code = code
109
+ finally:
110
+ with suppress(FileNotFoundError):
111
+ temp_path.unlink()
112
+ return formatted_code
113
+ except Exception as e:
114
+ print(f"Error during reformatting: {e}")
115
+ return code
116
+
117
+
118
+ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
119
+ our_path: Path
120
+ pkg_path: Path
121
+ pkg_name: str
122
+ console: Console
123
+ our_toml_path: Path | None = None
124
+ pkg_toml_path: Path | None = None
125
+ python_version: str = default_python_version
126
+ dry_run: bool = False
127
+
128
+ def swap_package_name(self, value: list[str] | str) -> list[str] | str:
129
+ if isinstance(value, list):
130
+ value.remove("crackerjack")
131
+ value.append(self.pkg_name)
132
+ else:
133
+ value = value.replace("crackerjack", self.pkg_name)
134
+ return value
21
135
 
22
136
  def update_pyproject_configs(self) -> None:
137
+ self._setup_toml_paths()
138
+ if self._is_crackerjack_project():
139
+ self._handle_crackerjack_project()
140
+ return
141
+ our_toml_config = self._load_our_toml()
142
+ pkg_toml_config = self._load_pkg_toml()
143
+ self._ensure_required_sections(pkg_toml_config)
144
+ self._update_tool_settings(our_toml_config, pkg_toml_config)
145
+ self._update_python_version(our_toml_config, pkg_toml_config)
146
+ self._save_pkg_toml(pkg_toml_config)
147
+
148
+ def _setup_toml_paths(self) -> None:
23
149
  toml_file = "pyproject.toml"
24
150
  self.our_toml_path = self.our_path / toml_file
25
151
  self.pkg_toml_path = self.pkg_path / toml_file
26
- if self.pkg_path.stem == "crackerjack":
152
+
153
+ def _is_crackerjack_project(self) -> bool:
154
+ return self.pkg_path.stem == "crackerjack"
155
+
156
+ def _handle_crackerjack_project(self) -> None:
157
+ if self.our_toml_path and self.pkg_toml_path:
27
158
  self.our_toml_path.write_text(self.pkg_toml_path.read_text())
28
- return
29
- our_toml_config: t.Any = loads(self.our_toml_path.read_text())
30
- pkg_toml_config: t.Any = loads(self.pkg_toml_path.read_text())
159
+
160
+ def _load_our_toml(self) -> dict[str, t.Any]:
161
+ if self.our_toml_path:
162
+ return loads(self.our_toml_path.read_text())
163
+ return {}
164
+
165
+ def _load_pkg_toml(self) -> dict[str, t.Any]:
166
+ if self.pkg_toml_path:
167
+ return loads(self.pkg_toml_path.read_text())
168
+ return {}
169
+
170
+ def _ensure_required_sections(self, pkg_toml_config: dict[str, t.Any]) -> None:
31
171
  pkg_toml_config.setdefault("tool", {})
32
172
  pkg_toml_config.setdefault("project", {})
33
- for tool, settings in our_toml_config["tool"].items():
173
+
174
+ def _update_tool_settings(
175
+ self, our_toml_config: dict[str, t.Any], pkg_toml_config: dict[str, t.Any]
176
+ ) -> None:
177
+ for tool, settings in our_toml_config.get("tool", {}).items():
34
178
  for setting, value in settings.items():
35
- if isinstance(value, str | list) and "crackerjack" in value:
36
- if isinstance(value, list):
37
- value.remove("crackerjack")
38
- value.append(self.pkg_name)
39
- else:
40
- value = value.replace("crackerjack", self.pkg_name)
179
+ if isinstance(value, dict):
180
+ for k, v in {
181
+ x: self.swap_package_name(y)
182
+ for x, y in value.items()
183
+ if isinstance(y, (str, list)) and "crackerjack" in str(y)
184
+ }.items():
185
+ settings[setting][k] = v
186
+ elif isinstance(value, (str, list)) and "crackerjack" in str(value):
187
+ value = self.swap_package_name(value)
41
188
  settings[setting] = value
42
189
  if setting in (
43
190
  "exclude-deps",
@@ -49,20 +196,27 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
49
196
  conf = pkg_toml_config["tool"].get(tool, {}).get(setting, [])
50
197
  settings[setting] = list(set(conf + value))
51
198
  pkg_toml_config["tool"][tool] = settings
52
- python_version_pattern = r"\s*W*(\d\.\d*)"
53
- requires_python = our_toml_config["project"]["requires-python"]
199
+
200
+ def _update_python_version(
201
+ self, our_toml_config: dict[str, t.Any], pkg_toml_config: dict[str, t.Any]
202
+ ) -> None:
203
+ python_version_pattern = "\\s*W*(\\d\\.\\d*)"
204
+ requires_python = our_toml_config.get("project", {}).get("requires-python", "")
54
205
  classifiers = []
55
- for classifier in pkg_toml_config["project"].get("classifiers", []):
206
+ for classifier in pkg_toml_config.get("project", {}).get("classifiers", []):
56
207
  classifier = re.sub(
57
208
  python_version_pattern, f" {self.python_version}", classifier
58
209
  )
59
210
  classifiers.append(classifier)
60
211
  pkg_toml_config["project"]["classifiers"] = classifiers
61
- pkg_toml_config["project"]["requires-python"] = requires_python
62
- self.pkg_toml_path.write_text(dumps(pkg_toml_config))
212
+ if requires_python:
213
+ pkg_toml_config["project"]["requires-python"] = requires_python
214
+
215
+ def _save_pkg_toml(self, pkg_toml_config: dict[str, t.Any]) -> None:
216
+ if self.pkg_toml_path:
217
+ self.pkg_toml_path.write_text(dumps(pkg_toml_config))
63
218
 
64
219
  def copy_configs(self) -> None:
65
- config_files = (".gitignore", ".pre-commit-config.yaml", ".libcst.codemod.yaml")
66
220
  for config in config_files:
67
221
  config_path = self.our_path / config
68
222
  pkg_config_path = self.pkg_path / config
@@ -72,90 +226,212 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
72
226
  continue
73
227
  if config != ".gitignore":
74
228
  pkg_config_path.write_text(
75
- (config_path.read_text()).replace("crackerjack", self.pkg_name)
229
+ config_path.read_text().replace("crackerjack", self.pkg_name)
76
230
  )
77
- execute(["git", "add", config])
231
+ self.execute_command(["git", "add", config])
232
+
233
+ def execute_command(
234
+ self, cmd: list[str], **kwargs: t.Any
235
+ ) -> subprocess.CompletedProcess[str]:
236
+ if self.dry_run:
237
+ self.console.print(f"[yellow]Would run: {' '.join(cmd)}[/yellow]")
238
+ return CompletedProcess(cmd, 0, "", "")
239
+ return execute(cmd, **kwargs)
240
+
241
+
242
+ class ProjectManager(BaseModel, arbitrary_types_allowed=True):
243
+ our_path: Path
244
+ pkg_path: Path
245
+ pkg_dir: Path | None = None
246
+ pkg_name: str = "crackerjack"
247
+ console: Console
248
+ code_cleaner: CodeCleaner
249
+ config_manager: ConfigManager
250
+ dry_run: bool = False
78
251
 
79
252
  def run_interactive(self, hook: str) -> None:
80
253
  success: bool = False
81
254
  while not success:
82
- fail = execute(["pre-commit", "run", hook.lower(), "--all-files"])
255
+ fail = self.execute_command(
256
+ ["pre-commit", "run", hook.lower(), "--all-files"]
257
+ )
83
258
  if fail.returncode > 0:
84
259
  retry = input(f"\n\n{hook.title()} failed. Retry? (y/N): ")
85
- print()
260
+ self.console.print()
86
261
  if retry.strip().lower() == "y":
87
262
  continue
88
263
  raise SystemExit(1)
89
264
  success = True
90
265
 
91
266
  def update_pkg_configs(self) -> None:
92
- self.copy_configs()
93
- installed_pkgs = execute(
94
- ["pdm", "list", "--freeze"],
95
- capture_output=True,
96
- text=True,
267
+ self.config_manager.copy_configs()
268
+ installed_pkgs = self.execute_command(
269
+ ["pdm", "list", "--freeze"], capture_output=True, text=True
97
270
  ).stdout.splitlines()
98
271
  if not len([pkg for pkg in installed_pkgs if "pre-commit" in pkg]):
99
- print("Initializing project...")
100
- execute(["pdm", "self", "add", "keyring"])
101
- execute(["pdm", "config", "python.use_uv", "true"])
102
- execute(["git", "init"])
103
- execute(["git", "branch", "-m", "main"])
104
- execute(["git", "add", "pyproject.toml"])
105
- execute(["git", "add", "pdm.lock"])
106
- execute(["pre-commit", "install"])
107
- execute(["git", "config", "advice.addIgnoredFile", "false"])
108
- self.update_pyproject_configs()
272
+ self.console.print("Initializing project...")
273
+ self.execute_command(["pdm", "self", "add", "keyring"])
274
+ self.execute_command(["pdm", "config", "python.use_uv", "true"])
275
+ self.execute_command(["git", "init"])
276
+ self.execute_command(["git", "branch", "-m", "main"])
277
+ self.execute_command(["git", "add", "pyproject.toml"])
278
+ self.execute_command(["git", "add", "pdm.lock"])
279
+ self.execute_command(["pre-commit", "install"])
280
+ self.execute_command(["git", "config", "advice.addIgnoredFile", "false"])
281
+ self.config_manager.update_pyproject_configs()
109
282
 
110
283
  def run_pre_commit(self) -> None:
111
- check_all = execute(["pre-commit", "run", "--all-files"])
284
+ self.console.print("\nRunning pre-commit hooks...\n")
285
+ check_all = self.execute_command(["pre-commit", "run", "--all-files"])
112
286
  if check_all.returncode > 0:
113
- check_all = execute(["pre-commit", "run", "--all-files"])
287
+ check_all = self.execute_command(["pre-commit", "run", "--all-files"])
114
288
  if check_all.returncode > 0:
115
- print("\n\nPre-commit failed. Please fix errors.\n")
289
+ self.console.print("\n\nPre-commit failed. Please fix errors.\n")
116
290
  raise SystemExit(1)
117
291
 
118
- def process(self, options: t.Any) -> None:
292
+ def execute_command(
293
+ self, cmd: list[str], **kwargs: t.Any
294
+ ) -> subprocess.CompletedProcess[str]:
295
+ if self.dry_run:
296
+ self.console.print(f"[yellow]Would run: {' '.join(cmd)}[/yellow]")
297
+ return CompletedProcess(cmd, 0, "", "")
298
+ return execute(cmd, **kwargs)
299
+
300
+
301
+ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
302
+ our_path: Path = Path(__file__).parent
303
+ pkg_path: Path = Path(Path.cwd())
304
+ pkg_dir: Path | None = None
305
+ pkg_name: str = "crackerjack"
306
+ python_version: str = default_python_version
307
+ console: Console = Console(force_terminal=True)
308
+ dry_run: bool = False
309
+ code_cleaner: CodeCleaner | None = None
310
+ config_manager: ConfigManager | None = None
311
+ project_manager: ProjectManager | None = None
312
+
313
+ def __init__(self, **data: t.Any) -> None:
314
+ super().__init__(**data)
315
+ self.code_cleaner = CodeCleaner(console=self.console)
316
+ self.config_manager = ConfigManager(
317
+ our_path=self.our_path,
318
+ pkg_path=self.pkg_path,
319
+ pkg_name=self.pkg_name,
320
+ console=self.console,
321
+ python_version=self.python_version,
322
+ dry_run=self.dry_run,
323
+ )
324
+ self.project_manager = ProjectManager(
325
+ our_path=self.our_path,
326
+ pkg_path=self.pkg_path,
327
+ pkg_dir=self.pkg_dir,
328
+ pkg_name=self.pkg_name,
329
+ console=self.console,
330
+ code_cleaner=self.code_cleaner,
331
+ config_manager=self.config_manager,
332
+ dry_run=self.dry_run,
333
+ )
334
+
335
+ def _setup_package(self) -> None:
119
336
  self.pkg_name = self.pkg_path.stem.lower().replace("-", "_")
120
337
  self.pkg_dir = self.pkg_path / self.pkg_name
121
338
  self.pkg_dir.mkdir(exist_ok=True)
122
- print("\nCrackerjacking...\n")
123
- if not options.do_not_update_configs:
124
- self.update_pkg_configs()
125
- execute(["pdm", "install"])
339
+ self.console.print("\nCrackerjacking...\n")
340
+ self.config_manager.pkg_name = self.pkg_name
341
+ self.project_manager.pkg_name = self.pkg_name
342
+ self.project_manager.pkg_dir = self.pkg_dir
343
+
344
+ def _update_project(self, options: t.Any) -> None:
345
+ if not options.no_config_updates:
346
+ self.project_manager.update_pkg_configs()
347
+ result: CompletedProcess[str] = self.execute_command(
348
+ ["pdm", "install"], capture_output=True, text=True
349
+ )
350
+ if result.returncode == 0:
351
+ self.console.print("PDM installed: ✅\n")
352
+ else:
353
+ self.console.print(
354
+ "\n\n❌ PDM installation failed. Is PDM is installed? Run `pipx install pdm` and try again.\n\n"
355
+ )
356
+
357
+ def _update_precommit(self, options: t.Any) -> None:
126
358
  if self.pkg_path.stem == "crackerjack" and options.update_precommit:
127
- execute(["pre-commit", "autoupdate"])
359
+ self.execute_command(["pre-commit", "autoupdate"])
360
+
361
+ def _run_interactive_hooks(self, options: t.Any) -> None:
128
362
  if options.interactive:
129
- for hook in ("refurb", "bandit", "pyright"):
130
- self.run_interactive(hook)
131
- self.run_pre_commit()
363
+ for hook in interactive_hooks:
364
+ self.project_manager.run_interactive(hook)
365
+
366
+ def _clean_project(self, options: t.Any) -> None:
367
+ if options.clean:
368
+ if self.pkg_dir:
369
+ self.code_cleaner.clean_files(self.pkg_dir)
370
+ if self.pkg_path.stem == "crackerjack":
371
+ tests_dir = self.pkg_path / "tests"
372
+ if tests_dir.exists() and tests_dir.is_dir():
373
+ self.console.print("\nCleaning tests directory...\n")
374
+ self.code_cleaner.clean_files(tests_dir)
375
+
376
+ def _run_tests(self, options: t.Any) -> None:
377
+ if options.test:
378
+ self.console.print("\n\nRunning tests...\n")
379
+ result = self.execute_command(["pytest"], capture_output=True, text=True)
380
+ if result.stdout:
381
+ self.console.print(result.stdout)
382
+ if result.returncode > 0:
383
+ if result.stderr:
384
+ self.console.print(result.stderr)
385
+ self.console.print("\n\n❌ Tests failed. Please fix errors.\n")
386
+ return
387
+ self.console.print("\n\n✅ Tests passed successfully!\n")
388
+
389
+ def _bump_version(self, options: t.Any) -> None:
132
390
  for option in (options.publish, options.bump):
133
391
  if option:
134
- execute(["pdm", "bump", option])
392
+ self.execute_command(["pdm", "bump", option])
135
393
  break
394
+
395
+ def _publish_project(self, options: t.Any) -> None:
136
396
  if options.publish:
137
- build = execute(["pdm", "build"], capture_output=True, text=True)
138
- print(build.stdout)
397
+ build = self.execute_command(
398
+ ["pdm", "build"], capture_output=True, text=True
399
+ )
400
+ self.console.print(build.stdout)
139
401
  if build.returncode > 0:
140
- print(build.stderr)
141
- print("\n\nBuild failed. Please fix errors.\n")
402
+ self.console.print(build.stderr)
403
+ self.console.print("\n\nBuild failed. Please fix errors.\n")
142
404
  raise SystemExit(1)
143
- execute(["pdm", "publish", "--no-build"])
405
+ self.execute_command(["pdm", "publish", "--no-build"])
406
+
407
+ def _commit_and_push(self, options: t.Any) -> None:
144
408
  if options.commit:
145
409
  commit_msg = input("\nCommit message: ")
146
- execute(
147
- [
148
- "git",
149
- "commit",
150
- "-m",
151
- commit_msg,
152
- "--no-verify",
153
- "--",
154
- ".",
155
- ]
410
+ self.execute_command(
411
+ ["git", "commit", "-m", commit_msg, "--no-verify", "--", "."]
156
412
  )
157
- execute(["git", "push", "origin", "main"])
158
- print("\nCrackerjack complete!\n")
413
+ self.execute_command(["git", "push", "origin", "main"])
414
+
415
+ def execute_command(
416
+ self, cmd: list[str], **kwargs: t.Any
417
+ ) -> subprocess.CompletedProcess[str]:
418
+ if self.dry_run:
419
+ self.console.print(f"[yellow]Would run: {' '.join(cmd)}[/yellow]")
420
+ return CompletedProcess(cmd, 0, "", "")
421
+ return execute(cmd, **kwargs)
422
+
423
+ def process(self, options: t.Any) -> None:
424
+ self._setup_package()
425
+ self._update_project(options)
426
+ self._update_precommit(options)
427
+ self._run_interactive_hooks(options)
428
+ self._clean_project(options)
429
+ self.project_manager.run_pre_commit()
430
+ self._run_tests(options)
431
+ self._bump_version(options)
432
+ self._publish_project(options)
433
+ self._commit_and_push(options)
434
+ self.console.print("\nCrackerjack complete!\n")
159
435
 
160
436
 
161
437
  crackerjack_it = Crackerjack().process
@@ -49,8 +49,9 @@ max-complexity = 12
49
49
  convention = "google"
50
50
 
51
51
  [tool.vulture]
52
- min_confidence = 84
52
+ min_confidence = 86
53
53
  paths = ["crackerjack",]
54
+ ignore_names = ["cls"]
54
55
 
55
56
  [tool.creosote]
56
57
  paths = [
@@ -119,7 +120,7 @@ pythonPlatform = "Darwin"
119
120
 
120
121
  [project]
121
122
  name = "crackerjack"
122
- version = "0.11.3"
123
+ version = "0.11.5"
123
124
  description = "Default template for PDM package"
124
125
  requires-python = ">=3.13"
125
126
  readme = "README.md"
@@ -145,18 +146,19 @@ classifiers = [
145
146
  "Typing :: Typed",
146
147
  ]
147
148
  dependencies = [
148
- "click>=8.1.8",
149
149
  "autotyping>=24.9.0",
150
150
  "pre-commit>=4.1.0",
151
151
  "pytest>=8.3.5",
152
152
  "pydantic>=2.10.6",
153
153
  "pdm-bump>=0.9.10",
154
- "pdm>=2.22.3",
155
- "uv>=0.6.4",
154
+ "pdm>=2.22.4",
155
+ "uv>=0.6.5",
156
156
  "pytest-cov>=6.0.0",
157
157
  "pytest-mock>=3.14.0",
158
158
  "tomli-w>=1.2.0",
159
159
  "pytest-asyncio>=0.25.3",
160
+ "rich>=13.9.4",
161
+ "typer>=0.15.2",
160
162
  ]
161
163
  authors = [
162
164
  { name = "lesleslie", email = "les@wedgwoodwebworks.com" },
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.1
2
+ Name: crackerjack
3
+ Version: 0.12.0
4
+ Summary: Default template for PDM package
5
+ Keywords: black,ruff,mypy,creosote,refurb
6
+ Author-Email: lesleslie <les@wedgwoodwebworks.com>
7
+ Maintainer-Email: lesleslie <les@wedgwoodwebworks.com>
8
+ License: BSD-3-CLAUSE
9
+ Classifier: Environment :: Console
10
+ Classifier: Operating System :: POSIX
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Topic :: Software Development :: Quality Assurance
16
+ Classifier: Topic :: Software Development :: Testing
17
+ Classifier: Topic :: Utilities
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: License :: OSI Approved :: BSD License
20
+ Classifier: Typing :: Typed
21
+ Project-URL: homepage, https://github.com/lesleslie/crackerjack
22
+ Project-URL: documentation, https://github.com/lesleslie/crackerjack
23
+ Project-URL: repository, https://github.com/lesleslie/crackerjack
24
+ Requires-Python: >=3.13
25
+ Requires-Dist: autotyping>=24.9.0
26
+ Requires-Dist: pre-commit>=4.1.0
27
+ Requires-Dist: pytest>=8.3.5
28
+ Requires-Dist: pydantic>=2.10.6
29
+ Requires-Dist: pdm-bump>=0.9.10
30
+ Requires-Dist: pdm>=2.22.4
31
+ Requires-Dist: uv>=0.6.5
32
+ Requires-Dist: pytest-cov>=6.0.0
33
+ Requires-Dist: pytest-mock>=3.14.0
34
+ Requires-Dist: tomli-w>=1.2.0
35
+ Requires-Dist: pytest-asyncio>=0.25.3
36
+ Requires-Dist: rich>=13.9.4
37
+ Requires-Dist: typer>=0.15.2
38
+ Description-Content-Type: text/markdown
39
+
40
+ # Crackerjack: Elevate Your Python Development
41
+
42
+ [![Code style: crackerjack](https://img.shields.io/badge/code%20style-crackerjack-000042)](https://github.com/lesleslie/crackerjack)
43
+ [![Python Version](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/)
44
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
45
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
46
+ [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
47
+ [![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
48
+ [![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
49
+ [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
50
+
51
+ **Crackerjack** (`ˈkra-kər-ˌjak`): *a person or thing of marked excellence.*
52
+
53
+ Crackerjack is an opinionated Python project management tool designed to help you create, maintain, and publish high-quality Python projects with ease. It combines best-in-class tools and a streamlined workflow to ensure code quality, consistency, and reliability.
54
+
55
+ ## The Crackerjack Philosophy
56
+
57
+ Crackerjack is built on the following core principles:
58
+
59
+ - **Code Clarity:** Code should be easy to read, understand, and maintain.
60
+ - **Automation:** Tedious tasks should be automated, allowing developers to focus on solving problems.
61
+ - **Consistency:** Code style, formatting, and project structure should be consistent across projects.
62
+ - **Reliability:** Tests are essential, and code should be checked rigorously.
63
+ - **Tool Integration:** Leverage powerful existing tools instead of reinventing the wheel.
64
+ - **Static Typing:** Static typing is essential for all development.
65
+
66
+ ## Key Features
67
+
68
+ Crackerjack provides:
69
+
70
+ - **Effortless Project Setup:** Initializes new Python projects with a standard directory structure, `pyproject.toml`, and essential configuration files.
71
+ - **PDM Integration:** Manages dependencies and virtual environments using [PDM](https://pdm.fming.dev/) (with [uv](https://github.com/astral-sh/uv) enabled for speed).
72
+ - **Automated Code Cleaning:** Removes unnecessary docstrings, line comments, and trailing whitespace.
73
+ - **Consistent Code Formatting:** Enforces a consistent style using [Ruff](https://github.com/astral-sh/ruff), the lightning-fast Python linter and formatter.
74
+ - **Comprehensive Pre-commit Hooks:** Installs and manages a robust suite of pre-commit hooks to ensure code quality (see the "Pre-commit Hooks" section below).
75
+ - **Interactive Checks:** Supports interactive pre-commit hooks (like `refurb`, `bandit`, and `pyright`) to allow you to fix issues in real-time.
76
+ - **Built-in Testing:** Automatically runs tests using `pytest`.
77
+ - **Easy Version Bumping:** Provides commands to bump the project version (micro, minor, or major).
78
+ - **Simplified Publishing:** Automates publishing to PyPI via PDM.
79
+ - **Commit and Push:** Commits and pushes your changes.
80
+
81
+ ## Pre-commit Hooks
82
+
83
+ Crackerjack automatically installs and manages these pre-commit hooks:
84
+
85
+ 1. **pdm-lock-check:** Ensures the `pdm.lock` file is up to date.
86
+ 2. **Core pre-commit-hooks:** Essential hooks from [pre-commit-hooks](https://github.com/pre-commit/pre-commit-hooks) (e.g., `trailing-whitespace`, `end-of-file-fixer`).
87
+ 3. **Ruff:** [Ruff](https://github.com/astral-sh/ruff) for linting, code formatting, and general code style enforcement.
88
+ 4. **Vulture:** [Vulture](https://github.com/jendrikseipp/vulture) to identify dead code.
89
+ 5. **Creosote:** [Creosote](https://github.com/fredrikaverpil/creosote) to detect unused dependencies.
90
+ 6. **Flynt:** [Flynt](https://github.com/ikamensh/flynt/) for converting string formatting to f-strings.
91
+ 7. **Codespell:** [Codespell](https://github.com/codespell-project/codespell) for correcting typos in the code.
92
+ 8. **Autotyping:** [Autotyping](https://github.com/JelleZijlstra/autotyping) for adding type hints.
93
+ 9. **Refurb:** [Refurb](https://github.com/dosisod/refurb) to suggest code improvements.
94
+ 10. **Bandit:** [Bandit](https://github.com/PyCQA/bandit) to identify potential security vulnerabilities.
95
+ 11. **Pyright:** [Pyright](https://github.com/RobertCraigie/pyright-python) for static type checking.
96
+ 12. **Ruff (again):** A final Ruff pass to ensure all changes comply with the enforced style.
97
+
98
+ ## The Crackerjack Style Guide
99
+
100
+ Crackerjack projects adhere to these guidelines:
101
+
102
+ - **Static Typing:** Use type hints consistently throughout your code.
103
+ - **Explicit Naming:** Choose clear, descriptive names for classes, functions, variables, and other identifiers.
104
+ - **Markdown for Documentation:** Use Markdown (`.md`) for all documentation, including docstrings, READMEs, etc.
105
+ - **Pathlib:** Use `pathlib.Path` for handling file and directory paths instead of `os.path`.
106
+ - **Consistent Imports:** Use `import typing as t` for type hinting.
107
+ - **Constants and Config:** Do not use all-caps for constants or configuration settings.
108
+ - **Path Parameters:** Functions that handle file operations should accept `pathlib.Path` objects as parameters.
109
+ - **Dependency Management:** Use PDM for dependency management, package building, and publishing.
110
+ - **Testing:** Use pytest as your testing framework.
111
+ - **Python Version:** Crackerjack projects support the latest Python versions.
112
+ - **Clear Code:** Avoid overly complex code.
113
+ - **Modular:** Functions should do one thing well.
114
+
115
+ ## Installation
116
+
117
+ 1. **Python:** Ensure you have Python 3.13 installed.
118
+ 2. **PDM:** Install [PDM](https://pdm.fming.dev/) using `pipx`:
119
+
120
+ ```
121
+ pipx install pdm
122
+ ```
123
+
124
+ 3. **Crackerjack:** Install Crackerjack and initialize in your project root using:
125
+ ```
126
+ pip install crackerjack
127
+ cd your_project_root
128
+ python -m crackerjack
129
+ ```
130
+
131
+ ## Usage
132
+
133
+ Run Crackerjack from the root of your Python project using:
134
+
135
+ python -m crackerjack
136
+
137
+
138
+ ### Command-Line Options
139
+
140
+ - `-c`, `--commit`: Commit changes to Git.
141
+ - `-i`, `--interactive`: Run pre-commit hooks interactively when possible.
142
+ - `-n`, `--no-config-updates`: Skip updating configuration files (e.g., `pyproject.toml`).
143
+ - `-u`, `--update-precommit`: Update pre-commit hooks to the latest versions.
144
+ - `-v`, `--verbose`: Enable verbose output.
145
+ - `-p`, `--publish <micro|minor|major>`: Bump the project version and publish to PyPI using PDM.
146
+ - `-b`, `--bump <micro|minor|major>`: Bump the project version without publishing.
147
+ - `-x`, `--clean`: Clean code by removing docstrings, line comments, and extra whitespace.
148
+ - `-t`, `--test`: Run tests using `pytest`.
149
+ - `-h`, `--help`: Display help.
150
+
151
+ ### Example Workflows
152
+
153
+ - **Run checks, bump version, publish, then commit:**
154
+ ```
155
+ python -m crackerjack -p minor -c
156
+ ```
157
+
158
+ - **Clean code, run checks, run tests, then commit:**
159
+ ````
160
+ python -m crackerjack -c -x -t
161
+ ```
162
+
163
+ - **Run checks skipping config updates:**
164
+ ```
165
+ python -m crackerjack -n
166
+ ```
167
+
168
+ - **Bump the version and publish to PyPI:**
169
+ ```
170
+ python -m crackerjack -p micro
171
+ ```
172
+
173
+ - **Bump the version without publishing:**
174
+ ```
175
+ python -m crackerjack -b major
176
+ ```
177
+ - **Update pre-commit hooks:**
178
+ ```
179
+ python -m crackerjack -u
180
+ ```
181
+ - **Get help:**
182
+ ```
183
+ python -m crackerjack -h
184
+ ```
185
+
186
+ ## Contributing
187
+
188
+ Crackerjack is an evolving project. Contributions are welcome! Please open a pull request or issue.
189
+
190
+ To contribute:
191
+
192
+ 1. Add Crackerjack as a development dependency to your project:
193
+ ```
194
+ pdm add -G dev crackerjack
195
+ ```
196
+
197
+ 2. Run checks and tests before submitting:
198
+ ```
199
+ python -m crackerjack -x -t
200
+ ```
201
+
202
+ This ensures your code meets all quality standards before submission.
203
+
204
+ ## License
205
+
206
+ This project is licensed under the terms of the BSD 3-Clause license.
207
+
208
+ ## Acknowledgments
209
+
210
+ - **PDM:** For excellent dependency and virtual environment management.
211
+ - **Ruff:** For lightning-fast linting and code formatting.
212
+ - **pre-commit:** For the robust hook management system.
213
+ - **pytest:** For the flexible and powerful testing framework.
214
+ - **uv:** For greatly improving PDM speeds.
215
+ - **bandit:** For finding security vulnerabilities.
216
+ - **vulture:** for dead code detection.
217
+ - **creosote:** For unused dependency detection.
218
+ - **flynt:** For f-string conversion.
219
+ - **codespell:** For spelling correction.
220
+ - **autotyping:** For automatically adding type hints.
221
+ - **refurb:** For code improvement suggestions.
222
+ - **pyright:** For static type checking.
223
+ - **Typer:** For the creation of the CLI.
224
+
225
+ ---
@@ -1,11 +1,17 @@
1
- crackerjack-0.11.4.dist-info/METADATA,sha256=sscYWhgl7F0TpE_pcQqrLV_882tSTQEYM1ipKOwBPys,6322
2
- crackerjack-0.11.4.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- crackerjack-0.11.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- crackerjack-0.11.4.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
5
- crackerjack/.gitignore,sha256=7qePRaD8q-U6oV3gvgAcwFF8GudcRGAWf-Z-0IDqMaE,207
1
+ crackerjack-0.12.0.dist-info/METADATA,sha256=WvBhT8kVf9gxBhKuM_yH4EwNdnuYGEpBEBKYIlptc8A,10289
2
+ crackerjack-0.12.0.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ crackerjack-0.12.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ crackerjack-0.12.0.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
5
+ crackerjack/.coverage,sha256=dLzPzp72qZEXohNfxnOAlRwvM9dqF06-HoFqfvXZd1U,53248
6
+ crackerjack/.gitignore,sha256=l8ErBAypC3rI6N9lhc7ZMdOw87t0Tz69ZW5C6uj15Wg,214
6
7
  crackerjack/.libcst.codemod.yaml,sha256=a8DlErRAIPV1nE6QlyXPAzTOgkB24_spl2E9hphuf5s,772
7
8
  crackerjack/.pdm.toml,sha256=dZe44HRcuxxCFESGG8SZIjmc-cGzSoyK3Hs6t4NYA8w,23
8
- crackerjack/.pre-commit-config.yaml,sha256=MdCTkavvr84yaP8B3DYuSjCfR_X4q3tpDO8yZAR5t9s,2265
9
+ crackerjack/.pre-commit-config.yaml,sha256=cLIzulHerHmCHjo5UWA2JAMm5d5PjwGpIp5Lx-nL3FQ,2267
10
+ crackerjack/.pytest_cache/.gitignore,sha256=Ptcxtl0GFQwTji2tsL4Gl1UIiKa0frjEXsya26i46b0,37
11
+ crackerjack/.pytest_cache/CACHEDIR.TAG,sha256=N9yI75oKvt2-gQU6bdj9-xOvthMEXqHrSlyBWnSjveQ,191
12
+ crackerjack/.pytest_cache/README.md,sha256=c_1vzN2ALEGaay2YPWwxc7fal1WKxLWJ7ewt_kQ9ua0,302
13
+ crackerjack/.pytest_cache/v/cache/nodeids,sha256=T1PNoYwrqgwDVLtfmj7L5e0Sq02OEbqHPC8RFhICuUU,2
14
+ crackerjack/.pytest_cache/v/cache/stepwise,sha256=T1PNoYwrqgwDVLtfmj7L5e0Sq02OEbqHPC8RFhICuUU,2
9
15
  crackerjack/.ruff_cache/.gitignore,sha256=aEiIwOuxfzdCmLZe4oB1JsBmCUxwG8x-u-HBCV9JT8E,1
10
16
  crackerjack/.ruff_cache/0.1.11/3256171999636029978,sha256=-RLDsRf5uj09SyFQVzjwQ1HkTxjIRxNLLE24SEJxD4g,248
11
17
  crackerjack/.ruff_cache/0.1.14/602324811142551221,sha256=HIYvldb69IHdMzquAA8JpzU2RDT9shEB_dPvzyeFZ_g,248
@@ -34,11 +40,13 @@ crackerjack/.ruff_cache/0.7.1/1024065805990144819,sha256=3Sww592NB0PWBNHU_UIqvqg
34
40
  crackerjack/.ruff_cache/0.7.1/285614542852677309,sha256=mOHKRzKoSvW-1sHtqI_LHWRt-mBinJ4rQRtp9Yqzv5I,224
35
41
  crackerjack/.ruff_cache/0.7.3/16061516852537040135,sha256=AWJR9gmaO7-wpv8mY1homuwI8CrMPI3VrnbXH-wRPlg,224
36
42
  crackerjack/.ruff_cache/0.8.4/16354268377385700367,sha256=Ksz4X8N6Z1i83N0vV1PxmBRlqgjrtzmDCOg7VBF4baQ,224
43
+ crackerjack/.ruff_cache/0.9.10/12813592349865671909,sha256=c68BYIC0rNDH3TrWX-RKYI1gb7KGKAck8MOpwzySX7U,224
37
44
  crackerjack/.ruff_cache/0.9.3/13948373885254993391,sha256=kGhtIkzPUtKAgvlKs3D8j4QM4qG8RhsHrmQJI69Sv3o,224
38
- crackerjack/.ruff_cache/0.9.9/12813592349865671909,sha256=RfNLBMX23ATK_GzSmnPl0h8AsUG9sZU3GwPCsVHe2FU,224
45
+ crackerjack/.ruff_cache/0.9.9/12813592349865671909,sha256=tmr8_vhRD2OxsVuMfbJPdT9fDFX-d5tfC5U9jgziyho,224
46
+ crackerjack/.ruff_cache/0.9.9/8843823720003377982,sha256=e4ymkXfQsUg5e_mtO34xTsaTvs1uA3_fI216Qq9qCAM,136
39
47
  crackerjack/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
40
- crackerjack/__init__.py,sha256=AuglbbJHkUJ2GdvyT0ca35ntexo1RkT2V6DgypoFeEk,121
41
- crackerjack/__main__.py,sha256=3TrS-Hejbx315O558j3MI2L59VX0Y6t0tz5L41NTVG0,1738
42
- crackerjack/crackerjack.py,sha256=Aa5Mb0p63W2AG0rcYzU1H2iez-z42eGqDP3dWPRbh9A,6693
43
- crackerjack/pyproject.toml,sha256=DMmVRWWGE0KWuaO_DrGp8YhqheIiaZim9ArfoGH_waw,3398
44
- crackerjack-0.11.4.dist-info/RECORD,,
48
+ crackerjack/__init__.py,sha256=EOKnIXfBAvxS55uPkpk5DbcNqVS29wja_IcCPaGwyus,141
49
+ crackerjack/__main__.py,sha256=nraD_3T7XokPEniztNRxfh6hMCRS7ihRucQWswb7j9I,3485
50
+ crackerjack/crackerjack.py,sha256=W7azMMGcZ5xZjthU6vIqHhXm0UAmENFSPJvb4pFaxjY,17401
51
+ crackerjack/pyproject.toml,sha256=z5FPRNjNaa9_6cb4gnEOdRdjqAOKI57Lunk-E3F3ZeM,3442
52
+ crackerjack-0.12.0.dist-info/RECORD,,
@@ -1,164 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: crackerjack
3
- Version: 0.11.4
4
- Summary: Default template for PDM package
5
- Keywords: black,ruff,mypy,creosote,refurb
6
- Author-Email: lesleslie <les@wedgwoodwebworks.com>
7
- Maintainer-Email: lesleslie <les@wedgwoodwebworks.com>
8
- License: BSD-3-CLAUSE
9
- Classifier: Environment :: Console
10
- Classifier: Operating System :: POSIX
11
- Classifier: Programming Language :: Python
12
- Classifier: Programming Language :: Python :: 3.13
13
- Classifier: Development Status :: 4 - Beta
14
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
- Classifier: Topic :: Software Development :: Quality Assurance
16
- Classifier: Topic :: Software Development :: Testing
17
- Classifier: Topic :: Utilities
18
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
- Classifier: License :: OSI Approved :: BSD License
20
- Classifier: Typing :: Typed
21
- Project-URL: homepage, https://github.com/lesleslie/crackerjack
22
- Project-URL: documentation, https://github.com/lesleslie/crackerjack
23
- Project-URL: repository, https://github.com/lesleslie/crackerjack
24
- Requires-Python: >=3.13
25
- Requires-Dist: click>=8.1.8
26
- Requires-Dist: autotyping>=24.9.0
27
- Requires-Dist: pre-commit>=4.1.0
28
- Requires-Dist: pytest>=8.3.5
29
- Requires-Dist: pydantic>=2.10.6
30
- Requires-Dist: pdm-bump>=0.9.10
31
- Requires-Dist: pdm>=2.22.3
32
- Requires-Dist: uv>=0.6.4
33
- Requires-Dist: pytest-cov>=6.0.0
34
- Requires-Dist: pytest-mock>=3.14.0
35
- Requires-Dist: tomli-w>=1.2.0
36
- Requires-Dist: pytest-asyncio>=0.25.3
37
- Description-Content-Type: text/markdown
38
-
39
- # Crackerjack Python
40
-
41
- [![Python: 3.13](https://img.shields.io/badge/python-3.13%2B-blue)](https://docs.python.org/3/)
42
- [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
43
- [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
44
- [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
45
- [![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
46
- [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
47
- [![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
48
- [![Code style: crackerjack](https://img.shields.io/badge/code%20style-crackerjack-000042)](https://github.com/lesleslie/crackerjack)
49
-
50
- Crackerjack is a python coding style which uses a minimalist approach to produce elegant, easy to read, code.
51
-
52
- crack·​er·​jack ˈkra-kər-ˌjak
53
- : a person or thing of marked excellence
54
-
55
- ### **Why Crackerjack?**
56
-
57
- Crackerjack works on the theory that with static typing and explicit class,
58
- function, variable, and other object names - the code should be
59
- straight forward to read. Documentation and tests should be able to write themselves using a generative ai.
60
- Crackerjack provides a set of guidelines and utilities to keep the codebase clean, elegant, standardized, and
61
- easily readable.
62
-
63
- ### **What does this package do?**
64
-
65
- This package:
66
-
67
- - streamlines and standardizes code style across numerous packages
68
-
69
- - installs, or updates, a project's pre-commit tools as well as .gitignore & other config files
70
- to comply with evolving crackerjack standards
71
-
72
- - runs the following pre-commit hooks (in order):
73
- * [pdm-lock-check](https://github.com/pdm-project/pdm)
74
- * various core [pre-commit-hooks](https://github.com/pre-commit/pre-commit-hooks)
75
- * [ruff](https://github.com/charliermarsh/ruff-pre-commit)
76
- * [vulture](https://github.com/jendrikseipp/vulture)
77
- * [creosote](https://github.com/fredrikaverpil/creosote)
78
- * [flynt](https://github.com/ikamensh/flynt/)
79
- * [codespell](https://github.com/codespell-project/codespell)
80
- * [autotyping](https://github.com/JelleZijlstra/autotyping)
81
- * [refurb](https://github.com/dosisod/refurb)
82
- * [bandit](https://github.com/PyCQA/bandit)
83
- * [pyright](https://github.com/RobertCraigie/pyright-python)
84
- * [ruff](https://github.com/charliermarsh/ruff-pre-commit) (again for sanity checking)
85
-
86
- - converts/creates documentation in Markdown (md) (work in progress)
87
-
88
- - runs tests and generates pytest mock stubs if needed (work in progress)
89
-
90
- - bumps the project version and publishes it to PyPI
91
-
92
- - commits changes to git repositories
93
-
94
- ### **What are the rules?**
95
-
96
- (...more what you'd call "guidelines" than actual rules. -Captain Barbossa )
97
-
98
- - code is statically typed
99
-
100
- - all docstrings, README's, and other documentation is to be done in Markdown (md)
101
-
102
- - use aiopath.AsyncPath or pathlib.Path not os.path
103
-
104
- - import typing as t
105
-
106
- - do not capitalize all letters in configuration settings or constants (we diverge from PEP-8 here
107
- for not other reason than it looks ugly)
108
-
109
- - functions that deal with path operations should get passed AsyncPaths or Paths - not strings
110
-
111
- - use PDM (uv support enabled) for dependency management and package building/publishing
112
-
113
- - use pdoc and mkdocs for producing documentation
114
-
115
- - use pytest for testing
116
-
117
- - be compliant with, and only support, the latest python version within 2 months after release
118
-
119
-
120
-
121
- [//]: # (- variable docstrings are supported as outlined in)
122
-
123
- [//]: # ( [PEP-224]&#40;https://www.python.org/dev/peps/pep-0224/&#41; as well as the module-level)
124
-
125
- [//]: # ( __pdoc__ dictionary &#40;see [pdoc docs]&#40;)
126
-
127
- [//]: # ( https://pdoc3.github.io/pdoc/doc/pdoc/#overriding-docstrings-with-__pdoc__&#41;&#41;)
128
-
129
-
130
- ### **Installation**
131
-
132
- From your projects root directory:
133
-
134
- ```pdm add -d crackerjack```
135
-
136
- ### **Usage**
137
-
138
- From your projects root directory:
139
-
140
- ```python -m crackerjack```
141
-
142
- For a full list of options:
143
-
144
- ```python -m crackerjack -h```
145
-
146
- When you ready to publish your project:
147
-
148
- ``python -m crackerjack -p micro``
149
-
150
- The -p option not only publishes your project but will bump your
151
- project version for you. The options are 'micro', 'minor', and 'major'.
152
- Put the -c option at the end and commit the bumped version to your git
153
- repository at the same time:
154
-
155
- ``python -m crackerjack -p micro -c``
156
-
157
- ### **Contributing**
158
-
159
- Crackerjack is currently an evolving standard. If you like the idea, but don't like certain things about it, or
160
- would like new features added, let me know in Discussions, Issues, or email me.
161
-
162
- ### **License**
163
-
164
- BSD-3-Clause