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        let value = tree_value_for_shell(
401            app_context,
402            config,
403            var.get_expr(),
404            &context.tree,
405            context.garden.as_ref(),
406        );
407        result.push(value.clone());
408
409        var.set_value(value);
410    }
411
412    result
413}
414
415/// Evaluate environments
416pub fn environment(
417    app_context: &model::ApplicationContext,
418    config: &model::Configuration,
419    context: &model::TreeContext,
420) -> model::Environment {
421    let mut result = model::Environment::new();
422    let mut vars = Vec::new();
423
424    // Evaluate environment variables defined at global scope.
425    for var in &config.environment {
426        vars.push((context.clone(), var));
427    }
428
429    let mut ready = false;
430    if let Some(garden_name) = context.garden.as_ref() {
431        // Evaluate garden environments.
432        if let Some(garden) = &config.gardens.get(garden_name) {
433            for ctx in query::trees_from_garden(app_context, config, None, garden) {
434                if let Some(tree) = ctx
435                    .config
436                    .and_then(|id| app_context.get_config(id).trees.get(&ctx.tree))
437                {
438                    for var in &tree.environment {
439                        vars.push((ctx.clone(), var));
440                    }
441                } else if let Some(tree) = config.trees.get(&ctx.tree) {
442                    for var in &tree.environment {
443                        vars.push((ctx.clone(), var));
444                    }
445                }
446            }
447
448            for var in &garden.environment {
449                vars.push((context.clone(), var));
450            }
451            ready = true;
452        }
453    } else if let Some(name) = &context.group {
454        // Evaluate group environments.
455        if let Some(group) = config.groups.get(name) {
456            for ctx in query::trees_from_group(app_context, config, None, None, group) {
457                if let Some(tree) = ctx
458                    .config
459                    .and_then(|id| app_context.get_config(id).trees.get(&ctx.tree))
460                {
461                    for var in &tree.environment {
462                        vars.push((ctx.clone(), var));
463                    }
464                } else if let Some(tree) = config.trees.get(&ctx.tree) {
465                    for var in &tree.environment {
466                        vars.push((ctx.clone(), var));
467                    }
468                }
469            }
470            ready = true;
471        }
472    }
473
474    // Evaluate a single tree environment when not handled above.
475    let single_tree;
476    if !ready {
477        if let Some(tree) = config.trees.get(&context.tree) {
478            single_tree = tree;
479            for var in &single_tree.environment {
480                vars.push((context.clone(), var));
481            }
482        }
483    }
484
485    let mut var_values = Vec::new();
486    for (ctx, var) in vars.iter_mut() {
487        let mut cloned_var = var.clone();
488        let graft_config = ctx.config.map(|id| app_context.get_config(id));
489        let values = multi_variable(app_context, config, graft_config, &mut cloned_var, ctx);
490        var_values.push((
491            tree_value(
492                app_context,
493                config,
494                graft_config,
495                var.get_name(),
496                ctx.tree.as_str(),
497                ctx.garden.as_ref(),
498            ),
499            values,
500        ));
501    }
502
503    // Loop over each value and evaluate the environment command.
504    // For "FOO=" values, record a simple (key, value), and update
505    // the values dict.  For "FOO" append values, check if it exists
506    // in values; if not, check the environment and bootstrap values.
507    // If still nothing, initialize it with the value and update the
508    // values map.
509    let mut values: IndexMap<String, String> = IndexMap::new();
510
511    for (var_name, env_values) in &var_values {
512        let mut name = var_name.clone();
513        let mut is_assign = false;
514        let mut is_append = false;
515
516        if syntax::is_replace_op(&name) {
517            is_assign = true;
518        }
519
520        if syntax::is_append_op(&name) {
521            is_append = true;
522        }
523
524        if is_assign || is_append {
525            syntax::trim_op_inplace(&mut name);
526        }
527
528        for value in env_values {
529            let mut current = String::new();
530            let mut exists = false;
531            if let Some(map_value) = values.get(&name) {
532                // Use the existing value
533                current.clone_from(map_value);
534                exists = true;
535            }
536            if !exists {
537                // Not found, try to get the current value from the environment
538                let mut has_env = false;
539                if let Ok(env_value) = std::env::var(&name) {
540                    let env_str: String = env_value;
541                    // Empty values are treated as not existing to prevent ":foo" or
542                    // "foo:" in the final result.
543                    if !env_str.is_empty() {
544                        current = env_str;
545                        has_env = true;
546                    }
547                }
548
549                if has_env && !is_assign {
550                    values.insert(name.clone(), current.clone());
551                } else {
552                    // Either no environment value or an assignment will
553                    // create the value if it's never been seen.
554                    values.insert(name.clone(), value.clone());
555                    result.push((name.clone(), value.clone()));
556                    continue;
557                }
558            }
559
560            // If it's an assignment, replace the value.
561            if is_assign {
562                values.insert(name.clone(), value.clone());
563                result.push((name.clone(), value.clone()));
564                continue;
565            }
566
567            // Append/prepend the value.
568            let mut path_values: Vec<String> = Vec::new();
569            if !is_append {
570                path_values.push(value.clone());
571            }
572            for path in current.split(':') {
573                path_values.push(path.into());
574            }
575            if is_append {
576                path_values.push(value.clone());
577            }
578
579            let path_value = path_values.join(":");
580            values.insert(name.clone(), path_value.clone());
581            result.push((name.clone(), path_value));
582        }
583    }
584
585    result
586}
587
588/// Return a vector of references to variables that reference the specified names.
589fn environment_value_vars<'a>(
590    app_context: &model::ApplicationContext,
591    config: &model::Configuration,
592    graft_config: Option<&model::Configuration>,
593    context: &model::TreeContext,
594    names: &[String],
595    variables: &'a Vec<model::MultiVariable>,
596) -> Vec<(model::TreeContext, String, &'a model::MultiVariable)> {
597    let mut vars = Vec::with_capacity(variables.len());
598    for var in variables {
599        if names.contains(var.get_name()) {
600            vars.push((context.clone(), var.get_name().to_string(), var));
601            continue;
602        }
603        if syntax::is_eval_candidate(var.get_name()) {
604            let name_value = tree_value(
605                app_context,
606                config,
607                graft_config.or(context.config.map(|cfg_id| app_context.get_config(cfg_id))),
608                var.get_name(),
609                &context.tree,
610                context.garden.as_ref(),
611            );
612            if names.contains(&name_value) {
613                vars.push((context.clone(), name_value, var));
614            }
615        }
616    }
617
618    vars
619}
620
621/// Evaluate a single environment variable value.
622fn environment_value(
623    app_context: &model::ApplicationContext,
624    config: &model::Configuration,
625    graft_config: Option<&model::Configuration>,
626    context: &model::TreeContext,
627    name: &str,
628) -> Option<String> {
629    let mut vars = Vec::new();
630    let name_prepend = name.to_string();
631    let name_append = format!("{name}+");
632    let name_replace = format!("{name}=");
633    let names = vec![name_prepend, name_append, name_replace];
634
635    // Evaluate environment variables defined at global scope.
636    vars.append(&mut environment_value_vars(
637        app_context,
638        config,
639        graft_config,
640        context,
641        &names,
642        &config.environment,
643    ));
644
645    if let Some(graft_cfg) = graft_config {
646        vars.append(&mut environment_value_vars(
647            app_context,
648            config,
649            graft_config,
650            context,
651            &names,
652            &graft_cfg.environment,
653        ));
654    }
655
656    // Evaluate garden environments.
657    let mut ready = false;
658    if let Some(garden_name) = context.garden.as_ref() {
659        let mut garden_ref: Option<&model::Garden> = None;
660        if let Some(garden) = graft_config.and_then(|cfg| cfg.gardens.get(garden_name)) {
661            garden_ref = Some(garden);
662        } else if let Some(garden) = config.gardens.get(garden_name) {
663            garden_ref = Some(garden);
664        }
665        if let Some(garden) = garden_ref {
666            for ctx in query::trees_from_garden(app_context, config, graft_config, garden) {
667                let garden_graft_config =
668                    ctx.config.map(|graft_id| app_context.get_config(graft_id));
669                if let Some(tree) = garden_graft_config.and_then(|cfg| cfg.trees.get(&ctx.tree)) {
670                    vars.append(&mut environment_value_vars(
671                        app_context,
672                        config,
673                        garden_graft_config,
674                        &ctx,
675                        &names,
676                        &tree.environment,
677                    ));
678                } else if let Some(tree) = config.trees.get(&ctx.tree) {
679                    vars.append(&mut environment_value_vars(
680                        app_context,
681                        config,
682                        graft_config,
683                        &ctx,
684                        &names,
685                        &tree.environment,
686                    ));
687                }
688            }
689            // Garden environment variables prepend over tree environment variables.
690            vars.append(&mut environment_value_vars(
691                app_context,
692                config,
693                graft_config,
694                context,
695                &names,
696                &garden.environment,
697            ));
698            ready = true;
699        }
700    } else if let Some(group_name) = context.group.as_ref() {
701        // Evaluate group environments.
702        if let Some(group) = config.groups.get(group_name) {
703            for ctx in query::trees_from_group(app_context, config, graft_config, None, group) {
704                let group_graft_config =
705                    ctx.config.map(|graft_id| app_context.get_config(graft_id));
706                if let Some(graft_cfg) = group_graft_config {
707                    if let Some(tree) = graft_cfg.trees.get(&ctx.tree) {
708                        vars.append(&mut environment_value_vars(
709                            app_context,
710                            config,
711                            group_graft_config,
712                            &ctx,
713                            &names,
714                            &tree.environment,
715                        ));
716                        ready = true;
717                    }
718                } else if let Some(tree) = config.trees.get(&ctx.tree) {
719                    vars.append(&mut environment_value_vars(
720                        app_context,
721                        config,
722                        group_graft_config,
723                        &ctx,
724                        &names,
725                        &tree.environment,
726                    ));
727                    ready = true;
728                }
729            }
730        }
731    }
732
733    // Evaluate a single tree environment when not handled above.
734    let single_tree;
735    if !ready {
736        if let Some(graft_cfg) = graft_config {
737            if let Some(tree) = graft_cfg.trees.get(&context.tree) {
738                single_tree = tree;
739                vars.append(&mut environment_value_vars(
740                    app_context,
741                    config,
742                    graft_config,
743                    context,
744                    &names,
745                    &single_tree.environment,
746                ));
747            }
748        } else if let Some(tree) = config.trees.get(&context.tree) {
749            single_tree = tree;
750            vars.append(&mut environment_value_vars(
751                app_context,
752                config,
753                graft_config,
754                context,
755                &names,
756                &single_tree.environment,
757            ));
758        }
759    }
760
761    let mut var_values = Vec::new();
762    for (ctx, name_value, var) in vars.iter_mut() {
763        let mut cloned_var = var.clone();
764        let values = multi_variable(
765            app_context,
766            config,
767            graft_config.or(ctx.config.map(|id| app_context.get_config(id))),
768            &mut cloned_var,
769            ctx,
770        );
771        var_values.push((name_value, values));
772    }
773
774    // Loop over each value and evaluate the environment command.
775    // For "FOO=" values, record a simple (key, value), and update
776    // the values dict.  For "FOO" append values, check if it exists
777    // in values; if not, check the environment and bootstrap values.
778    // If still nothing, initialize it with the value and update the
779    // values map.
780    let mut final_value: Option<String> = None;
781
782    for (var_name, env_values) in var_values {
783        let mut real_name = var_name.clone();
784        let mut is_assign = false;
785        let mut is_append = false;
786
787        if syntax::is_replace_op(var_name) {
788            is_assign = true;
789        }
790
791        if syntax::is_append_op(var_name) {
792            is_append = true;
793        }
794
795        if is_assign || is_append {
796            syntax::trim_op_inplace(&mut real_name);
797        }
798
799        for value in env_values {
800            let mut current = String::new();
801            let exists = if let Some(final_string_value) = final_value {
802                // Use the existing value
803                current.clone_from(&final_string_value);
804                true
805            } else {
806                false
807            };
808            if !exists {
809                // Not found, try to get the current value from the environment
810                let mut has_env = false;
811                if let Ok(env_value) = std::env::var(&real_name) {
812                    let env_str: String = env_value;
813                    // Empty values are treated as not existing to prevent ":foo" or
814                    // "foo:" in the final result.
815                    if !env_str.is_empty() {
816                        current = env_str;
817                        has_env = true;
818                    }
819                }
820
821                #[allow(unused_assignments)]
822                if has_env && !is_assign {
823                    final_value = Some(current.clone());
824                } else {
825                    // Either no environment value or an assignment will
826                    // create the value if it's never been seen.
827                    final_value = Some(value.clone());
828                    continue;
829                }
830            }
831
832            // If it's an assignment, replace the value.
833            if is_assign {
834                final_value = Some(value.clone());
835                continue;
836            }
837
838            // Append/prepend the value.
839            let mut path_values: Vec<String> = Vec::new();
840            if !is_append {
841                path_values.push(value.clone());
842            }
843            for path in current.split(':') {
844                if !path.is_empty() {
845                    path_values.push(path.to_string());
846                }
847            }
848            if is_append {
849                path_values.push(value.clone());
850            }
851
852            let path_value = path_values.join(":");
853            final_value = Some(path_value.clone());
854        }
855    }
856
857    final_value
858}
859
860/// Evaluate commands
861pub fn command(
862    app_context: &model::ApplicationContext,
863    context: &model::TreeContext,
864    name: &str,
865) -> Vec<Vec<String>> {
866    let mut vec_variables = Vec::new();
867    let mut result = Vec::new();
868    let config = match context.config {
869        Some(config_id) => app_context.get_config(config_id),
870        None => app_context.get_root_config(),
871    };
872
873    let pattern = match glob::Pattern::new(name) {
874        Ok(value) => value,
875        Err(_) => return result,
876    };
877
878    // Global commands
879    for (var_name, var) in &config.commands {
880        if pattern.matches(var_name) {
881            vec_variables.push(var.clone());
882        }
883    }
884
885    // Tree commands
886    if let Some(tree) = config.trees.get(&context.tree) {
887        for (var_name, var) in &tree.commands {
888            if pattern.matches(var_name) {
889                vec_variables.push(var.clone());
890            }
891        }
892    }
893
894    // Optional garden command scope
895    if let Some(garden_name) = &context.garden {
896        if let Some(garden) = &config.gardens.get(garden_name) {
897            for (var_name, var) in &garden.commands {
898                if pattern.matches(var_name) {
899                    vec_variables.push(var.clone());
900                }
901            }
902        }
903    }
904
905    for variables in vec_variables.iter_mut() {
906        result.push(variables_for_shell(app_context, config, variables, context));
907    }
908
909    result
910}
911
912/// Evaluate a variable with a tree context if it has not already been evaluated.
913pub(crate) fn tree_variable(
914    app_context: &model::ApplicationContext,
915    config: &model::Configuration,
916    graft_config: Option<&model::Configuration>,
917    tree_name: &str,
918    garden_name: Option<&model::GardenName>,
919    var: &model::Variable,
920) -> String {
921    if let Some(var_value) = var.get_value() {
922        return var_value.to_string();
923    }
924    if var.is_evaluating() {
925        return String::new();
926    }
927    var.set_evaluating(true);
928    let expr = var.get_expr();
929    let result = tree_value(
930        app_context,
931        config,
932        graft_config,
933        expr,
934        tree_name,
935        garden_name,
936    );
937    var.set_evaluating(false);
938    var.set_value(result.to_string());
939
940    result
941}
942
943/// Evaluate a variable if it has not already been evaluated.
944pub(crate) fn variable(
945    app_context: &model::ApplicationContext,
946    config: &model::Configuration,
947    var: &model::Variable,
948) -> String {
949    if let Some(var_value) = var.get_value() {
950        return var_value.to_string();
951    }
952    if var.is_evaluating() {
953        return String::new();
954    }
955    var.set_evaluating(true);
956    let expr = var.get_expr();
957    let result = value(app_context, config, expr);
958    var.set_evaluating(false);
959    var.set_value(result.to_string());
960
961    result
962}