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,62 @@
|
|
|
1
|
+
"""Format progress module."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FormatProgress:
|
|
9
|
+
"""Progress manager for format operations."""
|
|
10
|
+
|
|
11
|
+
def truncate_filename(self, filename: str) -> str:
|
|
12
|
+
"""Truncate filename to fit in progress bar."""
|
|
13
|
+
max_len = 28
|
|
14
|
+
if len(filename) > max_len:
|
|
15
|
+
return filename[: max_len - 3] + "..."
|
|
16
|
+
return filename
|
|
17
|
+
|
|
18
|
+
def create_progress_bar(self, total_files: int) -> tqdm:
|
|
19
|
+
"""Create a progress bar for file processing."""
|
|
20
|
+
return tqdm(
|
|
21
|
+
total=total_files,
|
|
22
|
+
unit="file",
|
|
23
|
+
leave=False,
|
|
24
|
+
dynamic_ncols=True,
|
|
25
|
+
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def update_progress_bar(
|
|
29
|
+
self,
|
|
30
|
+
pbar: tqdm,
|
|
31
|
+
truncated_name: str,
|
|
32
|
+
status: str,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Update progress bar with current status."""
|
|
35
|
+
pbar.set_description(f"Processing {truncated_name:<28} [{status}]")
|
|
36
|
+
pbar.update(1)
|
|
37
|
+
|
|
38
|
+
def close_progress_bar(self, pbar: tqdm, total_files: int) -> None:
|
|
39
|
+
"""Close the progress bar and ensure it shows 100%."""
|
|
40
|
+
try:
|
|
41
|
+
# Ensure progress bar shows 100% before closing
|
|
42
|
+
if total_files > 0:
|
|
43
|
+
pbar.n = total_files
|
|
44
|
+
pbar.refresh()
|
|
45
|
+
finally:
|
|
46
|
+
# Always close the progress bar
|
|
47
|
+
pbar.disable = True
|
|
48
|
+
pbar.close()
|
|
49
|
+
|
|
50
|
+
def print_format_error(self, file_path: Path, error_message: str) -> None:
|
|
51
|
+
"""Print format error message."""
|
|
52
|
+
print()
|
|
53
|
+
print(f"Formatting failed for file: {file_path}")
|
|
54
|
+
# Print the specific error message if available
|
|
55
|
+
if error_message:
|
|
56
|
+
print(f"Error details: {error_message}")
|
|
57
|
+
print("Stopping at first error as requested.")
|
|
58
|
+
|
|
59
|
+
def print_processing_error(self, file_path: Path, error: Exception) -> None:
|
|
60
|
+
"""Print processing error message."""
|
|
61
|
+
print()
|
|
62
|
+
print(f"Error processing {file_path}: {error}")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Format reporter module."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FormatReporter:
|
|
7
|
+
"""Reporter for format command."""
|
|
8
|
+
|
|
9
|
+
def generate_format_report(self, quiet, unsafe, paths, env):
|
|
10
|
+
"""Generate format report."""
|
|
11
|
+
# Create todos directory if it doesn't exist
|
|
12
|
+
todos_dir = Path("todos")
|
|
13
|
+
todos_dir.mkdir(exist_ok=True)
|
|
14
|
+
|
|
15
|
+
# Generate report content
|
|
16
|
+
report_lines = []
|
|
17
|
+
report_lines.append("# Format Report\n")
|
|
18
|
+
report_lines.append("## Configuration\n")
|
|
19
|
+
report_lines.append(f"- Quiet mode: {quiet}\n")
|
|
20
|
+
report_lines.append(f"- Unsafe fixes: {unsafe}\n")
|
|
21
|
+
report_lines.append(
|
|
22
|
+
f"- Custom paths: {paths if paths else 'None (using defaults)'}\n"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Save report
|
|
26
|
+
report_path = todos_dir / "format_report.md"
|
|
27
|
+
with open(report_path, "w", encoding="utf-8") as f:
|
|
28
|
+
f.write("\n".join(report_lines))
|
|
29
|
+
|
|
30
|
+
if not quiet:
|
|
31
|
+
print(f"Format report generated at {report_path}")
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python code formatting utilities for WL Commands."""
|
|
3
|
+
|
|
4
|
+
import subprocess
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ...utils.subprocess_utils import SubprocessExecutor, SubprocessResult
|
|
9
|
+
from ...utils.uv_run import run_uv_command
|
|
10
|
+
from .format_outcome import FormatFailure
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class FormatCommandResult:
|
|
15
|
+
"""Small result value for one formatter command (#164).
|
|
16
|
+
|
|
17
|
+
Carrying ``returncode`` and ``launch_error`` -- rather than a bool -- is
|
|
18
|
+
what lets the caller tell "ruff judged the file bad" from "ruff never ran"
|
|
19
|
+
without re-sniffing output text.
|
|
20
|
+
|
|
21
|
+
``launcher_failure`` carries the one case those two fields cannot express
|
|
22
|
+
(#200): ``uv run ruff ...`` exiting non-zero because **uv** failed -- an
|
|
23
|
+
unsatisfiable dependency, an offline cache miss, a malformed ``uv.lock``
|
|
24
|
+
-- with ruff never started. Those exit codes are otherwise the same ones
|
|
25
|
+
ruff uses for its own verdicts (``1`` for "violations remain", ``2`` for a
|
|
26
|
+
file or config it refuses), so the streams are what tell them apart (see
|
|
27
|
+
:func:`_launcher_failure` and :func:`_confirm_launcher_failure`), never a
|
|
28
|
+
substring of the text.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
ok: bool
|
|
32
|
+
output: str
|
|
33
|
+
returncode: int | None
|
|
34
|
+
launch_error: str | None
|
|
35
|
+
#: The launcher's own diagnostic when ``uv`` failed before its child could
|
|
36
|
+
#: produce a verdict; ``None`` when the child (whatever it said) ran.
|
|
37
|
+
launcher_failure: str | None = None
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def tool_missing(self) -> bool:
|
|
41
|
+
"""True when the formatter never got to run (not a format verdict)."""
|
|
42
|
+
return self.launch_error is not None or self.returncode == 127
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _launcher_failure(stdout: str, stderr: str, returncode: int | None) -> str | None:
|
|
46
|
+
"""``uv``'s own failure text, or ``None`` when the child produced a verdict.
|
|
47
|
+
|
|
48
|
+
#200: both ``uv`` failing and ``ruff check`` finding unfixable violations
|
|
49
|
+
exit ``1``. The streams separate them without sniffing either program's
|
|
50
|
+
wording:
|
|
51
|
+
|
|
52
|
+
* ruff prints **every** diagnostic to stdout, and its rc1 summary
|
|
53
|
+
(``Found N errors.``) there too, so a real verdict always leaves text on
|
|
54
|
+
stdout;
|
|
55
|
+
* uv reports its own failures on stderr and writes nothing to stdout.
|
|
56
|
+
|
|
57
|
+
A non-zero exit with an empty stdout and a non-empty stderr is therefore a
|
|
58
|
+
*candidate* launcher failure, and that stderr text is its reason.
|
|
59
|
+
|
|
60
|
+
rc ``>= 2`` cannot be settled here. ``uv`` exits ``2`` when it cannot start
|
|
61
|
+
the command at all (a malformed ``uv.lock``, an unrecognised flag), but
|
|
62
|
+
real ruff *also* exits ``2`` with an empty stdout and its own text on
|
|
63
|
+
stderr when it refuses a file (``error: Failed to parse ...``) or its
|
|
64
|
+
config (``ruff failed / Cause: Unknown rule selector``) -- measured
|
|
65
|
+
against real ruff, not assumed. :func:`_confirm_launcher_failure` settles
|
|
66
|
+
that case with a differential probe; this function only answers "could
|
|
67
|
+
have been the launcher".
|
|
68
|
+
|
|
69
|
+
Trade-off for rc1, deliberately fail-*open*: an rc1 that is silent on both
|
|
70
|
+
streams stays a ruff verdict. There is no evidence a launcher failed, and
|
|
71
|
+
guessing "formatter failure" there would abort ``wl lint``'s prefix --
|
|
72
|
+
skipping its own ruff run, mypy and the report, the regression #188 fixed.
|
|
73
|
+
If uv did fail silently, lint's own independent ruff run still reports the
|
|
74
|
+
breakage.
|
|
75
|
+
"""
|
|
76
|
+
if returncode is None or returncode < 1:
|
|
77
|
+
return None
|
|
78
|
+
if stdout.strip() or not stderr.strip():
|
|
79
|
+
return None
|
|
80
|
+
return stderr.strip()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _confirm_launcher_failure(
|
|
84
|
+
executor: SubprocessExecutor,
|
|
85
|
+
command: list,
|
|
86
|
+
env: dict,
|
|
87
|
+
result: SubprocessResult,
|
|
88
|
+
candidate: str,
|
|
89
|
+
) -> str | None:
|
|
90
|
+
"""Decide whether an ambiguous rc2 failure was uv's, not the child's.
|
|
91
|
+
|
|
92
|
+
Since ruff's own refusals share uv's rc2 shape (empty stdout, text on
|
|
93
|
+
stderr), the streams cannot separate "ruff refused this file/config" from
|
|
94
|
+
"ruff never started". The discriminator that does not read either
|
|
95
|
+
program's wording is differential: re-run the same child without the
|
|
96
|
+
``uv run`` prefix. A child that produces a *different* outcome when run
|
|
97
|
+
directly never got that far through uv, so the launcher failed; the same
|
|
98
|
+
shape of failure -- the same exit code, nothing on stdout -- is the
|
|
99
|
+
child's own diagnostic.
|
|
100
|
+
|
|
101
|
+
#215: "same shape" deliberately excludes the stderr *text*. The direct
|
|
102
|
+
re-run resolves a different ``ruff`` than ``uv run`` does -- the one on
|
|
103
|
+
``PATH``, which may be any release. Two ruff versions word the same refusal
|
|
104
|
+
(``--select INVALID``, a file it cannot parse) differently, so requiring
|
|
105
|
+
the texts to match promoted a genuine rc2 verdict to a launcher failure
|
|
106
|
+
whenever the developer's ``ruff`` drifted from the project's pin. Exit code
|
|
107
|
+
plus an empty stdout is what identifies the verdict; the wording belongs to
|
|
108
|
+
whichever release produced it.
|
|
109
|
+
|
|
110
|
+
A uv-managed ``ruff`` is usually *not* on PATH, so the direct re-run can
|
|
111
|
+
itself be unlaunchable. Then the question is turned around and asked of
|
|
112
|
+
the launcher: ``uv run ruff --version`` succeeds when uv can still start
|
|
113
|
+
ruff, which makes the rc2 ruff's own; if even that fails, the tool could
|
|
114
|
+
not be started in this environment and the launcher reading stands.
|
|
115
|
+
|
|
116
|
+
Only the ambiguous shape reaches here -- a run that left text on stdout is
|
|
117
|
+
a verdict, and rc0/rc1 are settled by :func:`_launcher_failure` -- and
|
|
118
|
+
every probe is captured (``quiet=True``), so nothing prints twice.
|
|
119
|
+
``ruff check --fix`` and ``ruff format`` are idempotent, so re-running them
|
|
120
|
+
changes nothing the first run did not already change.
|
|
121
|
+
|
|
122
|
+
Returns *candidate* (the stderr text) when the launcher is at fault, and
|
|
123
|
+
``None`` when the direct run fails the same way -- i.e. the diagnostic
|
|
124
|
+
belongs to the child.
|
|
125
|
+
"""
|
|
126
|
+
if len(command) < 3 or command[0] != "uv" or command[1] != "run":
|
|
127
|
+
# Not a uv-launched command: no launcher to blame.
|
|
128
|
+
return candidate
|
|
129
|
+
try:
|
|
130
|
+
probe = executor.run(command[2:], env=env, quiet=True)
|
|
131
|
+
except Exception:
|
|
132
|
+
probe = None
|
|
133
|
+
if probe is None or probe.launch_error is not None or probe.returncode == 127:
|
|
134
|
+
# The child is not directly runnable (usually: uv-managed, not on
|
|
135
|
+
# PATH). Ask uv whether it can start the tool at all.
|
|
136
|
+
return None if _launcher_can_start_child(executor, command, env) else candidate
|
|
137
|
+
# Same exit code with nothing on stdout is the child's verdict shape, no
|
|
138
|
+
# matter which ruff release produced the stderr wording (#215).
|
|
139
|
+
child_reached_the_same_verdict = (
|
|
140
|
+
probe.returncode == result.returncode and not (probe.stdout or "").strip()
|
|
141
|
+
)
|
|
142
|
+
return None if child_reached_the_same_verdict else candidate
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _launcher_can_start_child(
|
|
146
|
+
executor: SubprocessExecutor, command: list, env: dict
|
|
147
|
+
) -> bool:
|
|
148
|
+
"""Whether the launcher can start its child at all, asked cheaply.
|
|
149
|
+
|
|
150
|
+
``command`` is ``["uv", "run", "<tool>", ...]``; the probe keeps the
|
|
151
|
+
launcher prefix and the tool, and replaces the arguments with
|
|
152
|
+
``--version`` -- a side-effect-free run that succeeds only if uv can still
|
|
153
|
+
get as far as executing the tool.
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
probe = executor.run(
|
|
157
|
+
[*command[:2], command[2], "--version"], env=env, quiet=True
|
|
158
|
+
)
|
|
159
|
+
except Exception:
|
|
160
|
+
return False
|
|
161
|
+
return probe.launch_error is None and probe.returncode == 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _run_format_command(
|
|
165
|
+
command: list, env: dict, quiet: bool, passthrough: bool = False
|
|
166
|
+
) -> FormatCommandResult:
|
|
167
|
+
"""Run a formatting command and report what it actually did (#164)."""
|
|
168
|
+
# Use SubprocessExecutor for consistent command execution
|
|
169
|
+
executor = SubprocessExecutor()
|
|
170
|
+
|
|
171
|
+
# ``run_uv_command`` owns the "uv is not installed" retry, shared with the
|
|
172
|
+
# pre-commit run (#165); the executor stays here so a caller keeps one
|
|
173
|
+
# patch point for the command it starts.
|
|
174
|
+
final_result = run_uv_command(executor, command, env=env, quiet=not passthrough)
|
|
175
|
+
stdout = final_result.stdout or ""
|
|
176
|
+
stderr = final_result.stderr or ""
|
|
177
|
+
output = stdout + "\n" + stderr if stderr else stdout
|
|
178
|
+
|
|
179
|
+
# Handle command failure - only log the error, don't raise exception.
|
|
180
|
+
# ``passthrough`` runs already wrote their own output, so only the exit
|
|
181
|
+
# code is ours to add there; a captured run is echoed instead (#200
|
|
182
|
+
# MEDIUM-1), otherwise the diagnostics the verdict was built from would
|
|
183
|
+
# never reach a non-quiet user.
|
|
184
|
+
if not quiet:
|
|
185
|
+
if passthrough and final_result.returncode != 0:
|
|
186
|
+
error_msg = f"Command failed with return code {final_result.returncode}"
|
|
187
|
+
if stderr:
|
|
188
|
+
error_msg += f": {stderr.strip()}"
|
|
189
|
+
print(f"Warning: {error_msg}")
|
|
190
|
+
elif not passthrough and output.strip():
|
|
191
|
+
print(output.strip())
|
|
192
|
+
|
|
193
|
+
launcher_failure = _launcher_failure(stdout, stderr, final_result.returncode)
|
|
194
|
+
if launcher_failure is not None and final_result.returncode == 2:
|
|
195
|
+
# rc2 is shared by uv's own startup failures and ruff's own refusals;
|
|
196
|
+
# only a direct re-run of the same child can tell them apart (NIT-1).
|
|
197
|
+
launcher_failure = _confirm_launcher_failure(
|
|
198
|
+
executor, command, env, final_result, launcher_failure
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return FormatCommandResult(
|
|
202
|
+
ok=final_result.returncode == 0,
|
|
203
|
+
output=output,
|
|
204
|
+
returncode=final_result.returncode,
|
|
205
|
+
launch_error=final_result.launch_error,
|
|
206
|
+
launcher_failure=launcher_failure,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass(frozen=True)
|
|
211
|
+
class RuffCheckVerdict(FormatFailure):
|
|
212
|
+
"""A ``ruff check --fix`` verdict, not a formatter failure (#188).
|
|
213
|
+
|
|
214
|
+
``ruff check`` exits ``1`` when its own run leaves something to report --
|
|
215
|
+
after every fix it could apply. That is a *lint* finding: ``wl lint``
|
|
216
|
+
re-runs ruff itself and owns the verdict, so the exit ``1`` must not abort
|
|
217
|
+
its format prefix. It stays a failure for ``wl format``, which promises the
|
|
218
|
+
check step came back clean, and it keeps ``kind == "ruff"`` so every
|
|
219
|
+
existing ``FormatOutcome`` reader still sees a formatter-shaped failure.
|
|
220
|
+
|
|
221
|
+
It is a subclass rather than a fourth ``kind`` because the closed
|
|
222
|
+
vocabulary lives in ``format_outcome.py``, which this ticket does not own.
|
|
223
|
+
|
|
224
|
+
What actually lands here (#200). The check step's rc1 is *not* limited to
|
|
225
|
+
``F821``-style unfixable violations: ruff also reports a file it cannot
|
|
226
|
+
parse as ``invalid-syntax`` diagnostics on stdout and exits ``1``, so the
|
|
227
|
+
check step's own verdict is this one. Since
|
|
228
|
+
:meth:`FormatExecutor.format_python_file` returns at the failed check step,
|
|
229
|
+
``ruff format`` never runs for that file and the syntax error is carried
|
|
230
|
+
*only* as this verdict -- so ``wl lint`` does **not** stop before its own
|
|
231
|
+
ruff run; lint's run reports the ``invalid-syntax`` finding and the run
|
|
232
|
+
stays red.
|
|
233
|
+
|
|
234
|
+
A plain :class:`FormatFailure` instead means the check step's own verdict
|
|
235
|
+
was unavailable: ``uv`` failed to start ruff (:attr:`FormatCommandResult.launcher_failure`,
|
|
236
|
+
#200), the launcher is missing, or ruff exited ``>= 2`` because it refused
|
|
237
|
+
the config/arguments. For a *directory* run :func:`format_with_ruff` also
|
|
238
|
+
runs ``ruff format``, whose parse failure is rc2 (``error: Failed to parse
|
|
239
|
+
...``) and is that plain failure -- which is why a syntax error stops
|
|
240
|
+
``wl lint`` there, but not on the single-file path above.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _ruff_failure(
|
|
245
|
+
directory: str, result: FormatCommandResult, step: str
|
|
246
|
+
) -> FormatFailure:
|
|
247
|
+
"""Turn a failed formatter command into the failure vocabulary (#164)."""
|
|
248
|
+
if result.tool_missing or result.launcher_failure is not None:
|
|
249
|
+
# #200 NIT-2: name the actual startup failure. ``tool_missing`` is the
|
|
250
|
+
# closed vocabulary's only "never ran" slot, but saying "ruff could not
|
|
251
|
+
# be launched" for a uv failure reads as "install ruff"; a uv launcher
|
|
252
|
+
# failure gets its own wording, and a missing executable keeps the
|
|
253
|
+
# launch error it actually raised (F6: no over-attribution).
|
|
254
|
+
if result.launcher_failure is not None:
|
|
255
|
+
message = (
|
|
256
|
+
f"the uv launcher failed before ruff ran ({step}): "
|
|
257
|
+
f"{result.launcher_failure}"
|
|
258
|
+
)
|
|
259
|
+
else:
|
|
260
|
+
reason = result.launch_error or f"exit code {result.returncode}"
|
|
261
|
+
message = f"ruff could not be started ({step}): {reason}"
|
|
262
|
+
return FormatFailure(
|
|
263
|
+
path=Path(directory),
|
|
264
|
+
kind="tool_missing",
|
|
265
|
+
message=message,
|
|
266
|
+
)
|
|
267
|
+
message = result.output.strip() or (
|
|
268
|
+
f"{step} failed with return code {result.returncode}"
|
|
269
|
+
)
|
|
270
|
+
return FormatFailure(path=Path(directory), kind="ruff", message=message)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _ruff_check_failure(directory: str, result: FormatCommandResult) -> FormatFailure:
|
|
274
|
+
"""Classify a failed ``ruff check`` run (#188, #200).
|
|
275
|
+
|
|
276
|
+
Exit ``1`` from a ruff that *did* run is "violations remain" -- a lint
|
|
277
|
+
verdict, carried as :class:`RuffCheckVerdict`. Anything else is a
|
|
278
|
+
formatter failure the lint prefix must still stop on: a launcher that
|
|
279
|
+
never ran, ``uv`` itself failing with rc1 (``launcher_failure``, #200 --
|
|
280
|
+
indistinguishable from a verdict by exit code alone), or exit ``>= 2``
|
|
281
|
+
(ruff refused the config/arguments).
|
|
282
|
+
"""
|
|
283
|
+
if (
|
|
284
|
+
not result.tool_missing
|
|
285
|
+
and result.launcher_failure is None
|
|
286
|
+
and result.returncode == 1
|
|
287
|
+
):
|
|
288
|
+
message = result.output.strip() or "ruff check left unfixed violations"
|
|
289
|
+
return RuffCheckVerdict(path=Path(directory), kind="ruff", message=message)
|
|
290
|
+
return _ruff_failure(directory, result, "ruff check")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def format_with_ruff(
|
|
294
|
+
directory: str, env: dict, quiet: bool = False, unsafe: bool = False
|
|
295
|
+
) -> tuple[FormatFailure, ...]:
|
|
296
|
+
"""Format directory with ruff only; returns every command that failed."""
|
|
297
|
+
failures: list[FormatFailure] = []
|
|
298
|
+
try:
|
|
299
|
+
# Run ruff check with fix
|
|
300
|
+
ruff_check_cmd = ["uv", "run", "ruff", "check", "--fix"]
|
|
301
|
+
if unsafe:
|
|
302
|
+
ruff_check_cmd.append("--unsafe-fixes")
|
|
303
|
+
ruff_check_cmd.append(directory)
|
|
304
|
+
|
|
305
|
+
# #200 MEDIUM-1: both steps are captured even when not quiet. The
|
|
306
|
+
# passthrough run left stdout/stderr empty, so a launcher failure was
|
|
307
|
+
# indistinguishable from a verdict and ``wl lint`` / ``wl format <dir>``
|
|
308
|
+
# treated a ruff that never ran as a lint verdict. ``_run_format_command``
|
|
309
|
+
# echoes the captured text when not quiet.
|
|
310
|
+
check_result = _run_format_command(
|
|
311
|
+
ruff_check_cmd, env, quiet, passthrough=False
|
|
312
|
+
)
|
|
313
|
+
if not check_result.ok:
|
|
314
|
+
# #188: rc1 is "violations remain", which the lint prefix must not
|
|
315
|
+
# mistake for a formatter that could not run.
|
|
316
|
+
failures.append(_ruff_check_failure(directory, check_result))
|
|
317
|
+
|
|
318
|
+
# Run ruff format for code formatting
|
|
319
|
+
format_result = _run_format_command(
|
|
320
|
+
[
|
|
321
|
+
"uv",
|
|
322
|
+
"run",
|
|
323
|
+
"ruff",
|
|
324
|
+
"format",
|
|
325
|
+
directory,
|
|
326
|
+
],
|
|
327
|
+
env,
|
|
328
|
+
quiet,
|
|
329
|
+
passthrough=False,
|
|
330
|
+
)
|
|
331
|
+
if not format_result.ok:
|
|
332
|
+
failures.append(_ruff_failure(directory, format_result, "ruff format"))
|
|
333
|
+
|
|
334
|
+
# Note: We no longer manually handle trailing whitespace and end-of-file newlines here
|
|
335
|
+
# because pre-commit's trailing-whitespace and end-of-file-fixer hooks should handle this
|
|
336
|
+
# This ensures consistency between wl format and running pre-commit directly
|
|
337
|
+
except subprocess.CalledProcessError as e:
|
|
338
|
+
if not quiet:
|
|
339
|
+
print(f"Warning: ruff formatting failed with return code {e.returncode}")
|
|
340
|
+
raise
|
|
341
|
+
except FileNotFoundError as e:
|
|
342
|
+
if not quiet:
|
|
343
|
+
print(f"Warning: uv or ruff not found in PATH: {e}")
|
|
344
|
+
failures.append(
|
|
345
|
+
FormatFailure(
|
|
346
|
+
path=Path(directory),
|
|
347
|
+
kind="tool_missing",
|
|
348
|
+
message=f"uv or ruff not found in PATH: {e}",
|
|
349
|
+
)
|
|
350
|
+
)
|
|
351
|
+
except Exception as e:
|
|
352
|
+
if not quiet:
|
|
353
|
+
print(f"Warning occurred during ruff formatting: {e}")
|
|
354
|
+
failures.append(
|
|
355
|
+
FormatFailure(
|
|
356
|
+
path=Path(directory),
|
|
357
|
+
kind="ruff",
|
|
358
|
+
message=f"Warning occurred during ruff formatting: {e}",
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
return tuple(failures)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def format_with_python_tools(
|
|
365
|
+
directory: str, env: dict, quiet: bool = False, unsafe: bool = False
|
|
366
|
+
) -> tuple[FormatFailure, ...]:
|
|
367
|
+
"""Format directory with Python tools (ruff only)."""
|
|
368
|
+
try:
|
|
369
|
+
# Format with ruff only
|
|
370
|
+
return format_with_ruff(directory, env, quiet, unsafe)
|
|
371
|
+
except subprocess.CalledProcessError as e:
|
|
372
|
+
if not quiet:
|
|
373
|
+
print(f"Warning: Python formatting failed with return code {e.returncode}")
|
|
374
|
+
raise
|
|
375
|
+
except Exception as e:
|
|
376
|
+
if not quiet:
|
|
377
|
+
print(f"Warning occurred during Python formatting: {e}")
|
|
378
|
+
return (
|
|
379
|
+
FormatFailure(
|
|
380
|
+
path=Path(directory),
|
|
381
|
+
kind="ruff",
|
|
382
|
+
message=f"Warning occurred during Python formatting: {e}",
|
|
383
|
+
),
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def format_tools_scripts(
|
|
388
|
+
tools_dir: str, env: dict, quiet: bool, unsafe: bool = False
|
|
389
|
+
) -> None:
|
|
390
|
+
"""Format Python files in tools directory with ruff."""
|
|
391
|
+
tools_path = Path(tools_dir)
|
|
392
|
+
if not tools_path.exists():
|
|
393
|
+
if not quiet:
|
|
394
|
+
print(f"Tools directory {tools_dir} does not exist, skipping...")
|
|
395
|
+
return
|
|
396
|
+
|
|
397
|
+
# Find all Python files in tools directory
|
|
398
|
+
python_files = list(tools_path.glob("**/*.py"))
|
|
399
|
+
if python_files:
|
|
400
|
+
# Fix issues in tools Python files with ruff check
|
|
401
|
+
try:
|
|
402
|
+
ruff_check_cmd = ["uv", "run", "ruff", "check", "--fix"]
|
|
403
|
+
if unsafe:
|
|
404
|
+
ruff_check_cmd.append("--unsafe-fixes")
|
|
405
|
+
ruff_check_cmd.extend([str(f) for f in python_files])
|
|
406
|
+
|
|
407
|
+
_run_format_command(ruff_check_cmd, env, quiet, passthrough=not quiet)
|
|
408
|
+
except Exception as e:
|
|
409
|
+
if not quiet:
|
|
410
|
+
print(f"Warning: ruff check failed for tools scripts: {e}")
|
|
411
|
+
|
|
412
|
+
# Format tools Python files with ruff format
|
|
413
|
+
try:
|
|
414
|
+
_run_format_command(
|
|
415
|
+
["uv", "run", "ruff", "format"] + [str(f) for f in python_files],
|
|
416
|
+
env,
|
|
417
|
+
quiet,
|
|
418
|
+
passthrough=not quiet,
|
|
419
|
+
)
|
|
420
|
+
except Exception as e:
|
|
421
|
+
if not quiet:
|
|
422
|
+
print(f"Warning: ruff format failed for tools scripts: {e}")
|
|
423
|
+
else:
|
|
424
|
+
if not quiet:
|
|
425
|
+
print("No Python files found in tools directory")
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def format_examples(
|
|
429
|
+
examples_dir: str, env: dict, quiet: bool, unsafe: bool = False
|
|
430
|
+
) -> None:
|
|
431
|
+
"""Format examples directory with ruff."""
|
|
432
|
+
examples_path = Path(examples_dir)
|
|
433
|
+
if not examples_path.exists():
|
|
434
|
+
if not quiet:
|
|
435
|
+
print(f"Examples directory {examples_dir} does not exist, skipping...")
|
|
436
|
+
return
|
|
437
|
+
|
|
438
|
+
# Fix issues in examples with ruff check
|
|
439
|
+
try:
|
|
440
|
+
ruff_check_cmd = ["uv", "run", "ruff", "check", "--fix"]
|
|
441
|
+
if unsafe:
|
|
442
|
+
ruff_check_cmd.append("--unsafe-fixes")
|
|
443
|
+
ruff_check_cmd.append(examples_dir)
|
|
444
|
+
|
|
445
|
+
_run_format_command(ruff_check_cmd, env, quiet, passthrough=not quiet)
|
|
446
|
+
except Exception as e:
|
|
447
|
+
if not quiet:
|
|
448
|
+
print(f"Warning: ruff check failed for examples: {e}")
|
|
449
|
+
|
|
450
|
+
# Format examples with ruff format
|
|
451
|
+
try:
|
|
452
|
+
_run_format_command(
|
|
453
|
+
["uv", "run", "ruff", "format", examples_dir],
|
|
454
|
+
env,
|
|
455
|
+
quiet,
|
|
456
|
+
passthrough=not quiet,
|
|
457
|
+
)
|
|
458
|
+
except Exception as e:
|
|
459
|
+
if not quiet:
|
|
460
|
+
print(f"Warning: ruff format failed for examples: {e}")
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def generate_type_stubs(src_dir: str, typings_dir: str, env: dict, quiet: bool) -> None:
|
|
464
|
+
"""Generate type stubs."""
|
|
465
|
+
if not quiet:
|
|
466
|
+
print(f"Generating type stubs for {src_dir}...")
|
|
467
|
+
|
|
468
|
+
# Check if src_dir exists
|
|
469
|
+
src_path = Path(src_dir)
|
|
470
|
+
if not src_path.exists():
|
|
471
|
+
if not quiet:
|
|
472
|
+
print(f"Target {src_dir} does not exist, skipping...")
|
|
473
|
+
return
|
|
474
|
+
|
|
475
|
+
# Ensure typings directory exists
|
|
476
|
+
typings_path = Path(typings_dir)
|
|
477
|
+
typings_path.mkdir(parents=True, exist_ok=True)
|
|
478
|
+
|
|
479
|
+
try:
|
|
480
|
+
# Use uv run --with mypy python -c to call mypy.stubgen
|
|
481
|
+
# Use repr() to properly escape paths for the Python command
|
|
482
|
+
cmd = [
|
|
483
|
+
"uv",
|
|
484
|
+
"run",
|
|
485
|
+
"--with",
|
|
486
|
+
"mypy",
|
|
487
|
+
"python",
|
|
488
|
+
"-c",
|
|
489
|
+
f"from mypy.stubgen import main; main([{repr(src_dir)}, '-o', {repr(typings_dir)}])",
|
|
490
|
+
]
|
|
491
|
+
|
|
492
|
+
_run_format_command(cmd, env, quiet, passthrough=False)
|
|
493
|
+
except Exception as e:
|
|
494
|
+
if not quiet:
|
|
495
|
+
print(f"Warning: Error generating type stubs: {e}")
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
__all__ = [
|
|
499
|
+
"RuffCheckVerdict",
|
|
500
|
+
"_run_format_command",
|
|
501
|
+
"format_with_ruff",
|
|
502
|
+
"format_with_python_tools",
|
|
503
|
+
"format_tools_scripts",
|
|
504
|
+
"format_examples",
|
|
505
|
+
"generate_type_stubs",
|
|
506
|
+
]
|