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 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
415pub 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 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 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 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 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 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 current.clone_from(map_value);
534 exists = true;
535 }
536 if !exists {
537 let mut has_env = false;
539 if let Ok(env_value) = std::env::var(&name) {
540 let env_str: String = env_value;
541 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 values.insert(name.clone(), value.clone());
555 result.push((name.clone(), value.clone()));
556 continue;
557 }
558 }
559
560 if is_assign {
562 values.insert(name.clone(), value.clone());
563 result.push((name.clone(), value.clone()));
564 continue;
565 }
566
567 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
588fn 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
621fn 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 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 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 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 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 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 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 current.clone_from(&final_string_value);
804 true
805 } else {
806 false
807 };
808 if !exists {
809 let mut has_env = false;
811 if let Ok(env_value) = std::env::var(&real_name) {
812 let env_str: String = env_value;
813 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 final_value = Some(value.clone());
828 continue;
829 }
830 }
831
832 if is_assign {
834 final_value = Some(value.clone());
835 continue;
836 }
837
838 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
860pub 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 for (var_name, var) in &config.commands {
880 if pattern.matches(var_name) {
881 vec_variables.push(var.clone());
882 }
883 }
884
885 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 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
912pub(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
943pub(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}