use std::sync::atomic;
use anyhow::Result;
use clap::{Parser, ValueHint};
use rayon::prelude::*;
use crate::cli::GardenOptions;
use crate::{cmd, constants, errors, model, query};
#[derive(Parser, Clone, Debug)]
#[command(author, about, long_about)]
pub struct ExecOptions {
#[arg(long, short, default_value = "*")]
trees: String,
#[arg(long, short = 'N', short_alias = 'n')]
dry_run: bool,
#[arg(long = "jobs", short = 'j', value_name = "JOBS")]
num_jobs: Option<usize>,
#[arg(short, long)]
quiet: bool,
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
#[arg(value_hint=ValueHint::Other)]
query: String,
#[arg(allow_hyphen_values = true, trailing_var_arg = true, required = true, value_hint=ValueHint::CommandWithArguments)]
command: Vec<String>,
}
pub fn main(app_context: &model::ApplicationContext, exec_options: &mut ExecOptions) -> Result<()> {
exec_options.verbose += app_context.options.verbose;
if app_context.options.debug_level(constants::DEBUG_LEVEL_EXEC) > 0 {
debug!("query: {}", exec_options.query);
debug!("command: {:?}", exec_options.command);
}
exec_options.quiet |= app_context.options.quiet;
exec(app_context, exec_options)
}
fn is_valid_context(
app_context: &model::ApplicationContext,
pattern: &glob::Pattern,
context: &model::TreeContext,
) -> bool {
if !pattern.matches(&context.tree) {
return false;
}
let tree_opt = match context.config {
Some(graft_id) => app_context.get_config(graft_id).trees.get(&context.tree),
None => app_context.get_root_config().trees.get(&context.tree),
};
let tree = match tree_opt {
Some(tree) => tree,
None => return false,
};
if tree.is_symlink {
return false;
}
true
}
fn exec(app_context: &model::ApplicationContext, exec_options: &ExecOptions) -> Result<()> {
let quiet = exec_options.quiet;
let verbose = exec_options.verbose;
let dry_run = exec_options.dry_run;
let query = &exec_options.query;
let tree_pattern = &exec_options.trees;
let command = &exec_options.command;
cmd::initialize_threads_option(exec_options.num_jobs)?;
let config = app_context.get_root_config_mut();
let contexts = query::resolve_trees(app_context, config, None, query);
let pattern = glob::Pattern::new(tree_pattern).unwrap_or_default();
let exit_status = atomic::AtomicI32::new(errors::EX_OK);
if exec_options.num_jobs.is_some() {
contexts.par_iter().for_each(|context| {
let app_context_clone = app_context.clone();
let app_context = &app_context_clone;
if !is_valid_context(app_context, &pattern, context) {
return;
}
if let Err(errors::GardenError::ExitStatus(status)) = cmd::exec_in_context(
app_context,
app_context.get_root_config(),
context,
quiet,
verbose,
dry_run,
command,
) {
exit_status.store(status, atomic::Ordering::Release);
}
});
} else {
for context in &contexts {
if !is_valid_context(app_context, &pattern, context) {
continue;
}
if let Err(errors::GardenError::ExitStatus(status)) = cmd::exec_in_context(
app_context,
config,
context,
quiet,
verbose,
dry_run,
command,
) {
exit_status.store(status, atomic::Ordering::Release);
}
}
}
errors::exit_status_into_result(exit_status.load(atomic::Ordering::Acquire))
}