diff --git a/hooked-cli/Cargo.toml b/hooked-cli/Cargo.toml index be87236..076bf6c 100644 --- a/hooked-cli/Cargo.toml +++ b/hooked-cli/Cargo.toml @@ -13,6 +13,8 @@ path = "source/main.rs" [dependencies] color-eyre = "0.6.2" +owo-colors = "3.5.0" +subprocess = "0.2.9" tera = "1.17.1" [dependencies.clap] diff --git a/hooked-cli/source/cli/mod.rs b/hooked-cli/source/cli/mod.rs index 0b86e91..702d5e4 100644 --- a/hooked-cli/source/cli/mod.rs +++ b/hooked-cli/source/cli/mod.rs @@ -4,6 +4,10 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; +mod run; + +pub use run::hooked_run; + /// CLI arguments struct using [`clap::Parser`]. #[derive(Debug, Parser)] #[clap(about, author, version)] @@ -34,4 +38,11 @@ pub enum MainSubcommands { #[clap(long)] all: bool, }, + + /// Manually run hooks. + Run { + /// The hook type to run. + #[clap(value_parser = crate::HOOK_TYPES)] + hook_type: String, + }, } diff --git a/hooked-cli/source/cli/run.rs b/hooked-cli/source/cli/run.rs new file mode 100644 index 0000000..a6fa0c2 --- /dev/null +++ b/hooked-cli/source/cli/run.rs @@ -0,0 +1,62 @@ +use std::{io::Read, process::exit}; + +use { + color_eyre::{eyre::eyre, Result}, + hooked_library::{Config, ExitAction}, + owo_colors::{OwoColorize, Style}, + subprocess::{Exec, Redirection}, +}; + +pub fn hooked_run(config: Config, hook_type: String) -> Result<()> { + let success_style = Style::new().bold().green(); + let warn_style = Style::new().bold().yellow(); + let error_style = Style::new().bold().red(); + + if hook_type == "pre-commit" { + for hook in config.pre_commit { + let hook_name = hook.name.unwrap_or_else(|| "Unnamed Hook".to_string()); + + let command = match (hook.task.command, hook.task.script) { + (Some(command), _) => Ok(Exec::shell(command)), + + (None, Some(script_file)) => { + let script_path = config.general.directory.join(script_file); + let script_path_str = script_path + .to_str() + .ok_or_else(|| eyre!("Failed to convert path to str"))?; + Ok(Exec::shell(script_path_str)) + } + + (None, None) => Err(eyre!( + "No command or script provided for hook: {}", + hook_name + )), + }?; + + let mut process = command.stdout(Redirection::Pipe).popen()?; + let exit_status = process.wait()?; + let output = { + let mut output = String::new(); + let mut stdout_file = process.stdout.take().unwrap(); + stdout_file.read_to_string(&mut output)?; + output + }; + + let (stop, prefix, style) = match (exit_status.success(), hook.on_failure) + { + (true, _) => (false, "✓", success_style), + (false, ExitAction::Continue) => (false, "⚠", warn_style), + (false, ExitAction::Stop) => (true, "✗", error_style), + }; + + println!("\t{} {}", prefix.style(style), hook_name.style(style)); + println!("{}", output); + + if stop { + exit(1); + } + } + } + + Ok(()) +} diff --git a/hooked-cli/source/main.rs b/hooked-cli/source/main.rs index a894533..bb7bb71 100644 --- a/hooked-cli/source/main.rs +++ b/hooked-cli/source/main.rs @@ -24,7 +24,7 @@ use crate::cli::{Args, MainSubcommands}; pub const DEFAULT_TEMPLATE: &str = include_str!("templates/default.sh"); /// All supported hook types. -pub const HOOK_TYPES: &[&str] = &["pre-commit"]; +pub const HOOK_TYPES: [&str; 1] = ["pre-commit"]; mod cli; @@ -80,6 +80,10 @@ fn main() -> Result<()> { } } } + + MainSubcommands::Run { hook_type } => { + cli::hooked_run(config, hook_type)?; + } } Ok(())