77from pathlib import Path
88from typing import TYPE_CHECKING, ClassVar
99
10+ from sync_pre_commit_lock.config import HookRunner
11+
1012if TYPE_CHECKING:
1113 from collections.abc import Sequence
1214
1315 from sync_pre_commit_lock import Printer
1416
1517
16- class SetupPreCommitHooks:
17- install_pre_commit_hooks_command: ClassVar[Sequence[str | bytes]] = ["pre-commit", "install"]
18- check_pre_commit_version_command: ClassVar[Sequence[str | bytes]] = ["pre-commit", "--version"]
18+ class ResolvedHookRunner:
19+ """A resolved hook runner, bound to a concrete runner (never AUTO) and a command prefix."""
20+
21+ # Probe order for auto-detection: try prek first, then pre-commit
22+ _AUTO_ORDER: ClassVar[tuple[HookRunner, ...]] = (HookRunner.PREK, HookRunner.PRE_COMMIT)
23+
24+ def __init__(self, runner: HookRunner, command_prefix: Sequence[str] = ()) -> None:
25+ self.runner = runner
26+ self.command_prefix = command_prefix
27+
28+ @property
29+ def name(self) -> str:
30+ return self.runner.value
31+
32+ def execute(self, *args: str) -> Sequence[str | bytes]:
33+ return [*self.command_prefix, self.runner.value, *args]
34+
35+ def is_installed(self) -> bool:
36+ """Check if this runner is installed by running its --version command."""
37+ try:
38+ output = subprocess.check_output(self.execute("--version")).decode() # noqa: S603
39+ except (subprocess.CalledProcessError, FileNotFoundError):
40+ return False
41+ else:
42+ return self.runner.value in output
43+
44+ @classmethod
45+ def resolve(
46+ cls,
47+ hook_runner: HookRunner,
48+ command_prefix: Sequence[str] = (),
49+ printer: Printer | None = None,
50+ ) -> ResolvedHookRunner | None:
51+ """Resolve a HookRunner config to a concrete ResolvedHookRunner, or None if not found."""
52+ candidates = cls._AUTO_ORDER if hook_runner is HookRunner.AUTO else [hook_runner]
53+ for candidate in candidates:
54+ runner = cls(candidate, command_prefix)
55+ if runner.is_installed():
56+ if hook_runner is HookRunner.AUTO and printer:
57+ printer.debug(f"Auto-detected hook runner: {candidate.value}")
58+ return runner
59+ return None
60+
1961
20- def __init__(self, printer: Printer, dry_run: bool = False) -> None:
62+ class SetupPreCommitHooks:
63+ command_prefix: ClassVar[Sequence[str]] = ()
64+
65+ def __init__(
66+ self,
67+ printer: Printer,
68+ dry_run: bool = False,
69+ hook_runner: HookRunner = HookRunner.PRE_COMMIT,
70+ ) -> None:
2171 self.printer = printer
2272 self.dry_run = dry_run
73+ self.hook_runner = hook_runner
2374
2475 def execute(self) -> None:
25- if not self._is_pre_commit_package_installed():
26- self.printer.debug("pre-commit package is not installed (or detected). Skipping.")
76+ runner = ResolvedHookRunner.resolve(self.hook_runner, self.command_prefix, self.printer)
77+ if runner is None:
78+ self.printer.debug("No hook runner (pre-commit or prek) is installed (or detected). Skipping.")
2779 return
2880
2981 git_root = self._get_git_directory_path()
@@ -39,36 +91,25 @@ def execute(self) -> None:
3991 self.printer.debug("Dry run, skipping pre-commit hook installation.")
4092 return
4193
42- self._install_pre_commit_hooks( )
94+ self._install_hooks(runner )
4395
44- def _install_pre_commit_hooks (self) -> None:
96+ def _install_hooks (self, runner: ResolvedHookRunner ) -> None:
4597 try:
46- self.printer.info("Installing pre-commit hooks...")
98+ self.printer.info(f "Installing {runner.name} hooks...")
4799 return_code = subprocess.check_call( # noqa: S603
48- self.install_pre_commit_hooks_command ,
100+ runner.execute("install") ,
49101 # XXX We probably want to see the output, at least in verbose mode or if it fails
50102 stdout=subprocess.DEVNULL,
51103 stderr=subprocess.DEVNULL,
52104 )
53105 if return_code == 0:
54- self.printer.info("pre-commit hooks successfully installed!")
106+ self.printer.info(f"{runner.name} hooks successfully installed!")
55107 else:
56- self.printer.error("Failed to install pre-commit hooks")
108+ self.printer.error(f "Failed to install {runner.name} hooks")
57109 except Exception as e:
58- self.printer.error("Failed to install pre-commit hooks due to an unexpected error")
110+ self.printer.error(f "Failed to install {runner.name} hooks due to an unexpected error")
59111 self.printer.error(f"{e}")
60112
61- def _is_pre_commit_package_installed(self) -> bool:
62- try:
63- # Try is `pre-commit --version` works
64- output = subprocess.check_output( # noqa: S603
65- self.check_pre_commit_version_command,
66- ).decode()
67- except (subprocess.CalledProcessError, FileNotFoundError):
68- return False
69- else:
70- return "pre-commit" in output
71-
72113 @staticmethod
73114 def _are_pre_commit_hooks_installed(git_root: Path) -> bool:
74115 return (git_root / "hooks" / "pre-commit").exists()
0 commit comments