py-wlcli 0.8.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.
Files changed (320) hide show
  1. py_wlcli-0.8.0.dist-info/METADATA +139 -0
  2. py_wlcli-0.8.0.dist-info/RECORD +320 -0
  3. py_wlcli-0.8.0.dist-info/WHEEL +4 -0
  4. py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
  5. py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
  6. wl_cli/__init__.py +27 -0
  7. wl_cli/__main__.py +130 -0
  8. wl_cli/capabilities.py +79 -0
  9. wl_cli/cc/__init__.py +5 -0
  10. wl_cli/cc/checks.py +1019 -0
  11. wl_cli/cc/cli.py +370 -0
  12. wl_cli/cc/mutate.py +1350 -0
  13. wl_cli/cli_click.py +156 -0
  14. wl_cli/commands/__init__.py +84 -0
  15. wl_cli/commands/base.py +80 -0
  16. wl_cli/commands/buildcommands/__init__.py +1 -0
  17. wl_cli/commands/buildcommands/build.py +71 -0
  18. wl_cli/commands/buildcommands/build_utils.py +47 -0
  19. wl_cli/commands/buildcommands/builders/__init__.py +18 -0
  20. wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
  21. wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
  22. wl_cli/commands/buildcommands/builders/environment.py +81 -0
  23. wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
  24. wl_cli/commands/buildcommands/builders/stubs.py +58 -0
  25. wl_cli/commands/buildcommands/dist.py +326 -0
  26. wl_cli/commands/buildcommands/test.py +89 -0
  27. wl_cli/commands/clean/__init__.py +1 -0
  28. wl_cli/commands/clean/build.py +151 -0
  29. wl_cli/commands/clean/clean.py +294 -0
  30. wl_cli/commands/clean/clean_utils.py +77 -0
  31. wl_cli/commands/clean/core.py +236 -0
  32. wl_cli/commands/clean/dead_code/__init__.py +0 -0
  33. wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
  34. wl_cli/commands/clean/dead_code/detector.py +161 -0
  35. wl_cli/commands/clean/dead_code/fixer.py +176 -0
  36. wl_cli/commands/clean/dead_code/reporter.py +295 -0
  37. wl_cli/commands/clean/dead_code/utils.py +0 -0
  38. wl_cli/commands/clean/lfs.py +215 -0
  39. wl_cli/commands/clean/python.py +81 -0
  40. wl_cli/commands/clean/rust.py +91 -0
  41. wl_cli/commands/clean/utils/__init__.py +34 -0
  42. wl_cli/commands/clean/venv.py +91 -0
  43. wl_cli/commands/cleanup.py +73 -0
  44. wl_cli/commands/click_base.py +79 -0
  45. wl_cli/commands/config/__init__.py +1 -0
  46. wl_cli/commands/config/config.py +291 -0
  47. wl_cli/commands/debug/__init__.py +6 -0
  48. wl_cli/commands/debug/debug.py +309 -0
  49. wl_cli/commands/debug/debug_cache.py +86 -0
  50. wl_cli/commands/debug/debug_subagent.py +156 -0
  51. wl_cli/commands/debug/output_formatter.py +165 -0
  52. wl_cli/commands/debug/source_hasher.py +92 -0
  53. wl_cli/commands/format/__init__.py +13 -0
  54. wl_cli/commands/format/format_cache_adapter.py +125 -0
  55. wl_cli/commands/format/format_command.py +197 -0
  56. wl_cli/commands/format/format_coordinator.py +191 -0
  57. wl_cli/commands/format/format_executor.py +470 -0
  58. wl_cli/commands/format/format_outcome.py +44 -0
  59. wl_cli/commands/format/format_path_handler.py +1016 -0
  60. wl_cli/commands/format/format_progress.py +62 -0
  61. wl_cli/commands/format/format_reporter.py +31 -0
  62. wl_cli/commands/format/python_formatter.py +506 -0
  63. wl_cli/commands/format/rust_formatter.py +106 -0
  64. wl_cli/commands/gate/__init__.py +1 -0
  65. wl_cli/commands/gate/gate_command.py +862 -0
  66. wl_cli/commands/gate/preflight.py +445 -0
  67. wl_cli/commands/git/__init__.py +1 -0
  68. wl_cli/commands/git/config.py +169 -0
  69. wl_cli/commands/git/git_facts.py +60 -0
  70. wl_cli/commands/git/git_flow.py +86 -0
  71. wl_cli/commands/git/hook.py +112 -0
  72. wl_cli/commands/git/policy.py +216 -0
  73. wl_cli/commands/git/push_update.py +82 -0
  74. wl_cli/commands/initenv/__init__.py +1 -0
  75. wl_cli/commands/initenv/common/__init__.py +23 -0
  76. wl_cli/commands/initenv/common/check_git_bash.py +78 -0
  77. wl_cli/commands/initenv/common/error_handler.py +68 -0
  78. wl_cli/commands/initenv/common/exceptions.py +52 -0
  79. wl_cli/commands/initenv/components/__init__.py +4 -0
  80. wl_cli/commands/initenv/components/cargo_sync.py +240 -0
  81. wl_cli/commands/initenv/components/config_manager.py +106 -0
  82. wl_cli/commands/initenv/components/directory_utils.py +71 -0
  83. wl_cli/commands/initenv/components/git/__init__.py +9 -0
  84. wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
  85. wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
  86. wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
  87. wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
  88. wl_cli/commands/initenv/components/git/git_utility.py +254 -0
  89. wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
  90. wl_cli/commands/initenv/components/git_initializer.py +212 -0
  91. wl_cli/commands/initenv/components/i18n_manager.py +104 -0
  92. wl_cli/commands/initenv/components/log_manager.py +241 -0
  93. wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
  94. wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
  95. wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
  96. wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
  97. wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
  98. wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
  99. wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
  100. wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
  101. wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
  102. wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
  103. wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
  104. wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
  105. wl_cli/commands/initenv/components/rust_initializer.py +145 -0
  106. wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
  107. wl_cli/commands/initenv/components/symlink_manager.py +52 -0
  108. wl_cli/commands/initenv/components/venv_manager.py +118 -0
  109. wl_cli/commands/initenv/initenv.py +169 -0
  110. wl_cli/commands/initenv/services/__init__.py +11 -0
  111. wl_cli/commands/initenv/services/environment_initializer.py +288 -0
  112. wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
  113. wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
  114. wl_cli/commands/lfs/README.md +221 -0
  115. wl_cli/commands/lfs/__init__.py +5 -0
  116. wl_cli/commands/lfs/cli.py +106 -0
  117. wl_cli/commands/lfs/config.py +164 -0
  118. wl_cli/commands/lfs/hook.py +61 -0
  119. wl_cli/commands/lfs/initializer.py +531 -0
  120. wl_cli/commands/lfs/manager.py +95 -0
  121. wl_cli/commands/lfs/policy.py +165 -0
  122. wl_cli/commands/lfs/watcher.py +294 -0
  123. wl_cli/commands/lint/__init__.py +1 -0
  124. wl_cli/commands/lint/lint_command.py +297 -0
  125. wl_cli/commands/lint/lint_executor.py +409 -0
  126. wl_cli/commands/lint/lint_reporter.py +579 -0
  127. wl_cli/commands/lint/lint_run.py +258 -0
  128. wl_cli/commands/lint/mypy_config.py +90 -0
  129. wl_cli/commands/registry.py +103 -0
  130. wl_cli/commands/self/__init__.py +7 -0
  131. wl_cli/commands/self/common/__init__.py +8 -0
  132. wl_cli/commands/self/common/capabilities.py +30 -0
  133. wl_cli/commands/self/common/config.py +27 -0
  134. wl_cli/commands/self/common/version_cache.py +85 -0
  135. wl_cli/commands/self/handlers/__init__.py +19 -0
  136. wl_cli/commands/self/handlers/base_handler.py +28 -0
  137. wl_cli/commands/self/handlers/install_handler.py +85 -0
  138. wl_cli/commands/self/handlers/status_handler.py +66 -0
  139. wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
  140. wl_cli/commands/self/handlers/update_handler.py +102 -0
  141. wl_cli/commands/self/handlers/version_handler.py +16 -0
  142. wl_cli/commands/self/self_command.py +237 -0
  143. wl_cli/commands/self/services/__init__.py +15 -0
  144. wl_cli/commands/self/services/installation_manager.py +187 -0
  145. wl_cli/commands/self/services/local_installer.py +165 -0
  146. wl_cli/commands/self/services/pypi_updater.py +278 -0
  147. wl_cli/commands/self/services/windows_update.py +174 -0
  148. wl_cli/commands/testcommands/__init__.py +8 -0
  149. wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
  150. wl_cli/commands/testcommands/batch_processor.py +278 -0
  151. wl_cli/commands/testcommands/cache_manager.py +1156 -0
  152. wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
  153. wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
  154. wl_cli/commands/testcommands/progress_manager.py +79 -0
  155. wl_cli/commands/testcommands/python_env_detector.py +207 -0
  156. wl_cli/commands/testcommands/report_generator.py +101 -0
  157. wl_cli/commands/testcommands/single_test_executor.py +250 -0
  158. wl_cli/commands/testcommands/speed_checker.py +118 -0
  159. wl_cli/commands/testcommands/test_command_builder.py +165 -0
  160. wl_cli/commands/testcommands/test_commands.py +786 -0
  161. wl_cli/commands/testcommands/test_reporter.py +181 -0
  162. wl_cli/commands/testcommands/test_result_handler.py +96 -0
  163. wl_cli/commands/testcommands/test_runner.py +1343 -0
  164. wl_cli/commands/testcommands/test_utils.py +197 -0
  165. wl_cli/commands/testcommands/utils/__init__.py +17 -0
  166. wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
  167. wl_cli/commands/testcommands/utils/env_info.py +36 -0
  168. wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
  169. wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
  170. wl_cli/commands/testcommands/utils/project_root.py +61 -0
  171. wl_cli/commands/update/__init__.py +9 -0
  172. wl_cli/commands/update/services/__init__.py +21 -0
  173. wl_cli/commands/update/services/backup_manager.py +207 -0
  174. wl_cli/commands/update/services/component_manager.py +83 -0
  175. wl_cli/commands/update/services/conflict_resolver.py +189 -0
  176. wl_cli/commands/update/services/vendors_updater.py +85 -0
  177. wl_cli/commands/update/services/version_tracker.py +127 -0
  178. wl_cli/commands/update/update_command.py +270 -0
  179. wl_cli/commands/update/utils/__init__.py +3 -0
  180. wl_cli/commands/version/__init__.py +9 -0
  181. wl_cli/commands/version/version_command.py +155 -0
  182. wl_cli/commands/workflow.py +477 -0
  183. wl_cli/env/__init__.py +5 -0
  184. wl_cli/env/cli.py +167 -0
  185. wl_cli/env_status.py +157 -0
  186. wl_cli/envelope.py +91 -0
  187. wl_cli/exceptions.py +85 -0
  188. wl_cli/lib/__init__.py +3 -0
  189. wl_cli/lib/rust_utils.py +35 -0
  190. wl_cli/mcp/__init__.py +6 -0
  191. wl_cli/mcp/capabilities.py +19 -0
  192. wl_cli/mcp/cli.py +22 -0
  193. wl_cli/mcp/gitlab.py +553 -0
  194. wl_cli/mcp/k8s.py +344 -0
  195. wl_cli/mcp/protocol.py +159 -0
  196. wl_cli/platform.py +139 -0
  197. wl_cli/quality/__init__.py +18 -0
  198. wl_cli/utils/__init__.py +7 -0
  199. wl_cli/utils/cache.py +491 -0
  200. wl_cli/utils/cache_ledger.py +355 -0
  201. wl_cli/utils/cache_store.py +546 -0
  202. wl_cli/utils/config/__init__.py +27 -0
  203. wl_cli/utils/config/config_loader.py +163 -0
  204. wl_cli/utils/config/core.py +862 -0
  205. wl_cli/utils/config/defaults.py +142 -0
  206. wl_cli/utils/config/env_exporter.py +86 -0
  207. wl_cli/utils/config/env_parser.py +108 -0
  208. wl_cli/utils/config/validation.py +280 -0
  209. wl_cli/utils/error_handler.py +165 -0
  210. wl_cli/utils/file_copier.py +56 -0
  211. wl_cli/utils/file_operations.py +506 -0
  212. wl_cli/utils/language_utils.py +122 -0
  213. wl_cli/utils/lazy_import.py +240 -0
  214. wl_cli/utils/log_levels.py +35 -0
  215. wl_cli/utils/log_rotators.py +74 -0
  216. wl_cli/utils/logging.py +458 -0
  217. wl_cli/utils/output_context.py +142 -0
  218. wl_cli/utils/performance/__init__.py +1 -0
  219. wl_cli/utils/performance/decorators.py +111 -0
  220. wl_cli/utils/performance/exporter.py +38 -0
  221. wl_cli/utils/performance/models.py +16 -0
  222. wl_cli/utils/performance/monitor.py +161 -0
  223. wl_cli/utils/performance_monitor.py +32 -0
  224. wl_cli/utils/platform_adapter.py +131 -0
  225. wl_cli/utils/pre_commit.py +266 -0
  226. wl_cli/utils/pre_commit_config.py +72 -0
  227. wl_cli/utils/root.py +521 -0
  228. wl_cli/utils/root_fallback.py +256 -0
  229. wl_cli/utils/run_evidence.py +236 -0
  230. wl_cli/utils/structured_logger.py +18 -0
  231. wl_cli/utils/structured_logging/__init__.py +19 -0
  232. wl_cli/utils/structured_logging/config.py +337 -0
  233. wl_cli/utils/structured_logging/core.py +177 -0
  234. wl_cli/utils/structured_logging/env.py +56 -0
  235. wl_cli/utils/structured_logging/filtering.py +75 -0
  236. wl_cli/utils/structured_logging/filters.py +55 -0
  237. wl_cli/utils/structured_logging/formatters.py +251 -0
  238. wl_cli/utils/structured_logging/handlers.py +308 -0
  239. wl_cli/utils/structured_logging/logger.py +354 -0
  240. wl_cli/utils/subprocess.py +71 -0
  241. wl_cli/utils/subprocess_utils.py +260 -0
  242. wl_cli/utils/test_utils/__init__.py +5 -0
  243. wl_cli/utils/test_utils/test_filter.py +36 -0
  244. wl_cli/utils/uv_run.py +79 -0
  245. wl_cli/utils/uv_tool.py +117 -0
  246. wl_cli/utils/uv_tree.py +71 -0
  247. wl_cli/utils/uv_workspace.py +174 -0
  248. wl_cli/utils/version/__init__.py +15 -0
  249. wl_cli/utils/version/pypi_version_checker.py +95 -0
  250. wl_cli/utils/version/version_comparator.py +133 -0
  251. wl_cli/utils/version/version_detectors.py +312 -0
  252. wl_cli/utils/version/version_service.py +131 -0
  253. wl_cli/utils/version/version_updater.py +198 -0
  254. wl_cli/utils/wl_dir_updater.py +1440 -0
  255. wl_cli/utils/workspace_detector/__init__.py +28 -0
  256. wl_cli/utils/workspace_detector/core.py +161 -0
  257. wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
  258. wl_cli/utils/workspace_detector/exceptions.py +5 -0
  259. wl_cli/utils/workspace_detector/types.py +17 -0
  260. wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
  261. wl_cli/vendors/config/.codespellrc +2 -0
  262. wl_cli/vendors/config/dependencies.toml +10 -0
  263. wl_cli/vendors/config/mypy.ini +62 -0
  264. wl_cli/vendors/config/pyproject_defaults.toml +28 -0
  265. wl_cli/vendors/config/template_config.toml +24 -0
  266. wl_cli/vendors/config/tool_configs.toml +76 -0
  267. wl_cli/vendors/git/.gitignore +122 -0
  268. wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
  269. wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
  270. wl_cli/vendors/hooks/commit-msg.sample +24 -0
  271. wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
  272. wl_cli/vendors/hooks/post-update.sample +8 -0
  273. wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
  274. wl_cli/vendors/hooks/pre-commit +45 -0
  275. wl_cli/vendors/hooks/pre-commit.legacy +31 -0
  276. wl_cli/vendors/hooks/pre-commit.sample +49 -0
  277. wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
  278. wl_cli/vendors/hooks/pre-push.sample +60 -0
  279. wl_cli/vendors/hooks/pre-rebase.sample +169 -0
  280. wl_cli/vendors/hooks/pre-receive.sample +24 -0
  281. wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
  282. wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
  283. wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
  284. wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
  285. wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
  286. wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
  287. wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
  288. wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
  289. wl_cli/vendors/hooks/unix/post-update.sample +8 -0
  290. wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
  291. wl_cli/vendors/hooks/unix/pre-commit +190 -0
  292. wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
  293. wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
  294. wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
  295. wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
  296. wl_cli/vendors/hooks/unix/pre-push +417 -0
  297. wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
  298. wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
  299. wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
  300. wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
  301. wl_cli/vendors/hooks/update.sample +128 -0
  302. wl_cli/vendors/hooks/win/work.bat +63 -0
  303. wl_cli/vendors/project/.gitignore +90 -0
  304. wl_cli/vendors/project/.python-version +1 -0
  305. wl_cli/vendors/project/LICENSE +21 -0
  306. wl_cli/vendors/project/README.md +0 -0
  307. wl_cli/vendors/project/pyproject.toml +27 -0
  308. wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
  309. wl_cli/vendors/project/pyproject_workspace.toml +14 -0
  310. wl_cli/vendors/readme/README.md +120 -0
  311. wl_cli/vendors/rust/Cargo.toml +14 -0
  312. wl_cli/vendors/rust/lib/__init__.py +3 -0
  313. wl_cli/vendors/rust/lib/rust_utils.py +31 -0
  314. wl_cli/vendors/rust/src/lib.rs +13 -0
  315. wl_cli/vendors/rust/src/main.rs +3 -0
  316. wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
  317. wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
  318. wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
  319. wl_cli/vendors/sop_doc/python-sop.md +342 -0
  320. wl_cli/vendors/sop_doc/rust-readme.md +219 -0
@@ -0,0 +1,236 @@
1
+ """
2
+ Core cleaning utilities for clean command.
3
+ """
4
+
5
+ import concurrent.futures
6
+ import glob
7
+ import os
8
+ import shutil
9
+ from collections.abc import Callable
10
+ from typing import Any
11
+
12
+ from wl_cli.utils.logging import log_info_pair
13
+
14
+
15
+ def remove_directories(dirs_to_remove: list[str], dry_run: bool = False) -> None:
16
+ """Remove specific directories."""
17
+ for dir_name in dirs_to_remove:
18
+ if os.path.exists(dir_name):
19
+ try:
20
+ if dry_run:
21
+ log_info_pair(
22
+ f"Would remove directory: {dir_name}", f"将删除目录: {dir_name}"
23
+ )
24
+ else:
25
+ shutil.rmtree(dir_name)
26
+ log_info_pair(
27
+ f"Removed directory: {dir_name}", f"已删除目录: {dir_name}"
28
+ )
29
+ except Exception as e:
30
+ log_info_pair(
31
+ f"Failed to remove directory {dir_name}: {e}",
32
+ f"删除目录 {dir_name} 失败: {e}",
33
+ )
34
+
35
+
36
+ def remove_files(files_to_remove: list[str], dry_run: bool = False) -> None:
37
+ """Remove specific files, supporting wildcard patterns."""
38
+ for pattern in files_to_remove:
39
+ # Handle wildcard patterns
40
+ if any(char in pattern for char in "*?[]"):
41
+ matched_files = glob.glob(pattern, recursive=True)
42
+ for file_name in matched_files:
43
+ if os.path.isfile(file_name):
44
+ try:
45
+ if dry_run:
46
+ log_info_pair(
47
+ f"Would remove file: {file_name}",
48
+ f"将删除文件: {file_name}",
49
+ )
50
+ else:
51
+ os.remove(file_name)
52
+ log_info_pair(
53
+ f"Removed file: {file_name}", f"已删除文件: {file_name}"
54
+ )
55
+ except Exception as e:
56
+ log_info_pair(
57
+ f"Failed to remove file {file_name}: {e}",
58
+ f"删除文件 {file_name} 失败: {e}",
59
+ )
60
+ else:
61
+ # Handle exact file paths
62
+ if os.path.exists(pattern) and os.path.isfile(pattern):
63
+ try:
64
+ if dry_run:
65
+ log_info_pair(
66
+ f"Would remove file: {pattern}", f"将删除文件: {pattern}"
67
+ )
68
+ else:
69
+ os.remove(pattern)
70
+ log_info_pair(
71
+ f"Removed file: {pattern}", f"已删除文件: {pattern}"
72
+ )
73
+ except Exception as e:
74
+ log_info_pair(
75
+ f"Failed to remove file {pattern}: {e}",
76
+ f"删除文件 {pattern} 失败: {e}",
77
+ )
78
+
79
+
80
+ def _execute_cleaning_task(task: tuple[Callable, dict]) -> tuple[str, bool, Any]:
81
+ """
82
+ Execute a cleaning task with error handling
83
+ 执行清理任务并处理错误
84
+ """
85
+ task_func, task_args = task
86
+ task_name = task_func.__name__
87
+
88
+ # Get human-readable task name
89
+ task_display_names = {
90
+ "clean_build_artifacts": "Build artifacts",
91
+ "clean_lfs_artifacts": "Git LFS artifacts",
92
+ "clean_rust_artifacts": "Rust artifacts",
93
+ "remove_virtual_environments": "Virtual environments",
94
+ "remove_auto_activation_scripts": "Auto-activation scripts",
95
+ "remove_rust_analyzer_dirs": "Rust analyzer directories",
96
+ "remove_uv_lock": "UV lock file",
97
+ }
98
+ display_name = task_display_names.get(task_name, task_name)
99
+
100
+ try:
101
+ log_info_pair(
102
+ f"\n=== Starting: {display_name} ===", f"\n=== 开始: {display_name} ==="
103
+ )
104
+
105
+ task_func(**task_args)
106
+
107
+ log_info_pair(
108
+ f"=== Completed: {display_name} ===", f"=== 完成: {display_name} ==="
109
+ )
110
+ return task_name, True, None
111
+ except Exception as e:
112
+ log_info_pair(
113
+ f"=== Failed: {display_name} ===", f"=== 失败: {display_name} ==="
114
+ )
115
+ log_info_pair(f"Error: {e}", f"错误: {e}")
116
+ return task_name, False, e
117
+
118
+
119
+ def clean_all_artifacts(
120
+ dry_run: bool = False, config: str | None = None, parallel: bool = False
121
+ ) -> None:
122
+ """
123
+ Clean all artifacts including virtual environment
124
+ 清理所有产物,包括虚拟环境
125
+ """
126
+ log_info_pair("Cleaning all artifacts...", "正在清理所有产物...")
127
+
128
+ # Log dry run mode
129
+ if dry_run:
130
+ log_info_pair(
131
+ "Dry run mode: No files will be deleted.", "Dry run模式:不会删除任何文件。"
132
+ )
133
+
134
+ # Log parallel mode
135
+ if parallel:
136
+ log_info_pair("Parallel execution enabled.", "已启用并行执行。")
137
+
138
+ # Prepare cleaning tasks
139
+ tasks: list[tuple[Callable, dict]] = []
140
+
141
+ # Lazy imports to avoid circular imports (build.py imports from core.py)
142
+ from wl_cli.commands.clean.build import clean_build_artifacts
143
+ from wl_cli.commands.clean.lfs import clean_lfs_artifacts
144
+ from wl_cli.commands.clean.rust import (
145
+ clean_rust_artifacts as _clean_rust_artifacts,
146
+ )
147
+ from wl_cli.commands.clean.rust import (
148
+ remove_rust_analyzer_dirs,
149
+ )
150
+ from wl_cli.commands.clean.venv import (
151
+ remove_auto_activation_scripts,
152
+ remove_uv_lock,
153
+ remove_virtual_environments,
154
+ )
155
+
156
+ # Add build artifacts cleaning
157
+ tasks.append((clean_build_artifacts, {"dry_run": dry_run, "config": config}))
158
+
159
+ # Add LFS cleaning
160
+ tasks.append((clean_lfs_artifacts, {"dry_run": dry_run}))
161
+
162
+ # Add Rust artifacts cleaning
163
+ tasks.append((_clean_rust_artifacts, {"dry_run": dry_run}))
164
+
165
+ # Add virtual environment cleaning
166
+ tasks.append((remove_virtual_environments, {"dry_run": dry_run}))
167
+
168
+ # Add auto-activation scripts removal
169
+ tasks.append((remove_auto_activation_scripts, {"dry_run": dry_run}))
170
+
171
+ # Add rust-analyzer directories removal
172
+ tasks.append((remove_rust_analyzer_dirs, {"dry_run": dry_run}))
173
+
174
+ # Add uv.lock file removal
175
+ tasks.append((remove_uv_lock, {"dry_run": dry_run}))
176
+
177
+ # Execute tasks
178
+ success_count = 0
179
+ failure_count = 0
180
+ total_tasks = len(tasks)
181
+ completed_tasks = 0
182
+
183
+ log_info_pair(
184
+ f"Starting {total_tasks} cleaning tasks...",
185
+ f"开始执行 {total_tasks} 个清理任务...",
186
+ )
187
+
188
+ if parallel:
189
+ # Parallel execution
190
+ with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
191
+ # Submit all tasks
192
+ future_to_task = {
193
+ executor.submit(_execute_cleaning_task, task): task for task in tasks
194
+ }
195
+
196
+ # Process results as they complete
197
+ for future in concurrent.futures.as_completed(future_to_task):
198
+ task_name, success, error = future.result()
199
+ completed_tasks += 1
200
+
201
+ if success:
202
+ success_count += 1
203
+ else:
204
+ failure_count += 1
205
+
206
+ # Show progress
207
+ progress_percent = (completed_tasks / total_tasks) * 100
208
+ log_info_pair(
209
+ f"Progress: {completed_tasks}/{total_tasks} ({progress_percent:.1f}%)",
210
+ f"进度: {completed_tasks}/{total_tasks} ({progress_percent:.1f}%)",
211
+ )
212
+ else:
213
+ # Sequential execution (default)
214
+ for task in tasks:
215
+ task_name, success, error = _execute_cleaning_task(task)
216
+ completed_tasks += 1
217
+
218
+ if success:
219
+ success_count += 1
220
+ else:
221
+ failure_count += 1
222
+
223
+ # Show progress
224
+ progress_percent = (completed_tasks / total_tasks) * 100
225
+ log_info_pair(
226
+ f"Progress: {completed_tasks}/{total_tasks} ({progress_percent:.1f}%)",
227
+ f"进度: {completed_tasks}/{total_tasks} ({progress_percent:.1f}%)",
228
+ )
229
+
230
+ # Log summary
231
+ log_info_pair(
232
+ f"Cleaning operations completed: {success_count} succeeded, {failure_count} failed",
233
+ f"清理操作完成: {success_count} 成功, {failure_count} 失败",
234
+ )
235
+
236
+ log_info_pair("All artifacts cleaning completed.", "所有产物清理完成。")
File without changes
@@ -0,0 +1,70 @@
1
+ """Dead code command module.
2
+ Main entry point for the dead code cleanup functionality.
3
+ """
4
+
5
+ from ....utils.root import RootKind
6
+ from ....utils.root_fallback import current_root
7
+ from .detector import DeadCodeDetector
8
+ from .fixer import DeadCodeFixer
9
+ from .reporter import DeadCodeReporter, TestSuggestion
10
+
11
+
12
+ class DeadCodeCommand:
13
+ """Command for dead code cleanup."""
14
+
15
+ def __init__(self) -> None:
16
+ """Initialize the dead code command."""
17
+ self.detector = DeadCodeDetector()
18
+ self.fixer = DeadCodeFixer()
19
+
20
+ def run(self, dry_run: bool = False, json: bool = False) -> int:
21
+ """Run the dead code cleanup command.
22
+
23
+ Args:
24
+ dry_run: Whether to only scan without fixing.
25
+ json: Whether to output JSON format.
26
+
27
+ Returns:
28
+ Exit code (0 for success, 1 for error).
29
+ """
30
+ # The reporter emits through ``log_*_pair``, which picks the language
31
+ # itself (#167), so no language is threaded through here.
32
+ reporter = DeadCodeReporter()
33
+
34
+ try:
35
+ # Find project root: single-root consumer (#160), taken as a bare
36
+ # Path because that is this command's contract.
37
+ project_root = current_root(RootKind.PROJECT).path
38
+
39
+ # Step 1: Detect dead code
40
+ issues = self.detector.detect(project_root)
41
+ test_suggestions: list[TestSuggestion] = []
42
+
43
+ # Step 2: Fix issues
44
+ if not dry_run:
45
+ issues = self.fixer.fix(issues, project_root, dry_run=False)
46
+
47
+ # Step 3: Report
48
+ if json:
49
+ json_output = reporter.report_json(issues, test_suggestions)
50
+ print(json_output)
51
+ else:
52
+ reporter.report_cli(issues, test_suggestions, dry_run=dry_run)
53
+
54
+ return 0
55
+
56
+ except Exception as e:
57
+ from ....utils.logging import log_error_pair
58
+
59
+ if json:
60
+ # For JSON output, print error in JSON format without banners
61
+ import json as json_module
62
+
63
+ error_output = {"error": str(e), "error_zh": "运行死代码清理时出错"}
64
+ print(json_module.dumps(error_output, ensure_ascii=False))
65
+ else:
66
+ log_error_pair(
67
+ f"Error running dead code cleanup: {e}",
68
+ f"运行死代码清理时出错: {e}",
69
+ )
70
+ return 1
@@ -0,0 +1,161 @@
1
+ """
2
+ Dead code detector module.
3
+ Handles detection of dead code using Ruff.
4
+ """
5
+
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from ....utils.logging import log_error_pair
11
+ from ....utils.subprocess_utils import SubprocessExecutor, SubprocessResult
12
+ from .reporter import DeadCodeIssue
13
+
14
+
15
+ class DeadCodeDetector:
16
+ """Detector for dead code issues."""
17
+
18
+ def __init__(self) -> None:
19
+ """Initialize the dead code detector."""
20
+ self.executor = SubprocessExecutor()
21
+
22
+ def detect(self, project_root: Path) -> list[DeadCodeIssue]:
23
+ """
24
+ Detect dead code issues.
25
+
26
+ Args:
27
+ project_root: Root directory of the project.
28
+
29
+ Returns:
30
+ List of DeadCodeIssue objects.
31
+ """
32
+ issues: list[DeadCodeIssue] = []
33
+
34
+ # Run Ruff detection
35
+ ruff_issues = self._detect_with_ruff(project_root)
36
+ issues.extend(ruff_issues)
37
+
38
+ return issues
39
+
40
+ def _detect_with_ruff(self, project_root: Path) -> list[DeadCodeIssue]:
41
+ """
42
+ Detect dead code using Ruff.
43
+
44
+ Args:
45
+ project_root: Root directory of the project.
46
+
47
+ Returns:
48
+ List of DeadCodeIssue objects from Ruff.
49
+ """
50
+ issues: list[DeadCodeIssue] = []
51
+
52
+ # Prepare Ruff command
53
+ cmd = [
54
+ "ruff",
55
+ "check",
56
+ "--select",
57
+ "F401,F841",
58
+ "--output-format",
59
+ "json",
60
+ ".",
61
+ ]
62
+
63
+ # Run Ruff command
64
+ result = self.executor.run(command=cmd, cwd=project_root, quiet=True)
65
+
66
+ # Only a Ruff that never ran has nothing to say. Every other non-zero
67
+ # exit still carries a report: Ruff exits 1 exactly when it *found*
68
+ # violations, which is the case this detector exists for (#150).
69
+ if self._ruff_unavailable(result):
70
+ log_error_pair(
71
+ "Ruff is not installed. Please install Ruff to use dead code detection.",
72
+ "Ruff 未安装。请安装 Ruff 以使用死代码检测功能。",
73
+ )
74
+ return issues
75
+
76
+ # Parse Ruff output
77
+ if result.stdout:
78
+ try:
79
+ ruff_output = json.loads(result.stdout)
80
+ issues = self._parse_ruff_output(ruff_output)
81
+ except json.JSONDecodeError:
82
+ # If JSON parsing fails, return empty list
83
+ pass
84
+
85
+ return issues
86
+
87
+ @staticmethod
88
+ def _ruff_unavailable(result: SubprocessResult) -> bool:
89
+ """Whether the ``ruff`` process never got as far as reporting.
90
+
91
+ Deliberately **not** ``returncode != 0``: Ruff exits 1 whenever it has
92
+ violations, so that test silently discarded every finding (#150). The
93
+ discriminator is :attr:`SubprocessResult.launch_error`, the field set
94
+ only on the ``except`` arm of
95
+ :meth:`SubprocessExecutor._execute_command` -- i.e. when the
96
+ executable could not be started at all (#100/#101). ``127`` is kept as
97
+ a belt-and-braces spelling of a shell's "command not found", which
98
+ Ruff itself never returns.
99
+
100
+ The former ``"not found" in stderr`` sniff is gone with it: a substring
101
+ of a child's *stderr* cannot distinguish "no such tool" from a Ruff
102
+ that ran, reported something and exited 1.
103
+
104
+ Args:
105
+ result: The finished Ruff subprocess result.
106
+
107
+ Returns:
108
+ True when Ruff never produced a report.
109
+ """
110
+ if result.launch_error is not None:
111
+ return True
112
+ return result.returncode == 127
113
+
114
+ def _parse_ruff_output(
115
+ self, ruff_output: list[dict[str, Any]]
116
+ ) -> list[DeadCodeIssue]:
117
+ """
118
+ Parse Ruff JSON output into DeadCodeIssue objects.
119
+
120
+ Args:
121
+ ruff_output: Ruff JSON output as a list of dictionaries.
122
+
123
+ Returns:
124
+ List of DeadCodeIssue objects.
125
+ """
126
+ issues: list[DeadCodeIssue] = []
127
+
128
+ for item in ruff_output:
129
+ code = item.get("code", "")
130
+ filename = item.get("filename", "")
131
+ line = item.get("location", {}).get("row", None)
132
+ message = item.get("message", "")
133
+
134
+ if code == "F401":
135
+ # Unused import
136
+ # Extract import name from message like "`os` imported but unused"
137
+ name = message.split("`")[1] if "`" in message else None
138
+ issue = DeadCodeIssue(
139
+ type="unused_import",
140
+ file=filename,
141
+ line=line,
142
+ name=name,
143
+ severity="safe",
144
+ fixed=False,
145
+ )
146
+ issues.append(issue)
147
+ elif code == "F841":
148
+ # Unused variable
149
+ # Extract variable name from message like "Local variable `x` is assigned to but never used"
150
+ name = message.split("`")[1] if "`" in message else None
151
+ issue = DeadCodeIssue(
152
+ type="unused_variable",
153
+ file=filename,
154
+ line=line,
155
+ name=name,
156
+ severity="safe",
157
+ fixed=False,
158
+ )
159
+ issues.append(issue)
160
+
161
+ return issues
@@ -0,0 +1,176 @@
1
+ """
2
+ Dead code fixer module.
3
+ Handles automatic fixing of dead code issues using Ruff.
4
+ """
5
+
6
+ from pathlib import Path
7
+
8
+ from ....utils.logging import log_error_pair
9
+ from ....utils.subprocess_utils import SubprocessExecutor, SubprocessResult
10
+ from .reporter import DeadCodeIssue
11
+
12
+
13
+ class DeadCodeFixer:
14
+ """Fixer for dead code issues."""
15
+
16
+ def __init__(self) -> None:
17
+ """Initialize the dead code fixer."""
18
+ self.executor = SubprocessExecutor()
19
+
20
+ def fix(
21
+ self, issues: list[DeadCodeIssue], project_root: Path, dry_run: bool = False
22
+ ) -> list[DeadCodeIssue]:
23
+ """
24
+ Fix dead code issues.
25
+
26
+ Args:
27
+ issues: List of DeadCodeIssue objects.
28
+ project_root: Root directory of the project.
29
+ dry_run: Whether to only preview fixes without applying them.
30
+
31
+ Returns:
32
+ List of DeadCodeIssue objects with fixed flag updated.
33
+ """
34
+ # Only fix safe issues (unused imports and variables)
35
+ safe_issues = [issue for issue in issues if issue.severity == "safe"]
36
+
37
+ if not safe_issues:
38
+ return issues
39
+
40
+ # Run Ruff with --fix (or not in dry-run)
41
+ result = self._run_ruff_fix(project_root, dry_run)
42
+
43
+ # Check if Ruff is not installed. ``--fix`` still exits 1 when it
44
+ # *repaired* something (or has repairs left to make), so the exit code
45
+ # alone cannot tell "Ruff ran and worked" from "Ruff never ran" -- see
46
+ # :meth:`_ruff_unavailable`, the same discriminator the detector uses
47
+ # (#150/#153).
48
+ if self._ruff_unavailable(result):
49
+ log_error_pair(
50
+ "Ruff is not installed. Please install Ruff to use dead code fixing.",
51
+ "Ruff 未安装。请安装 Ruff 以使用死代码修复功能。",
52
+ )
53
+ return issues
54
+
55
+ # Ruff is installed but it did *not* run to completion: any exit other
56
+ # than 0 (clean) or 1 (ran, and has findings) is a failure -- a bad
57
+ # config, an unusable argument -- that happened before a single repair
58
+ # was attempted. Falling through would be worse than useless: the next
59
+ # step, :meth:`_determine_fixed_issues`, re-runs the *detector*, which
60
+ # fails the same way and reports nothing left, so every safe issue would
61
+ # be re-labelled ``fixed`` with no repair behind it (#155 M1). Before
62
+ # #153 the coarse ``returncode != 0`` test happened to cover this path;
63
+ # the narrowed discriminator needs this branch to cover it again.
64
+ if result.returncode not in (0, 1):
65
+ log_error_pair(
66
+ f"Ruff failed with exit code {result.returncode}; nothing was fixed.",
67
+ f"Ruff 运行失败,退出码 {result.returncode};未修复任何死代码。",
68
+ )
69
+ return issues
70
+
71
+ if dry_run:
72
+ # In dry-run mode, we can't know exactly what was fixed,
73
+ # so we just return the issues as is
74
+ return issues
75
+
76
+ # Determine which issues were fixed by comparing with a fresh detection
77
+ fixed_issues = self._determine_fixed_issues(issues, project_root)
78
+
79
+ return fixed_issues
80
+
81
+ def _run_ruff_fix(self, project_root: Path, dry_run: bool) -> SubprocessResult:
82
+ """
83
+ Run Ruff fix command.
84
+
85
+ Args:
86
+ project_root: Root directory of the project.
87
+ dry_run: Whether to only preview fixes.
88
+
89
+ Returns:
90
+ SubprocessResult from Ruff execution.
91
+ """
92
+ cmd = ["ruff", "check", "--select", "F401,F841"]
93
+
94
+ if not dry_run:
95
+ cmd.append("--fix")
96
+
97
+ cmd.append(".")
98
+
99
+ return self.executor.run(command=cmd, cwd=project_root, quiet=True)
100
+
101
+ @staticmethod
102
+ def _ruff_unavailable(result: SubprocessResult) -> bool:
103
+ """Whether the ``ruff`` process never got as far as repairing anything.
104
+
105
+ Deliberately **not** ``returncode != 0``: ``ruff check --fix`` exits
106
+ **1** whenever it repaired something, still has unfixable findings, or
107
+ both -- so the old test short-circuited the fix path exactly when Ruff
108
+ had done its job, and no repaired issue was ever counted as ``fixed``
109
+ (#153, the sibling of the detector's #150). The discriminator is
110
+ :attr:`SubprocessResult.launch_error`, the field set only when the
111
+ executable could not be started at all (#100/#101). ``127`` is kept as
112
+ a belt-and-braces spelling of a shell's "command not found", which Ruff
113
+ itself never returns.
114
+
115
+ The former ``"not found" in stderr`` sniff is gone with it: a substring
116
+ of a child's *stderr* cannot distinguish "no such tool" from a Ruff that
117
+ ran and exited 1.
118
+
119
+ This predicate is spelled out identically on :class:`DeadCodeDetector`,
120
+ and a third copy of the same rule guards the ``uv`` launcher in
121
+ ``commands/format/python_formatter.py``. Sharing one helper was weighed
122
+ for #155 (M2) and recorded, not done: the two-line body is the whole
123
+ function, the three call sites sit in two unrelated packages (so no
124
+ single home reads naturally -- the ``uv`` site is not about Ruff at
125
+ all), and the only package-neutral home, ``utils/subprocess_utils.py``,
126
+ is outside the files #155 touches.
127
+
128
+ Args:
129
+ result: The finished Ruff subprocess result.
130
+
131
+ Returns:
132
+ True when Ruff never produced a report.
133
+ """
134
+ if result.launch_error is not None:
135
+ return True
136
+ return result.returncode == 127
137
+
138
+ def _determine_fixed_issues(
139
+ self, original_issues: list[DeadCodeIssue], project_root: Path
140
+ ) -> list[DeadCodeIssue]:
141
+ """
142
+ Determine which issues were fixed by running detection again.
143
+
144
+ Args:
145
+ original_issues: Original list of issues.
146
+ project_root: Root directory of the project.
147
+
148
+ Returns:
149
+ List of issues with fixed flag updated.
150
+ """
151
+ from .detector import DeadCodeDetector
152
+
153
+ detector = DeadCodeDetector()
154
+ new_issues = detector.detect(project_root)
155
+
156
+ # Create a set of remaining issues for quick lookup
157
+ remaining_issues_set = set()
158
+ for issue in new_issues:
159
+ key = (issue.type, issue.file, issue.line, issue.name)
160
+ remaining_issues_set.add(key)
161
+
162
+ # Mark issues that are not in remaining_issues_set as fixed
163
+ result = []
164
+ for issue in original_issues:
165
+ key = (issue.type, issue.file, issue.line, issue.name)
166
+ issue_copy = DeadCodeIssue(
167
+ type=issue.type,
168
+ file=issue.file,
169
+ line=issue.line,
170
+ name=issue.name,
171
+ severity=issue.severity,
172
+ fixed=(key not in remaining_issues_set and issue.severity == "safe"),
173
+ )
174
+ result.append(issue_copy)
175
+
176
+ return result