mod filesystem;
pub use filesystem::FilesystemDataStore;
use std::path::{Path, PathBuf};
use crate::Result;
pub trait DataStore: Send + Sync {
fn create_data_link(&self, pack: &str, handler: &str, source_file: &Path) -> Result<PathBuf>;
fn create_user_link(&self, datastore_path: &Path, user_path: &Path) -> Result<()>;
fn run_and_record(
&self,
pack: &str,
handler: &str,
executable: &str,
arguments: &[String],
sentinel: &str,
force: bool,
) -> Result<()>;
fn has_sentinel(&self, pack: &str, handler: &str, sentinel: &str) -> Result<bool>;
fn remove_state(&self, pack: &str, handler: &str) -> Result<()>;
fn has_handler_state(&self, pack: &str, handler: &str) -> Result<bool>;
fn list_pack_handlers(&self, pack: &str) -> Result<Vec<String>>;
fn list_handler_sentinels(&self, pack: &str, handler: &str) -> Result<Vec<String>>;
fn sentinel_path(&self, pack: &str, handler: &str, sentinel: &str) -> std::path::PathBuf;
}
pub trait CommandRunner: Send + Sync {
fn run(&self, executable: &str, arguments: &[String]) -> Result<CommandOutput>;
}
#[derive(Debug, Clone)]
pub struct CommandOutput {
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
}
pub struct ShellCommandRunner;
pub(crate) fn format_command_for_display(executable: &str, arguments: &[String]) -> String {
if arguments.is_empty() {
return executable.to_string();
}
let args = arguments
.iter()
.map(|arg| {
if arg.is_empty()
|| arg.chars().any(char::is_whitespace)
|| arg.contains('"')
|| arg.contains('\'')
{
format!("{arg:?}")
} else {
arg.clone()
}
})
.collect::<Vec<_>>()
.join(" ");
format!("{executable} {args}")
}
impl CommandRunner for ShellCommandRunner {
fn run(&self, executable: &str, arguments: &[String]) -> Result<CommandOutput> {
let output = std::process::Command::new(executable)
.args(arguments)
.output()
.map_err(|e| crate::DodotError::CommandFailed {
command: format_command_for_display(executable, arguments),
exit_code: -1,
stderr: e.to_string(),
})?;
let exit_code = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
if !output.status.success() {
return Err(crate::DodotError::CommandFailed {
command: format_command_for_display(executable, arguments),
exit_code,
stderr,
});
}
Ok(CommandOutput {
exit_code,
stdout,
stderr,
})
}
}