use std::collections::HashMap;
use crate::{cmd, model, path, query, syntax};
fn expand_tree_vars(
app_context: &model::ApplicationContext,
config: &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) {
let expr = var.get_expr();
let result = tree_value(app_context, config, expr, tree_name, garden_name);
var.set_value(result.clone());
return Some(result);
}
if syntax::is_graft(name) {
if let Ok((graft_id, remainder)) = config.get_graft_id(name) {
return expand_tree_vars(
app_context,
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,
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,
app_context.get_config(graft_id),
tree_name,
garden_name,
remainder,
);
}
}
if let Some(garden_name) = garden_name {
if let Some(var) = config
.gardens
.get(garden_name)
.and_then(|garden| garden.variables.get(name))
{
if let Some(var_value) = var.get_value() {
return Some(var_value.to_string());
}
let expr = var.get_expr();
let result = tree_value(app_context, config, expr, tree_name, Some(garden_name));
var.set_value(result.clone());
return Some(result);
}
}
if let Some(var) = config
.trees
.get(tree_name)
.and_then(|tree| tree.variables.get(name))
{
if let Some(var_value) = var.get_value() {
return Some(var_value.to_string());
}
let expr = var.get_expr();
let result = tree_value(app_context, config, expr, tree_name, garden_name);
var.set_value(result.to_string());
return Some(result);
}
if let Some(var) = config.variables.get(name) {
let expr = var.get_expr();
let result = tree_value(app_context, config, expr, tree_name, garden_name);
var.set_value(result.clone());
return Some(result);
}
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 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) {
if let Some(var_value) = var.get_value() {
return Some(var_value.to_string());
}
let expr = var.get_expr();
let result = value(app_context, config, expr);
var.set_value(result.clone());
return Some(result);
}
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("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,
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, 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, 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("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,
multi_var: &mut model::MultiVariable,
context: &model::TreeContext,
) -> Vec<String> {
let mut result = Vec::new();
for var in multi_var.iter() {
if let Some(value) = var.get_value() {
result.push(value.to_string());
continue;
}
let value = tree_value(
app_context,
config,
var.get_expr(),
&context.tree,
context.garden.as_ref(),
);
result.push(value.clone());
var.set_value(value);
}
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;
}
let value = tree_value_for_shell(
app_context,
config,
var.get_expr(),
&context.tree,
context.garden.as_ref(),
);
result.push(value.clone());
var.set_value(value);
}
result
}
pub fn environment(
app_context: &model::ApplicationContext,
config: &model::Configuration,
context: &model::TreeContext,
) -> Vec<(String, String)> {
let mut result = Vec::new();
let mut vars = Vec::new();
for var in &config.environment {
vars.push((context.clone(), var.clone()));
}
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, garden) {
let config = match ctx.config {
Some(config_id) => app_context.get_config(config_id),
None => config,
};
if let Some(tree) = config.trees.get(&ctx.tree) {
for var in &tree.environment {
vars.push((ctx.clone(), var.clone()));
}
}
}
for var in &garden.environment {
vars.push((context.clone(), var.clone()));
}
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, group) {
let config = match ctx.config {
Some(config_id) => app_context.get_config(config_id),
None => config,
};
if let Some(tree) = config.trees.get(&ctx.tree) {
for var in &tree.environment {
vars.push((ctx.clone(), var.clone()));
}
}
}
ready = true;
}
}
if !ready {
if let Some(tree) = config.trees.get(&context.tree) {
for var in &tree.environment {
vars.push((context.clone(), var.clone()));
}
}
}
let mut var_values = Vec::new();
for (ctx, var) in vars.iter_mut() {
var_values.push((
tree_value(
app_context,
config,
var.get_name(),
&ctx.tree,
ctx.garden.as_ref(),
),
multi_variable(app_context, config, var, ctx),
));
}
let mut values: HashMap<String, String> = HashMap::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 = map_value.clone();
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
}
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
}