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,266 @@
|
|
|
1
|
+
"""What a ``pre-commit`` run means, in one place (#165).
|
|
2
|
+
|
|
3
|
+
Before this module, three sets of rules read the same output: the format path
|
|
4
|
+
handler classified hooks (``- hook id:`` blocks + the auto-fix marker), its
|
|
5
|
+
filter decided what to show (``Failed`` lines), and the lint reporter decided
|
|
6
|
+
what to report (line tails ending in ``Passed``/``Failed``). The run was also
|
|
7
|
+
started two ways -- ``uv run pre-commit`` for format, a bare ``pre-commit`` for
|
|
8
|
+
lint. Now both commands call :func:`run_pre_commit` and read the answer from
|
|
9
|
+
:class:`PreCommitRun`.
|
|
10
|
+
|
|
11
|
+
This module knows about *one run*. It does not know where the config lives, and
|
|
12
|
+
it never reads the filesystem to find out -- see :func:`run_pre_commit`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from .subprocess_utils import SubprocessResult
|
|
20
|
+
from .uv_run import run_uv_command, uv_unavailable
|
|
21
|
+
|
|
22
|
+
#: pre-commit detail line naming the hook a ``Failed`` block belongs to. It is
|
|
23
|
+
#: printed for every hook pre-commit *failed* and for none it passed, which is
|
|
24
|
+
#: what makes it the marker identifying a hook verdict in the run's output.
|
|
25
|
+
_HOOK_ID_PREFIX = "- hook id:"
|
|
26
|
+
|
|
27
|
+
#: pre-commit detail line emitted when a hook *rewrote* files -- an auto-fix,
|
|
28
|
+
#: the expected outcome of running a formatter, which must leave the format
|
|
29
|
+
#: gate green. The built-in fixers (``trailing-whitespace``,
|
|
30
|
+
#: ``end-of-file-fixer``) exit ``1`` after rewriting and print this marker next
|
|
31
|
+
#: to their ``- exit code: 1`` line; hooks that only *report* a failure print
|
|
32
|
+
#: the exit-code line without it.
|
|
33
|
+
_MODIFIED_MARKER = "- files were modified by this hook"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class PreCommitRun:
|
|
38
|
+
"""One finished ``pre-commit run``, and the verdict it carries.
|
|
39
|
+
|
|
40
|
+
The exit code alone cannot answer "did a hook judge a failure?": pre-commit
|
|
41
|
+
also exits non-zero when a hook merely rewrote files. The verdict, the
|
|
42
|
+
blocks and the filtered display text are all *derived* from ``result`` so
|
|
43
|
+
they can never disagree with each other.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
result: SubprocessResult
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def stdout(self) -> str:
|
|
50
|
+
"""Captured stdout (empty string, never ``None``)."""
|
|
51
|
+
return self.result.stdout or ""
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def stderr(self) -> str:
|
|
55
|
+
"""Captured stderr (empty string, never ``None``).
|
|
56
|
+
|
|
57
|
+
Sits beside :attr:`stdout` because lint used to merge the two before
|
|
58
|
+
reading the verdict (#165 audit patch 1).
|
|
59
|
+
"""
|
|
60
|
+
return self.result.stderr or ""
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def combined_output(self) -> str:
|
|
64
|
+
"""stdout and stderr joined in that order, as lint used to see them."""
|
|
65
|
+
parts = [part for part in (self.stdout, self.stderr) if part]
|
|
66
|
+
return "\n".join(parts)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def verdict_blocks(self) -> tuple[tuple[str, ...], ...]:
|
|
70
|
+
"""One block per hook pre-commit reported, auto-fixes included.
|
|
71
|
+
|
|
72
|
+
A block is a maximal run of ``-``-prefixed lines carrying
|
|
73
|
+
pre-commit's own ``- hook id:`` line. Requiring that line is what keeps
|
|
74
|
+
other output from inventing a block: pre-commit prints it per *failing*
|
|
75
|
+
hook and never for a passing one, while hook output can contain
|
|
76
|
+
``-``-prefixed lines of its own (``ruff`` prints `` --> file:1:8``) or a
|
|
77
|
+
line ending in the word ``Failed`` (the phantom block of #149 review
|
|
78
|
+
D2).
|
|
79
|
+
"""
|
|
80
|
+
return _hook_verdict_blocks(self.stdout)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def failed_blocks(self) -> tuple[tuple[str, ...], ...]:
|
|
84
|
+
"""The verdict blocks that are real failures -- auto-fixes excluded.
|
|
85
|
+
|
|
86
|
+
Auto-fix blocks are dropped for the same reason they do not fail the
|
|
87
|
+
run (#149): they only rewrote files. ``FormatFailure.details`` is built
|
|
88
|
+
directly from this property (#164/#165).
|
|
89
|
+
"""
|
|
90
|
+
return tuple(
|
|
91
|
+
block for block in self.verdict_blocks if not _is_auto_fix_block(block)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def hooks_failed(self) -> bool:
|
|
96
|
+
"""Classify the run as "a hook judged failure" or not.
|
|
97
|
+
|
|
98
|
+
``pre-commit`` exits non-zero for two different reasons, and only one
|
|
99
|
+
of them may turn the format gate red (#149):
|
|
100
|
+
|
|
101
|
+
* a hook **rewrote files** (auto-fix) -- expected while formatting;
|
|
102
|
+
* a hook **judged the check failed** (``check-yaml`` parse error,
|
|
103
|
+
``codespell`` hit, ...) -- must propagate.
|
|
104
|
+
|
|
105
|
+
The two are *not* told apart by the exit code. Captured from real
|
|
106
|
+
``pre-commit`` runs, every built-in fixer exits ``1`` after rewriting
|
|
107
|
+
and prints ``- exit code: 1`` right beside
|
|
108
|
+
``- files were modified by this hook``; a reporting hook prints the
|
|
109
|
+
exit-code line *without* that marker. So the modification marker
|
|
110
|
+
decides: **a hook that rewrote files never counts as a failure**
|
|
111
|
+
(review D1).
|
|
112
|
+
|
|
113
|
+
The accepted trade-off: a hook that rewrites files *and* leaves a real
|
|
114
|
+
violation behind (``ruff --fix`` with findings left over) reads as an
|
|
115
|
+
auto-fix and stays green. No hook in this repo's managed config behaves
|
|
116
|
+
that way -- its fixers (``end-of-file-fixer``, ``trailing-whitespace``,
|
|
117
|
+
``ruff-format``) only rewrite, and ``ruff``/``codespell``/``check-yaml``
|
|
118
|
+
only report -- and recovering that case would cost a second full
|
|
119
|
+
pre-commit run on every dirty tree.
|
|
120
|
+
|
|
121
|
+
A non-zero exit with no hook verdict at all -- pre-commit refusing to
|
|
122
|
+
run, e.g. an invalid config -- is a real failure as well.
|
|
123
|
+
"""
|
|
124
|
+
if self.result.returncode == 0:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
blocks = self.verdict_blocks
|
|
128
|
+
if blocks and all(_is_auto_fix_block(block) for block in blocks):
|
|
129
|
+
# Non-zero *only* because hooks rewrote files: not a failure.
|
|
130
|
+
return False
|
|
131
|
+
return True
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def filtered_output(self) -> str | None:
|
|
135
|
+
"""Only the sections pre-commit marked failed -- lint's display view.
|
|
136
|
+
|
|
137
|
+
Passing hooks print nothing but their ``Passed`` header, so the filter
|
|
138
|
+
drops their header lines and keeps each failed hook's detail lines --
|
|
139
|
+
including stderr, which lint used to append to the stdout it filtered.
|
|
140
|
+
Returns ``None`` when nothing failed.
|
|
141
|
+
|
|
142
|
+
This is deliberately **not** what ``wl format`` shows (#172). Format's
|
|
143
|
+
display has to agree with the verdict it reports: it prints
|
|
144
|
+
:attr:`failed_blocks` (the same blocks :attr:`hooks_failed` was
|
|
145
|
+
computed from) and falls back to :attr:`combined_output` when
|
|
146
|
+
pre-commit failed before printing any hook verdict. Text-filtered
|
|
147
|
+
output can include auto-fix sections the verdict ignores, and can hide
|
|
148
|
+
a stderr-only failure; the blocks cannot.
|
|
149
|
+
"""
|
|
150
|
+
return _filter_failed_sections(self.combined_output)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _hook_verdict_blocks(output: str) -> tuple[tuple[str, ...], ...]:
|
|
154
|
+
"""Group pre-commit output into one detail block per reported hook."""
|
|
155
|
+
blocks: list[list[str]] = []
|
|
156
|
+
current: list[str] = []
|
|
157
|
+
for line in output.splitlines():
|
|
158
|
+
stripped = line.strip()
|
|
159
|
+
if stripped.startswith("-"):
|
|
160
|
+
current.append(stripped)
|
|
161
|
+
elif current:
|
|
162
|
+
blocks.append(current)
|
|
163
|
+
current = []
|
|
164
|
+
if current:
|
|
165
|
+
blocks.append(current)
|
|
166
|
+
return tuple(
|
|
167
|
+
tuple(block)
|
|
168
|
+
for block in blocks
|
|
169
|
+
if any(line.startswith(_HOOK_ID_PREFIX) for line in block)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _is_auto_fix_block(block: tuple[str, ...]) -> bool:
|
|
174
|
+
"""True when this failing hook's only effect was rewriting files."""
|
|
175
|
+
return any(_MODIFIED_MARKER in line for line in block)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _filter_failed_sections(text: str) -> str | None:
|
|
179
|
+
"""Keep the failed hooks' header and detail lines; drop the passed ones."""
|
|
180
|
+
filtered: list[str] = []
|
|
181
|
+
in_failure = False
|
|
182
|
+
for line in text.splitlines():
|
|
183
|
+
stripped = line.strip()
|
|
184
|
+
if stripped.endswith("Passed"):
|
|
185
|
+
in_failure = False
|
|
186
|
+
continue
|
|
187
|
+
if stripped.endswith("Failed"):
|
|
188
|
+
in_failure = True
|
|
189
|
+
filtered.append(line)
|
|
190
|
+
elif in_failure:
|
|
191
|
+
filtered.append(line)
|
|
192
|
+
return "\n".join(filtered) if filtered else None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def run_pre_commit(
|
|
196
|
+
*,
|
|
197
|
+
files: Sequence[str] | None,
|
|
198
|
+
config: Path | None,
|
|
199
|
+
cwd: Path,
|
|
200
|
+
env: dict[str, str] | None = None,
|
|
201
|
+
quiet: bool = True,
|
|
202
|
+
prefer_uv: bool = True,
|
|
203
|
+
) -> PreCommitRun:
|
|
204
|
+
"""Run ``pre-commit run`` once and return its :class:`PreCommitRun`.
|
|
205
|
+
|
|
206
|
+
With ``prefer_uv`` the run starts as ``uv run pre-commit`` (the formatters'
|
|
207
|
+
launcher); otherwise it starts as the bare ``pre-commit`` on ``PATH`` (what
|
|
208
|
+
``wl lint`` wants -- it already runs inside the project environment and
|
|
209
|
+
does not need uv re-resolving it). Either launcher falls back to the other
|
|
210
|
+
when ``uv_unavailable`` says it never ran -- a ``launch_error``, or the
|
|
211
|
+
shell's ``127`` "command not found" -- so a machine with only one of the
|
|
212
|
+
two still runs the hooks.
|
|
213
|
+
|
|
214
|
+
``config=None`` means *let pre-commit use its own config discovery* and
|
|
215
|
+
nothing else. This function never probes the filesystem: what a missing
|
|
216
|
+
config means is the caller's explicit choice (#165 audit patch 2). ``wl
|
|
217
|
+
format`` skips the run when its managed config is absent; ``wl lint`` falls
|
|
218
|
+
back to the default discovery by passing ``None``.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
files: Paths to check; ``None`` or an empty sequence means
|
|
222
|
+
``--all-files``. An empty sequence used to reach pre-commit as
|
|
223
|
+
"no paths", which silently checks only the staged files instead of
|
|
224
|
+
everything (#172).
|
|
225
|
+
config: Explicit config path, or ``None`` for pre-commit's discovery.
|
|
226
|
+
cwd: Directory the run happens in (lint runs from the project root).
|
|
227
|
+
env: Environment for the child process.
|
|
228
|
+
quiet: Whether to capture the child's output.
|
|
229
|
+
prefer_uv: Start through ``uv run``; ``False`` starts with the bare
|
|
230
|
+
``pre-commit`` executable.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
The finished run; its ``result`` may still carry ``launch_error``.
|
|
234
|
+
"""
|
|
235
|
+
# Resolved at call time so patching the class -- the seam both callers
|
|
236
|
+
# already use for their other children (``lint_executor``'s ruff/mypy
|
|
237
|
+
# runs, the git-flow hint in the same format handler) -- intercepts this
|
|
238
|
+
# run too. Each caller still builds its own instance, here and in the
|
|
239
|
+
# handler; the class is the one patch point, not a shared instance.
|
|
240
|
+
from .subprocess_utils import SubprocessExecutor
|
|
241
|
+
|
|
242
|
+
command = ["pre-commit", "run"]
|
|
243
|
+
if not files:
|
|
244
|
+
command.append("--all-files")
|
|
245
|
+
else:
|
|
246
|
+
command.extend(files)
|
|
247
|
+
if config is not None:
|
|
248
|
+
command.extend(["--config", str(config)])
|
|
249
|
+
|
|
250
|
+
executor = SubprocessExecutor()
|
|
251
|
+
if prefer_uv:
|
|
252
|
+
result = run_uv_command(
|
|
253
|
+
executor, ["uv", "run", *command], env=env, cwd=cwd, quiet=quiet
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
result = executor.run(command, env=env, cwd=cwd, quiet=quiet)
|
|
257
|
+
if uv_unavailable(result):
|
|
258
|
+
# The bare launcher never started: retry once through ``uv run``,
|
|
259
|
+
# the launcher ``wl format`` starts with.
|
|
260
|
+
try:
|
|
261
|
+
result = executor.run(
|
|
262
|
+
["uv", "run", *command], env=env, cwd=cwd, quiet=quiet
|
|
263
|
+
)
|
|
264
|
+
except Exception:
|
|
265
|
+
pass
|
|
266
|
+
return PreCommitRun(result)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Where the managed pre-commit config lives -- one answer, DEPLOY-anchored (#189).
|
|
2
|
+
|
|
3
|
+
The self-sync writes ``.wl/.pre-commit-config.yaml`` at the **DEPLOY** root
|
|
4
|
+
(``wl_dir_updater`` -> ``setup_handler`` -> ``generate_configs(context.deploy)``),
|
|
5
|
+
while ``wl format`` and ``wl lint`` used to look it up at the **PROJECT** root.
|
|
6
|
+
In the ``git root > sub-package with its own pyproject.toml`` layout the two
|
|
7
|
+
roots differ, so the managed config written at the git top was invisible from
|
|
8
|
+
the sub-package: ``wl format`` skipped every hook and exited 0, and ``wl lint``
|
|
9
|
+
handed pre-commit a root with no config (``InvalidConfigError``), staying red
|
|
10
|
+
forever.
|
|
11
|
+
|
|
12
|
+
:func:`managed_pre_commit_config` is the single definition of that location;
|
|
13
|
+
both consumers take the path from it and keep only their own *missing config*
|
|
14
|
+
policy (format skips the run, lint falls back to pre-commit's own discovery).
|
|
15
|
+
Nothing else is re-anchored: the file-tool scopes (ruff, mypy) stay where they
|
|
16
|
+
were -- only this one lookup moves from PROJECT to DEPLOY.
|
|
17
|
+
|
|
18
|
+
The anchor is deliberately **narrow**: DEPLOY is the git work-tree top whenever
|
|
19
|
+
there is one, so a ``.wl/.pre-commit-config.yaml`` placed by hand inside a
|
|
20
|
+
sub-package is *not* adopted in that layout -- the only managed config consulted
|
|
21
|
+
is the one at the work-tree top. That exclusion is the anchor alone: this module
|
|
22
|
+
never reads ``config.json`` and never probes for the config file, so whether a
|
|
23
|
+
by-hand copy happens to sit next to a ``config.json`` changes nothing (the
|
|
24
|
+
``LEGACY`` kind is the only root-resolution rule that looks at ``config.json``).
|
|
25
|
+
With no work tree, DEPLOY falls back to the project-marker root, which can
|
|
26
|
+
legitimately be the sub-package itself. One step further, when *start* is in no
|
|
27
|
+
work tree and no project marker (``.wl`` directory / ``pyproject.toml`` file)
|
|
28
|
+
sits at or above it, the caller-side fallback policy
|
|
29
|
+
(:func:`~wl_cli.utils.root_fallback.resolve_root_or_start`, the one place ADR
|
|
30
|
+
0006 puts that policy) substitutes *start* itself -- so a bare directory yields
|
|
31
|
+
``<start>/.wl/.pre-commit-config.yaml``. This is a decision, not an accident:
|
|
32
|
+
one anchor is what makes format and lint agree. It is recorded in
|
|
33
|
+
``docs/adr/0008-managed-pre-commit-config-anchored-at-deploy-root.md`` and
|
|
34
|
+
pinned by ``tests/test_managed_pre_commit_config_203.py``.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
from .root import RootKind
|
|
40
|
+
from .root_fallback import resolve_root_or_start
|
|
41
|
+
|
|
42
|
+
#: The managed control file's name inside a root's ``.wl`` directory.
|
|
43
|
+
_MANAGED_CONFIG_NAME = ".pre-commit-config.yaml"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def managed_pre_commit_config(start: Path) -> Path:
|
|
47
|
+
"""The managed ``.wl/.pre-commit-config.yaml`` of *start*'s DEPLOY root.
|
|
48
|
+
|
|
49
|
+
*start* is the anchor the caller already holds -- ``wl format`` passes its
|
|
50
|
+
``PROJECT`` root, ``wl lint`` the project root it runs from -- and the
|
|
51
|
+
**DEPLOY** root is resolved from it here, once, because that is where the
|
|
52
|
+
self-sync writes the file: the git work-tree top when there is one, else
|
|
53
|
+
the project marker root. Passing the PROJECT root is therefore right in
|
|
54
|
+
both layouts: when the two coincide the answer is unchanged (#125), and in
|
|
55
|
+
the sub-package layout it climbs to the git top where the file actually is
|
|
56
|
+
(#189).
|
|
57
|
+
|
|
58
|
+
The anchor has one step more than ``utils/root.py``'s own ``DEPLOY`` rule:
|
|
59
|
+
when *start* is in no git work tree and no project marker sits at or above
|
|
60
|
+
it, :func:`~wl_cli.utils.root_fallback.resolve_root_or_start` substitutes
|
|
61
|
+
*start* itself (ADR 0006's caller-side fallback, not part of the root
|
|
62
|
+
module's rule), so the derived path is ``<start>/.wl/.pre-commit-config.yaml``.
|
|
63
|
+
|
|
64
|
+
The path is **derived, never probed**: this function does not check whether
|
|
65
|
+
the config file exists, so each caller keeps its own decision about what a
|
|
66
|
+
missing config means. Deriving it is not I/O-free, though -- resolving the
|
|
67
|
+
DEPLOY root runs ``git rev-parse --show-toplevel`` and, when that misses,
|
|
68
|
+
stats the project markers on the way up. What it never does is *look for the
|
|
69
|
+
config*: no existence probe, no walk past the anchor hunting for another
|
|
70
|
+
copy.
|
|
71
|
+
"""
|
|
72
|
+
return resolve_root_or_start(start, RootKind.DEPLOY).wl_dir() / _MANAGED_CONFIG_NAME
|