Skip to main content

garden/
eval.rs

1use crate::{cmd, constants, model, model::IndexMap, path, query, syntax};
2
3/// Expand variables across all scopes (garden, tree, and global).
4/// - `app_context`: reference to the top-level ApplicationContext.
5/// - `config`: reference to Configuration to use for evaluation
6/// - `tree_name`: name of the tree being evaluated.
7/// - `garden_name`: optional garden name being evaluated.
8/// - `name`: the name of the variable being expanded.
9fn expand_tree_vars(
10    app_context: &model::ApplicationContext,
11    config: &model::Configuration,
12    graft_config: Option<&model::Configuration>,
13    tree_name: &str,
14    garden_name: Option<&model::GardenName>,
15    name: &str,
16) -> Option<String> {
17    // Special case $0, $1, .. $N so they can be used in commands.
18    if syntax::is_digit(name) {
19        return Some(format!("${name}"));
20    }
21    // Check for the variable in override scope defined by "garden -D name=value".
22    if let Some(var) = config.override_variables.get(name) {
23        return Some(tree_variable(
24            app_context,
25            config,
26            graft_config,
27            tree_name,
28            garden_name,
29            var,
30        ));
31    }
32
33    // Special-case evaluation of ${graft::values}.
34    if syntax::is_graft(name) {
35        // First, try the current config.
36        if let Ok((graft_id, remainder)) = config.get_graft_id(name) {
37            return expand_tree_vars(
38                app_context,
39                config,
40                Some(app_context.get_config(graft_id)),
41                tree_name,
42                garden_name,
43                remainder,
44            );
45        }
46
47        // If nothing was found then try the parent config.
48        if let Some(parent_id) = config.parent_id {
49            let parent_config = app_context.get_config(parent_id);
50            if let Ok((graft_id, remainder)) = parent_config.get_graft_id(name) {
51                return expand_tree_vars(
52                    app_context,
53                    config,
54                    Some(app_context.get_config(graft_id)),
55                    tree_name,
56                    garden_name,
57                    remainder,
58                );
59            }
60        }
61        // Lastly, try the root configuraiton.
62        if let Ok((graft_id, remainder)) = app_context.get_root_config().get_graft_id(name) {
63            return expand_tree_vars(
64                app_context,
65                config,
66                Some(app_context.get_config(graft_id)),
67                tree_name,
68                garden_name,
69                remainder,
70            );
71        }
72    }
73
74    // Check for the variable at the grafted garden scope.
75    // Garden scope overrides tree and global scope.
76    if let Some(garden_name) = garden_name {
77        if let Some(var) = graft_config
78            .and_then(|cfg| cfg.gardens.get(garden_name))
79            .and_then(|garden| garden.variables.get(name))
80        {
81            return Some(tree_variable(
82                app_context,
83                config,
84                graft_config,
85                tree_name,
86                Some(garden_name),
87                var,
88            ));
89        }
90
91        // Check for the variable at the root garden scope.
92        if let Some(var) = config
93            .gardens
94            .get(garden_name)
95            .and_then(|garden| garden.variables.get(name))
96        {
97            return Some(tree_variable(
98                app_context,
99                config,
100                graft_config,
101                tree_name,
102                Some(garden_name),
103                var,
104            ));
105        }
106    }
107
108    // Nothing was found -- check for the variable in grafted scopes.
109    if let Some(graft_cfg) = graft_config {
110        if let Some(var) = graft_cfg
111            .trees
112            .get(tree_name)
113            .and_then(|tree| tree.variables.get(name))
114        {
115            return Some(tree_variable(
116                app_context,
117                config,
118                graft_config,
119                tree_name,
120                garden_name,
121                var,
122            ));
123        }
124        // Nothing was found. Check for the variable in global/config scope.
125        if let Some(var) = graft_cfg.variables.get(name) {
126            return Some(tree_variable(
127                app_context,
128                config,
129                graft_config,
130                tree_name,
131                garden_name,
132                var,
133            ));
134        }
135    }
136
137    // Nothing was found -- check for the variable in tree scope.
138    if let Some(var) = config
139        .trees
140        .get(tree_name)
141        .and_then(|tree| tree.variables.get(name))
142    {
143        return Some(tree_variable(
144            app_context,
145            config,
146            graft_config,
147            tree_name,
148            garden_name,
149            var,
150        ));
151    }
152    if name == constants::TREE_NAME {
153        return Some(tree_name.to_string());
154    }
155
156    // Nothing was found. Check for the variable in global/config scope.
157    if let Some(var) = config.variables.get(name) {
158        return Some(tree_variable(
159            app_context,
160            config,
161            graft_config,
162            tree_name,
163            garden_name,
164            var,
165        ));
166    }
167
168    // Nothing was found. Check for garden environment variables.
169    let context = model::TreeContext::new(
170        tree_name,
171        graft_config.and_then(|cfg| cfg.get_id()),
172        garden_name.cloned(),
173        None,
174    );
175    if let Some(environ) = environment_value(app_context, config, graft_config, &context, name) {
176        return Some(environ);
177    }
178
179    // If nothing was found then check for OS environment variables.
180    if let Ok(env_value) = std::env::var(name) {
181        return Some(env_value);
182    }
183
184    // Nothing was found -> empty value
185    Some(String::new())
186}
187
188/// Expand variables at global scope only
189fn expand_vars(
190    app_context: &model::ApplicationContext,
191    config: &model::Configuration,
192    name: &str,
193) -> Option<String> {
194    // Special case $0, $1, .. $N so they can be used in commands.
195    if syntax::is_digit(name) {
196        return Some(format!("${name}"));
197    }
198    // Check for the variable in override scope defined by "garden -D name=value".
199    if let Some(var) = config.override_variables.get(name) {
200        return Some(variable(app_context, config, var));
201    }
202
203    if syntax::is_graft(name) {
204        let (graft_id, remainder) = match config.get_graft_id(name) {
205            Ok((graft_id, remainder)) => (graft_id, remainder),
206            Err(_) => return Some(String::new()),
207        };
208        return expand_graft_vars(app_context, graft_id, remainder);
209    }
210
211    // Check for the variable in the current configuration's global scope.
212    if let Some(var) = config.variables.get(name) {
213        return Some(variable(app_context, config, var));
214    }
215
216    // Walk up the parent hierarchy to resolve variables defined by graft parents.
217    if let Some(parent_id) = config.parent_id {
218        let parent_config = app_context.get_config(parent_id);
219        return expand_vars(app_context, parent_config, name);
220    }
221
222    // If nothing was found then check for environment variables.
223    if let Ok(env_value) = std::env::var(name) {
224        return Some(env_value);
225    }
226
227    // Nothing was found -> empty value
228    Some(String::new())
229}
230
231/// Expand graft variables of the form "graft::name".
232fn expand_graft_vars(
233    app_context: &model::ApplicationContext,
234    graft_id: model::ConfigId,
235    name: &str,
236) -> Option<String> {
237    if syntax::is_graft(name) {
238        let (graft_id, remainder) = match app_context.get_config(graft_id).get_graft_id(name) {
239            Ok((graft_id, remainder)) => (graft_id, remainder),
240            Err(_) => return Some(String::new()),
241        };
242        return expand_graft_vars(app_context, graft_id, remainder);
243    }
244
245    expand_vars(app_context, app_context.get_config(graft_id), name)
246}
247
248/// Resolve ~ to the current user's home directory
249fn home_dir() -> Option<String> {
250    // Honor $HOME when set in the environment.
251    if let Ok(home) = std::env::var(constants::ENV_HOME) {
252        return Some(home);
253    }
254    dirs::home_dir().map(|x| x.to_string_lossy().to_string())
255}
256
257/// Resolve an expression in a garden/tree/global scope
258pub fn tree_value(
259    app_context: &model::ApplicationContext,
260    config: &model::Configuration,
261    graft_config: Option<&model::Configuration>,
262    expr: &str,
263    tree_name: &str,
264    garden_name: Option<&model::GardenName>,
265) -> String {
266    let is_exec = syntax::is_exec(expr);
267    let escaped_value;
268    let escaped_expr = if is_exec {
269        escaped_value = syntax::escape_shell_variables(expr);
270        escaped_value.as_str()
271    } else {
272        expr
273    };
274    let expanded = shellexpand::full_with_context_no_errors(escaped_expr, home_dir, |x| {
275        expand_tree_vars(app_context, config, graft_config, tree_name, garden_name, x)
276    })
277    .to_string();
278
279    // NOTE: an environment must not be calculated here otherwise any
280    // exec expression will implicitly depend on the entire environment,
281    // and potentially many variables (including itself).  Exec expressions
282    // always use the default environment.
283    if is_exec {
284        let pathbuf = config.get_tree_pathbuf(tree_name);
285        exec_expression(&expanded, pathbuf)
286    } else {
287        expanded
288    }
289}
290
291/// Resolve an expression in a garden/tree/global scope for execution by a shell.
292/// This is used to generate the commands used internally by garden.
293fn tree_value_for_shell(
294    app_context: &model::ApplicationContext,
295    config: &model::Configuration,
296    expr: &str,
297    tree_name: &model::TreeName,
298    garden_name: Option<&model::GardenName>,
299) -> String {
300    let is_exec = syntax::is_exec(expr);
301    let expanded = shellexpand::full_with_context_no_errors(
302        &syntax::escape_shell_variables(expr),
303        home_dir,
304        |x| expand_tree_vars(app_context, config, None, tree_name, garden_name, x),
305    )
306    .to_string();
307
308    // NOTE: an environment must not be calculated here otherwise any
309    // exec expression will implicitly depend on the entire environment,
310    // and potentially many variables (including itself).  Exec expressions
311    // always use the default environment.
312    if is_exec {
313        let pathbuf = config.get_tree_pathbuf(tree_name);
314        exec_expression(&expanded, pathbuf)
315    } else {
316        expanded
317    }
318}
319
320/// Resolve a variable in configuration/global scope
321pub fn value(
322    app_context: &model::ApplicationContext,
323    config: &model::Configuration,
324    expr: &str,
325) -> String {
326    let is_exec = syntax::is_exec(expr);
327    let escaped_value;
328    let escaped_expr = if is_exec {
329        escaped_value = syntax::escape_shell_variables(expr);
330        escaped_value.as_str()
331    } else {
332        expr
333    };
334    let expanded = shellexpand::full_with_context_no_errors(escaped_expr, home_dir, |x| {
335        expand_vars(app_context, config, x)
336    })
337    .to_string();
338
339    if is_exec {
340        exec_expression(&expanded, None)
341    } else {
342        expanded
343    }
344}
345
346/// Evaluate `$ <command>` command strings, AKA "exec expressions".
347/// The result of the expression is the stdout output from the command.
348fn exec_expression(string: &str, pathbuf: Option<std::path::PathBuf>) -> String {
349    let cmd = syntax::trim_exec(string);
350    let mut proc = subprocess::Exec::shell(cmd);
351    // Run the exec expression inside the tree's directory when specified.
352    if let Some(pathbuf) = pathbuf {
353        let current_dir = path::current_dir_string();
354        proc = proc.cwd(pathbuf.clone());
355        // Set $PWD to ensure that commands that are sensitive to it see the right value.
356        proc = proc.env(constants::ENV_PWD, pathbuf.to_str().unwrap_or(&current_dir));
357    }
358
359    cmd::stdout_to_string(proc).unwrap_or_default()
360}
361
362/// Evaluate a variable in the given context
363pub fn multi_variable(
364    app_context: &model::ApplicationContext,
365    config: &model::Configuration,
366    graft_config: Option<&model::Configuration>,
367    multi_var: &mut model::MultiVariable,
368    context: &model::TreeContext,
369) -> Vec<String> {
370    let mut result = Vec::new();
371    for var in multi_var.iter() {
372        let value = tree_variable(
373            app_context,
374            config,
375            graft_config,
376            &context.tree,
377            context.garden.as_ref(),
378            var,
379        );
380        result.push(value.to_string());
381    }
382
383    result
384}
385
386/// Evaluate a variable in the given context for execution in a shell
387pub(crate) fn variables_for_shell(
388    app_context: &model::ApplicationContext,
389    config: &model::Configuration,
390    variables: &mut Vec<model::Variable>,
391    context: &model::TreeContext,
392) -> Vec<String> {
393    let mut result = Vec::new();
394
395    for var in variables {
396        if let Some(value) = var.get_value() {
397            result.push(value.to_string());
398            continue;
399        }
400        var.set_evaluation_started();
401        let raw_value = tree_value_for_shell(
402            app_context,
403            config,
404            var.get_expr(),
405            &context.tree,
406            context.garden.as_ref(),
407        );
408        let value = get_value_from_environment(var, raw_value);
409        var.set_value(value.to_string());
410
411        result.push(value);
412    }
413
414    result
415}
416
417/// Evaluate environments
418pub fn environment(
419    app_context: &model::ApplicationContext,
420    config: &model::Configuration,
421    context: &model::TreeContext,
422) -> model::Environment {
423    let mut result = model::Environment::new();
424    let mut vars = Vec::new();
425
426    // Evaluate environment variables defined at global scope.
427    for var in &config.environment {
428        vars.push((context.clone(), var));
429    }
430
431    let mut ready = false;
432    if let Some(garden_name) = context.garden.as_ref() {
433        // Evaluate garden environments.
434        if let Some(garden) = &config.gardens.get(garden_name) {
435            for ctx in query::trees_from_garden(app_context, config, None, garden) {
436                if let Some(tree) = ctx
437                    .config
438                    .and_then(|id| app_context.get_config(id).trees.get(&ctx.tree))
439                {
440                    for var in &tree.environment {
441                        vars.push((ctx.clone(), var));
442                    }
443                } else if let Some(tree) = config.trees.get(&ctx.tree) {
444                    for var in &tree.environment {
445                        vars.push((ctx.clone(), var));
446                    }
447                }
448            }
449
450            for var in &garden.environment {
451                vars.push((context.clone(), var));
452            }
453            ready = true;
454        }
455    } else if let Some(name) = &context.group {
456        // Evaluate group environments.
457        if let Some(group) = config.groups.get(name) {
458            for ctx in query::trees_from_group(app_context, config, None, None, group) {
459                if let Some(tree) = ctx
460                    .config
461                    .and_then(|id| app_context.get_config(id).trees.get(&ctx.tree))
462                {
463                    for var in &tree.environment {
464                        vars.push((ctx.clone(), var));
465                    }
466                } else if let Some(tree) = config.trees.get(&ctx.tree) {
467                    for var in &tree.environment {
468                        vars.push((ctx.clone(), var));
469                    }
470                }
471            }
472            ready = true;
473        }
474    }
475
476    // Evaluate a single tree environment when not handled above.
477    let single_tree;
478    if !ready {
479        if let Some(tree) = config.trees.get(&context.tree) {
480            single_tree = tree;
481            for var in &single_tree.environment {
482                vars.push((context.clone(), var));
483            }
484        }
485    }
486
487    let mut var_values = Vec::new();
488    for (ctx, var) in vars.iter_mut() {
489        let mut cloned_var = var.clone();
490        let graft_config = ctx.config.map(|id| app_context.get_config(id));
491        let values = multi_variable(app_context, config, graft_config, &mut cloned_var, ctx);
492        var_values.push((
493            tree_value(
494                app_context,
495                config,
496                graft_config,
497                var.get_name(),
498                ctx.tree.as_str(),
499                ctx.garden.as_ref(),
500            ),
501            values,
502        ));
503    }
504
505    // Loop over each value and evaluate the environment command.
506    // For "FOO=" values, record a simple (key, value), and update
507    // the values dict.  For "FOO" append values, check if it exists
508    // in values; if not, check the environment and bootstrap values.
509    // If still nothing, initialize it with the value and update the
510    // values map.
511    let mut values: IndexMap<String, String> = IndexMap::new();
512
513    for (var_name, env_values) in &var_values {
514        let mut name = var_name.clone();
515        let mut is_assign = false;
516        let mut is_append = false;
517
518        if syntax::is_replace_op(&name) {
519            is_assign = true;
520        }
521
522        if syntax::is_append_op(&name) {
523            is_append = true;
524        }
525
526        if is_assign || is_append {
527            syntax::trim_op_inplace(&mut name);
528        }
529
530        for value in env_values {
531            let mut current = String::new();
532            let mut exists = false;
533            if let Some(map_value) = values.get(&name) {
534                // Use the existing value
535                current.clone_from(map_value);
536                exists = true;
537            }
538            if !exists {
539                // Not found, try to get the current value from the environment
540                let mut has_env = false;
541                if let Ok(env_value) = std::env::var(&name) {
542                    let env_str: String = env_value;
543                    // Empty values are treated as not existing to prevent ":foo" or
544                    // "foo:" in the final result.
545                    if !env_str.is_empty() {
546                        current = env_str;
547                        has_env = true;
548                    }
549                }
550
551                if has_env && !is_assign {
552                    values.insert(name.clone(), current.clone());
553                } else {
554                    // Either no environment value or an assignment will
555                    // create the value if it's never been seen.
556                    values.insert(name.clone(), value.clone());
557                    result.push((name.clone(), value.clone()));
558                    continue;
559                }
560            }
561
562            // If it's an assignment, replace the value.
563            if is_assign {
564                values.insert(name.clone(), value.clone());
565                result.push((name.clone(), value.clone()));
566                continue;
567            }
568
569            // Append/prepend the value.
570            let mut path_values: Vec<String> = Vec::new();
571            if !is_append {
572                path_values.push(value.clone());
573            }
574            for path in current.split(':') {
575                path_values.push(path.into());
576            }
577            if is_append {
578                path_values.push(value.clone());
579            }
580
581            let path_value = path_values.join(":");
582            values.insert(name.clone(), path_value.clone());
583            result.push((name.clone(), path_value));
584        }
585    }
586
587    result
588}
589
590/// Return a vector of references to variables that reference the specified names.
591fn environment_value_vars<'a>(
592    app_context: &model::ApplicationContext,
593    config: &model::Configuration,
594    graft_config: Option<&model::Configuration>,
595    context: &model::TreeContext,
596    names: &[String],
597    variables: &'a Vec<model::MultiVariable>,
598) -> Vec<(model::TreeContext, String, &'a model::MultiVariable)> {
599    let mut vars = Vec::with_capacity(variables.len());
600    for var in variables {
601        if names.contains(var.get_name()) {
602            vars.push((context.clone(), var.get_name().to_string(), var));
603            continue;
604        }
605        if syntax::is_eval_candidate(var.get_name()) {
606            let name_value = tree_value(
607                app_context,
608                config,
609                graft_config.or(context.config.map(|cfg_id| app_context.get_config(cfg_id))),
610                var.get_name(),
611                &context.tree,
612                context.garden.as_ref(),
613            );
614            if names.contains(&name_value) {
615                vars.push((context.clone(), name_value, var));
616            }
617        }
618    }
619
620    vars
621}
622
623/// Evaluate a single environment variable value.
624fn environment_value(
625    app_context: &model::ApplicationContext,
626    config: &model::Configuration,
627    graft_config: Option<&model::Configuration>,
628    context: &model::TreeContext,
629    name: &str,
630) -> Option<String> {
631    let mut vars = Vec::new();
632    let name_prepend = name.to_string();
633    let name_append = format!("{name}+");
634    let name_replace = format!("{name}=");
635    let names = vec![name_prepend, name_append, name_replace];
636
637    // Evaluate environment variables defined at global scope.
638    vars.append(&mut environment_value_vars(
639        app_context,
640        config,
641        graft_config,
642        context,
643        &names,
644        &config.environment,
645    ));
646
647    if let Some(graft_cfg) = graft_config {
648        vars.append(&mut environment_value_vars(
649            app_context,
650            config,
651            graft_config,
652            context,
653            &names,
654            &graft_cfg.environment,
655        ));
656    }
657
658    // Evaluate garden environments.
659    let mut ready = false;
660    if let Some(garden_name) = context.garden.as_ref() {
661        let mut garden_ref: Option<&model::Garden> = None;
662        if let Some(garden) = graft_config.and_then(|cfg| cfg.gardens.get(garden_name)) {
663            garden_ref = Some(garden);
664        } else if let Some(garden) = config.gardens.get(garden_name) {
665            garden_ref = Some(garden);
666        }
667        if let Some(garden) = garden_ref {
668            for ctx in query::trees_from_garden(app_context, config, graft_config, garden) {
669                let garden_graft_config =
670                    ctx.config.map(|graft_id| app_context.get_config(graft_id));
671                if let Some(tree) = garden_graft_config.and_then(|cfg| cfg.trees.get(&ctx.tree)) {
672                    vars.append(&mut environment_value_vars(
673                        app_context,
674                        config,
675                        garden_graft_config,
676                        &ctx,
677                        &names,
678                        &tree.environment,
679                    ));
680                } else if let Some(tree) = config.trees.get(&ctx.tree) {
681                    vars.append(&mut environment_value_vars(
682                        app_context,
683                        config,
684                        graft_config,
685                        &ctx,
686                        &names,
687                        &tree.environment,
688                    ));
689                }
690            }
691            // Garden environment variables prepend over tree environment variables.
692            vars.append(&mut environment_value_vars(
693                app_context,
694                config,
695                graft_config,
696                context,
697                &names,
698                &garden.environment,
699            ));
700            ready = true;
701        }
702    } else if let Some(group_name) = context.group.as_ref() {
703        // Evaluate group environments.
704        if let Some(group) = config.groups.get(group_name) {
705            for ctx in query::trees_from_group(app_context, config, graft_config, None, group) {
706                let group_graft_config =
707                    ctx.config.map(|graft_id| app_context.get_config(graft_id));
708                if let Some(graft_cfg) = group_graft_config {
709                    if let Some(tree) = graft_cfg.trees.get(&ctx.tree) {
710                        vars.append(&mut environment_value_vars(
711                            app_context,
712                            config,
713                            group_graft_config,
714                            &ctx,
715                            &names,
716                            &tree.environment,
717                        ));
718                        ready = true;
719                    }
720                } else if let Some(tree) = config.trees.get(&ctx.tree) {
721                    vars.append(&mut environment_value_vars(
722                        app_context,
723                        config,
724                        group_graft_config,
725                        &ctx,
726                        &names,
727                        &tree.environment,
728                    ));
729                    ready = true;
730                }
731            }
732        }
733    }
734
735    // Evaluate a single tree environment when not handled above.
736    let single_tree;
737    if !ready {
738        if let Some(graft_cfg) = graft_config {
739            if let Some(tree) = graft_cfg.trees.get(&context.tree) {
740                single_tree = tree;
741                vars.append(&mut environment_value_vars(
742                    app_context,
743                    config,
744                    graft_config,
745                    context,
746                    &names,
747                    &single_tree.environment,
748                ));
749            }
750        } else if let Some(tree) = config.trees.get(&context.tree) {
751            single_tree = tree;
752            vars.append(&mut environment_value_vars(
753                app_context,
754                config,
755                graft_config,
756                context,
757                &names,
758                &single_tree.environment,
759            ));
760        }
761    }
762
763    let mut var_values = Vec::new();
764    for (ctx, name_value, var) in vars.iter_mut() {
765        let mut cloned_var = var.clone();
766        let values = multi_variable(
767            app_context,
768            config,
769            graft_config.or(ctx.config.map(|id| app_context.get_config(id))),
770            &mut cloned_var,
771            ctx,
772        );
773        var_values.push((name_value, values));
774    }
775
776    // Loop over each value and evaluate the environment command.
777    // For "FOO=" values, record a simple (key, value), and update
778    // the values dict.  For "FOO" append values, check if it exists
779    // in values; if not, check the environment and bootstrap values.
780    // If still nothing, initialize it with the value and update the
781    // values map.
782    let mut final_value: Option<String> = None;
783
784    for (var_name, env_values) in var_values {
785        let mut real_name = var_name.clone();
786        let mut is_assign = false;
787        let mut is_append = false;
788
789        if syntax::is_replace_op(var_name) {
790            is_assign = true;
791        }
792
793        if syntax::is_append_op(var_name) {
794            is_append = true;
795        }
796
797        if is_assign || is_append {
798            syntax::trim_op_inplace(&mut real_name);
799        }
800
801        for value in env_values {
802            let mut current = String::new();
803            let exists = if let Some(final_string_value) = final_value {
804                // Use the existing value
805                current.clone_from(&final_string_value);
806                true
807            } else {
808                false
809            };
810            if !exists {
811                // Not found, try to get the current value from the environment
812                let mut has_env = false;
813                if let Ok(env_value) = std::env::var(&real_name) {
814                    let env_str: String = env_value;
815                    // Empty values are treated as not existing to prevent ":foo" or
816                    // "foo:" in the final result.
817                    if !env_str.is_empty() {
818                        current = env_str;
819                        has_env = true;
820                    }
821                }
822
823                #[allow(unused_assignments)]
824                if has_env && !is_assign {
825                    final_value = Some(current.clone());
826                } else {
827                    // Either no environment value or an assignment will
828                    // create the value if it's never been seen.
829                    final_value = Some(value.clone());
830                    continue;
831                }
832            }
833
834            // If it's an assignment, replace the value.
835            if is_assign {
836                final_value = Some(value.clone());
837                continue;
838            }
839
840            // Append/prepend the value.
841            let mut path_values: Vec<String> = Vec::new();
842            if !is_append {
843                path_values.push(value.clone());
844            }
845            for path in current.split(':') {
846                if !path.is_empty() {
847                    path_values.push(path.to_string());
848                }
849            }
850            if is_append {
851                path_values.push(value.clone());
852            }
853
854            let path_value = path_values.join(":");
855            final_value = Some(path_value.clone());
856        }
857    }
858
859    final_value
860}
861
862/// Evaluate commands
863pub fn command(
864    app_context: &model::ApplicationContext,
865    context: &model::TreeContext,
866    name: &str,
867) -> Vec<Vec<String>> {
868    let mut vec_variables = Vec::new();
869    let mut result = Vec::new();
870    let config = match context.config {
871        Some(config_id) => app_context.get_config(config_id),
872        None => app_context.get_root_config(),
873    };
874
875    let pattern = match glob::Pattern::new(name) {
876        Ok(value) => value,
877        Err(_) => return result,
878    };
879
880    // Global commands
881    for (var_name, var) in &config.commands {
882        if pattern.matches(var_name) {
883            vec_variables.push(var.clone());
884        }
885    }
886
887    // Tree commands
888    if let Some(tree) = config.trees.get(&context.tree) {
889        for (var_name, var) in &tree.commands {
890            if pattern.matches(var_name) {
891                vec_variables.push(var.clone());
892            }
893        }
894    }
895
896    // Optional garden command scope
897    if let Some(garden_name) = &context.garden {
898        if let Some(garden) = &config.gardens.get(garden_name) {
899            for (var_name, var) in &garden.commands {
900                if pattern.matches(var_name) {
901                    vec_variables.push(var.clone());
902                }
903            }
904        }
905    }
906
907    for variables in vec_variables.iter_mut() {
908        result.push(variables_for_shell(app_context, config, variables, context));
909    }
910
911    result
912}
913
914/// Evaluate a variable with a tree context if it has not already been evaluated.
915pub(crate) fn tree_variable(
916    app_context: &model::ApplicationContext,
917    config: &model::Configuration,
918    graft_config: Option<&model::Configuration>,
919    tree_name: &str,
920    garden_name: Option<&model::GardenName>,
921    var: &model::Variable,
922) -> String {
923    if let Some(var_value) = var.get_value() {
924        return var_value.to_string();
925    }
926    if var.is_evaluating() {
927        return String::new();
928    }
929    var.set_evaluation_started();
930    let expr = var.get_expr();
931    let raw_value = tree_value(
932        app_context,
933        config,
934        graft_config,
935        expr,
936        tree_name,
937        garden_name,
938    );
939    let value = get_value_from_environment(var, raw_value);
940    var.set_value(value.to_string());
941
942    value
943}
944
945/// Evaluate a variable if it has not already been evaluated.
946pub(crate) fn variable(
947    app_context: &model::ApplicationContext,
948    config: &model::Configuration,
949    var: &model::Variable,
950) -> String {
951    if let Some(var_value) = var.get_value() {
952        return var_value.to_string();
953    }
954    if var.is_evaluating() {
955        return String::new();
956    }
957    var.set_evaluation_started();
958    let expr = var.get_expr();
959    let raw_value = value(app_context, config, expr);
960    let value = get_value_from_environment(var, raw_value);
961    var.set_value(value.to_string());
962
963    value
964}
965
966/// Fallback to environment variables when required variables are empty.
967fn get_value_from_environment(variable: &model::Variable, value: String) -> String {
968    if variable.is_required() && value.is_empty() {
969        // Fallback to environment variables for required variables that resolve to empty values.
970        if let Ok(env_value) = std::env::var(variable.get_name()) {
971            return env_value;
972        }
973    }
974    value
975}