mod link;
mod run;
mod stage;
use tracing::debug;
use crate::datastore::DataStore;
use crate::fs::Fs;
use crate::operations::{HandlerIntent, OperationResult};
use crate::paths::Pather;
use crate::Result;
pub struct Executor<'a> {
datastore: &'a dyn DataStore,
fs: &'a dyn Fs,
paths: &'a dyn Pather,
dry_run: bool,
force: bool,
provision_rerun: bool,
auto_chmod_exec: bool,
}
impl<'a> Executor<'a> {
pub fn new(
datastore: &'a dyn DataStore,
fs: &'a dyn Fs,
paths: &'a dyn Pather,
dry_run: bool,
force: bool,
provision_rerun: bool,
auto_chmod_exec: bool,
) -> Self {
Self {
datastore,
fs,
paths,
dry_run,
force,
provision_rerun,
auto_chmod_exec,
}
}
pub fn execute(&self, intents: Vec<HandlerIntent>) -> Result<Vec<OperationResult>> {
debug!(
count = intents.len(),
dry_run = self.dry_run,
force = self.force,
"executor starting"
);
let mut results = Vec::new();
for intent in intents {
let intent_results = if self.dry_run {
self.simulate(&intent)
} else {
self.execute_one(&intent)?
};
results.extend(intent_results);
}
let succeeded = results.iter().filter(|r| r.success).count();
let failed = results.iter().filter(|r| !r.success).count();
debug!(succeeded, failed, "executor finished");
Ok(results)
}
fn execute_one(&self, intent: &HandlerIntent) -> Result<Vec<OperationResult>> {
match intent {
HandlerIntent::Link { .. } => self.execute_link(intent),
HandlerIntent::Stage { .. } => self.execute_stage(intent),
HandlerIntent::Run { .. } => self.execute_run(intent),
}
}
fn simulate(&self, intent: &HandlerIntent) -> Vec<OperationResult> {
match intent {
HandlerIntent::Link { .. } => self.simulate_link(intent),
HandlerIntent::Stage { .. } => self.simulate_stage(intent),
HandlerIntent::Run { .. } => self.simulate_run(intent),
}
}
}
#[cfg(test)]
mod test_support;
#[cfg(test)]
mod tests {
use super::test_support::make_datastore;
use super::Executor;
use crate::operations::HandlerIntent;
use crate::testing::TempEnvironment;
#[test]
fn dry_run_does_not_modify_filesystem() {
let env = TempEnvironment::builder()
.pack("vim")
.file("vimrc", "x")
.done()
.build();
let (ds, _) = make_datastore(&env);
let executor = Executor::new(
&ds,
env.fs.as_ref(),
env.paths.as_ref(),
true,
false,
false,
true,
);
let results = executor
.execute(vec![
HandlerIntent::Link {
pack: "vim".into(),
handler: "symlink".into(),
source: env.dotfiles_root.join("vim/vimrc"),
user_path: env.home.join(".vimrc"),
},
HandlerIntent::Stage {
pack: "vim".into(),
handler: "shell".into(),
source: env.dotfiles_root.join("vim/vimrc"),
},
HandlerIntent::Run {
pack: "vim".into(),
handler: "install".into(),
executable: "echo".into(),
arguments: vec!["hi".into()],
sentinel: "s1".into(),
},
])
.unwrap();
assert_eq!(results.len(), 3); for r in &results {
assert!(r.success);
assert!(r.message.contains("[dry-run]"), "msg: {}", r.message);
}
env.assert_not_exists(&env.home.join(".vimrc"));
env.assert_no_handler_state("vim", "symlink");
env.assert_no_handler_state("vim", "shell");
env.assert_no_handler_state("vim", "install");
}
#[test]
fn execute_multiple_intents_sequentially() {
let env = TempEnvironment::builder()
.pack("vim")
.file("vimrc", "set nocompatible")
.file("gvimrc", "set guifont=Mono")
.done()
.build();
let (ds, _) = make_datastore(&env);
let executor = Executor::new(
&ds,
env.fs.as_ref(),
env.paths.as_ref(),
false,
false,
false,
true,
);
let results = executor
.execute(vec![
HandlerIntent::Link {
pack: "vim".into(),
handler: "symlink".into(),
source: env.dotfiles_root.join("vim/vimrc"),
user_path: env.home.join(".vimrc"),
},
HandlerIntent::Link {
pack: "vim".into(),
handler: "symlink".into(),
source: env.dotfiles_root.join("vim/gvimrc"),
user_path: env.home.join(".gvimrc"),
},
])
.unwrap();
assert_eq!(results.len(), 2); assert!(results.iter().all(|r| r.success));
env.assert_double_link(
"vim",
"symlink",
"vimrc",
&env.dotfiles_root.join("vim/vimrc"),
&env.home.join(".vimrc"),
);
env.assert_double_link(
"vim",
"symlink",
"gvimrc",
&env.dotfiles_root.join("vim/gvimrc"),
&env.home.join(".gvimrc"),
);
}
}