use std::path::{Path, PathBuf};
use crate::error::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Runner {
PreCommit,
Prek,
Lefthook,
Hk,
}
impl Runner {
pub fn bin(self) -> &'static str {
match self {
Runner::PreCommit => "pre-commit",
Runner::Prek => "prek",
Runner::Lefthook => "lefthook",
Runner::Hk => "hk",
}
}
pub fn autodetect(root: &Path) -> Result<Option<Runner>> {
let candidates = [
(Runner::Hk, &["hk.pkl"][..]),
(
Runner::Lefthook,
&[
"lefthook.yml",
"lefthook.yaml",
".lefthook.yml",
".lefthook.yaml",
][..],
),
(
Runner::PreCommit,
&[".pre-commit-config.yaml", ".pre-commit-config.yml"][..],
),
];
let mut found: Vec<Runner> = Vec::new();
for (runner, files) in candidates {
if files.iter().any(|f| root.join(f).exists()) {
found.push(runner);
}
}
match found.as_slice() {
[] => Ok(None),
[one] => Ok(Some(*one)),
many => Err(crate::error::JjHooksError::Parse(format!(
"multiple hook-runner configs found at workspace root: {:?}. Use --runner to pick one.",
many.iter().map(|r| r.bin()).collect::<Vec<_>>()
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stage {
PreCommit,
PrePush,
}
impl Stage {
pub fn as_str(self) -> &'static str {
match self {
Stage::PreCommit => "pre-commit",
Stage::PrePush => "pre-push",
}
}
}
pub fn hook_command(runner: Runner, stage: Stage, from: &str, to: &str) -> Vec<String> {
match runner {
Runner::PreCommit | Runner::Prek => vec![
runner.bin().into(),
"run".into(),
"--hook-stage".into(),
stage.as_str().into(),
"--from-ref".into(),
from.into(),
"--to-ref".into(),
to.into(),
],
Runner::Hk => vec![
runner.bin().into(),
"run".into(),
stage.as_str().into(),
"--from-ref".into(),
from.into(),
"--to-ref".into(),
to.into(),
],
Runner::Lefthook => panic!(
"lefthook does not take ref bounds; use lefthook_command with a file list instead"
),
}
}
pub fn lefthook_command(stage: Stage, files: &[PathBuf]) -> Vec<String> {
let mut argv = vec!["lefthook".into(), "run".into(), stage.as_str().into()];
for f in files {
argv.push("--file".into());
argv.push(f.to_string_lossy().into_owned());
}
argv
}
pub fn prefer_prek_when_available(autodetected: Runner, prek_present: bool) -> Runner {
match (autodetected, prek_present) {
(Runner::PreCommit, true) => Runner::Prek,
_ => autodetected,
}
}
pub fn prek_on_path() -> bool {
which("prek").is_some()
}
fn which(bin: &str) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&path) {
let candidate = dir.join(bin);
if candidate.is_file() {
return Some(candidate);
}
}
None
}