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.
- py_wlcli-0.8.0.dist-info/METADATA +139 -0
- py_wlcli-0.8.0.dist-info/RECORD +320 -0
- py_wlcli-0.8.0.dist-info/WHEEL +4 -0
- py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
- py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
- wl_cli/__init__.py +27 -0
- wl_cli/__main__.py +130 -0
- wl_cli/capabilities.py +79 -0
- wl_cli/cc/__init__.py +5 -0
- wl_cli/cc/checks.py +1019 -0
- wl_cli/cc/cli.py +370 -0
- wl_cli/cc/mutate.py +1350 -0
- wl_cli/cli_click.py +156 -0
- wl_cli/commands/__init__.py +84 -0
- wl_cli/commands/base.py +80 -0
- wl_cli/commands/buildcommands/__init__.py +1 -0
- wl_cli/commands/buildcommands/build.py +71 -0
- wl_cli/commands/buildcommands/build_utils.py +47 -0
- wl_cli/commands/buildcommands/builders/__init__.py +18 -0
- wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
- wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
- wl_cli/commands/buildcommands/builders/environment.py +81 -0
- wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
- wl_cli/commands/buildcommands/builders/stubs.py +58 -0
- wl_cli/commands/buildcommands/dist.py +326 -0
- wl_cli/commands/buildcommands/test.py +89 -0
- wl_cli/commands/clean/__init__.py +1 -0
- wl_cli/commands/clean/build.py +151 -0
- wl_cli/commands/clean/clean.py +294 -0
- wl_cli/commands/clean/clean_utils.py +77 -0
- wl_cli/commands/clean/core.py +236 -0
- wl_cli/commands/clean/dead_code/__init__.py +0 -0
- wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
- wl_cli/commands/clean/dead_code/detector.py +161 -0
- wl_cli/commands/clean/dead_code/fixer.py +176 -0
- wl_cli/commands/clean/dead_code/reporter.py +295 -0
- wl_cli/commands/clean/dead_code/utils.py +0 -0
- wl_cli/commands/clean/lfs.py +215 -0
- wl_cli/commands/clean/python.py +81 -0
- wl_cli/commands/clean/rust.py +91 -0
- wl_cli/commands/clean/utils/__init__.py +34 -0
- wl_cli/commands/clean/venv.py +91 -0
- wl_cli/commands/cleanup.py +73 -0
- wl_cli/commands/click_base.py +79 -0
- wl_cli/commands/config/__init__.py +1 -0
- wl_cli/commands/config/config.py +291 -0
- wl_cli/commands/debug/__init__.py +6 -0
- wl_cli/commands/debug/debug.py +309 -0
- wl_cli/commands/debug/debug_cache.py +86 -0
- wl_cli/commands/debug/debug_subagent.py +156 -0
- wl_cli/commands/debug/output_formatter.py +165 -0
- wl_cli/commands/debug/source_hasher.py +92 -0
- wl_cli/commands/format/__init__.py +13 -0
- wl_cli/commands/format/format_cache_adapter.py +125 -0
- wl_cli/commands/format/format_command.py +197 -0
- wl_cli/commands/format/format_coordinator.py +191 -0
- wl_cli/commands/format/format_executor.py +470 -0
- wl_cli/commands/format/format_outcome.py +44 -0
- wl_cli/commands/format/format_path_handler.py +1016 -0
- wl_cli/commands/format/format_progress.py +62 -0
- wl_cli/commands/format/format_reporter.py +31 -0
- wl_cli/commands/format/python_formatter.py +506 -0
- wl_cli/commands/format/rust_formatter.py +106 -0
- wl_cli/commands/gate/__init__.py +1 -0
- wl_cli/commands/gate/gate_command.py +862 -0
- wl_cli/commands/gate/preflight.py +445 -0
- wl_cli/commands/git/__init__.py +1 -0
- wl_cli/commands/git/config.py +169 -0
- wl_cli/commands/git/git_facts.py +60 -0
- wl_cli/commands/git/git_flow.py +86 -0
- wl_cli/commands/git/hook.py +112 -0
- wl_cli/commands/git/policy.py +216 -0
- wl_cli/commands/git/push_update.py +82 -0
- wl_cli/commands/initenv/__init__.py +1 -0
- wl_cli/commands/initenv/common/__init__.py +23 -0
- wl_cli/commands/initenv/common/check_git_bash.py +78 -0
- wl_cli/commands/initenv/common/error_handler.py +68 -0
- wl_cli/commands/initenv/common/exceptions.py +52 -0
- wl_cli/commands/initenv/components/__init__.py +4 -0
- wl_cli/commands/initenv/components/cargo_sync.py +240 -0
- wl_cli/commands/initenv/components/config_manager.py +106 -0
- wl_cli/commands/initenv/components/directory_utils.py +71 -0
- wl_cli/commands/initenv/components/git/__init__.py +9 -0
- wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
- wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
- wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
- wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
- wl_cli/commands/initenv/components/git/git_utility.py +254 -0
- wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
- wl_cli/commands/initenv/components/git_initializer.py +212 -0
- wl_cli/commands/initenv/components/i18n_manager.py +104 -0
- wl_cli/commands/initenv/components/log_manager.py +241 -0
- wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
- wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
- wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
- wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
- wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
- wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
- wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
- wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
- wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
- wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
- wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
- wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
- wl_cli/commands/initenv/components/rust_initializer.py +145 -0
- wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
- wl_cli/commands/initenv/components/symlink_manager.py +52 -0
- wl_cli/commands/initenv/components/venv_manager.py +118 -0
- wl_cli/commands/initenv/initenv.py +169 -0
- wl_cli/commands/initenv/services/__init__.py +11 -0
- wl_cli/commands/initenv/services/environment_initializer.py +288 -0
- wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
- wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
- wl_cli/commands/lfs/README.md +221 -0
- wl_cli/commands/lfs/__init__.py +5 -0
- wl_cli/commands/lfs/cli.py +106 -0
- wl_cli/commands/lfs/config.py +164 -0
- wl_cli/commands/lfs/hook.py +61 -0
- wl_cli/commands/lfs/initializer.py +531 -0
- wl_cli/commands/lfs/manager.py +95 -0
- wl_cli/commands/lfs/policy.py +165 -0
- wl_cli/commands/lfs/watcher.py +294 -0
- wl_cli/commands/lint/__init__.py +1 -0
- wl_cli/commands/lint/lint_command.py +297 -0
- wl_cli/commands/lint/lint_executor.py +409 -0
- wl_cli/commands/lint/lint_reporter.py +579 -0
- wl_cli/commands/lint/lint_run.py +258 -0
- wl_cli/commands/lint/mypy_config.py +90 -0
- wl_cli/commands/registry.py +103 -0
- wl_cli/commands/self/__init__.py +7 -0
- wl_cli/commands/self/common/__init__.py +8 -0
- wl_cli/commands/self/common/capabilities.py +30 -0
- wl_cli/commands/self/common/config.py +27 -0
- wl_cli/commands/self/common/version_cache.py +85 -0
- wl_cli/commands/self/handlers/__init__.py +19 -0
- wl_cli/commands/self/handlers/base_handler.py +28 -0
- wl_cli/commands/self/handlers/install_handler.py +85 -0
- wl_cli/commands/self/handlers/status_handler.py +66 -0
- wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
- wl_cli/commands/self/handlers/update_handler.py +102 -0
- wl_cli/commands/self/handlers/version_handler.py +16 -0
- wl_cli/commands/self/self_command.py +237 -0
- wl_cli/commands/self/services/__init__.py +15 -0
- wl_cli/commands/self/services/installation_manager.py +187 -0
- wl_cli/commands/self/services/local_installer.py +165 -0
- wl_cli/commands/self/services/pypi_updater.py +278 -0
- wl_cli/commands/self/services/windows_update.py +174 -0
- wl_cli/commands/testcommands/__init__.py +8 -0
- wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
- wl_cli/commands/testcommands/batch_processor.py +278 -0
- wl_cli/commands/testcommands/cache_manager.py +1156 -0
- wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
- wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
- wl_cli/commands/testcommands/progress_manager.py +79 -0
- wl_cli/commands/testcommands/python_env_detector.py +207 -0
- wl_cli/commands/testcommands/report_generator.py +101 -0
- wl_cli/commands/testcommands/single_test_executor.py +250 -0
- wl_cli/commands/testcommands/speed_checker.py +118 -0
- wl_cli/commands/testcommands/test_command_builder.py +165 -0
- wl_cli/commands/testcommands/test_commands.py +786 -0
- wl_cli/commands/testcommands/test_reporter.py +181 -0
- wl_cli/commands/testcommands/test_result_handler.py +96 -0
- wl_cli/commands/testcommands/test_runner.py +1343 -0
- wl_cli/commands/testcommands/test_utils.py +197 -0
- wl_cli/commands/testcommands/utils/__init__.py +17 -0
- wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
- wl_cli/commands/testcommands/utils/env_info.py +36 -0
- wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
- wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
- wl_cli/commands/testcommands/utils/project_root.py +61 -0
- wl_cli/commands/update/__init__.py +9 -0
- wl_cli/commands/update/services/__init__.py +21 -0
- wl_cli/commands/update/services/backup_manager.py +207 -0
- wl_cli/commands/update/services/component_manager.py +83 -0
- wl_cli/commands/update/services/conflict_resolver.py +189 -0
- wl_cli/commands/update/services/vendors_updater.py +85 -0
- wl_cli/commands/update/services/version_tracker.py +127 -0
- wl_cli/commands/update/update_command.py +270 -0
- wl_cli/commands/update/utils/__init__.py +3 -0
- wl_cli/commands/version/__init__.py +9 -0
- wl_cli/commands/version/version_command.py +155 -0
- wl_cli/commands/workflow.py +477 -0
- wl_cli/env/__init__.py +5 -0
- wl_cli/env/cli.py +167 -0
- wl_cli/env_status.py +157 -0
- wl_cli/envelope.py +91 -0
- wl_cli/exceptions.py +85 -0
- wl_cli/lib/__init__.py +3 -0
- wl_cli/lib/rust_utils.py +35 -0
- wl_cli/mcp/__init__.py +6 -0
- wl_cli/mcp/capabilities.py +19 -0
- wl_cli/mcp/cli.py +22 -0
- wl_cli/mcp/gitlab.py +553 -0
- wl_cli/mcp/k8s.py +344 -0
- wl_cli/mcp/protocol.py +159 -0
- wl_cli/platform.py +139 -0
- wl_cli/quality/__init__.py +18 -0
- wl_cli/utils/__init__.py +7 -0
- wl_cli/utils/cache.py +491 -0
- wl_cli/utils/cache_ledger.py +355 -0
- wl_cli/utils/cache_store.py +546 -0
- wl_cli/utils/config/__init__.py +27 -0
- wl_cli/utils/config/config_loader.py +163 -0
- wl_cli/utils/config/core.py +862 -0
- wl_cli/utils/config/defaults.py +142 -0
- wl_cli/utils/config/env_exporter.py +86 -0
- wl_cli/utils/config/env_parser.py +108 -0
- wl_cli/utils/config/validation.py +280 -0
- wl_cli/utils/error_handler.py +165 -0
- wl_cli/utils/file_copier.py +56 -0
- wl_cli/utils/file_operations.py +506 -0
- wl_cli/utils/language_utils.py +122 -0
- wl_cli/utils/lazy_import.py +240 -0
- wl_cli/utils/log_levels.py +35 -0
- wl_cli/utils/log_rotators.py +74 -0
- wl_cli/utils/logging.py +458 -0
- wl_cli/utils/output_context.py +142 -0
- wl_cli/utils/performance/__init__.py +1 -0
- wl_cli/utils/performance/decorators.py +111 -0
- wl_cli/utils/performance/exporter.py +38 -0
- wl_cli/utils/performance/models.py +16 -0
- wl_cli/utils/performance/monitor.py +161 -0
- wl_cli/utils/performance_monitor.py +32 -0
- wl_cli/utils/platform_adapter.py +131 -0
- wl_cli/utils/pre_commit.py +266 -0
- wl_cli/utils/pre_commit_config.py +72 -0
- wl_cli/utils/root.py +521 -0
- wl_cli/utils/root_fallback.py +256 -0
- wl_cli/utils/run_evidence.py +236 -0
- wl_cli/utils/structured_logger.py +18 -0
- wl_cli/utils/structured_logging/__init__.py +19 -0
- wl_cli/utils/structured_logging/config.py +337 -0
- wl_cli/utils/structured_logging/core.py +177 -0
- wl_cli/utils/structured_logging/env.py +56 -0
- wl_cli/utils/structured_logging/filtering.py +75 -0
- wl_cli/utils/structured_logging/filters.py +55 -0
- wl_cli/utils/structured_logging/formatters.py +251 -0
- wl_cli/utils/structured_logging/handlers.py +308 -0
- wl_cli/utils/structured_logging/logger.py +354 -0
- wl_cli/utils/subprocess.py +71 -0
- wl_cli/utils/subprocess_utils.py +260 -0
- wl_cli/utils/test_utils/__init__.py +5 -0
- wl_cli/utils/test_utils/test_filter.py +36 -0
- wl_cli/utils/uv_run.py +79 -0
- wl_cli/utils/uv_tool.py +117 -0
- wl_cli/utils/uv_tree.py +71 -0
- wl_cli/utils/uv_workspace.py +174 -0
- wl_cli/utils/version/__init__.py +15 -0
- wl_cli/utils/version/pypi_version_checker.py +95 -0
- wl_cli/utils/version/version_comparator.py +133 -0
- wl_cli/utils/version/version_detectors.py +312 -0
- wl_cli/utils/version/version_service.py +131 -0
- wl_cli/utils/version/version_updater.py +198 -0
- wl_cli/utils/wl_dir_updater.py +1440 -0
- wl_cli/utils/workspace_detector/__init__.py +28 -0
- wl_cli/utils/workspace_detector/core.py +161 -0
- wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
- wl_cli/utils/workspace_detector/exceptions.py +5 -0
- wl_cli/utils/workspace_detector/types.py +17 -0
- wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
- wl_cli/vendors/config/.codespellrc +2 -0
- wl_cli/vendors/config/dependencies.toml +10 -0
- wl_cli/vendors/config/mypy.ini +62 -0
- wl_cli/vendors/config/pyproject_defaults.toml +28 -0
- wl_cli/vendors/config/template_config.toml +24 -0
- wl_cli/vendors/config/tool_configs.toml +76 -0
- wl_cli/vendors/git/.gitignore +122 -0
- wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
- wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/post-update.sample +8 -0
- wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/pre-commit +45 -0
- wl_cli/vendors/hooks/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/pre-push.sample +60 -0
- wl_cli/vendors/hooks/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
- wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
- wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
- wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
- wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/unix/post-update.sample +8 -0
- wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/unix/pre-commit +190 -0
- wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
- wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/unix/pre-push +417 -0
- wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
- wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/update.sample +128 -0
- wl_cli/vendors/hooks/win/work.bat +63 -0
- wl_cli/vendors/project/.gitignore +90 -0
- wl_cli/vendors/project/.python-version +1 -0
- wl_cli/vendors/project/LICENSE +21 -0
- wl_cli/vendors/project/README.md +0 -0
- wl_cli/vendors/project/pyproject.toml +27 -0
- wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
- wl_cli/vendors/project/pyproject_workspace.toml +14 -0
- wl_cli/vendors/readme/README.md +120 -0
- wl_cli/vendors/rust/Cargo.toml +14 -0
- wl_cli/vendors/rust/lib/__init__.py +3 -0
- wl_cli/vendors/rust/lib/rust_utils.py +31 -0
- wl_cli/vendors/rust/src/lib.rs +13 -0
- wl_cli/vendors/rust/src/main.rs +3 -0
- wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
- wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
- wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
- wl_cli/vendors/sop_doc/python-sop.md +342 -0
- 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"
|