mod detector;
mod generator;
mod picker;
use std::path::PathBuf;
use crate::{Result, env};
pub(crate) const DEFAULT_HOOKS: &[&str] = &["pre-commit", "check", "fix"];
#[derive(Debug, clap::Args)]
#[clap(alias = "generate")]
pub struct Init {
#[clap(short, long)]
force: bool,
#[clap(short, long)]
interactive: bool,
#[clap(long, verbatim_doc_comment)]
mise: bool,
}
impl Init {
pub async fn run(&self) -> Result<()> {
let hk_file = PathBuf::from("hk.pkl");
let version = env!("CARGO_PKG_VERSION");
if *env::HK_MISE || self.mise {
self.write_mise_toml()?;
}
if hk_file.exists() && !self.force {
warn!("hk.pkl already exists, run with --force to overwrite");
return Ok(());
}
let project_root = std::env::current_dir()?;
let detections = detector::detect_builtins(&project_root);
let hook_content = if self.interactive {
self.run_interactive(&detections, version)?
} else {
self.run_auto(&detections, version)
};
xx::file::write(&hk_file, &hook_content)?;
if !detections.is_empty() && !self.interactive {
let summary = detections
.iter()
.map(|d| format!("{} ({})", d.builtin.name, d.reason))
.collect::<Vec<_>>()
.join(", ");
println!("Detected: {}", summary);
}
println!("Created hk.pkl");
Ok(())
}
fn run_interactive(&self, detections: &[detector::Detection], version: &str) -> Result<String> {
if !detections.is_empty() {
println!("\nScanning project...");
for detection in detections {
println!(
" Detected: {} ({})",
detection.builtin.name, detection.reason
);
}
println!();
}
let builtins = picker::pick_builtins(detections)?;
if builtins.is_empty() {
return Ok(generator::generate_default_template(version));
}
let hooks = picker::pick_hooks()?;
let hooks = if hooks.is_empty() {
warn!("No hooks selected, using defaults");
DEFAULT_HOOKS.iter().map(|s| s.to_string()).collect()
} else {
hooks
};
Ok(generator::generate_pkl(&builtins, &hooks, version))
}
fn run_auto(&self, detections: &[detector::Detection], version: &str) -> String {
if detections.is_empty() {
return generator::generate_default_template(version);
}
let builtins: Vec<_> = detections.iter().map(|d| d.builtin).collect();
let hooks: Vec<String> = DEFAULT_HOOKS.iter().map(|s| s.to_string()).collect();
generator::generate_pkl(&builtins, &hooks, version)
}
fn write_mise_toml(&self) -> Result<()> {
let mise_toml = PathBuf::from("mise.toml");
let mise_content = r#"[tools]
hk = "latest"
pkl = "latest"
[tasks.pre-commit]
run = "hk run pre-commit"
"#;
if mise_toml.exists() && !self.force {
warn!("mise.toml already exists, run with --force to overwrite");
} else {
xx::file::write(mise_toml, mise_content)?;
println!("Generated mise.toml");
}
Ok(())
}
}