use super::SessionSpec;
use crate::snippets::error::{Error, Result};
use crate::snippets::validators::run_command;
use std::collections::{BTreeMap, HashMap};
use std::path::Path;
pub(super) type HookInvocation = (String, BTreeMap<String, String>);
pub(super) type HookOutcomes = HashMap<HookInvocation, HookOutcome>;
#[derive(Clone)]
pub(super) enum HookOutcome {
Succeeded,
TimedOut { command: String, timeout_secs: u64 },
Failed(String),
}
impl HookOutcome {
fn record(outcome: &Result<()>) -> Self {
match outcome {
Ok(()) => Self::Succeeded,
Err(Error::Timeout { command, timeout_secs }) => Self::TimedOut {
command: command.clone(),
timeout_secs: *timeout_secs,
},
Err(other) => Self::Failed(other.to_string()),
}
}
fn replay(&self) -> Result<()> {
match self {
Self::Succeeded => Ok(()),
Self::TimedOut { command, timeout_secs } => Err(Error::Timeout {
command: command.clone(),
timeout_secs: *timeout_secs,
}),
Self::Failed(message) => Err(Error::Other(message.clone())),
}
}
}
pub(super) fn run_before_once(
source: &str,
spec: &SessionSpec,
timeout_secs: u64,
ran: &mut HookOutcomes,
) -> Result<()> {
let invocation = (source.to_owned(), spec.env.clone());
if let Some(previous) = ran.get(&invocation) {
tracing::debug!(
command = %source,
working_directory = %spec.working_directory.display(),
language = %spec.language,
"reusing a `before` hook already run for this working directory in this run"
);
return previous.replay();
}
let outcome = run_before(source, &spec.working_directory, &spec.env, timeout_secs);
ran.insert(invocation, HookOutcome::record(&outcome));
outcome
}
fn run_before(source: &str, working_directory: &Path, env: &BTreeMap<String, String>, timeout_secs: u64) -> Result<()> {
let mut command = shell_command(source);
command.current_dir(working_directory);
command.envs(env);
let (success, output) = run_command(&mut command, timeout_secs)?;
if success {
Ok(())
} else {
Err(Error::Other(format!("before command failed: {output}")))
}
}
#[cfg(unix)]
fn shell_command(source: &str) -> std::process::Command {
let mut command = std::process::Command::new("sh");
command.args(["-c", source]);
command
}
#[cfg(windows)]
fn shell_command(source: &str) -> std::process::Command {
let mut command = std::process::Command::new("cmd");
command.args(["/C", source]);
command
}