use crate::{constants, display, errors, eval, model, syntax};
pub(crate) fn result_from_exit_status(exit_status: i32) -> Result<(), errors::GardenError> {
match exit_status {
errors::EX_OK => Ok(()),
_ => Err(errors::GardenError::ExitStatus(exit_status)),
}
}
pub fn status(exec: subprocess::Exec) -> i32 {
status_code(exec.join())
}
fn status_code(result: subprocess::Result<subprocess::ExitStatus>) -> i32 {
match result {
Ok(subprocess::ExitStatus::Exited(status)) => status as i32,
Ok(subprocess::ExitStatus::Signaled(status)) => status as i32,
Ok(subprocess::ExitStatus::Other(status)) => status,
Ok(subprocess::ExitStatus::Undetermined) => errors::EX_ERROR,
Err(_) => errors::EX_ERROR,
}
}
fn stdout(capture: &subprocess::CaptureData) -> String {
capture.stdout_str().trim_end().to_string()
}
fn command_error_from_popen_error(
command: String,
popen_err: subprocess::PopenError,
) -> errors::CommandError {
let status = match popen_err {
subprocess::PopenError::IoError(err) => err.raw_os_error().unwrap_or(1),
_ => 1,
};
errors::CommandError::ExitStatus { command, status }
}
pub(crate) fn capture_stdout(
exec: subprocess::Exec,
) -> Result<subprocess::CaptureData, errors::CommandError> {
let command = exec.to_cmdline_lossy();
let capture = exec
.stdout(subprocess::Redirection::Pipe)
.stderr(subprocess::NullFile {}) .capture();
match capture {
Ok(result) => {
let status = exit_status(result.exit_status);
if status == 0 {
Ok(result)
} else {
Err(errors::CommandError::ExitStatus { command, status })
}
}
Err(err) => Err(command_error_from_popen_error(command, err)),
}
}
pub(crate) fn exit_status(status: subprocess::ExitStatus) -> i32 {
match status {
subprocess::ExitStatus::Exited(status) => status as i32,
subprocess::ExitStatus::Signaled(status) => status as i32,
subprocess::ExitStatus::Other(status) => status,
subprocess::ExitStatus::Undetermined => errors::EX_ERROR,
}
}
pub fn stdout_to_string(exec: subprocess::Exec) -> Result<String, errors::CommandError> {
Ok(stdout(&capture_stdout(exec)?))
}
pub(crate) fn exec_cmd<S>(command: &[S]) -> subprocess::Exec
where
S: AsRef<std::ffi::OsStr>,
{
if command.len() > 1 {
subprocess::Exec::cmd(&command[0]).args(&command[1..])
} else {
subprocess::Exec::cmd(&command[0])
}
}
pub fn exec_in_dir<P, S>(command: &[S], path: &P) -> subprocess::Exec
where
P: AsRef<std::path::Path> + std::convert::AsRef<std::ffi::OsStr> + ?Sized,
S: AsRef<std::ffi::OsStr>,
{
exec_cmd(command).cwd(path).env(constants::ENV_PWD, path)
}
pub(crate) fn exec_in_context<S>(
app_context: &model::ApplicationContext,
config: &model::Configuration,
context: &model::TreeContext,
quiet: bool,
verbose: u8,
dry_run: bool,
command: &[S],
) -> Result<(), errors::GardenError>
where
S: AsRef<std::ffi::OsStr>,
{
let path;
if let Some(tree) = config.trees.get(&context.tree) {
path = tree.path_as_ref()?;
if !display::print_tree(tree, config.tree_branches, verbose, quiet) {
return Ok(());
}
} else {
return Ok(());
}
let env = eval::environment(app_context, config, context);
let command_vec = resolve_command(command, &env);
if verbose > 1 || dry_run {
display::print_command_string_vec(&command_vec);
}
if dry_run {
return Ok(());
}
let mut exec = exec_in_dir(&command_vec, path);
for (name, value) in &env {
exec = exec.env(name, value);
}
result_from_exit_status(status(exec))
}
fn resolve_command<S>(command: &[S], env: &[(String, String)]) -> Vec<String>
where
S: AsRef<std::ffi::OsStr>,
{
let mut cmd_path = std::path::PathBuf::from(&command[0]);
if !cmd_path.is_absolute() {
for (name, value) in env {
if name == constants::ENV_PATH {
if let Some(path_buf) = std::env::split_paths(&value).find_map(|dir| {
let full_path = dir.join(&cmd_path);
if full_path.is_file() {
Some(full_path)
} else {
None
}
}) {
cmd_path = path_buf;
}
break;
}
}
}
let mut command_vec = Vec::with_capacity(command.len());
command_vec.push(cmd_path.to_string_lossy().to_string());
for arg in &command[1..] {
let curpath = std::path::PathBuf::from(arg);
command_vec.push(curpath.to_string_lossy().into());
}
command_vec
}
pub(crate) fn current_exe() -> String {
match std::env::current_exe() {
Err(_) => constants::GARDEN.into(),
Ok(path) => path.to_string_lossy().into(),
}
}
pub(crate) fn get_command_values(
app_context: &model::ApplicationContext,
context: &model::TreeContext,
name: &str,
) -> Vec<String> {
let config = match context.config {
Some(config_id) => app_context.get_config(config_id),
None => app_context.get_root_config(),
};
let mut vec_variables = Vec::new();
for (command_name, var) in &config.commands {
if name == command_name {
vec_variables.push(var.clone());
}
}
if let Some(tree) = config.trees.get(&context.tree) {
for (command_name, var) in &tree.commands {
if name == command_name {
vec_variables.push(var.clone());
}
}
}
if let Some(garden_name) = &context.garden {
if let Some(garden) = &config.gardens.get(garden_name) {
for (command_name, var) in &garden.commands {
if name == command_name {
vec_variables.push(var.clone());
}
}
}
}
let mut commands = Vec::with_capacity(vec_variables.len() * 2);
for variables in vec_variables.iter_mut() {
let values = eval::variables_for_shell(app_context, config, variables, context);
commands.extend(values);
}
commands
}
pub(crate) fn expand_command_names(
app_context: &model::ApplicationContext,
context: &model::TreeContext,
name: &str,
) -> Vec<String> {
let pre_name = syntax::pre_command(name);
let post_name = syntax::post_command(name);
let pre_commands = get_command_values(app_context, context, &pre_name);
let post_commands = get_command_values(app_context, context, &post_name);
let mut command_names = Vec::with_capacity(pre_commands.len() + 1 + post_commands.len());
for cmd_name in pre_commands.iter() {
if cmd_name != name {
command_names.extend(expand_command_names(app_context, context, cmd_name));
}
}
command_names.push(name.to_string());
for cmd_name in post_commands.iter() {
if cmd_name != name {
command_names.extend(expand_command_names(app_context, context, cmd_name));
}
}
command_names
}
pub(crate) fn shell_quote(arg: &str) -> String {
shlex::try_quote(arg)
.map(|quoted_arg| quoted_arg.to_string())
.unwrap_or_else(|_| arg.to_string())
}