1use crate::{cmd, constants, model, model::IndexMap, path, query, syntax};
2
3fn 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 if syntax::is_digit(name) {
19 return Some(format!("${name}"));
20 }
21 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 if syntax::is_graft(name) {
35 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 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 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 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 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 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 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 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 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 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 let Ok(env_value) = std::env::var(name) {
181 return Some(env_value);
182 }
183
184 Some(String::new())
186}
187
188fn expand_vars(
190 app_context: &model::ApplicationContext,
191 config: &model::Configuration,
192 name: &str,
193) -> Option<String> {
194 if syntax::is_digit(name) {
196 return Some(format!("${name}"));
197 }
198 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 if let Some(var) = config.variables.get(name) {
213 return Some(variable(app_context, config, var));
214 }
215
216 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 let Ok(env_value) = std::env::var(name) {
224 return Some(env_value);
225 }
226
227 Some(String::new())
229}
230
231fn 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
248fn home_dir() -> Option<String> {
250 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
257pub 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 if is_exec {
284 let pathbuf = config.get_tree_pathbuf(tree_name);
285 exec_expression(&expanded, pathbuf)
286 } else {
287 expanded
288 }
289}
290
291fn 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 if is_exec {
313 let pathbuf = config.get_tree_pathbuf(tree_name);
314 exec_expression(&expanded, pathbuf)
315 } else {
316 expanded
317 }
318}
319
320pub 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
346fn 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 if let Some(pathbuf) = pathbuf {
353 let current_dir = path::current_dir_string();
354 proc = proc.cwd(pathbuf.clone());
355 proc = proc.env(constants::ENV_PWD, pathbuf.to_str().unwrap_or(¤t_dir));
357 }
358
359 cmd::stdout_to_string(proc).unwrap_or_default()
360}
361
362pub 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
386pub(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
417pub 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 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 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 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 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 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 current.clone_from(map_value);
536 exists = true;
537 }
538 if !exists {
539 let mut has_env = false;
541 if let Ok(env_value) = std::env::var(&name) {
542 let env_str: String = env_value;
543 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 values.insert(name.clone(), value.clone());
557 result.push((name.clone(), value.clone()));
558 continue;
559 }
560 }
561
562 if is_assign {
564 values.insert(name.clone(), value.clone());
565 result.push((name.clone(), value.clone()));
566 continue;
567 }
568
569 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
590fn 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
623fn 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 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 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 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 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 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 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 current.clone_from(&final_string_value);
806 true
807 } else {
808 false
809 };
810 if !exists {
811 let mut has_env = false;
813 if let Ok(env_value) = std::env::var(&real_name) {
814 let env_str: String = env_value;
815 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 final_value = Some(value.clone());
830 continue;
831 }
832 }
833
834 if is_assign {
836 final_value = Some(value.clone());
837 continue;
838 }
839
840 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
862pub 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 for (var_name, var) in &config.commands {
882 if pattern.matches(var_name) {
883 vec_variables.push(var.clone());
884 }
885 }
886
887 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 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
914pub(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
945pub(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
966fn get_value_from_environment(variable: &model::Variable, value: String) -> String {
968 if variable.is_required() && value.is_empty() {
969 if let Ok(env_value) = std::env::var(variable.get_name()) {
971 return env_value;
972 }
973 }
974 value
975}