use std::path::Path;
use crate::handlers::run_once::RunOnceCommand;
use crate::handlers::{ExecutionPhase, HANDLER_INSTALL};
pub struct InstallCommand;
impl RunOnceCommand for InstallCommand {
fn handler_name(&self) -> &str {
HANDLER_INSTALL
}
fn phase(&self) -> ExecutionPhase {
ExecutionPhase::Setup
}
fn command_for(&self, path: &Path) -> (String, Vec<String>) {
(
interpreter_for(path).to_string(),
vec!["--".into(), path.to_string_lossy().into_owned()],
)
}
fn status_deployed(&self) -> &str {
"installed"
}
fn status_pending(&self) -> &str {
"never run"
}
fn status_ran_different(&self) -> &str {
"older version"
}
}
fn interpreter_for(path: &Path) -> &'static str {
match path.extension().and_then(|e| e.to_str()) {
Some("zsh") => "zsh",
_ => "bash",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fs::Fs;
use crate::handlers::run_once::RunOnceHandler;
use crate::handlers::{Handler, HandlerConfig};
use crate::operations::HandlerIntent;
use crate::rules::RuleMatch;
use crate::testing::TempEnvironment;
use std::collections::HashMap;
#[test]
fn interpreter_for_selects_by_extension() {
assert_eq!(interpreter_for(Path::new("install.sh")), "bash");
assert_eq!(interpreter_for(Path::new("install.bash")), "bash");
assert_eq!(interpreter_for(Path::new("install.zsh")), "zsh");
assert_eq!(interpreter_for(Path::new("install")), "bash");
assert_eq!(interpreter_for(Path::new("install.ksh")), "bash");
assert_eq!(interpreter_for(Path::new("/a/b/install.zsh")), "zsh");
}
#[test]
fn install_command_identity() {
assert_eq!(InstallCommand.handler_name(), HANDLER_INSTALL);
assert_eq!(InstallCommand.phase(), ExecutionPhase::Setup);
assert_eq!(InstallCommand.status_deployed(), "installed");
assert_eq!(InstallCommand.status_pending(), "never run");
}
#[test]
fn install_command_picks_interpreter_per_script_via_handler() {
let env = TempEnvironment::builder()
.pack("vim")
.file("install.sh", "echo sh")
.file("install.bash", "echo bash")
.file("install.zsh", "echo zsh")
.done()
.build();
let runner = crate::datastore::NoopCommandRunner;
let handler = RunOnceHandler::new(env.fs.as_ref(), &runner, InstallCommand);
let make_match = |name: &str| RuleMatch {
relative_path: name.into(),
absolute_path: env.dotfiles_root.join(format!("vim/{name}")),
pack: "vim".into(),
handler: "install".into(),
is_dir: false,
options: HashMap::new(),
preprocessor_source: None,
rendered_bytes: None,
};
let matches = vec![
make_match("install.sh"),
make_match("install.bash"),
make_match("install.zsh"),
];
let pather = crate::paths::XdgPather::builder()
.home(&env.home)
.dotfiles_root(&env.dotfiles_root)
.build()
.unwrap();
let intents = handler
.to_intents(
&matches,
&HandlerConfig::default(),
&pather,
env.fs.as_ref() as &dyn Fs,
)
.unwrap();
let chosen: Vec<(String, String)> = intents
.iter()
.map(|i| match i {
HandlerIntent::Run {
executable,
arguments,
..
} => (
executable.clone(),
arguments
.last()
.cloned()
.and_then(|p| {
Path::new(&p)
.file_name()
.map(|n| n.to_string_lossy().into_owned())
})
.unwrap_or_default(),
),
other => panic!("expected Run, got {other:?}"),
})
.collect();
assert!(chosen.contains(&("bash".into(), "install.sh".into())));
assert!(chosen.contains(&("bash".into(), "install.bash".into())));
assert!(chosen.contains(&("zsh".into(), "install.zsh".into())));
}
#[test]
fn install_command_emits_run_intent_with_expected_shape() {
let env = TempEnvironment::builder()
.pack("vim")
.file("install.sh", "#!/bin/sh\nsetup")
.done()
.build();
let runner = crate::datastore::NoopCommandRunner;
let handler = RunOnceHandler::new(env.fs.as_ref(), &runner, InstallCommand);
let matches = vec![RuleMatch {
relative_path: "install.sh".into(),
absolute_path: env.dotfiles_root.join("vim/install.sh"),
pack: "vim".into(),
handler: "install".into(),
is_dir: false,
options: HashMap::new(),
preprocessor_source: None,
rendered_bytes: None,
}];
let pather = crate::paths::XdgPather::builder()
.home(&env.home)
.dotfiles_root(&env.dotfiles_root)
.build()
.unwrap();
let intents = handler
.to_intents(
&matches,
&HandlerConfig::default(),
&pather,
env.fs.as_ref() as &dyn Fs,
)
.unwrap();
assert_eq!(intents.len(), 1);
match &intents[0] {
HandlerIntent::Run {
pack,
handler: h,
executable,
arguments,
sentinel,
filename,
content_hash,
} => {
assert_eq!(pack, "vim");
assert_eq!(h, HANDLER_INSTALL);
assert_eq!(executable, "bash");
assert_eq!(arguments[0], "--");
assert!(arguments[1].ends_with("install.sh"));
assert!(sentinel.starts_with("install.sh-"));
assert_eq!(sentinel.len(), "install.sh-".len() + 16);
assert_eq!(filename, "install.sh");
assert_eq!(content_hash.len(), 16);
assert_eq!(*sentinel, format!("{filename}-{content_hash}"));
}
other => panic!("expected Run, got {other:?}"),
}
}
}