devklean 1.0.1__tar.gz → 1.0.2__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 (84) hide show
  1. {devklean-1.0.1 → devklean-1.0.2}/CHANGELOG.md +15 -1
  2. {devklean-1.0.1 → devklean-1.0.2}/PKG-INFO +4 -1
  3. {devklean-1.0.1 → devklean-1.0.2}/README.md +2 -0
  4. {devklean-1.0.1 → devklean-1.0.2}/pyproject.toml +1 -0
  5. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/__main__.py +1 -1
  6. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/_version.py +1 -1
  7. devklean-1.0.2/src/devklean/cli/__init__.py +11 -0
  8. devklean-1.0.2/src/devklean/cli/commands/__init__.py +13 -0
  9. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/commands/clean.py +7 -1
  10. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/main.py +3 -1
  11. devklean-1.0.2/src/devklean/deletion/__init__.py +31 -0
  12. devklean-1.0.2/src/devklean/output/__init__.py +15 -0
  13. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/output/console.py +5 -3
  14. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/tui.py +7 -1
  15. {devklean-1.0.1 → devklean-1.0.2}/tests/test_windows_guard.py +3 -0
  16. devklean-1.0.1/src/devklean/cli/__init__.py +0 -3
  17. devklean-1.0.1/src/devklean/cli/commands/__init__.py +0 -4
  18. devklean-1.0.1/src/devklean/deletion/__init__.py +0 -17
  19. devklean-1.0.1/src/devklean/output/__init__.py +0 -5
  20. {devklean-1.0.1 → devklean-1.0.2}/.gitignore +0 -0
  21. {devklean-1.0.1 → devklean-1.0.2}/LICENSE +0 -0
  22. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/__init__.py +0 -0
  23. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/commands/common.py +0 -0
  24. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/commands/doctor.py +0 -0
  25. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/commands/history.py +0 -0
  26. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/commands/restore.py +0 -0
  27. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/commands/scan.py +0 -0
  28. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/confirmation.py +0 -0
  29. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/dispatcher.py +0 -0
  30. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/cli/parser.py +0 -0
  31. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/config/__init__.py +0 -0
  32. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/config/defaults.py +0 -0
  33. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/config/manager.py +0 -0
  34. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/config/models.py +0 -0
  35. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/config/paths.py +0 -0
  36. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/config/targets.py +0 -0
  37. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/deletion/history.py +0 -0
  38. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/deletion/integrity.py +0 -0
  39. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/deletion/metadata.py +0 -0
  40. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/deletion/paths.py +0 -0
  41. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/deletion/safety.py +0 -0
  42. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/deletion/trash.py +0 -0
  43. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/formatting.py +0 -0
  44. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/logging_setup.py +0 -0
  45. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/models.py +0 -0
  46. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/output/base.py +0 -0
  47. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/output/history_payload.py +0 -0
  48. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/output/json.py +0 -0
  49. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/output/scan_payload.py +0 -0
  50. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/output/sorting.py +0 -0
  51. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/output/text.py +0 -0
  52. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/output/theme.py +0 -0
  53. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/scanner/__init__.py +0 -0
  54. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/scanner/filters.py +0 -0
  55. {devklean-1.0.1 → devklean-1.0.2}/src/devklean/scanner/scanner.py +0 -0
  56. {devklean-1.0.1 → devklean-1.0.2}/tests/benchmark_scanner.py +0 -0
  57. {devklean-1.0.1 → devklean-1.0.2}/tests/conftest.py +0 -0
  58. {devklean-1.0.1 → devklean-1.0.2}/tests/test_clean_flow.py +0 -0
  59. {devklean-1.0.1 → devklean-1.0.2}/tests/test_cli_parser.py +0 -0
  60. {devklean-1.0.1 → devklean-1.0.2}/tests/test_config.py +0 -0
  61. {devklean-1.0.1 → devklean-1.0.2}/tests/test_config_precedence.py +0 -0
  62. {devklean-1.0.1 → devklean-1.0.2}/tests/test_confirmation.py +0 -0
  63. {devklean-1.0.1 → devklean-1.0.2}/tests/test_console.py +0 -0
  64. {devklean-1.0.1 → devklean-1.0.2}/tests/test_deletion.py +0 -0
  65. {devklean-1.0.1 → devklean-1.0.2}/tests/test_doctor.py +0 -0
  66. {devklean-1.0.1 → devklean-1.0.2}/tests/test_dry_run.py +0 -0
  67. {devklean-1.0.1 → devklean-1.0.2}/tests/test_formatting.py +0 -0
  68. {devklean-1.0.1 → devklean-1.0.2}/tests/test_history.py +0 -0
  69. {devklean-1.0.1 → devklean-1.0.2}/tests/test_integration.py +0 -0
  70. {devklean-1.0.1 → devklean-1.0.2}/tests/test_integrity.py +0 -0
  71. {devklean-1.0.1 → devklean-1.0.2}/tests/test_json_output.py +0 -0
  72. {devklean-1.0.1 → devklean-1.0.2}/tests/test_logging.py +0 -0
  73. {devklean-1.0.1 → devklean-1.0.2}/tests/test_main_signal.py +0 -0
  74. {devklean-1.0.1 → devklean-1.0.2}/tests/test_models.py +0 -0
  75. {devklean-1.0.1 → devklean-1.0.2}/tests/test_output_sorting.py +0 -0
  76. {devklean-1.0.1 → devklean-1.0.2}/tests/test_packaging.py +0 -0
  77. {devklean-1.0.1 → devklean-1.0.2}/tests/test_perf_scan.py +0 -0
  78. {devklean-1.0.1 → devklean-1.0.2}/tests/test_restore.py +0 -0
  79. {devklean-1.0.1 → devklean-1.0.2}/tests/test_safety.py +0 -0
  80. {devklean-1.0.1 → devklean-1.0.2}/tests/test_scan_json_cli.py +0 -0
  81. {devklean-1.0.1 → devklean-1.0.2}/tests/test_scan_permissions.py +0 -0
  82. {devklean-1.0.1 → devklean-1.0.2}/tests/test_scan_settings.py +0 -0
  83. {devklean-1.0.1 → devklean-1.0.2}/tests/test_scanner.py +0 -0
  84. {devklean-1.0.1 → devklean-1.0.2}/tests/test_text_renderer.py +0 -0
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.2] - 2026-07-01
11
+
12
+ ### Changed
13
+
14
+ - Replaced `print()` calls in the console layer with `click.echo()` for improved
15
+ Unicode and Windows terminal compatibility.
16
+ - Fixed `src/devklean/__main__.py` to correctly import `main` from
17
+ `devklean.cli.main`.
18
+ - Made package `__init__.py` imports lazy to avoid pulling in `send2trash`
19
+ during unrelated imports.
20
+ - Added `click` as a runtime dependency.
21
+ - Updated Windows guard test to work in subprocesses with `PYTHONPATH=src`.
22
+
10
23
  ## [1.0.1] - 2026-06-30
11
24
 
12
25
  ### Fixed
@@ -91,7 +104,8 @@ First public pre-release.
91
104
  per-target sizing.
92
105
  - **Packaging** — distributable via `pip`/`pipx`; MIT licensed.
93
106
 
94
- [Unreleased]: https://github.com/smurftyy/devklean/compare/v1.0.1...HEAD
107
+ [Unreleased]: https://github.com/smurftyy/devklean/compare/v1.0.2...HEAD
108
+ [1.0.2]: https://github.com/smurftyy/devklean/compare/v1.0.1...v1.0.2
95
109
  [1.0.1]: https://github.com/smurftyy/devklean/compare/v1.0.0...v1.0.1
96
110
  [1.0.0]: https://github.com/smurftyy/devklean/compare/v0.1.0...v1.0.0
97
111
  [0.1.0]: https://github.com/smurftyy/devklean/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devklean
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Clean up common development artifacts (node_modules, .venv, caches) to reclaim disk space
5
5
  Project-URL: Homepage, https://github.com/smurftyy/devklean
6
6
  Project-URL: Repository, https://github.com/smurftyy/devklean
@@ -24,6 +24,7 @@ Classifier: Programming Language :: Python :: 3.12
24
24
  Classifier: Topic :: System :: Filesystems
25
25
  Classifier: Topic :: Utilities
26
26
  Requires-Python: >=3.8
27
+ Requires-Dist: click>=8.1.0
27
28
  Requires-Dist: send2trash>=1.8.2
28
29
  Requires-Dist: tomli>=2.0.0; python_version < '3.11'
29
30
  Provides-Extra: dev
@@ -43,6 +44,8 @@ Description-Content-Type: text/markdown
43
44
 
44
45
  **Reclaim disk space by safely cleaning up development artifacts** — `node_modules`, `.venv`, build caches, and other regenerable directories — with a trash-backed safety net so nothing is ever lost to a typo.
45
46
 
47
+ ![devklean in action](docs/assets/devklean.png)
48
+
46
49
  <!-- TODO: insert terminal recording GIF of `devklean clean` here -->
47
50
 
48
51
  ## Why
@@ -7,6 +7,8 @@
7
7
 
8
8
  **Reclaim disk space by safely cleaning up development artifacts** — `node_modules`, `.venv`, build caches, and other regenerable directories — with a trash-backed safety net so nothing is ever lost to a typo.
9
9
 
10
+ ![devklean in action](docs/assets/devklean.png)
11
+
10
12
  <!-- TODO: insert terminal recording GIF of `devklean clean` here -->
11
13
 
12
14
  ## Why
@@ -31,6 +31,7 @@ classifiers = [
31
31
  ]
32
32
  dependencies = [
33
33
  "tomli>=2.0.0; python_version < '3.11'",
34
+ "click>=8.1.0",
34
35
  "send2trash>=1.8.2",
35
36
  ]
36
37
 
@@ -1,4 +1,4 @@
1
- from devklean.cli import main
1
+ from devklean.cli.main import main
2
2
 
3
3
  if __name__ == "__main__":
4
4
  main()
@@ -5,4 +5,4 @@ this file (see ``[tool.hatch.version]`` in pyproject.toml), so a release bump is
5
5
  a one-line change here and nowhere else.
6
6
  """
7
7
 
8
- __version__ = "1.0.1"
8
+ __version__ = "1.0.2"
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import import_module
4
+
5
+ __all__ = ["main"]
6
+
7
+
8
+ def __getattr__(name: str):
9
+ if name == "main":
10
+ return import_module("devklean.cli.main").main
11
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import import_module
4
+
5
+ __all__ = ["run_clean", "run_scan"]
6
+
7
+
8
+ def __getattr__(name: str):
9
+ if name == "run_clean":
10
+ return import_module("devklean.cli.commands.clean").run_clean
11
+ if name == "run_scan":
12
+ return import_module("devklean.cli.commands.scan").run_scan
13
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -9,11 +9,17 @@ from devklean.cli.confirmation import (
9
9
  exceeds_threshold,
10
10
  )
11
11
  from devklean.config.models import AppConfig
12
- from devklean.deletion import SafetyValidator, delete_items
12
+ from devklean.deletion.safety import SafetyValidator
13
13
  from devklean.models import CleanableItem
14
14
  from devklean.output.base import Renderer
15
15
 
16
16
 
17
+ def delete_items(*args, **kwargs):
18
+ from devklean.deletion import delete_items as _delete_items
19
+
20
+ return _delete_items(*args, **kwargs)
21
+
22
+
17
23
  def _confirm_deletion(
18
24
  renderer: Renderer,
19
25
  count: int,
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import sys
4
4
 
5
+ import click
6
+
5
7
  from devklean.cli.dispatcher import dispatch
6
8
  from devklean.cli.parser import build_parser, resolve_bare_invocation
7
9
  from devklean.config import ConfigManager
@@ -62,5 +64,5 @@ def main() -> None:
62
64
  sys.exit(exit_code)
63
65
  except KeyboardInterrupt:
64
66
  # 130 is the Unix convention for a SIGINT-terminated process (128 + 2).
65
- print("\nAborted.", file=sys.stderr)
67
+ click.echo("\nAborted.", file=sys.stderr)
66
68
  sys.exit(130)
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import import_module
4
+
5
+ __all__ = [
6
+ "IntegrityReport",
7
+ "MetadataManager",
8
+ "SafetyValidator",
9
+ "SafetyViolation",
10
+ "check_integrity",
11
+ "delete_items",
12
+ "get_deletion_metadata_dir",
13
+ ]
14
+
15
+
16
+ def __getattr__(name: str):
17
+ if name == "IntegrityReport":
18
+ return import_module("devklean.deletion.integrity").IntegrityReport
19
+ if name == "check_integrity":
20
+ return import_module("devklean.deletion.integrity").check_integrity
21
+ if name == "MetadataManager":
22
+ return import_module("devklean.deletion.metadata").MetadataManager
23
+ if name == "get_deletion_metadata_dir":
24
+ return import_module("devklean.deletion.paths").get_deletion_metadata_dir
25
+ if name == "SafetyValidator":
26
+ return import_module("devklean.deletion.safety").SafetyValidator
27
+ if name == "SafetyViolation":
28
+ return import_module("devklean.deletion.safety").SafetyViolation
29
+ if name == "delete_items":
30
+ return import_module("devklean.deletion.trash").delete_items
31
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import import_module
4
+
5
+ __all__ = ["JsonRenderer", "Renderer", "TextRenderer"]
6
+
7
+
8
+ def __getattr__(name: str):
9
+ if name == "Renderer":
10
+ return import_module("devklean.output.base").Renderer
11
+ if name == "JsonRenderer":
12
+ return import_module("devklean.output.json").JsonRenderer
13
+ if name == "TextRenderer":
14
+ return import_module("devklean.output.text").TextRenderer
15
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -4,6 +4,8 @@ import os
4
4
  import sys
5
5
  from typing import TextIO
6
6
 
7
+ import click
8
+
7
9
  from devklean.output.theme import Palette, get_theme
8
10
 
9
11
  # Semantic symbols — fixed across every command and renderer.
@@ -73,7 +75,7 @@ class Console:
73
75
 
74
76
  def _line(self, symbol: str, role: str, message: str) -> None:
75
77
  prefix = self.paint(symbol, role)
76
- print(f"{prefix} {message}", file=self._stream)
78
+ click.echo(f"{prefix} {message}", file=self._stream)
77
79
 
78
80
  def success(self, message: str) -> None:
79
81
  self._line(SYM_SUCCESS, "success", message)
@@ -88,7 +90,7 @@ class Console:
88
90
  self._line(SYM_INFO, "info", message)
89
91
 
90
92
  def detail(self, message: str) -> None:
91
- print(self.paint(message, "detail"), file=self._stream)
93
+ click.echo(self.paint(message, "detail"), file=self._stream)
92
94
 
93
95
  def plain(self, message: str = "") -> None:
94
- print(message, file=self._stream)
96
+ click.echo(message, file=self._stream)
@@ -5,13 +5,19 @@ from devklean.cli.confirmation import (
5
5
  confirm_large_deletion,
6
6
  exceeds_threshold,
7
7
  )
8
- from devklean.deletion import SafetyValidator, delete_items
8
+ from devklean.deletion.safety import SafetyValidator
9
9
  from devklean.formatting import format_size, truncate
10
10
  from devklean.models import CleanableItem
11
11
  from devklean.output.base import Renderer
12
12
  from devklean.output.sorting import items_by_size_desc
13
13
 
14
14
 
15
+ def delete_items(*args, **kwargs):
16
+ from devklean.deletion import delete_items as _delete_items
17
+
18
+ return _delete_items(*args, **kwargs)
19
+
20
+
15
21
  def interactive_ui(stdscr, items: list[CleanableItem], dry_run: bool) -> list[int] | None:
16
22
  import curses # Unix-only; imported lazily so this module loads on Windows.
17
23
 
@@ -10,6 +10,7 @@ These are the Windows-relevant behaviors, so they run on every platform.
10
10
  from __future__ import annotations
11
11
 
12
12
  import io
13
+ import os
13
14
  import subprocess
14
15
  import sys
15
16
  from argparse import Namespace
@@ -52,10 +53,12 @@ def test_tui_module_imports_cold_without_curses() -> None:
52
53
  crash every command on Windows) and an import-order/circular-import
53
54
  regression that an in-process import would mask.
54
55
  """
56
+ env = {**os.environ, "PYTHONPATH": str(Path(__file__).resolve().parents[1] / "src")}
55
57
  result = subprocess.run(
56
58
  [sys.executable, "-c", "import devklean.tui; print(devklean.tui.run_interactive.__name__)"],
57
59
  capture_output=True,
58
60
  text=True,
61
+ env=env,
59
62
  )
60
63
  assert result.returncode == 0, result.stderr
61
64
  assert "run_interactive" in result.stdout
@@ -1,3 +0,0 @@
1
- from devklean.cli.main import main
2
-
3
- __all__ = ["main"]
@@ -1,4 +0,0 @@
1
- from devklean.cli.commands.clean import run_clean
2
- from devklean.cli.commands.scan import run_scan
3
-
4
- __all__ = ["run_clean", "run_scan"]
@@ -1,17 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from devklean.deletion.integrity import IntegrityReport, check_integrity
4
- from devklean.deletion.metadata import MetadataManager
5
- from devklean.deletion.paths import get_deletion_metadata_dir
6
- from devklean.deletion.safety import SafetyValidator, SafetyViolation
7
- from devklean.deletion.trash import delete_items
8
-
9
- __all__ = [
10
- "IntegrityReport",
11
- "MetadataManager",
12
- "SafetyValidator",
13
- "SafetyViolation",
14
- "check_integrity",
15
- "delete_items",
16
- "get_deletion_metadata_dir",
17
- ]
@@ -1,5 +0,0 @@
1
- from devklean.output.base import Renderer
2
- from devklean.output.json import JsonRenderer
3
- from devklean.output.text import TextRenderer
4
-
5
- __all__ = ["JsonRenderer", "Renderer", "TextRenderer"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes