use crate::{cmd, constants, model, model::IndexMap, path, query, syntax};
fn expand_tree_vars(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
tree_name: &str,
garden_name: Option<&model::GardenName>,
name: &str,
) -> Option<String> {
if syntax::is_digit(name) {
return Some(format!("${name}"));
}
if let Some(var) = config.override_variables.get(name) {
return Some(tree_variable(
app_context,
config,
graft_config,
tree_name,
garden_name,
var,
));
}
if syntax::is_graft(name) {
if let Ok((graft_id, remainder)) = config.get_graft_id(name) {
return expand_tree_vars(
app_context,
config,
Some(app_context.get_config(graft_id)),
tree_name,
garden_name,
remainder,
);
}
if let Some(parent_id) = config.parent_id {
let parent_config = app_context.get_config(parent_id);
if let Ok((graft_id, remainder)) = parent_config.get_graft_id(name) {
return expand_tree_vars(
app_context,
config,
Some(app_context.get_config(graft_id)),
tree_name,
garden_name,
remainder,
);
}
}
if let Ok((graft_id, remainder)) = app_context.get_root_config().get_graft_id(name) {
return expand_tree_vars(
app_context,
config,
Some(app_context.get_config(graft_id)),
tree_name,
garden_name,
remainder,
);
}
}
if let Some(garden_name) = garden_name {
if let Some(var) = graft_config
.and_then(|cfg| cfg.gardens.get(garden_name))
.and_then(|garden| garden.variables.get(name))
{
return Some(tree_variable(
app_context,
config,
graft_config,
tree_name,
Some(garden_name),
var,
));
}
if let Some(var) = config
.gardens
.get(garden_name)
.and_then(|garden| garden.variables.get(name))
{
return Some(tree_variable(
app_context,
config,
graft_config,
tree_name,
Some(garden_name),
var,
));
}
}
if let Some(graft_cfg) = graft_config {
if let Some(var) = graft_cfg
.trees
.get(tree_name)
.and_then(|tree| tree.variables.get(name))
{
return Some(tree_variable(
app_context,
config,
graft_config,
tree_name,
garden_name,
var,
));
}
if let Some(var) = graft_cfg.variables.get(name) {
return Some(tree_variable(
app_context,
config,
graft_config,
tree_name,
garden_name,
var,
));
}
}
if let Some(var) = config
.trees
.get(tree_name)
.and_then(|tree| tree.variables.get(name))
{
return Some(tree_variable(
app_context,
config,
graft_config,
tree_name,
garden_name,
var,
));
}
if name == constants::TREE_NAME {
return Some(tree_name.to_string());
}
if let Some(var) = config.variables.get(name) {
return Some(tree_variable(
app_context,
config,
graft_config,
tree_name,
garden_name,
var,
));
}
let context = model::TreeContext::new(
tree_name,
graft_config.and_then(|cfg| cfg.get_id()),
garden_name.cloned(),
None,
);
if let Some(environ) = environment_value(app_context, config, graft_config, &context, name) {
return Some(environ);
}
if let Ok(env_value) = std::env::var(name) {
return Some(env_value);
}
Some(String::new())
}
fn expand_vars(
app_context: &model::ApplicationContext,
config: &model::Configuration,
name: &str,
) -> Option<String> {
if syntax::is_digit(name) {
return Some(format!("${name}"));
}
if let Some(var) = config.override_variables.get(name) {
return Some(variable(app_context, config, var));
}
if syntax::is_graft(name) {
let (graft_id, remainder) = match config.get_graft_id(name) {
Ok((graft_id, remainder)) => (graft_id, remainder),
Err(_) => return Some(String::new()),
};
return expand_graft_vars(app_context, graft_id, remainder);
}
if let Some(var) = config.variables.get(name) {
return Some(variable(app_context, config, var));
}
if let Some(parent_id) = config.parent_id {
let parent_config = app_context.get_config(parent_id);
return expand_vars(app_context, parent_config, name);
}
if let Ok(env_value) = std::env::var(name) {
return Some(env_value);
}
Some(String::new())
}
fn expand_graft_vars(
app_context: &model::ApplicationContext,
graft_id: model::ConfigId,
name: &str,
) -> Option<String> {
if syntax::is_graft(name) {
let (graft_id, remainder) = match app_context.get_config(graft_id).get_graft_id(name) {
Ok((graft_id, remainder)) => (graft_id, remainder),
Err(_) => return Some(String::new()),
};
return expand_graft_vars(app_context, graft_id, remainder);
}
expand_vars(app_context, app_context.get_config(graft_id), name)
}
fn home_dir() -> Option<String> {
if let Ok(home) = std::env::var(constants::ENV_HOME) {
return Some(home);
}
dirs::home_dir().map(|x| x.to_string_lossy().to_string())
}
pub fn tree_value(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
expr: &str,
tree_name: &str,
garden_name: Option<&model::GardenName>,
) -> String {
let is_exec = syntax::is_exec(expr);
let escaped_value;
let escaped_expr = if is_exec {
escaped_value = syntax::escape_shell_variables(expr);
escaped_value.as_str()
} else {
expr
};
let expanded = shellexpand::full_with_context_no_errors(escaped_expr, home_dir, |x| {
expand_tree_vars(app_context, config, graft_config, tree_name, garden_name, x)
})
.to_string();
if is_exec {
let pathbuf = config.get_tree_pathbuf(tree_name);
exec_expression(&expanded, pathbuf)
} else {
expanded
}
}
fn tree_value_for_shell(
app_context: &model::ApplicationContext,
config: &model::Configuration,
expr: &str,
tree_name: &model::TreeName,
garden_name: Option<&model::GardenName>,
) -> String {
let is_exec = syntax::is_exec(expr);
let expanded = shellexpand::full_with_context_no_errors(
&syntax::escape_shell_variables(expr),
home_dir,
|x| expand_tree_vars(app_context, config, None, tree_name, garden_name, x),
)
.to_string();
if is_exec {
let pathbuf = config.get_tree_pathbuf(tree_name);
exec_expression(&expanded, pathbuf)
} else {
expanded
}
}
pub fn value(
app_context: &model::ApplicationContext,
config: &model::Configuration,
expr: &str,
) -> String {
let is_exec = syntax::is_exec(expr);
let escaped_value;
let escaped_expr = if is_exec {
escaped_value = syntax::escape_shell_variables(expr);
escaped_value.as_str()
} else {
expr
};
let expanded = shellexpand::full_with_context_no_errors(escaped_expr, home_dir, |x| {
expand_vars(app_context, config, x)
})
.to_string();
if is_exec {
exec_expression(&expanded, None)
} else {
expanded
}
}
fn exec_expression(string: &str, pathbuf: Option<std::path::PathBuf>) -> String {
let cmd = syntax::trim_exec(string);
let mut proc = subprocess::Exec::shell(cmd);
if let Some(pathbuf) = pathbuf {
let current_dir = path::current_dir_string();
proc = proc.cwd(pathbuf.clone());
proc = proc.env(constants::ENV_PWD, pathbuf.to_str().unwrap_or(¤t_dir));
}
cmd::stdout_to_string(proc).unwrap_or_default()
}
pub fn multi_variable(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
multi_var: &mut model::MultiVariable,
context: &model::TreeContext,
) -> Vec<String> {
let mut result = Vec::new();
for var in multi_var.iter() {
let value = tree_variable(
app_context,
config,
graft_config,
&context.tree,
context.garden.as_ref(),
var,
);
result.push(value.to_string());
}
result
}
pub(crate) fn variables_for_shell(
app_context: &model::ApplicationContext,
config: &model::Configuration,
variables: &mut Vec<model::Variable>,
context: &model::TreeContext,
) -> Vec<String> {
let mut result = Vec::new();
for var in variables {
if let Some(value) = var.get_value() {
result.push(value.to_string());
continue;
}
var.set_evaluation_started();
let raw_value = tree_value_for_shell(
app_context,
config,
var.get_expr(),
&context.tree,
context.garden.as_ref(),
);
let value = get_value_from_environment(var, raw_value);
var.set_value(value.to_string());
result.push(value);
}
result
}
pub fn environment(
app_context: &model::ApplicationContext,
config: &model::Configuration,
context: &model::TreeContext,
) -> model::Environment {
let mut result = model::Environment::new();
let mut vars = Vec::new();
for var in &config.environment {
vars.push((context.clone(), var));
}
let mut ready = false;
if let Some(garden_name) = context.garden.as_ref() {
if let Some(garden) = &config.gardens.get(garden_name) {
for ctx in query::trees_from_garden(app_context, config, None, garden) {
if let Some(tree) = ctx
.config
.and_then(|id| app_context.get_config(id).trees.get(&ctx.tree))
{
for var in &tree.environment {
vars.push((ctx.clone(), var));
}
} else if let Some(tree) = config.trees.get(&ctx.tree) {
for var in &tree.environment {
vars.push((ctx.clone(), var));
}
}
}
for var in &garden.environment {
vars.push((context.clone(), var));
}
ready = true;
}
} else if let Some(name) = &context.group {
if let Some(group) = config.groups.get(name) {
for ctx in query::trees_from_group(app_context, config, None, None, group) {
if let Some(tree) = ctx
.config
.and_then(|id| app_context.get_config(id).trees.get(&ctx.tree))
{
for var in &tree.environment {
vars.push((ctx.clone(), var));
}
} else if let Some(tree) = config.trees.get(&ctx.tree) {
for var in &tree.environment {
vars.push((ctx.clone(), var));
}
}
}
ready = true;
}
}
let single_tree;
if !ready {
if let Some(tree) = config.trees.get(&context.tree) {
single_tree = tree;
for var in &single_tree.environment {
vars.push((context.clone(), var));
}
}
}
let mut var_values = Vec::new();
for (ctx, var) in vars.iter_mut() {
let mut cloned_var = var.clone();
let graft_config = ctx.config.map(|id| app_context.get_config(id));
let values = multi_variable(app_context, config, graft_config, &mut cloned_var, ctx);
var_values.push((
tree_value(
app_context,
config,
graft_config,
var.get_name(),
ctx.tree.as_str(),
ctx.garden.as_ref(),
),
values,
));
}
let mut values: IndexMap<String, String> = IndexMap::new();
for (var_name, env_values) in &var_values {
let mut name = var_name.clone();
let mut is_assign = false;
let mut is_append = false;
if syntax::is_replace_op(&name) {
is_assign = true;
}
if syntax::is_append_op(&name) {
is_append = true;
}
if is_assign || is_append {
syntax::trim_op_inplace(&mut name);
}
for value in env_values {
let mut current = String::new();
let mut exists = false;
if let Some(map_value) = values.get(&name) {
current.clone_from(map_value);
exists = true;
}
if !exists {
let mut has_env = false;
if let Ok(env_value) = std::env::var(&name) {
let env_str: String = env_value;
if !env_str.is_empty() {
current = env_str;
has_env = true;
}
}
if has_env && !is_assign {
values.insert(name.clone(), current.clone());
} else {
values.insert(name.clone(), value.clone());
result.push((name.clone(), value.clone()));
continue;
}
}
if is_assign {
values.insert(name.clone(), value.clone());
result.push((name.clone(), value.clone()));
continue;
}
let mut path_values: Vec<String> = Vec::new();
if !is_append {
path_values.push(value.clone());
}
for path in current.split(':') {
path_values.push(path.into());
}
if is_append {
path_values.push(value.clone());
}
let path_value = path_values.join(":");
values.insert(name.clone(), path_value.clone());
result.push((name.clone(), path_value));
}
}
result
}
fn environment_value_vars<'a>(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
context: &model::TreeContext,
names: &[String],
variables: &'a Vec<model::MultiVariable>,
) -> Vec<(model::TreeContext, String, &'a model::MultiVariable)> {
let mut vars = Vec::with_capacity(variables.len());
for var in variables {
if names.contains(var.get_name()) {
vars.push((context.clone(), var.get_name().to_string(), var));
continue;
}
if syntax::is_eval_candidate(var.get_name()) {
let name_value = tree_value(
app_context,
config,
graft_config.or(context.config.map(|cfg_id| app_context.get_config(cfg_id))),
var.get_name(),
&context.tree,
context.garden.as_ref(),
);
if names.contains(&name_value) {
vars.push((context.clone(), name_value, var));
}
}
}
vars
}
fn environment_value(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
context: &model::TreeContext,
name: &str,
) -> Option<String> {
let mut vars = Vec::new();
let name_prepend = name.to_string();
let name_append = format!("{name}+");
let name_replace = format!("{name}=");
let names = vec![name_prepend, name_append, name_replace];
vars.append(&mut environment_value_vars(
app_context,
config,
graft_config,
context,
&names,
&config.environment,
));
if let Some(graft_cfg) = graft_config {
vars.append(&mut environment_value_vars(
app_context,
config,
graft_config,
context,
&names,
&graft_cfg.environment,
));
}
let mut ready = false;
if let Some(garden_name) = context.garden.as_ref() {
let mut garden_ref: Option<&model::Garden> = None;
if let Some(garden) = graft_config.and_then(|cfg| cfg.gardens.get(garden_name)) {
garden_ref = Some(garden);
} else if let Some(garden) = config.gardens.get(garden_name) {
garden_ref = Some(garden);
}
if let Some(garden) = garden_ref {
for ctx in query::trees_from_garden(app_context, config, graft_config, garden) {
let garden_graft_config =
ctx.config.map(|graft_id| app_context.get_config(graft_id));
if let Some(tree) = garden_graft_config.and_then(|cfg| cfg.trees.get(&ctx.tree)) {
vars.append(&mut environment_value_vars(
app_context,
config,
garden_graft_config,
&ctx,
&names,
&tree.environment,
));
} else if let Some(tree) = config.trees.get(&ctx.tree) {
vars.append(&mut environment_value_vars(
app_context,
config,
graft_config,
&ctx,
&names,
&tree.environment,
));
}
}
vars.append(&mut environment_value_vars(
app_context,
config,
graft_config,
context,
&names,
&garden.environment,
));
ready = true;
}
} else if let Some(group_name) = context.group.as_ref() {
if let Some(group) = config.groups.get(group_name) {
for ctx in query::trees_from_group(app_context, config, graft_config, None, group) {
let group_graft_config =
ctx.config.map(|graft_id| app_context.get_config(graft_id));
if let Some(graft_cfg) = group_graft_config {
if let Some(tree) = graft_cfg.trees.get(&ctx.tree) {
vars.append(&mut environment_value_vars(
app_context,
config,
group_graft_config,
&ctx,
&names,
&tree.environment,
));
ready = true;
}
} else if let Some(tree) = config.trees.get(&ctx.tree) {
vars.append(&mut environment_value_vars(
app_context,
config,
group_graft_config,
&ctx,
&names,
&tree.environment,
));
ready = true;
}
}
}
}
let single_tree;
if !ready {
if let Some(graft_cfg) = graft_config {
if let Some(tree) = graft_cfg.trees.get(&context.tree) {
single_tree = tree;
vars.append(&mut environment_value_vars(
app_context,
config,
graft_config,
context,
&names,
&single_tree.environment,
));
}
} else if let Some(tree) = config.trees.get(&context.tree) {
single_tree = tree;
vars.append(&mut environment_value_vars(
app_context,
config,
graft_config,
context,
&names,
&single_tree.environment,
));
}
}
let mut var_values = Vec::new();
for (ctx, name_value, var) in vars.iter_mut() {
let mut cloned_var = var.clone();
let values = multi_variable(
app_context,
config,
graft_config.or(ctx.config.map(|id| app_context.get_config(id))),
&mut cloned_var,
ctx,
);
var_values.push((name_value, values));
}
let mut final_value: Option<String> = None;
for (var_name, env_values) in var_values {
let mut real_name = var_name.clone();
let mut is_assign = false;
let mut is_append = false;
if syntax::is_replace_op(var_name) {
is_assign = true;
}
if syntax::is_append_op(var_name) {
is_append = true;
}
if is_assign || is_append {
syntax::trim_op_inplace(&mut real_name);
}
for value in env_values {
let mut current = String::new();
let exists = if let Some(final_string_value) = final_value {
current.clone_from(&final_string_value);
true
} else {
false
};
if !exists {
let mut has_env = false;
if let Ok(env_value) = std::env::var(&real_name) {
let env_str: String = env_value;
if !env_str.is_empty() {
current = env_str;
has_env = true;
}
}
#[allow(unused_assignments)]
if has_env && !is_assign {
final_value = Some(current.clone());
} else {
final_value = Some(value.clone());
continue;
}
}
if is_assign {
final_value = Some(value.clone());
continue;
}
let mut path_values: Vec<String> = Vec::new();
if !is_append {
path_values.push(value.clone());
}
for path in current.split(':') {
if !path.is_empty() {
path_values.push(path.to_string());
}
}
if is_append {
path_values.push(value.clone());
}
let path_value = path_values.join(":");
final_value = Some(path_value.clone());
}
}
final_value
}
pub fn command(
app_context: &model::ApplicationContext,
context: &model::TreeContext,
name: &str,
) -> Vec<Vec<String>> {
let mut vec_variables = Vec::new();
let mut result = Vec::new();
let config = match context.config {
Some(config_id) => app_context.get_config(config_id),
None => app_context.get_root_config(),
};
let pattern = match glob::Pattern::new(name) {
Ok(value) => value,
Err(_) => return result,
};
for (var_name, var) in &config.commands {
if pattern.matches(var_name) {
vec_variables.push(var.clone());
}
}
if let Some(tree) = config.trees.get(&context.tree) {
for (var_name, var) in &tree.commands {
if pattern.matches(var_name) {
vec_variables.push(var.clone());
}
}
}
if let Some(garden_name) = &context.garden {
if let Some(garden) = &config.gardens.get(garden_name) {
for (var_name, var) in &garden.commands {
if pattern.matches(var_name) {
vec_variables.push(var.clone());
}
}
}
}
for variables in vec_variables.iter_mut() {
result.push(variables_for_shell(app_context, config, variables, context));
}
result
}
pub(crate) fn tree_variable(
app_context: &model::ApplicationContext,
config: &model::Configuration,
graft_config: Option<&model::Configuration>,
tree_name: &str,
garden_name: Option<&model::GardenName>,
var: &model::Variable,
) -> String {
if let Some(var_value) = var.get_value() {
return var_value.to_string();
}
if var.is_evaluating() {
return String::new();
}
var.set_evaluation_started();
let expr = var.get_expr();
let raw_value = tree_value(
app_context,
config,
graft_config,
expr,
tree_name,
garden_name,
);
let value = get_value_from_environment(var, raw_value);
var.set_value(value.to_string());
value
}
pub(crate) fn variable(
app_context: &model::ApplicationContext,
config: &model::Configuration,
var: &model::Variable,
) -> String {
if let Some(var_value) = var.get_value() {
return var_value.to_string();
}
if var.is_evaluating() {
return String::new();
}
var.set_evaluation_started();
let expr = var.get_expr();
let raw_value = value(app_context, config, expr);
let value = get_value_from_environment(var, raw_value);
var.set_value(value.to_string());
value
}
fn get_value_from_environment(variable: &model::Variable, value: String) -> String {
if variable.is_required() && value.is_empty() {
if let Ok(env_value) = std::env::var(variable.get_name()) {
return env_value;
}
}
value
}