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,506 @@
1
+ """File operations utilities for WL Commands."""
2
+
3
+ import fnmatch
4
+ import os
5
+ import re
6
+ import shutil
7
+ import time
8
+ from pathlib import Path
9
+ from typing import cast
10
+
11
+ from .platform_adapter import PlatformAdapter
12
+
13
+ WINDOWS_DRIVE_PATTERN = re.compile(r"^[a-zA-Z]:[\\/]")
14
+
15
+
16
+ class FileOperationResult:
17
+ """Result of a file operation."""
18
+
19
+ def __init__(
20
+ self, success: bool, message: str = "", files_affected: list[str] | None = None
21
+ ):
22
+ self.success = success
23
+ self.message = message
24
+ self.files_affected = files_affected or []
25
+
26
+
27
+ class FileOperationsCache:
28
+ """Cache for file operations to avoid redundant operations."""
29
+
30
+ def __init__(self):
31
+ self._existence_cache: dict[str, bool] = {}
32
+ self._directory_contents_cache: dict[str, list[str]] = {}
33
+
34
+ def clear(self) -> None:
35
+ """Clear the cache."""
36
+ self._existence_cache.clear()
37
+ self._directory_contents_cache.clear()
38
+
39
+
40
+ class FileOperations:
41
+ """Unified file operations utility class."""
42
+
43
+ def __init__(self):
44
+ self.cache = FileOperationsCache()
45
+ self.is_windows: bool = PlatformAdapter.is_windows()
46
+
47
+ def _handle_windows_operation(self, operation, *args, **kwargs):
48
+ """
49
+ Handle Windows file locking issues by retrying operations.
50
+
51
+ Args:
52
+ operation: The operation to perform
53
+ *args: Arguments for the operation
54
+ **kwargs: Keyword arguments for the operation
55
+
56
+ Returns:
57
+ The result of the operation
58
+ """
59
+ if not self.is_windows:
60
+ return operation(*args, **kwargs)
61
+
62
+ max_attempts = 3
63
+ for attempt in range(max_attempts):
64
+ try:
65
+ return operation(*args, **kwargs)
66
+ except PermissionError:
67
+ if attempt < max_attempts - 1:
68
+ time.sleep(0.5)
69
+ continue
70
+ else:
71
+ raise
72
+
73
+ def exists(self, path: str | Path) -> bool:
74
+ """
75
+ Check if a path exists.
76
+
77
+ Args:
78
+ path: Path to check
79
+
80
+ Returns:
81
+ True if path exists, False otherwise
82
+ """
83
+ if path is None:
84
+ return False
85
+
86
+ if not isinstance(path, str | Path):
87
+ raise TypeError(f"Expected str or Path, got {type(path).__name__}")
88
+
89
+ path_str = str(path)
90
+
91
+ # Check for Windows-style absolute paths (drive letter + colon) on non-Windows platforms
92
+ if not self.is_windows and WINDOWS_DRIVE_PATTERN.match(path_str):
93
+ # On non-Windows platforms, Windows drive paths don't exist
94
+ return False
95
+
96
+ # Convert Windows-style backslashes to platform-specific separators
97
+ path_str = path_str.replace("\\", os.path.sep)
98
+
99
+ # Prevent path traversal
100
+ if ".." in path_str:
101
+ raise ValueError("Path traversal detected")
102
+
103
+ if path_str in self.cache._existence_cache:
104
+ return cast("bool", self.cache._existence_cache[path_str])
105
+
106
+ result = Path(path_str).exists()
107
+ self.cache._existence_cache[path_str] = result
108
+ return result
109
+
110
+ def get_files_in_directory(
111
+ self, directory: str | Path, pattern: str = "*", recursive: bool = True
112
+ ) -> list[Path]:
113
+ """
114
+ Get all files in a directory matching a pattern.
115
+
116
+ Args:
117
+ directory: Directory to search
118
+ pattern: File pattern to match (e.g., "*.py")
119
+ recursive: Whether to search recursively
120
+
121
+ Returns:
122
+ List of matching files
123
+ """
124
+ if directory is None:
125
+ return []
126
+
127
+ if not isinstance(directory, str | Path):
128
+ raise TypeError(f"Expected str or Path, got {type(directory).__name__}")
129
+
130
+ directory_str = str(directory)
131
+
132
+ # Convert Windows-style backslashes to platform-specific separators
133
+ directory_str = directory_str.replace("\\", os.path.sep)
134
+
135
+ # Prevent path traversal
136
+ if ".." in directory_str:
137
+ raise ValueError("Path traversal detected")
138
+
139
+ cache_key = f"{directory_str}:{pattern}:{recursive}"
140
+
141
+ if cache_key in self.cache._directory_contents_cache:
142
+ # Convert cached strings back to Path objects
143
+ return [Path(p) for p in self.cache._directory_contents_cache[cache_key]]
144
+
145
+ dir_path = Path(directory_str)
146
+ if not self.exists(dir_path):
147
+ self.cache._directory_contents_cache[cache_key] = []
148
+ return []
149
+
150
+ if recursive:
151
+ files = list(dir_path.rglob(pattern))
152
+ else:
153
+ files = list(dir_path.glob(pattern))
154
+
155
+ # Cache the result as strings
156
+ file_strings = [str(f) for f in files]
157
+ self.cache._directory_contents_cache[cache_key] = file_strings
158
+
159
+ return files
160
+
161
+ def copy_file(self, src: str | Path, dst: str | Path) -> FileOperationResult:
162
+ """
163
+ Copy a file from src to dst.
164
+
165
+ Args:
166
+ src: Source file path
167
+ dst: Destination file path
168
+
169
+ Returns:
170
+ FileOperationResult indicating success or failure
171
+
172
+ Raises:
173
+ TypeError: If src or dst is not a str or Path
174
+ ValueError: If src or dst is None or contains path traversal
175
+ """
176
+ # Validate input parameters
177
+ if src is None or dst is None:
178
+ raise ValueError("Source or destination path cannot be None")
179
+
180
+ if not isinstance(src, str | Path):
181
+ raise TypeError(
182
+ f"Expected str or Path for source, got {type(src).__name__}"
183
+ )
184
+
185
+ if not isinstance(dst, str | Path):
186
+ raise TypeError(
187
+ f"Expected str or Path for destination, got {type(dst).__name__}"
188
+ )
189
+
190
+ # Convert Windows-style backslashes to platform-specific separators for src
191
+ src_str = str(src)
192
+ src_str = src_str.replace("\\", os.path.sep)
193
+
194
+ # Prevent path traversal
195
+ if ".." in src_str:
196
+ raise ValueError("Path traversal detected in source path")
197
+
198
+ src_path = Path(src_str)
199
+
200
+ # Convert Windows-style backslashes to platform-specific separators for dst
201
+ dst_str = str(dst)
202
+ dst_str = dst_str.replace("\\", os.path.sep)
203
+
204
+ # Check for Windows-style absolute paths (drive letter + colon) on non-Windows platforms
205
+ if not self.is_windows and WINDOWS_DRIVE_PATTERN.match(str(dst)):
206
+ # On non-Windows platforms, Windows drive paths are not supported
207
+ return FileOperationResult(
208
+ False, f"Windows drive paths not supported on this platform: {dst}"
209
+ )
210
+
211
+ # Prevent path traversal
212
+ if ".." in dst_str:
213
+ raise ValueError("Path traversal detected in destination path")
214
+
215
+ dst_path = Path(dst_str)
216
+
217
+ try:
218
+ if not self.exists(src_path):
219
+ return FileOperationResult(
220
+ False, f"Source file does not exist: {src_path}"
221
+ )
222
+
223
+ # Create parent directories if they don't exist
224
+ dst_path.parent.mkdir(parents=True, exist_ok=True)
225
+
226
+ # Handle Windows file locking issues
227
+ self._handle_windows_operation(shutil.copy2, src_path, dst_path)
228
+
229
+ # Update cache
230
+ self._update_cache_for_file_copy(dst_path)
231
+
232
+ return FileOperationResult(
233
+ True, f"Successfully copied {src_path} to {dst_path}", [str(dst_path)]
234
+ )
235
+ except Exception as e:
236
+ return FileOperationResult(
237
+ False, f"Failed to copy file from {src} to {dst}: {str(e)}"
238
+ )
239
+
240
+ def _should_exclude(
241
+ self, item: Path, src_path: Path, exclude_patterns: list[str]
242
+ ) -> bool:
243
+ """Check if an item should be excluded based on patterns.
244
+
245
+ Args:
246
+ item: Path to check
247
+ src_path: Source directory path
248
+ exclude_patterns: List of patterns to exclude
249
+
250
+ Returns:
251
+ True if item should be excluded, False otherwise
252
+ """
253
+ for pattern in exclude_patterns:
254
+ if fnmatch.fnmatch(item.name, pattern) or fnmatch.fnmatch(
255
+ str(item.relative_to(src_path)), pattern
256
+ ):
257
+ return True
258
+ return False
259
+
260
+ def _clear_destination_cache(self, dst_str: str) -> None:
261
+ """Clear cache entries related to the destination directory.
262
+
263
+ Args:
264
+ dst_str: Destination directory path as string
265
+ """
266
+ # Clear existence cache
267
+ if dst_str in self.cache._existence_cache:
268
+ del self.cache._existence_cache[dst_str]
269
+
270
+ # Clear directory contents cache
271
+ cache_keys_to_remove = []
272
+ for key in self.cache._directory_contents_cache:
273
+ if key.startswith(dst_str):
274
+ cache_keys_to_remove.append(key)
275
+
276
+ for key in cache_keys_to_remove:
277
+ if key in self.cache._directory_contents_cache:
278
+ del self.cache._directory_contents_cache[key]
279
+
280
+ def _update_cache_for_file_copy(self, dst_path: Path) -> None:
281
+ """Update cache after file copy operation.
282
+
283
+ Args:
284
+ dst_path: Destination file path
285
+ """
286
+ dst_str = str(dst_path)
287
+ # Update cache for destination file
288
+ if dst_str in self.cache._existence_cache:
289
+ del self.cache._existence_cache[dst_str]
290
+
291
+ # Clear directory contents cache for parent directory
292
+ parent_dir = str(dst_path.parent)
293
+ self._clear_destination_cache(parent_dir)
294
+
295
+ def _copy_item(
296
+ self,
297
+ item: Path,
298
+ src_path: Path,
299
+ dst_path: Path,
300
+ exclude_patterns: list[str] | None,
301
+ ) -> list[str]:
302
+ """Copy a single item (file or directory).
303
+
304
+ Args:
305
+ item: Item to copy
306
+ src_path: Source directory path
307
+ dst_path: Destination directory path
308
+ exclude_patterns: List of patterns to exclude
309
+
310
+ Returns:
311
+ List of copied files
312
+ """
313
+ copied_files: list[str] = []
314
+
315
+ # Skip excluded patterns
316
+ if exclude_patterns and self._should_exclude(item, src_path, exclude_patterns):
317
+ return copied_files
318
+
319
+ relative_path = item.relative_to(src_path)
320
+ dst_item = dst_path / relative_path
321
+
322
+ if item.is_file():
323
+ dst_item.parent.mkdir(parents=True, exist_ok=True)
324
+ # Handle Windows file locking issues
325
+ self._handle_windows_operation(shutil.copy2, item, dst_item)
326
+ copied_files.append(str(dst_item))
327
+ elif item.is_dir():
328
+ dst_item.mkdir(parents=True, exist_ok=True)
329
+
330
+ return copied_files
331
+
332
+ def copy_directory(
333
+ self,
334
+ src: str | Path,
335
+ dst: str | Path,
336
+ exclude_patterns: list[str] | None = None,
337
+ ) -> FileOperationResult:
338
+ """
339
+ Copy a directory from src to dst.
340
+
341
+ Args:
342
+ src: Source directory path
343
+ dst: Destination directory path
344
+ exclude_patterns: List of patterns to exclude (e.g., ["*.tmp", "__pycache__"])
345
+
346
+ Returns:
347
+ FileOperationResult indicating success or failure
348
+
349
+ Raises:
350
+ TypeError: If src or dst is not a str or Path
351
+ ValueError: If src or dst is None or contains path traversal
352
+ """
353
+ # Validate input parameters
354
+ if src is None or dst is None:
355
+ raise ValueError("Source or destination path cannot be None")
356
+
357
+ if not isinstance(src, str | Path):
358
+ raise TypeError(
359
+ f"Expected str or Path for source, got {type(src).__name__}"
360
+ )
361
+
362
+ if not isinstance(dst, str | Path):
363
+ raise TypeError(
364
+ f"Expected str or Path for destination, got {type(dst).__name__}"
365
+ )
366
+
367
+ # Convert Windows-style backslashes to platform-specific separators for src
368
+ src_str = str(src)
369
+ src_str = src_str.replace("\\", os.path.sep)
370
+
371
+ # Prevent path traversal
372
+ if ".." in src_str:
373
+ raise ValueError("Path traversal detected in source path")
374
+
375
+ src_path = Path(src_str)
376
+
377
+ # Convert Windows-style backslashes to platform-specific separators for dst
378
+ dst_str = str(dst)
379
+ dst_str = dst_str.replace("\\", os.path.sep)
380
+
381
+ # Prevent path traversal
382
+ if ".." in dst_str:
383
+ raise ValueError("Path traversal detected in destination path")
384
+
385
+ dst_path = Path(dst_str)
386
+
387
+ try:
388
+ if not self.exists(src_path):
389
+ return FileOperationResult(
390
+ False, f"Source directory does not exist: {src_path}"
391
+ )
392
+
393
+ if not src_path.is_dir():
394
+ return FileOperationResult(
395
+ False, f"Source path is not a directory: {src_path}"
396
+ )
397
+
398
+ # Create destination directory
399
+ dst_path.mkdir(parents=True, exist_ok=True)
400
+
401
+ copied_files = []
402
+
403
+ for item in src_path.rglob("*"):
404
+ copied_files.extend(
405
+ self._copy_item(item, src_path, dst_path, exclude_patterns)
406
+ )
407
+
408
+ # Clear relevant cache entries
409
+ dst_str = str(dst_path)
410
+ self._clear_destination_cache(dst_str)
411
+
412
+ return FileOperationResult(
413
+ True,
414
+ f"Successfully copied directory {src_path} to {dst_path}",
415
+ copied_files,
416
+ )
417
+ except Exception as e:
418
+ return FileOperationResult(
419
+ False, f"Failed to copy directory from {src} to {dst}: {str(e)}"
420
+ )
421
+
422
+ def remove_directory(self, directory: str | Path) -> FileOperationResult:
423
+ """
424
+ Remove a directory and all its contents.
425
+
426
+ Args:
427
+ directory: Directory to remove
428
+
429
+ Returns:
430
+ FileOperationResult indicating success or failure
431
+
432
+ Raises:
433
+ TypeError: If directory is not a str or Path
434
+ ValueError: If directory is None or contains path traversal
435
+ """
436
+ # Validate input parameters
437
+ if directory is None:
438
+ raise ValueError("Directory path cannot be None")
439
+
440
+ if not isinstance(directory, str | Path):
441
+ raise TypeError(f"Expected str or Path, got {type(directory).__name__}")
442
+
443
+ # Convert Windows-style backslashes to platform-specific separators
444
+ dir_str = str(directory)
445
+ dir_str = dir_str.replace("\\", os.path.sep)
446
+
447
+ # Prevent path traversal
448
+ if ".." in dir_str:
449
+ raise ValueError("Path traversal detected")
450
+
451
+ dir_path = Path(dir_str)
452
+
453
+ try:
454
+ if not self.exists(dir_path):
455
+ return FileOperationResult(
456
+ True, f"Directory does not exist: {dir_path}"
457
+ )
458
+
459
+ if not dir_path.is_dir():
460
+ return FileOperationResult(
461
+ False, f"Path is not a directory: {dir_path}"
462
+ )
463
+
464
+ # Handle Windows file locking issues
465
+ self._handle_windows_operation(shutil.rmtree, dir_path)
466
+
467
+ # Clear relevant cache entries
468
+ dir_str = str(dir_path)
469
+ if dir_str in self.cache._existence_cache:
470
+ del self.cache._existence_cache[dir_str]
471
+
472
+ # Clear directory contents cache entries that might be affected
473
+ cache_keys_to_remove = []
474
+ for key in self.cache._directory_contents_cache:
475
+ if key.startswith(dir_str):
476
+ cache_keys_to_remove.append(key)
477
+
478
+ for key in cache_keys_to_remove:
479
+ if key in self.cache._directory_contents_cache:
480
+ del self.cache._directory_contents_cache[key]
481
+
482
+ return FileOperationResult(
483
+ True, f"Successfully removed directory {dir_path}"
484
+ )
485
+ except Exception as e:
486
+ return FileOperationResult(
487
+ False, f"Failed to remove directory {directory}: {str(e)}"
488
+ )
489
+
490
+ def clear_cache(self) -> None:
491
+ """Clear the file operations cache."""
492
+ self.cache.clear()
493
+
494
+
495
+ # Global instance
496
+ _file_operations = FileOperations()
497
+
498
+
499
+ def get_file_operations() -> FileOperations:
500
+ """
501
+ Get the global file operations instance.
502
+
503
+ Returns:
504
+ The global file operations instance
505
+ """
506
+ return _file_operations
@@ -0,0 +1,122 @@
1
+ """Language detection utilities for WL Commands."""
2
+
3
+ import locale
4
+ import os
5
+
6
+ # Cache for system language detection
7
+ _system_chinese_cache = None
8
+
9
+
10
+ def _is_system_chinese() -> bool:
11
+ """
12
+ Check if the system language is Chinese.
13
+
14
+ Returns:
15
+ bool: True if system language is Chinese, False otherwise
16
+ """
17
+ global _system_chinese_cache
18
+
19
+ # Use cached value if available
20
+ if _system_chinese_cache is not None:
21
+ return _system_chinese_cache
22
+
23
+ try:
24
+ # Try different locale-related environment variables
25
+ lang = (
26
+ os.environ.get("LANG", "")
27
+ or os.environ.get("LANGUAGE", "")
28
+ or os.environ.get("LC_ALL", "")
29
+ or os.environ.get("LC_MESSAGES", "")
30
+ )
31
+
32
+ # Fall back to locale.getlocale() if environment variables don't provide language info
33
+ if not lang:
34
+ try:
35
+ locale_value = locale.getlocale()[0] # Get language code from locale
36
+ if locale_value:
37
+ lang = locale_value
38
+ except (ValueError, TypeError):
39
+ # If getlocale() fails, use empty string
40
+ pass
41
+
42
+ # Check if any of the locale identifiers contain Chinese language codes
43
+ # 更宽松的检测逻辑,确保能正确检测中文
44
+ result = any(chinese_lang in lang.lower() for chinese_lang in ["zh", "chinese"])
45
+
46
+ # 调试信息 (已注释掉,避免影响测试)
47
+ # if lang:
48
+ # print(f"检测到语言设置: {lang}, 结果: {result}")
49
+
50
+ # Cache the result
51
+ _system_chinese_cache = result
52
+ return result
53
+ except (OSError, KeyError, ValueError):
54
+ # If we can't determine the language, default to English
55
+ _system_chinese_cache = False
56
+ return False
57
+
58
+
59
+ # Cache for language setting
60
+ _language_setting_cache = None
61
+
62
+
63
+ def reset_language_caches() -> None:
64
+ """重置语言检测缓存,用于测试
65
+
66
+ This function resets both the system Chinese language cache and the language setting cache.
67
+ It's primarily used for testing purposes to ensure clean state between tests.
68
+ """
69
+ global _system_chinese_cache, _language_setting_cache
70
+ _system_chinese_cache = None
71
+ _language_setting_cache = None
72
+
73
+
74
+ def _should_display_language(lang: str) -> bool:
75
+ """
76
+ Check if message in specified language should be displayed.
77
+
78
+ Args:
79
+ lang (str): Language of the message ("en" or "zh")
80
+
81
+ Returns:
82
+ bool: True if message should be displayed
83
+ """
84
+ global _language_setting_cache
85
+
86
+ # Use cached language setting if available
87
+ if _language_setting_cache is None:
88
+ try:
89
+ # Check environment variable first (highest priority)
90
+ if "WL_LANGUAGE" in os.environ:
91
+ _language_setting_cache = os.environ["WL_LANGUAGE"]
92
+ else:
93
+ # Try to import config module
94
+ try:
95
+ from .config import get_config
96
+
97
+ _language_setting_cache = get_config("language", "auto")
98
+ except ImportError:
99
+ # Fall back to auto detection
100
+ _language_setting_cache = "auto"
101
+ except (ImportError, KeyError):
102
+ # Default to auto if config is not available
103
+ _language_setting_cache = "auto"
104
+
105
+ # Use cached language setting
106
+ language_setting = _language_setting_cache
107
+
108
+ # If language is set to auto, display based on system language
109
+ if language_setting == "auto":
110
+ # If system language is Chinese, only show Chinese messages
111
+ # Otherwise, show English messages
112
+ is_system_chinese = _is_system_chinese()
113
+ return (lang == "zh" and is_system_chinese) or (
114
+ lang == "en" and not is_system_chinese
115
+ )
116
+
117
+ # If language is set to specific language, only display that language
118
+ if language_setting in ["en", "zh"]:
119
+ return language_setting == lang
120
+ else:
121
+ # Default to showing English for invalid language settings
122
+ return lang == "en"