1use std::cell::{Cell, UnsafeCell};
2use std::str::FromStr;
3
4use better_default::Default;
5use indextree::{Arena, NodeId};
6use is_terminal::IsTerminal;
7use strum::VariantNames;
8use strum_macros;
9use which::which;
10
11use crate::cli::GardenOptions;
12use crate::{cli, collections, config, constants, errors, eval, path, syntax};
13
14pub(crate) type IndexMap<K, V> = indexmap::IndexMap<K, V>;
15pub(crate) type IndexSet<V> = indexmap::IndexSet<V>;
16pub type StringSet = indexmap::IndexSet<String>;
17
18pub type TreeName = String;
20
21pub type GroupName = String;
23
24pub type GardenName = String;
26
27pub type GraftName = String;
29
30pub type ConfigId = NodeId;
32
33pub(crate) type Environment = Vec<(String, String)>;
35
36#[derive(Debug, Default)]
51pub struct Variable {
52 name: String,
53 expr: String,
54 value: UnsafeCell<Option<String>>,
55 evaluating: Cell<bool>,
56 required: bool,
57}
58
59impl_display_brief!(Variable);
60
61impl Clone for Variable {
64 fn clone(&self) -> Self {
65 Self {
66 name: self.name.clone(),
67 expr: self.expr.clone(),
68 value: UnsafeCell::new(self.get_value().cloned()),
69 evaluating: Cell::new(false),
70 required: self.required,
71 }
72 }
73}
74
75impl Variable {
76 pub(crate) fn from_expr(name: String, expr: String) -> Self {
78 Variable {
79 name,
80 expr,
81 value: UnsafeCell::new(None),
82 evaluating: Cell::new(false),
83 required: false,
84 }
85 }
86
87 pub(crate) fn from_resolved_expr(name: String, expr: String) -> Self {
89 Variable {
90 name,
91 expr: expr.clone(),
92 value: UnsafeCell::new(Some(expr)),
93 evaluating: Cell::new(false),
94 required: false,
95 }
96 }
97
98 pub(crate) fn new(name: String, expr: String, required: bool) -> Self {
100 Variable {
101 name,
102 expr,
103 required,
104 value: UnsafeCell::new(None),
105 evaluating: Cell::new(false),
106 }
107 }
108
109 pub(crate) fn is_empty(&self) -> bool {
111 self.expr.is_empty()
112 }
113
114 pub(crate) fn is_evaluating(&self) -> bool {
117 self.evaluating.get()
118 }
119
120 pub(crate) fn is_required(&self) -> bool {
122 self.required
123 }
124
125 pub(crate) fn set_evaluation_started(&self) {
127 self.set_evaluating(true);
128 }
129
130 fn set_evaluating(&self, value: bool) {
132 self.evaluating.set(value);
133 }
134
135 pub(crate) fn get_name(&self) -> &String {
137 &self.name
138 }
139
140 pub fn get_expr(&self) -> &String {
142 &self.expr
143 }
144
145 pub(crate) fn get_expr_mut(&mut self) -> &mut String {
147 &mut self.expr
148 }
149
150 pub(crate) fn set_expr(&mut self, expr: String) {
152 self.expr = expr;
153 }
154
155 pub fn get_value(&self) -> Option<&String> {
157 unsafe { (*self.value.get()).as_ref() }
158 }
159
160 pub(crate) fn set_value(&self, value: String) {
162 if !self.required || !value.is_empty() {
164 self.set_evaluating(false);
165 unsafe {
166 *self.value.get() = Some(value);
167 }
168 } else if self.required {
169 eprintln!("error: required variable '{}' is empty", self.name);
170 std::process::exit(errors::EX_DATAERR);
171 }
172 }
173
174 pub(crate) fn reset(&self) {
176 unsafe {
177 *self.value.get() = None;
178 }
179 }
180}
181
182pub(crate) type MultiVariableMap = IndexMap<String, Vec<Variable>>;
184
185fn reset_map_variables(vec_variables: &MultiVariableMap) {
187 for variables in vec_variables.values() {
188 for variable in variables {
189 variable.reset();
190 }
191 }
192}
193
194pub(crate) type VariableMap = IndexMap<String, Variable>;
196
197#[derive(Clone, Debug)]
199pub struct MultiVariable {
200 name: String,
201 variables: Vec<Variable>,
202}
203
204impl MultiVariable {
205 pub(crate) fn new(name: String, variables: Vec<Variable>) -> Self {
206 MultiVariable { name, variables }
207 }
208
209 pub fn get(&self, idx: usize) -> &Variable {
210 &self.variables[idx]
211 }
212
213 pub fn get_name(&self) -> &String {
214 &self.name
215 }
216
217 pub fn len(&self) -> usize {
218 self.variables.len()
219 }
220
221 pub fn is_empty(&self) -> bool {
222 self.variables.is_empty()
223 }
224
225 pub fn reset(&self) {
226 for var in &self.variables {
227 var.reset();
228 }
229 }
230
231 pub fn iter(&self) -> std::slice::Iter<'_, Variable> {
232 self.variables.iter()
233 }
234}
235
236#[derive(Clone, Debug, Default)]
238pub struct Tree {
239 pub commands: MultiVariableMap,
240 pub environment: Vec<MultiVariable>,
241 pub gitconfig: MultiVariableMap,
242 pub remotes: VariableMap,
243 pub(crate) symlink: Variable,
244 pub templates: StringSet,
245 pub variables: VariableMap,
246 pub branch: Variable,
247 pub(crate) branches: VariableMap,
248 pub worktree: Variable,
249 #[default(string!("origin"))]
250 pub(crate) default_remote: String,
251 pub(crate) clone_depth: i64,
252 pub(crate) is_single_branch: bool,
253 pub is_symlink: bool,
254 pub is_bare_repository: bool,
255 pub is_worktree: bool,
256 pub(crate) description: String,
257 pub(crate) links: Vec<Variable>,
258
259 name: String,
260 path: Variable,
261}
262
263impl Tree {
264 pub fn get_name(&self) -> &String {
265 &self.name
266 }
267
268 pub fn set_name(&mut self, name: String) {
270 self.name = name;
271 }
272
273 pub(crate) fn get_name_mut(&mut self) -> &mut String {
274 &mut self.name
275 }
276
277 pub fn get_path(&self) -> &Variable {
278 &self.path
279 }
280
281 pub(crate) fn get_path_mut(&mut self) -> &mut Variable {
282 &mut self.path
283 }
284
285 pub(crate) fn path_is_valid(&self) -> bool {
286 self.path.get_value().is_some()
287 }
288
289 pub(crate) fn canonical_pathbuf(&self) -> Option<std::path::PathBuf> {
291 if let Some(pathbuf) = self.pathbuf() {
292 if let Ok(canon_path) = path::canonicalize(pathbuf) {
293 return Some(canon_path);
294 }
295 }
296
297 None
298 }
299
300 pub(crate) fn pathbuf(&self) -> Option<std::path::PathBuf> {
302 if !self.path_is_valid() {
303 return None;
304 }
305 self.path.get_value().map(std::path::PathBuf::from)
306 }
307
308 pub fn path_as_ref(&self) -> Result<&String, errors::GardenError> {
309 match self.path.get_value() {
310 Some(value) => Ok(value),
311 None => Err(errors::GardenError::ConfigurationError(format!(
312 "unset tree path for {}",
313 self.name
314 ))),
315 }
316 }
317
318 pub(crate) fn symlink_as_ref(&self) -> Result<&String, errors::GardenError> {
319 match self.symlink.get_value() {
320 Some(value) => Ok(value),
321 None => Err(errors::GardenError::ConfigurationError(format!(
322 "unset tree path for {}",
323 self.name
324 ))),
325 }
326 }
327
328 pub(crate) fn add_builtin_variables(&mut self) {
330 self.variables.insert(
331 constants::TREE_NAME.to_string(),
332 Variable::from_expr(constants::TREE_NAME.to_string(), self.get_name().clone()),
333 );
334
335 self.variables.insert(
337 constants::TREE_PATH.to_string(),
338 Variable::from_expr(
339 constants::TREE_PATH.to_string(),
340 self.get_path().get_expr().clone(),
341 ),
342 );
343 }
344
345 pub(crate) fn reset_variables(&self) {
346 for var in self.variables.values() {
350 var.reset();
351 }
352 for env in &self.environment {
353 env.reset();
354 }
355
356 reset_map_variables(&self.gitconfig);
357 reset_map_variables(&self.commands);
358 }
359
360 pub(crate) fn clone_from_tree(&mut self, tree: &Tree) {
362 collections::append_map(&mut self.commands, &tree.commands);
363 collections::append_map(&mut self.gitconfig, &tree.gitconfig);
364 collections::append_map(&mut self.variables, &tree.variables);
365 collections::append_map(&mut self.remotes, &tree.remotes);
366 collections::append_set(&mut self.templates, &tree.templates);
367
368 self.environment.append(&mut tree.environment.clone());
370 if tree.clone_depth > 0 {
372 self.clone_depth = tree.clone_depth;
373 }
374 if tree.is_bare_repository {
375 self.is_bare_repository = tree.is_bare_repository;
376 }
377 if tree.is_single_branch {
378 self.is_single_branch = tree.is_single_branch;
379 }
380 if tree.is_worktree {
381 self.is_worktree = tree.is_worktree;
382 }
383 if tree.is_symlink {
384 self.is_symlink = tree.is_symlink;
385 }
386 if !tree.branch.is_empty() {
387 self.branch = tree.branch.clone();
388 }
389 if !tree.symlink.is_empty() {
390 self.symlink = tree.symlink.clone();
391 }
392 if !tree.worktree.is_empty() {
393 self.worktree = tree.worktree.clone();
394 }
395 self.default_remote = tree.default_remote.to_string();
396 self.description = tree.description.to_string();
397 self.links.clone_from(&tree.links);
398
399 self.update_flags();
400 }
401
402 pub(crate) fn update_flags(&mut self) {
404 if !self.symlink.is_empty() {
405 self.is_symlink = true;
406 }
407 if !self.worktree.is_empty() {
408 self.is_worktree = true;
409 }
410 if self.is_worktree {
411 self.is_bare_repository = false;
412 }
413 }
414
415 pub(crate) fn eval_branch(&self, eval_context: &EvalContext) -> String {
417 self.get_branch(
418 eval_context.app_context,
419 eval_context.config,
420 eval_context.graft_config,
421 eval_context.tree_context,
422 )
423 }
424
425 pub(crate) fn get_branch(
427 &self,
428 app_context: &ApplicationContext,
429 config: &Configuration,
430 graft_config: Option<&Configuration>,
431 tree_context: &TreeContext,
432 ) -> String {
433 eval::tree_variable(
434 app_context,
435 config,
436 graft_config,
437 &tree_context.tree,
438 tree_context.garden.as_ref(),
439 &self.branch,
440 )
441 }
442
443 pub(crate) fn eval_url(&self, eval_context: &EvalContext) -> Option<String> {
445 self.get_url(
446 eval_context.app_context,
447 eval_context.config,
448 eval_context.graft_config,
449 eval_context.tree_context,
450 )
451 }
452
453 pub(crate) fn get_url(
455 &self,
456 app_context: &ApplicationContext,
457 config: &Configuration,
458 graft_config: Option<&Configuration>,
459 context: &TreeContext,
460 ) -> Option<String> {
461 self.remotes.get(&self.default_remote).map(|remote| {
462 eval::tree_variable(
463 app_context,
464 config,
465 graft_config,
466 &context.tree,
467 context.garden.as_ref(),
468 remote,
469 )
470 })
471 }
472
473 pub(crate) fn eval_worktree(&self, eval_context: &EvalContext) -> String {
475 self.get_worktree(
476 eval_context.app_context,
477 eval_context.config,
478 eval_context.graft_config,
479 eval_context.tree_context,
480 )
481 }
482
483 pub(crate) fn get_worktree(
485 &self,
486 app_context: &ApplicationContext,
487 config: &Configuration,
488 graft_config: Option<&Configuration>,
489 tree_context: &TreeContext,
490 ) -> String {
491 eval::tree_variable(
492 app_context,
493 config,
494 graft_config,
495 &tree_context.tree,
496 tree_context.garden.as_ref(),
497 &self.worktree,
498 )
499 }
500
501 pub(crate) fn get_remote_for_branch(
503 &self,
504 eval_context: &EvalContext,
505 branch: &str,
506 ) -> Option<String> {
507 let remote_branch = self.get_upstream_branch(eval_context, branch)?;
508 let remote = remote_branch.split_once('/')?.0;
509 if self.remotes.contains_key(remote) {
510 Some(remote.to_string())
511 } else {
512 None
513 }
514 }
515
516 pub(crate) fn get_upstream_branch(
518 &self,
519 eval_context: &EvalContext,
520 branch: &str,
521 ) -> Option<String> {
522 if branch.is_empty() {
523 return None;
524 }
525 self.branches
526 .get(branch)
527 .map(|remote_branch_var| eval_context.tree_variable(remote_branch_var))
528 }
529}
530
531#[derive(Clone, Debug, Default)]
532pub struct Group {
533 name: String,
534 pub members: StringSet,
535}
536
537impl Group {
538 pub fn get_name(&self) -> &String {
539 &self.name
540 }
541
542 pub(crate) fn get_name_owned(&self) -> String {
544 self.get_name().to_owned()
545 }
546
547 pub(crate) fn get_name_mut(&mut self) -> &mut String {
548 &mut self.name
549 }
550}
551
552pub type GroupMap = IndexMap<GroupName, Group>;
554
555#[derive(Clone, Debug, Default)]
559pub struct Template {
560 pub tree: Tree,
561 pub extend: StringSet,
562 name: String,
563}
564
565impl Template {
566 pub fn get_name(&self) -> &String {
567 &self.name
568 }
569
570 pub(crate) fn get_name_mut(&mut self) -> &mut String {
571 &mut self.name
572 }
573
574 pub(crate) fn apply(&self, tree: &mut Tree) {
576 tree.clone_from_tree(&self.tree);
577 }
578}
579
580#[derive(Clone, Debug, Default)]
582pub struct Garden {
583 pub commands: MultiVariableMap,
584 pub environment: Vec<MultiVariable>,
585 pub gitconfig: MultiVariableMap,
586 pub groups: StringSet,
587 pub trees: StringSet,
588 pub variables: VariableMap,
589 name: GardenName,
590}
591
592impl Garden {
593 pub fn get_name(&self) -> &GardenName {
594 &self.name
595 }
596
597 pub(crate) fn get_name_mut(&mut self) -> &mut String {
598 &mut self.name
599 }
600}
601
602pub type GardenMap = IndexMap<GardenName, Garden>;
604
605fn get_default_shell() -> String {
607 if which(constants::SHELL_ZSH).is_ok() {
608 constants::SHELL_ZSH
609 } else if which(constants::SHELL_BASH).is_ok() {
610 constants::SHELL_BASH
611 } else if which(constants::SHELL_DASH).is_ok() {
612 constants::SHELL_DASH
613 } else {
614 constants::SHELL_SH
615 }
616 .to_string()
617}
618
619#[derive(Clone, Debug, Default)]
621pub struct Configuration {
622 pub commands: MultiVariableMap,
623 pub debug: IndexMap<String, u8>,
624 pub environment: Vec<MultiVariable>,
625 pub gardens: GardenMap,
626 pub grafts: IndexMap<GraftName, Graft>,
627 pub groups: GroupMap,
628 pub path: Option<std::path::PathBuf>,
629 pub dirname: Option<std::path::PathBuf>,
630 pub root: Variable,
631 pub root_is_dynamic: bool,
632 pub root_path: std::path::PathBuf,
633 pub shell: String,
634 pub interactive_shell: String,
635 pub templates: IndexMap<String, Template>,
636 pub tree_search_path: Vec<std::path::PathBuf>,
637 pub trees: IndexMap<TreeName, Tree>,
638 pub variables: VariableMap,
639 pub override_variables: VariableMap,
642 pub config_verbose: u8,
643 pub quiet: bool,
644 pub verbose: u8,
645 pub(crate) shell_exit_on_error: bool,
646 pub(crate) shell_word_split: bool,
647 pub(crate) tree_branches: bool,
648 pub(crate) parent_id: Option<ConfigId>,
649 id: Option<ConfigId>,
650}
651
652impl_display!(Configuration);
653
654impl Configuration {
655 pub fn new() -> Self {
657 Configuration {
658 id: None,
659 parent_id: None,
660 shell: get_default_shell(),
661 shell_exit_on_error: true,
662 shell_word_split: true,
663 tree_branches: true,
664 ..std::default::Default::default()
665 }
666 }
667
668 pub(crate) fn initialize(&mut self, app_context: &ApplicationContext) {
669 let expr = self.root.get_expr().to_string();
671 let mut value = eval::value(app_context, self, &expr);
672 if expr.is_empty() {
673 if self.root_is_dynamic {
674 let current_dir = path::current_dir_string();
677 self.root.set_expr(current_dir.clone());
678 self.root.set_value(current_dir);
679 self.root_path = path::current_dir();
680 } else {
681 self.root
683 .set_expr(string!(constants::GARDEN_CONFIG_DIR_EXPR));
684 if let Some(ref dirname) = self.dirname {
685 self.root.set_value(dirname.to_string_lossy().to_string());
686 self.root_path = dirname.to_path_buf();
687 }
688 }
689 } else {
690 self.root_path = std::path::PathBuf::from(&value);
692 if let Ok(root_path_canon) = path::canonicalize(&self.root_path) {
693 if root_path_canon != self.root_path {
694 value = root_path_canon
695 .to_str()
696 .unwrap_or(value.as_str())
697 .to_string();
698 self.root_path = root_path_canon;
699 }
700 }
701 self.root.set_value(value);
702 }
703 self.update_tree_paths(app_context); self.synthesize_default_tree(); self.reset();
707 }
708
709 pub(crate) fn graft_id(&self) -> Option<NodeId> {
711 self.parent_id.and(self.get_id())
712 }
713
714 pub(crate) fn update(
715 &mut self,
716 app_context: &ApplicationContext,
717 config: Option<&std::path::Path>,
718 root: Option<&std::path::Path>,
719 config_verbose: u8,
720 parent: Option<ConfigId>,
721 ) -> Result<(), errors::GardenError> {
722 if let Some(parent_id) = parent {
723 self.set_parent(parent_id);
724 }
725 self.config_verbose = config_verbose;
726 self.quiet = app_context.options.quiet;
727 self.verbose = app_context.options.verbose;
728
729 let root_pathbuf_option = root.map(path::abspath);
731 if let Some(root_path) = root_pathbuf_option {
732 self.root.set_expr(root_path.to_string_lossy().to_string());
733 }
734
735 let mut basename = string!(constants::GARDEN_CONFIG);
736
737 let mut found = false;
739 if let Some(config_path) = config {
740 if config_path.is_file() || config_path.is_absolute() {
741 self.set_path(&config_path);
745 found = true;
746 } else {
747 basename = config_path.to_string_lossy().into();
750 }
751 }
752
753 if !found {
754 for entry in config::search_path() {
755 let mut candidate = entry.to_path_buf();
756 candidate.push(basename.clone());
757 if candidate.exists() {
758 self.set_path(&candidate);
759 found = true;
760 break;
761 }
762 }
763 }
764 if config_verbose > 0 {
765 debug!(
766 "config: path: {:?}, root: {:?}, found: {}",
767 self.path, self.root, found
768 );
769 }
770
771 if found {
772 let config_path = self.get_path()?;
774 if let Ok(config_string) = std::fs::read_to_string(config_path) {
775 config::parse(app_context, &config_string, config_verbose, self)?;
776 }
777 }
778
779 Ok(())
780 }
781
782 pub(crate) fn update_options(
784 &mut self,
785 options: &cli::MainOptions,
786 ) -> Result<(), errors::GardenError> {
787 let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
788 if self.path.is_none() {
789 error!("unable to find a configuration file -- use --config <path>");
790 }
791 if config_verbose > 1 {
792 eprintln!("config: {:?}", self.get_path()?);
793 }
794 if config_verbose > 2 {
795 debug!("{}", self);
796 }
797 for key in &options.debug {
798 let current = *self.debug.get(key).unwrap_or(&0);
799 self.debug.insert(key.into(), current + 1);
800 }
801 self.apply_defines(&options.define);
802
803 Ok(())
804 }
805
806 pub(crate) fn apply_defines(&mut self, defines: &Vec<String>) {
808 for k_eq_v in defines {
809 let name: String;
810 let expr: String;
811 let values: Vec<&str> = k_eq_v.splitn(2, '=').collect();
812 if values.len() == 1 {
813 name = values[0].to_string();
814 expr = string!("");
815 } else if values.len() == 2 {
816 name = values[0].to_string();
817 expr = values[1].to_string();
818 } else {
819 error!("unable to split '{}'", k_eq_v);
820 }
821 match name.as_str() {
823 constants::GARDEN_INTERACTIVE_SHELL => {
824 self.interactive_shell = expr;
825 }
826 constants::GARDEN_SHELL => {
827 self.shell = expr;
828 }
829 constants::GARDEN_SHELL_ERREXIT => {
830 set_bool(name.as_str(), &expr, &mut self.shell_exit_on_error);
831 }
832 constants::GARDEN_SHELL_WORDSPLIT => {
833 set_bool(name.as_str(), &expr, &mut self.shell_word_split);
834 }
835 constants::GARDEN_TREE_BRANCHES => {
836 set_bool(name.as_str(), &expr, &mut self.tree_branches);
837 }
838 _ => {
839 self.override_variables
840 .insert(name.to_string(), Variable::from_expr(name, expr));
841 }
842 }
843 }
844 }
845
846 pub(crate) fn update_quiet_and_verbose_variables(&mut self, quiet: bool, verbose: u8) {
848 let quiet_string = if self.quiet || quiet { "--quiet" } else { "" }.to_string();
850 self.variables.insert(
851 constants::GARDEN_CMD_QUIET.to_string(),
852 Variable::from_resolved_expr(constants::GARDEN_CMD_QUIET.to_string(), quiet_string),
853 );
854 let verbose = self.verbose + verbose;
855 let verbose_string = if verbose > 0 {
856 format!("-{}", "v".repeat(verbose.into()))
857 } else {
858 string!("")
859 };
860 self.variables.insert(
861 constants::GARDEN_CMD_VERBOSE.to_string(),
862 Variable::from_resolved_expr(constants::GARDEN_CMD_VERBOSE.to_string(), verbose_string),
863 );
864 }
865
866 pub(crate) fn reset(&mut self) {
867 self.reset_variables();
869 self.reset_builtin_variables()
871 }
872
873 fn reset_builtin_variables(&mut self) {
874 if let Some(var) = self.variables.get_mut(constants::GARDEN_ROOT) {
876 if let Some(value) = self.root.get_value() {
877 var.set_expr(value.into());
878 var.set_value(value.into());
879 }
880 }
881
882 for tree in self.trees.values_mut() {
883 let tree_name = String::from(tree.get_name());
885 if let Some(var) = tree.variables.get_mut(constants::TREE_NAME) {
886 var.set_expr(tree_name.to_string());
887 var.set_value(tree_name);
888 }
889 let tree_path = match tree.path_as_ref() {
891 Ok(path) => String::from(path),
892 Err(_) => continue,
893 };
894 if let Some(var) = tree.variables.get_mut(constants::TREE_PATH) {
896 var.set_expr(tree_path.to_string());
897 var.set_value(tree_path);
898 }
899 }
900 }
901
902 fn update_tree_paths(&mut self, app_context: &ApplicationContext) {
906 let mut path_values = Vec::new();
908 let mut symlink_values = Vec::new();
909
910 for (name, tree) in &self.trees {
911 path_values.push((name.clone(), tree.path.get_expr().clone()));
912 if tree.is_symlink {
913 symlink_values.push((name.clone(), tree.symlink.get_expr().clone()));
914 }
915 }
916
917 for (name, value) in &path_values {
919 let result = self.eval_tree_path(app_context, value);
920 if let Some(tree) = self.trees.get_mut(name) {
921 tree.path.set_value(result);
922 }
923 }
924
925 for (name, value) in &symlink_values {
927 let result = self.eval_tree_path(app_context, value);
928 if let Some(tree) = self.trees.get_mut(name) {
929 tree.symlink.set_value(result);
930 }
931 }
932 }
933
934 fn synthesize_default_tree(&mut self) {
936 if !self.commands.is_empty() && self.trees.is_empty() {
937 let dirname_string = self.dirname_string();
938 let mut tree = Tree::default();
939 tree.path.set_expr(dirname_string.clone());
940 tree.path.set_value(dirname_string);
941 tree.description = string!("The default tree for garden commands.");
942 tree.add_builtin_variables();
943 tree.set_name(string!(constants::DOT));
944 self.trees.insert(string!(constants::DOT), tree);
945 }
946 }
947
948 pub(crate) fn tree_path(&self, path: &str) -> String {
950 if std::path::PathBuf::from(path).is_absolute() {
951 path.into()
953 } else {
954 let mut path_buf = self.root_path.to_path_buf();
956 path_buf.push(path);
957
958 path_buf.to_string_lossy().into()
959 }
960 }
961
962 pub(crate) fn relative_pathbuf(&self, path: &str) -> std::path::PathBuf {
964 let pathbuf = std::path::PathBuf::from(path);
965 if pathbuf.is_absolute() {
966 if let Ok(pathbuf_canon) = path::canonicalize(&pathbuf) {
968 pathbuf_canon
969 } else {
970 pathbuf
971 }
972 } else {
973 let mut path_buf = self.root_path.to_path_buf();
975 path_buf.push(path);
976
977 path_buf
978 }
979 }
980
981 fn eval_tree_path(&mut self, app_context: &ApplicationContext, path: &str) -> String {
983 let value = eval::value(app_context, self, path);
984 self.tree_path(&value)
985 }
986
987 pub(crate) fn config_pathbuf(&self, path: &str) -> Option<std::path::PathBuf> {
989 let path_buf = std::path::PathBuf::from(path);
990 if path_buf.is_absolute() {
991 Some(path_buf)
993 } else if let Some(dirname) = self.dirname.as_ref() {
994 let mut abs_path_buf = dirname.to_path_buf();
996 abs_path_buf.push(path_buf);
997
998 Some(abs_path_buf)
999 } else {
1000 None
1001 }
1002 }
1003
1004 fn config_pathbuf_from_include(
1007 &self,
1008 include_path: &std::path::Path,
1009 path: &str,
1010 ) -> Option<std::path::PathBuf> {
1011 let mut path_buf = std::path::PathBuf::from(path);
1012 if path_buf.is_absolute() {
1013 return Some(path_buf);
1015 }
1016
1017 if let Some(dirname) = include_path.parent() {
1019 path_buf = dirname.to_path_buf();
1021 path_buf.push(path);
1022
1023 if path_buf.exists() {
1024 return Some(path_buf);
1025 }
1026 }
1027
1028 self.config_pathbuf(path)
1030 }
1031
1032 fn config_path(&self, path: &str) -> String {
1034 if let Some(path_buf) = self.config_pathbuf(path) {
1035 path_buf.to_string_lossy().to_string()
1036 } else {
1037 self.tree_path(path)
1038 }
1039 }
1040
1041 fn dirname_string(&self) -> String {
1045 match self.dirname {
1046 Some(ref dirname) => dirname.to_string_lossy().to_string(),
1047 None => path::current_dir_string(),
1048 }
1049 }
1050
1051 pub(crate) fn fallback_execdir_string(&self) -> String {
1053 if self.root_path.exists() {
1054 return self.root_path.to_string_lossy().to_string();
1055 }
1056 if let Some(dirname) = self.dirname.as_ref() {
1057 if dirname.exists() {
1058 return dirname.to_string_lossy().to_string();
1059 }
1060 }
1061 path::current_dir_string()
1062 }
1063
1064 pub(crate) fn eval_config_path(&self, app_context: &ApplicationContext, path: &str) -> String {
1066 let value = eval::value(app_context, self, path);
1067 self.config_path(&value)
1068 }
1069
1070 pub(crate) fn eval_config_pathbuf_from_include(
1072 &self,
1073 app_context: &ApplicationContext,
1074 include_path: Option<&std::path::Path>,
1075 path: &str,
1076 ) -> Option<std::path::PathBuf> {
1077 let value = eval::value(app_context, self, path);
1078
1079 if let Some(include_path) = include_path {
1080 self.config_pathbuf_from_include(include_path, &value)
1081 } else {
1082 self.config_pathbuf(&value)
1083 }
1084 .or_else(|| Some(std::path::PathBuf::from(&value)))
1085 }
1086
1087 pub(crate) fn reset_variables(&mut self) {
1089 for var in self.variables.values() {
1090 var.reset();
1091 }
1092 for env in &self.environment {
1093 env.reset();
1094 }
1095
1096 reset_map_variables(&self.commands);
1097
1098 for tree in self.trees.values() {
1099 tree.reset_variables();
1100 }
1101 }
1102
1103 pub(crate) fn set_id(&mut self, id: ConfigId) {
1105 self.id = Some(id);
1106 }
1107
1108 pub(crate) fn get_id(&self) -> Option<ConfigId> {
1109 self.id
1110 }
1111
1112 pub(crate) fn set_parent(&mut self, id: ConfigId) {
1114 self.parent_id = Some(id);
1115 }
1116
1117 pub(crate) fn set_path(&mut self, path: &dyn AsRef<std::path::Path>) {
1119 let config_path = path.as_ref().to_path_buf();
1120 let mut dirname = config_path.clone();
1121 dirname.pop();
1122
1123 self.dirname = Some(dirname);
1124 self.path = Some(config_path);
1125 }
1126
1127 pub(crate) fn get_path(&self) -> Result<&std::path::PathBuf, errors::GardenError> {
1129 self.path.as_ref().ok_or_else(|| {
1130 errors::GardenError::AssertionError("Configuration path is unset".into())
1131 })
1132 }
1133
1134 pub(crate) fn get_path_for_display(&self) -> String {
1137 let default_pathbuf = std::path::PathBuf::from(constants::DOT);
1138 self.path
1139 .as_ref()
1140 .unwrap_or(&default_pathbuf)
1141 .display()
1142 .to_string()
1143 }
1144
1145 pub(crate) fn contains_graft(&self, name: &str) -> bool {
1147 let graft_name = syntax::trim(name);
1148 self.grafts.contains_key(graft_name)
1149 }
1150
1151 pub(crate) fn get_graft(&self, name: &str) -> Result<&Graft, errors::GardenError> {
1153 let graft_name = syntax::trim(name);
1154 self.grafts.get(graft_name).ok_or_else(|| {
1155 errors::GardenError::ConfigurationError(format!("{name}: no such graft"))
1156 })
1157 }
1158
1159 pub(crate) fn get_graft_id<'a>(
1162 &self,
1163 value: &'a str,
1164 ) -> Result<(ConfigId, &'a str), errors::GardenError> {
1165 let (graft_name, remainder) = match syntax::split_graft(value) {
1166 Some((graft_name, remainder)) => (graft_name, remainder),
1167 None => {
1168 return Err(errors::GardenError::ConfigurationError(format!(
1169 "{value}: invalid graft expression"
1170 )))
1171 }
1172 };
1173 let graft = self.get_graft(graft_name)?;
1174 let graft_id = graft
1175 .get_id()
1176 .ok_or(errors::GardenError::ConfigurationError(format!(
1177 "{graft_name}: no such graft"
1178 )))?;
1179
1180 Ok((graft_id, remainder))
1181 }
1182
1183 pub fn get_tree(&self, name: &str) -> Option<&Tree> {
1185 self.trees.get(name)
1186 }
1187
1188 pub fn get_garden(&self, name: &str) -> Option<&Garden> {
1190 self.gardens.get(name)
1191 }
1192
1193 pub(crate) fn get_tree_pathbuf(&self, tree_name: &str) -> Option<std::path::PathBuf> {
1195 self.get_tree(tree_name)
1196 .map(|tree| tree.canonical_pathbuf())
1197 .unwrap_or(None)
1198 }
1199}
1200
1201fn set_bool(name: &str, expr: &str, output: &mut bool) {
1203 if let Some(value) = syntax::string_to_bool(expr) {
1204 *output = value;
1205 } else {
1206 error!(
1207 "'{}' is not a valid value for \"{}\". Must be true, false, 0 or 1",
1208 name, expr
1209 );
1210 }
1211}
1212
1213#[derive(Clone, Debug, Default)]
1214pub struct Graft {
1215 id: Option<ConfigId>,
1216 name: String,
1217 pub root: String,
1218 pub config: String,
1219}
1220
1221impl Graft {
1222 pub fn new(name: String, root: String, config: String) -> Self {
1223 Graft {
1224 id: None,
1225 name,
1226 root,
1227 config,
1228 }
1229 }
1230
1231 pub fn get_name(&self) -> &String {
1232 &self.name
1233 }
1234
1235 pub fn get_id(&self) -> Option<ConfigId> {
1236 self.id
1237 }
1238
1239 pub(crate) fn set_id(&mut self, id: ConfigId) {
1240 self.id = Some(id);
1241 }
1242}
1243
1244#[derive(Clone, Debug)]
1246pub(crate) struct EvalContext<'a> {
1247 pub(crate) app_context: &'a ApplicationContext,
1248 pub(crate) config: &'a Configuration,
1249 pub(crate) graft_config: Option<&'a Configuration>,
1250 pub(crate) tree_context: &'a TreeContext,
1251}
1252
1253impl EvalContext<'_> {
1254 pub(crate) fn new<'a>(
1256 app_context: &'a ApplicationContext,
1257 config: &'a Configuration,
1258 graft_config: Option<&'a Configuration>,
1259 tree_context: &'a TreeContext,
1260 ) -> EvalContext<'a> {
1261 EvalContext {
1262 app_context,
1263 config,
1264 graft_config,
1265 tree_context,
1266 }
1267 }
1268
1269 pub(crate) fn from_app_context<'a>(
1271 app_context: &'a ApplicationContext,
1272 tree_context: &'a TreeContext,
1273 ) -> EvalContext<'a> {
1274 let config = app_context.get_root_config();
1275 let graft_config = tree_context
1276 .config
1277 .map(|config_id| app_context.get_config(config_id));
1278 EvalContext::new(app_context, config, graft_config, tree_context)
1279 }
1280
1281 pub(crate) fn tree_value(&self, value: &str) -> String {
1283 eval::tree_value(
1284 self.app_context,
1285 self.config,
1286 self.graft_config,
1287 value,
1288 &self.tree_context.tree,
1289 self.tree_context.garden.as_ref(),
1290 )
1291 }
1292
1293 pub(crate) fn tree_variable(&self, var: &Variable) -> String {
1295 eval::tree_variable(
1296 self.app_context,
1297 self.config,
1298 self.graft_config,
1299 &self.tree_context.tree,
1300 self.tree_context.garden.as_ref(),
1301 var,
1302 )
1303 }
1304}
1305
1306#[derive(Clone, Debug)]
1307pub struct TreeContext {
1308 pub tree: TreeName,
1309 pub config: Option<ConfigId>,
1310 pub garden: Option<GardenName>,
1311 pub group: Option<String>,
1312}
1313
1314impl TreeContext {
1315 pub fn new(
1317 tree: &str,
1318 config: Option<ConfigId>,
1319 garden: Option<GardenName>,
1320 group: Option<String>,
1321 ) -> Self {
1322 TreeContext {
1323 tree: TreeName::from(tree),
1324 config,
1325 garden,
1326 group,
1327 }
1328 }
1329}
1330
1331#[derive(Debug, Default)]
1332pub struct TreeQuery {
1333 pub query: String,
1334 pub pattern: glob::Pattern,
1335 pub is_default: bool,
1336 pub is_garden: bool,
1337 pub is_group: bool,
1338 pub is_tree: bool,
1339 pub include_gardens: bool,
1340 pub include_groups: bool,
1341 pub include_trees: bool,
1342}
1343
1344impl TreeQuery {
1345 pub fn new(query: &str) -> Self {
1346 let mut is_default = false;
1347 let mut is_tree = false;
1348 let mut is_garden = false;
1349 let mut is_group = false;
1350 let mut include_gardens = true;
1351 let mut include_groups = true;
1352 let mut include_trees = true;
1353
1354 if syntax::is_garden(query) {
1355 is_garden = true;
1356 include_groups = false;
1357 include_trees = false;
1358 } else if syntax::is_group(query) {
1359 is_group = true;
1360 include_gardens = false;
1361 include_trees = false;
1362 } else if syntax::is_tree(query) {
1363 is_tree = true;
1364 include_gardens = false;
1365 include_groups = false;
1366 } else {
1367 is_default = true;
1368 }
1369 let glob_pattern = syntax::trim(query);
1370 let pattern = glob::Pattern::new(glob_pattern).unwrap_or_default();
1371
1372 TreeQuery {
1373 query: query.into(),
1374 is_default,
1375 is_garden,
1376 is_group,
1377 is_tree,
1378 include_gardens,
1379 include_groups,
1380 include_trees,
1381 pattern,
1382 }
1383 }
1384}
1385
1386#[derive(
1387 Clone,
1388 Debug,
1389 Default,
1390 PartialEq,
1391 Eq,
1392 strum_macros::EnumString,
1393 strum_macros::Display,
1394 strum_macros::VariantNames,
1395)]
1396#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
1397pub enum ColorMode {
1398 #[default]
1400 Auto,
1401 #[strum(
1403 serialize = "1",
1404 serialize = "on",
1405 serialize = "true",
1406 serialize = "always",
1407 serialize = "y",
1408 serialize = "yes"
1409 )]
1410 On,
1411 #[strum(
1413 serialize = "0",
1414 serialize = "off",
1415 serialize = "false",
1416 serialize = "never",
1417 serialize = "n",
1418 serialize = "no"
1419 )]
1420 Off,
1421}
1422
1423impl ColorMode {
1424 pub fn parse_from_str(string: &str) -> Result<ColorMode, String> {
1426 ColorMode::from_str(string).map_err(|_| format!("choices are {:?}", Self::VARIANTS))
1427 }
1428
1429 pub fn is_enabled(&self) -> bool {
1430 match self {
1431 ColorMode::Auto => std::io::stdout().is_terminal(),
1432 ColorMode::Off => false,
1433 ColorMode::On => true,
1434 }
1435 }
1436
1437 pub fn update(&mut self) {
1438 if *self == ColorMode::Auto {
1439 if self.is_enabled() {
1442 *self = ColorMode::On;
1443 } else {
1444 *self = ColorMode::Off;
1445 }
1446 }
1447
1448 if *self == ColorMode::Off {
1449 yansi::disable();
1450 }
1451 }
1452}
1453
1454#[derive(
1455 Clone,
1456 Debug,
1457 Default,
1458 PartialEq,
1459 Eq,
1460 strum_macros::EnumString,
1461 strum_macros::Display,
1462 strum_macros::VariantNames,
1463)]
1464#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
1465pub enum TreeSortMode {
1466 #[default]
1467 None,
1468 Name,
1469 Time,
1470}
1471
1472impl TreeSortMode {
1473 pub(crate) fn parse_from_str(string: &str) -> Result<TreeSortMode, String> {
1475 TreeSortMode::from_str(string).map_err(|_| format!("choices are {:?}", Self::VARIANTS))
1476 }
1477}
1478
1479#[derive(Debug)]
1480pub struct ApplicationContext {
1481 pub options: cli::MainOptions,
1482 arena: UnsafeCell<Arena<Configuration>>,
1483 root_id: ConfigId,
1484}
1485
1486unsafe impl Sync for ApplicationContext {}
1502
1503impl Clone for ApplicationContext {
1506 fn clone(&self) -> Self {
1507 let mut arena: Arena<Configuration> = Arena::new();
1508 if let Some(self_arena) = unsafe { self.arena.get().as_ref() } {
1509 for node in self_arena.iter() {
1510 arena.new_node(node.get().clone());
1511 }
1512 }
1513 Self {
1514 arena: UnsafeCell::new(arena),
1515 options: self.options.clone(),
1516 root_id: self.root_id,
1517 }
1518 }
1519}
1520
1521impl ApplicationContext {
1522 pub fn new(options: cli::MainOptions) -> Self {
1524 let mut arena = Arena::new();
1525 let config = Configuration::new();
1526 let root_id = arena.new_node(config);
1527
1528 let app_context = ApplicationContext {
1529 arena: UnsafeCell::new(arena),
1530 root_id,
1531 options,
1532 };
1533 let config = app_context.get_root_config_mut();
1535 config.set_id(root_id);
1536
1537 app_context
1538 }
1539
1540 pub fn from_options(options: &cli::MainOptions) -> Result<Self, errors::GardenError> {
1542 let app_context = Self::new(options.clone());
1543 let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
1544
1545 app_context.get_root_config_mut().update(
1546 &app_context,
1547 options.config.as_deref(),
1548 options.root.as_deref(),
1549 config_verbose,
1550 None,
1551 )?;
1552 app_context.get_root_config_mut().update_options(options)?;
1553 config::read_grafts(&app_context)?;
1554
1555 Ok(app_context)
1556 }
1557
1558 pub fn from_path_and_root(
1560 path: &dyn AsRef<std::path::Path>,
1561 root: Option<&std::path::Path>,
1562 ) -> Result<Self, errors::GardenError> {
1563 let options = cli::MainOptions::new();
1564 let app_context = Self::new(options.clone());
1565 let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
1566 app_context.get_root_config_mut().update(
1567 &app_context,
1568 Some(path.as_ref()),
1569 root,
1570 config_verbose,
1571 None,
1572 )?;
1573 config::read_grafts(&app_context)?;
1575
1576 Ok(app_context)
1577 }
1578
1579 pub fn from_path(path: &dyn AsRef<std::path::Path>) -> Result<Self, errors::GardenError> {
1581 if let Some(root_dir) = path.as_ref().parent().map(std::path::Path::to_owned) {
1582 Self::from_path_and_root(path, Some(&root_dir))
1583 } else {
1584 Self::from_path_and_root(path, None)
1585 }
1586 }
1587
1588 pub fn from_path_string(path: &str) -> Result<Self, errors::GardenError> {
1590 Self::from_path(&std::path::PathBuf::from(path))
1591 }
1592
1593 pub fn from_string(string: &str) -> Result<Self, errors::GardenError> {
1595 let options = cli::MainOptions::new();
1596 let app_context = Self::new(options);
1597
1598 config::parse(&app_context, string, 0, app_context.get_root_config_mut())?;
1599 config::read_grafts(&app_context)?;
1600
1601 Ok(app_context)
1602 }
1603
1604 pub fn get_config(&self, id: ConfigId) -> &Configuration {
1605 unsafe { (*self.arena.get()).get(id).unwrap().get() }
1606 }
1607
1608 #[allow(clippy::mut_from_ref)]
1609 pub(crate) fn get_config_mut(&self, id: ConfigId) -> &mut Configuration {
1610 unsafe { (*self.arena.get()).get_mut(id).unwrap().get_mut() }
1611 }
1612
1613 pub fn get_root_id(&self) -> ConfigId {
1614 self.root_id
1615 }
1616
1617 pub fn get_root_config(&self) -> &Configuration {
1618 self.get_config(self.get_root_id())
1619 }
1620
1621 pub fn get_root_config_mut(&self) -> &mut Configuration {
1622 self.get_config_mut(self.get_root_id())
1623 }
1624
1625 pub(crate) fn add_graft(&self, parent: ConfigId, config: Configuration) -> ConfigId {
1627 let graft_id = unsafe { (*self.arena.get()).new_node(config) }; if let Some(arena) = unsafe { self.arena.get().as_mut() } {
1629 parent.append(graft_id, arena);
1630 }
1631
1632 self.get_config_mut(graft_id).set_id(graft_id);
1633
1634 graft_id
1635 }
1636
1637 pub(crate) fn add_graft_config(
1639 &self,
1640 config_id: ConfigId,
1641 graft_name: &str,
1642 path: &std::path::Path,
1643 root: Option<&std::path::Path>,
1644 ) -> Result<(), errors::GardenError> {
1645 let path = path.to_path_buf();
1646 let config_verbose = self.options.debug_level(constants::DEBUG_LEVEL_CONFIG);
1647 let mut graft_config = Configuration::new();
1648 graft_config.tree_branches = self.get_config(config_id).tree_branches;
1650 graft_config.shell_exit_on_error = self.get_config(config_id).shell_exit_on_error;
1651 graft_config.shell_word_split = self.get_config(config_id).shell_word_split;
1652 graft_config.update(self, Some(&path), root, config_verbose, Some(config_id))?;
1654
1655 let graft_id = self.add_graft(config_id, graft_config);
1657 if let Some(graft_config) = self.get_config_mut(config_id).grafts.get_mut(graft_name) {
1659 graft_config.set_id(graft_id);
1660 }
1661 config::read_grafts_recursive(self, graft_id)?;
1663
1664 Ok(())
1665 }
1666}
1667
1668#[derive(Clone, Debug, PartialEq, Eq)]
1670pub enum GitTreeType {
1671 Parent,
1673 Worktree(std::path::PathBuf),
1675 Tree,
1677 Bare,
1679}
1680
1681#[derive(Clone, Debug)]
1683pub struct GitTreeDetails {
1684 pub branch: String,
1685 pub tree_type: GitTreeType,
1686}
1687
1688pub(crate) fn is_valid_context(
1690 app_context: &ApplicationContext,
1691 pattern: &glob::Pattern,
1692 context: &TreeContext,
1693) -> bool {
1694 if !pattern.matches(&context.tree) {
1695 return false;
1696 }
1697 let tree_opt = match context.config {
1698 Some(graft_id) => app_context.get_config(graft_id).trees.get(&context.tree),
1699 None => app_context.get_root_config().trees.get(&context.tree),
1700 };
1701 let tree = match tree_opt {
1702 Some(tree) => tree,
1703 None => return false,
1704 };
1705 if tree.is_symlink {
1707 return false;
1708 }
1709
1710 true
1711}