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 expr: String,
53 value: UnsafeCell<Option<String>>,
54 evaluating: Cell<bool>,
55}
56
57impl_display_brief!(Variable);
58
59impl Clone for Variable {
62 fn clone(&self) -> Self {
63 Self {
64 expr: self.expr.clone(),
65 value: UnsafeCell::new(self.get_value().cloned()),
66 evaluating: Cell::new(false),
67 }
68 }
69}
70
71impl Variable {
72 pub(crate) fn new(expr: String, value: Option<String>) -> Self {
73 Variable {
74 expr,
75 value: UnsafeCell::new(value),
76 evaluating: Cell::new(false),
77 }
78 }
79
80 pub(crate) fn is_empty(&self) -> bool {
82 self.expr.is_empty()
83 }
84
85 pub(crate) fn is_evaluating(&self) -> bool {
88 self.evaluating.get()
89 }
90
91 pub(crate) fn set_evaluating(&self, value: bool) {
93 self.evaluating.set(value);
94 }
95
96 pub fn get_expr(&self) -> &String {
98 &self.expr
99 }
100
101 pub(crate) fn get_expr_mut(&mut self) -> &mut String {
103 &mut self.expr
104 }
105
106 pub(crate) fn set_expr(&mut self, expr: String) {
108 self.expr = expr;
109 }
110
111 pub(crate) fn set_value(&self, value: String) {
113 unsafe {
114 *self.value.get() = Some(value);
115 }
116 }
117
118 pub fn get_value(&self) -> Option<&String> {
120 unsafe { (*self.value.get()).as_ref() }
121 }
122
123 pub(crate) fn reset(&self) {
125 unsafe {
126 *self.value.get() = None;
127 }
128 }
129}
130
131pub(crate) type MultiVariableMap = IndexMap<String, Vec<Variable>>;
133
134fn reset_map_variables(vec_variables: &MultiVariableMap) {
136 for variables in vec_variables.values() {
137 for variable in variables {
138 variable.reset();
139 }
140 }
141}
142
143pub(crate) type VariableMap = IndexMap<String, Variable>;
145
146#[derive(Clone, Debug)]
148pub struct MultiVariable {
149 name: String,
150 variables: Vec<Variable>,
151}
152
153impl MultiVariable {
154 pub(crate) fn new(name: String, variables: Vec<Variable>) -> Self {
155 MultiVariable { name, variables }
156 }
157
158 pub fn get(&self, idx: usize) -> &Variable {
159 &self.variables[idx]
160 }
161
162 pub fn get_name(&self) -> &String {
163 &self.name
164 }
165
166 pub fn len(&self) -> usize {
167 self.variables.len()
168 }
169
170 pub fn is_empty(&self) -> bool {
171 self.variables.is_empty()
172 }
173
174 pub fn reset(&self) {
175 for var in &self.variables {
176 var.reset();
177 }
178 }
179
180 pub fn iter(&self) -> std::slice::Iter<Variable> {
181 self.variables.iter()
182 }
183}
184
185#[derive(Clone, Debug, Default)]
187pub struct Tree {
188 pub commands: MultiVariableMap,
189 pub environment: Vec<MultiVariable>,
190 pub gitconfig: MultiVariableMap,
191 pub remotes: VariableMap,
192 pub(crate) symlink: Variable,
193 pub templates: StringSet,
194 pub variables: VariableMap,
195 pub branch: Variable,
196 pub(crate) branches: VariableMap,
197 pub worktree: Variable,
198 #[default(string!("origin"))]
199 pub(crate) default_remote: String,
200 pub(crate) clone_depth: i64,
201 pub(crate) is_single_branch: bool,
202 pub is_symlink: bool,
203 pub is_bare_repository: bool,
204 pub is_worktree: bool,
205 pub(crate) description: String,
206 pub(crate) links: Vec<Variable>,
207
208 name: String,
209 path: Variable,
210}
211
212impl Tree {
213 pub fn get_name(&self) -> &String {
214 &self.name
215 }
216
217 pub fn set_name(&mut self, name: String) {
219 self.name = name;
220 }
221
222 pub(crate) fn get_name_mut(&mut self) -> &mut String {
223 &mut self.name
224 }
225
226 pub fn get_path(&self) -> &Variable {
227 &self.path
228 }
229
230 pub(crate) fn get_path_mut(&mut self) -> &mut Variable {
231 &mut self.path
232 }
233
234 pub(crate) fn path_is_valid(&self) -> bool {
235 self.path.get_value().is_some()
236 }
237
238 pub(crate) fn canonical_pathbuf(&self) -> Option<std::path::PathBuf> {
240 if let Some(pathbuf) = self.pathbuf() {
241 if let Ok(canon_path) = path::canonicalize(pathbuf) {
242 return Some(canon_path);
243 }
244 }
245
246 None
247 }
248
249 pub(crate) fn pathbuf(&self) -> Option<std::path::PathBuf> {
251 if !self.path_is_valid() {
252 return None;
253 }
254 self.path.get_value().map(std::path::PathBuf::from)
255 }
256
257 pub fn path_as_ref(&self) -> Result<&String, errors::GardenError> {
258 match self.path.get_value() {
259 Some(value) => Ok(value),
260 None => Err(errors::GardenError::ConfigurationError(format!(
261 "unset tree path for {}",
262 self.name
263 ))),
264 }
265 }
266
267 pub(crate) fn symlink_as_ref(&self) -> Result<&String, errors::GardenError> {
268 match self.symlink.get_value() {
269 Some(value) => Ok(value),
270 None => Err(errors::GardenError::ConfigurationError(format!(
271 "unset tree path for {}",
272 self.name
273 ))),
274 }
275 }
276
277 pub(crate) fn add_builtin_variables(&mut self) {
279 self.variables.insert(
280 string!(constants::TREE_NAME),
281 Variable::new(self.get_name().clone(), None),
282 );
283
284 self.variables.insert(
286 string!(constants::TREE_PATH),
287 Variable::new(self.get_path().get_expr().clone(), None),
288 );
289 }
290
291 pub(crate) fn reset_variables(&self) {
292 for var in self.variables.values() {
296 var.reset();
297 }
298 for env in &self.environment {
299 env.reset();
300 }
301
302 reset_map_variables(&self.gitconfig);
303 reset_map_variables(&self.commands);
304 }
305
306 pub(crate) fn clone_from_tree(&mut self, tree: &Tree) {
308 collections::append_map(&mut self.commands, &tree.commands);
309 collections::append_map(&mut self.gitconfig, &tree.gitconfig);
310 collections::append_map(&mut self.variables, &tree.variables);
311 collections::append_map(&mut self.remotes, &tree.remotes);
312 collections::append_set(&mut self.templates, &tree.templates);
313
314 self.environment.append(&mut tree.environment.clone());
316 if tree.clone_depth > 0 {
318 self.clone_depth = tree.clone_depth;
319 }
320 if tree.is_bare_repository {
321 self.is_bare_repository = tree.is_bare_repository;
322 }
323 if tree.is_single_branch {
324 self.is_single_branch = tree.is_single_branch;
325 }
326 if tree.is_worktree {
327 self.is_worktree = tree.is_worktree;
328 }
329 if tree.is_symlink {
330 self.is_symlink = tree.is_symlink;
331 }
332 if !tree.branch.is_empty() {
333 self.branch = tree.branch.clone();
334 }
335 if !tree.symlink.is_empty() {
336 self.symlink = tree.symlink.clone();
337 }
338 if !tree.worktree.is_empty() {
339 self.worktree = tree.worktree.clone();
340 }
341 self.default_remote = tree.default_remote.to_string();
342 self.description = tree.description.to_string();
343 self.links.clone_from(&tree.links);
344
345 self.update_flags();
346 }
347
348 pub(crate) fn update_flags(&mut self) {
350 if !self.symlink.is_empty() {
351 self.is_symlink = true;
352 }
353 if !self.worktree.is_empty() {
354 self.is_worktree = true;
355 }
356 if self.is_worktree {
357 self.is_bare_repository = false;
358 }
359 }
360
361 pub(crate) fn eval_branch(&self, eval_context: &EvalContext) -> String {
363 self.get_branch(
364 eval_context.app_context,
365 eval_context.config,
366 eval_context.graft_config,
367 eval_context.tree_context,
368 )
369 }
370
371 pub(crate) fn get_branch(
373 &self,
374 app_context: &ApplicationContext,
375 config: &Configuration,
376 graft_config: Option<&Configuration>,
377 tree_context: &TreeContext,
378 ) -> String {
379 eval::tree_variable(
380 app_context,
381 config,
382 graft_config,
383 &tree_context.tree,
384 tree_context.garden.as_ref(),
385 &self.branch,
386 )
387 }
388
389 pub(crate) fn eval_url(&self, eval_context: &EvalContext) -> Option<String> {
391 self.get_url(
392 eval_context.app_context,
393 eval_context.config,
394 eval_context.graft_config,
395 eval_context.tree_context,
396 )
397 }
398
399 pub(crate) fn get_url(
401 &self,
402 app_context: &ApplicationContext,
403 config: &Configuration,
404 graft_config: Option<&Configuration>,
405 context: &TreeContext,
406 ) -> Option<String> {
407 self.remotes.get(&self.default_remote).map(|remote| {
408 eval::tree_variable(
409 app_context,
410 config,
411 graft_config,
412 &context.tree,
413 context.garden.as_ref(),
414 remote,
415 )
416 })
417 }
418
419 pub(crate) fn eval_worktree(&self, eval_context: &EvalContext) -> String {
421 self.get_worktree(
422 eval_context.app_context,
423 eval_context.config,
424 eval_context.graft_config,
425 eval_context.tree_context,
426 )
427 }
428
429 pub(crate) fn get_worktree(
431 &self,
432 app_context: &ApplicationContext,
433 config: &Configuration,
434 graft_config: Option<&Configuration>,
435 tree_context: &TreeContext,
436 ) -> String {
437 eval::tree_variable(
438 app_context,
439 config,
440 graft_config,
441 &tree_context.tree,
442 tree_context.garden.as_ref(),
443 &self.worktree,
444 )
445 }
446
447 pub(crate) fn get_remote_for_branch(
449 &self,
450 eval_context: &EvalContext,
451 branch: &str,
452 ) -> Option<String> {
453 let remote_branch = self.get_upstream_branch(eval_context, branch)?;
454 let remote = remote_branch.split_once('/')?.0;
455 if self.remotes.contains_key(remote) {
456 Some(remote.to_string())
457 } else {
458 None
459 }
460 }
461
462 pub(crate) fn get_upstream_branch(
464 &self,
465 eval_context: &EvalContext,
466 branch: &str,
467 ) -> Option<String> {
468 if branch.is_empty() {
469 return None;
470 }
471 self.branches
472 .get(branch)
473 .map(|remote_branch_var| eval_context.tree_variable(remote_branch_var))
474 }
475}
476
477#[derive(Clone, Debug, Default)]
478pub struct Group {
479 name: String,
480 pub members: StringSet,
481}
482
483impl Group {
484 pub fn get_name(&self) -> &String {
485 &self.name
486 }
487
488 pub(crate) fn get_name_owned(&self) -> String {
490 self.get_name().to_owned()
491 }
492
493 pub(crate) fn get_name_mut(&mut self) -> &mut String {
494 &mut self.name
495 }
496}
497
498pub type GroupMap = IndexMap<GroupName, Group>;
500
501#[derive(Clone, Debug, Default)]
505pub struct Template {
506 pub tree: Tree,
507 pub extend: StringSet,
508 name: String,
509}
510
511impl Template {
512 pub fn get_name(&self) -> &String {
513 &self.name
514 }
515
516 pub(crate) fn get_name_mut(&mut self) -> &mut String {
517 &mut self.name
518 }
519
520 pub(crate) fn apply(&self, tree: &mut Tree) {
522 tree.clone_from_tree(&self.tree);
523 }
524}
525
526#[derive(Clone, Debug, Default)]
528pub struct Garden {
529 pub commands: MultiVariableMap,
530 pub environment: Vec<MultiVariable>,
531 pub gitconfig: MultiVariableMap,
532 pub groups: StringSet,
533 pub trees: StringSet,
534 pub variables: VariableMap,
535 name: GardenName,
536}
537
538impl Garden {
539 pub fn get_name(&self) -> &GardenName {
540 &self.name
541 }
542
543 pub(crate) fn get_name_mut(&mut self) -> &mut String {
544 &mut self.name
545 }
546}
547
548pub type GardenMap = IndexMap<GardenName, Garden>;
550
551fn get_default_shell() -> String {
553 if which(constants::SHELL_ZSH).is_ok() {
554 constants::SHELL_ZSH
555 } else if which(constants::SHELL_BASH).is_ok() {
556 constants::SHELL_BASH
557 } else if which(constants::SHELL_DASH).is_ok() {
558 constants::SHELL_DASH
559 } else {
560 constants::SHELL_SH
561 }
562 .to_string()
563}
564
565#[derive(Clone, Debug, Default)]
567pub struct Configuration {
568 pub commands: MultiVariableMap,
569 pub debug: IndexMap<String, u8>,
570 pub environment: Vec<MultiVariable>,
571 pub gardens: GardenMap,
572 pub grafts: IndexMap<GraftName, Graft>,
573 pub groups: GroupMap,
574 pub path: Option<std::path::PathBuf>,
575 pub dirname: Option<std::path::PathBuf>,
576 pub root: Variable,
577 pub root_is_dynamic: bool,
578 pub root_path: std::path::PathBuf,
579 pub shell: String,
580 pub interactive_shell: String,
581 pub templates: IndexMap<String, Template>,
582 pub tree_search_path: Vec<std::path::PathBuf>,
583 pub trees: IndexMap<TreeName, Tree>,
584 pub variables: VariableMap,
585 pub override_variables: VariableMap,
588 pub config_verbose: u8,
589 pub quiet: bool,
590 pub verbose: u8,
591 pub(crate) shell_exit_on_error: bool,
592 pub(crate) shell_word_split: bool,
593 pub(crate) tree_branches: bool,
594 pub(crate) parent_id: Option<ConfigId>,
595 id: Option<ConfigId>,
596}
597
598impl_display!(Configuration);
599
600impl Configuration {
601 pub fn new() -> Self {
603 Configuration {
604 id: None,
605 parent_id: None,
606 shell: get_default_shell(),
607 shell_exit_on_error: true,
608 shell_word_split: true,
609 tree_branches: true,
610 ..std::default::Default::default()
611 }
612 }
613
614 pub(crate) fn initialize(&mut self, app_context: &ApplicationContext) {
615 let expr = self.root.get_expr().to_string();
617 let mut value = eval::value(app_context, self, &expr);
618 if expr.is_empty() {
619 if self.root_is_dynamic {
620 let current_dir = path::current_dir_string();
623 self.root.set_expr(current_dir.clone());
624 self.root.set_value(current_dir);
625 self.root_path = path::current_dir();
626 } else {
627 self.root
629 .set_expr(string!(constants::GARDEN_CONFIG_DIR_EXPR));
630 if let Some(ref dirname) = self.dirname {
631 self.root.set_value(dirname.to_string_lossy().to_string());
632 self.root_path = dirname.to_path_buf();
633 }
634 }
635 } else {
636 self.root_path = std::path::PathBuf::from(&value);
638 if let Ok(root_path_canon) = path::canonicalize(&self.root_path) {
639 if root_path_canon != self.root_path {
640 value = root_path_canon
641 .to_str()
642 .unwrap_or(value.as_str())
643 .to_string();
644 self.root_path = root_path_canon;
645 }
646 }
647 self.root.set_value(value);
648 }
649 self.update_tree_paths(app_context); self.synthesize_default_tree(); self.reset();
653 }
654
655 pub(crate) fn graft_id(&self) -> Option<NodeId> {
657 self.parent_id.and(self.get_id())
658 }
659
660 pub(crate) fn update(
661 &mut self,
662 app_context: &ApplicationContext,
663 config: Option<&std::path::Path>,
664 root: Option<&std::path::Path>,
665 config_verbose: u8,
666 parent: Option<ConfigId>,
667 ) -> Result<(), errors::GardenError> {
668 if let Some(parent_id) = parent {
669 self.set_parent(parent_id);
670 }
671 self.config_verbose = config_verbose;
672 self.quiet = app_context.options.quiet;
673 self.verbose = app_context.options.verbose;
674
675 let root_pathbuf_option = root.map(path::abspath);
677 if let Some(root_path) = root_pathbuf_option {
678 self.root.set_expr(root_path.to_string_lossy().to_string());
679 }
680
681 let mut basename = string!(constants::GARDEN_CONFIG);
682
683 let mut found = false;
685 if let Some(config_path) = config {
686 if config_path.is_file() || config_path.is_absolute() {
687 self.set_path(&config_path);
691 found = true;
692 } else {
693 basename = config_path.to_string_lossy().into();
696 }
697 }
698
699 if !found {
700 for entry in config::search_path() {
701 let mut candidate = entry.to_path_buf();
702 candidate.push(basename.clone());
703 if candidate.exists() {
704 self.set_path(&candidate);
705 found = true;
706 break;
707 }
708 }
709 }
710 if config_verbose > 0 {
711 debug!(
712 "config: path: {:?}, root: {:?}, found: {}",
713 self.path, self.root, found
714 );
715 }
716
717 if found {
718 let config_path = self.get_path()?;
720 if let Ok(config_string) = std::fs::read_to_string(config_path) {
721 config::parse(app_context, &config_string, config_verbose, self)?;
722 }
723 }
724
725 Ok(())
726 }
727
728 pub(crate) fn update_options(
730 &mut self,
731 options: &cli::MainOptions,
732 ) -> Result<(), errors::GardenError> {
733 let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
734 if self.path.is_none() {
735 error!("unable to find a configuration file -- use --config <path>");
736 }
737 if config_verbose > 1 {
738 eprintln!("config: {:?}", self.get_path()?);
739 }
740 if config_verbose > 2 {
741 debug!("{}", self);
742 }
743 for key in &options.debug {
744 let current = *self.debug.get(key).unwrap_or(&0);
745 self.debug.insert(key.into(), current + 1);
746 }
747 self.apply_defines(&options.define);
748
749 Ok(())
750 }
751
752 pub(crate) fn apply_defines(&mut self, defines: &Vec<String>) {
754 for k_eq_v in defines {
755 let name: String;
756 let expr: String;
757 let values: Vec<&str> = k_eq_v.splitn(2, '=').collect();
758 if values.len() == 1 {
759 name = values[0].to_string();
760 expr = string!("");
761 } else if values.len() == 2 {
762 name = values[0].to_string();
763 expr = values[1].to_string();
764 } else {
765 error!("unable to split '{}'", k_eq_v);
766 }
767 match name.as_str() {
769 constants::GARDEN_INTERACTIVE_SHELL => {
770 self.interactive_shell = expr;
771 }
772 constants::GARDEN_SHELL => {
773 self.shell = expr;
774 }
775 constants::GARDEN_SHELL_ERREXIT => {
776 set_bool(name.as_str(), &expr, &mut self.shell_exit_on_error);
777 }
778 constants::GARDEN_SHELL_WORDSPLIT => {
779 set_bool(name.as_str(), &expr, &mut self.shell_word_split);
780 }
781 constants::GARDEN_TREE_BRANCHES => {
782 set_bool(name.as_str(), &expr, &mut self.tree_branches);
783 }
784 _ => {
785 self.override_variables
786 .insert(name, Variable::new(expr, None));
787 }
788 }
789 }
790 }
791
792 pub(crate) fn update_quiet_and_verbose_variables(&mut self, quiet: bool, verbose: u8) {
794 let quiet_string = if self.quiet || quiet { "--quiet" } else { "" }.to_string();
796 self.variables.insert(
797 string!(constants::GARDEN_CMD_QUIET),
798 Variable::new(quiet_string.clone(), Some(quiet_string)),
799 );
800 let verbose = self.verbose + verbose;
801 let verbose_string = if verbose > 0 {
802 format!("-{}", "v".repeat(verbose.into()))
803 } else {
804 string!("")
805 };
806 self.variables.insert(
807 string!(constants::GARDEN_CMD_VERBOSE),
808 Variable::new(verbose_string.clone(), Some(verbose_string)),
809 );
810 }
811
812 pub(crate) fn reset(&mut self) {
813 self.reset_variables();
815 self.reset_builtin_variables()
817 }
818
819 fn reset_builtin_variables(&mut self) {
820 if let Some(var) = self.variables.get_mut(constants::GARDEN_ROOT) {
822 if let Some(value) = self.root.get_value() {
823 var.set_expr(value.into());
824 var.set_value(value.into());
825 }
826 }
827
828 for tree in self.trees.values_mut() {
829 let tree_name = String::from(tree.get_name());
831 if let Some(var) = tree.variables.get_mut(constants::TREE_NAME) {
832 var.set_expr(tree_name.to_string());
833 var.set_value(tree_name);
834 }
835 let tree_path = match tree.path_as_ref() {
837 Ok(path) => String::from(path),
838 Err(_) => continue,
839 };
840 if let Some(var) = tree.variables.get_mut(constants::TREE_PATH) {
842 var.set_expr(tree_path.to_string());
843 var.set_value(tree_path);
844 }
845 }
846 }
847
848 fn update_tree_paths(&mut self, app_context: &ApplicationContext) {
852 let mut path_values = Vec::new();
854 let mut symlink_values = Vec::new();
855
856 for (name, tree) in &self.trees {
857 path_values.push((name.clone(), tree.path.get_expr().clone()));
858 if tree.is_symlink {
859 symlink_values.push((name.clone(), tree.symlink.get_expr().clone()));
860 }
861 }
862
863 for (name, value) in &path_values {
865 let result = self.eval_tree_path(app_context, value);
866 if let Some(tree) = self.trees.get_mut(name) {
867 tree.path.set_value(result);
868 }
869 }
870
871 for (name, value) in &symlink_values {
873 let result = self.eval_tree_path(app_context, value);
874 if let Some(tree) = self.trees.get_mut(name) {
875 tree.symlink.set_value(result);
876 }
877 }
878 }
879
880 fn synthesize_default_tree(&mut self) {
882 if !self.commands.is_empty() && self.trees.is_empty() {
883 let dirname_string = self.dirname_string();
884 let mut tree = Tree::default();
885 tree.path.set_expr(dirname_string.clone());
886 tree.path.set_value(dirname_string);
887 tree.description = string!("The default tree for garden commands.");
888 tree.add_builtin_variables();
889 tree.set_name(string!(constants::DOT));
890 self.trees.insert(string!(constants::DOT), tree);
891 }
892 }
893
894 pub(crate) fn tree_path(&self, path: &str) -> String {
896 if std::path::PathBuf::from(path).is_absolute() {
897 path.into()
899 } else {
900 let mut path_buf = self.root_path.to_path_buf();
902 path_buf.push(path);
903
904 path_buf.to_string_lossy().into()
905 }
906 }
907
908 pub(crate) fn relative_pathbuf(&self, path: &str) -> std::path::PathBuf {
910 let pathbuf = std::path::PathBuf::from(path);
911 if pathbuf.is_absolute() {
912 if let Ok(pathbuf_canon) = path::canonicalize(&pathbuf) {
914 pathbuf_canon
915 } else {
916 pathbuf
917 }
918 } else {
919 let mut path_buf = self.root_path.to_path_buf();
921 path_buf.push(path);
922
923 path_buf
924 }
925 }
926
927 fn eval_tree_path(&mut self, app_context: &ApplicationContext, path: &str) -> String {
929 let value = eval::value(app_context, self, path);
930 self.tree_path(&value)
931 }
932
933 pub(crate) fn config_pathbuf(&self, path: &str) -> Option<std::path::PathBuf> {
935 let path_buf = std::path::PathBuf::from(path);
936 if path_buf.is_absolute() {
937 Some(path_buf)
939 } else if let Some(dirname) = self.dirname.as_ref() {
940 let mut abs_path_buf = dirname.to_path_buf();
942 abs_path_buf.push(path_buf);
943
944 Some(abs_path_buf)
945 } else {
946 None
947 }
948 }
949
950 fn config_pathbuf_from_include(
953 &self,
954 include_path: &std::path::Path,
955 path: &str,
956 ) -> Option<std::path::PathBuf> {
957 let mut path_buf = std::path::PathBuf::from(path);
958 if path_buf.is_absolute() {
959 return Some(path_buf);
961 }
962
963 if let Some(dirname) = include_path.parent() {
965 path_buf = dirname.to_path_buf();
967 path_buf.push(path);
968
969 if path_buf.exists() {
970 return Some(path_buf);
971 }
972 }
973
974 self.config_pathbuf(path)
976 }
977
978 fn config_path(&self, path: &str) -> String {
980 if let Some(path_buf) = self.config_pathbuf(path) {
981 path_buf.to_string_lossy().to_string()
982 } else {
983 self.tree_path(path)
984 }
985 }
986
987 fn dirname_string(&self) -> String {
991 match self.dirname {
992 Some(ref dirname) => dirname.to_string_lossy().to_string(),
993 None => path::current_dir_string(),
994 }
995 }
996
997 pub(crate) fn fallback_execdir_string(&self) -> String {
999 if self.root_path.exists() {
1000 return self.root_path.to_string_lossy().to_string();
1001 }
1002 if let Some(dirname) = self.dirname.as_ref() {
1003 if dirname.exists() {
1004 return dirname.to_string_lossy().to_string();
1005 }
1006 }
1007 path::current_dir_string()
1008 }
1009
1010 pub(crate) fn eval_config_path(&self, app_context: &ApplicationContext, path: &str) -> String {
1012 let value = eval::value(app_context, self, path);
1013 self.config_path(&value)
1014 }
1015
1016 pub(crate) fn eval_config_pathbuf_from_include(
1018 &self,
1019 app_context: &ApplicationContext,
1020 include_path: Option<&std::path::Path>,
1021 path: &str,
1022 ) -> Option<std::path::PathBuf> {
1023 let value = eval::value(app_context, self, path);
1024
1025 if let Some(include_path) = include_path {
1026 self.config_pathbuf_from_include(include_path, &value)
1027 } else {
1028 self.config_pathbuf(&value)
1029 }
1030 .or_else(|| Some(std::path::PathBuf::from(&value)))
1031 }
1032
1033 pub(crate) fn reset_variables(&mut self) {
1035 for var in self.variables.values() {
1036 var.reset();
1037 }
1038 for env in &self.environment {
1039 env.reset();
1040 }
1041
1042 reset_map_variables(&self.commands);
1043
1044 for tree in self.trees.values() {
1045 tree.reset_variables();
1046 }
1047 }
1048
1049 pub(crate) fn set_id(&mut self, id: ConfigId) {
1051 self.id = Some(id);
1052 }
1053
1054 pub(crate) fn get_id(&self) -> Option<ConfigId> {
1055 self.id
1056 }
1057
1058 pub(crate) fn set_parent(&mut self, id: ConfigId) {
1060 self.parent_id = Some(id);
1061 }
1062
1063 pub(crate) fn set_path(&mut self, path: &dyn AsRef<std::path::Path>) {
1065 let config_path = path.as_ref().to_path_buf();
1066 let mut dirname = config_path.clone();
1067 dirname.pop();
1068
1069 self.dirname = Some(dirname);
1070 self.path = Some(config_path);
1071 }
1072
1073 pub(crate) fn get_path(&self) -> Result<&std::path::PathBuf, errors::GardenError> {
1075 self.path.as_ref().ok_or_else(|| {
1076 errors::GardenError::AssertionError("Configuration path is unset".into())
1077 })
1078 }
1079
1080 pub(crate) fn get_path_for_display(&self) -> String {
1083 let default_pathbuf = std::path::PathBuf::from(constants::DOT);
1084 self.path
1085 .as_ref()
1086 .unwrap_or(&default_pathbuf)
1087 .display()
1088 .to_string()
1089 }
1090
1091 pub(crate) fn contains_graft(&self, name: &str) -> bool {
1093 let graft_name = syntax::trim(name);
1094 self.grafts.contains_key(graft_name)
1095 }
1096
1097 pub(crate) fn get_graft(&self, name: &str) -> Result<&Graft, errors::GardenError> {
1099 let graft_name = syntax::trim(name);
1100 self.grafts.get(graft_name).ok_or_else(|| {
1101 errors::GardenError::ConfigurationError(format!("{name}: no such graft"))
1102 })
1103 }
1104
1105 pub(crate) fn get_graft_id<'a>(
1108 &self,
1109 value: &'a str,
1110 ) -> Result<(ConfigId, &'a str), errors::GardenError> {
1111 let (graft_name, remainder) = match syntax::split_graft(value) {
1112 Some((graft_name, remainder)) => (graft_name, remainder),
1113 None => {
1114 return Err(errors::GardenError::ConfigurationError(format!(
1115 "{value}: invalid graft expression"
1116 )))
1117 }
1118 };
1119 let graft = self.get_graft(graft_name)?;
1120 let graft_id = graft
1121 .get_id()
1122 .ok_or(errors::GardenError::ConfigurationError(format!(
1123 "{graft_name}: no such graft"
1124 )))?;
1125
1126 Ok((graft_id, remainder))
1127 }
1128
1129 pub fn get_tree(&self, name: &str) -> Option<&Tree> {
1131 self.trees.get(name)
1132 }
1133
1134 pub fn get_garden(&self, name: &str) -> Option<&Garden> {
1136 self.gardens.get(name)
1137 }
1138
1139 pub(crate) fn get_tree_pathbuf(&self, tree_name: &str) -> Option<std::path::PathBuf> {
1141 self.get_tree(tree_name)
1142 .map(|tree| tree.canonical_pathbuf())
1143 .unwrap_or(None)
1144 }
1145}
1146
1147fn set_bool(name: &str, expr: &str, output: &mut bool) {
1149 if let Some(value) = syntax::string_to_bool(expr) {
1150 *output = value;
1151 } else {
1152 error!(
1153 "'{}' is not a valid value for \"{}\". Must be true, false, 0 or 1",
1154 name, expr
1155 );
1156 }
1157}
1158
1159#[derive(Clone, Debug, Default)]
1160pub struct Graft {
1161 id: Option<ConfigId>,
1162 name: String,
1163 pub root: String,
1164 pub config: String,
1165}
1166
1167impl Graft {
1168 pub fn new(name: String, root: String, config: String) -> Self {
1169 Graft {
1170 id: None,
1171 name,
1172 root,
1173 config,
1174 }
1175 }
1176
1177 pub fn get_name(&self) -> &String {
1178 &self.name
1179 }
1180
1181 pub fn get_id(&self) -> Option<ConfigId> {
1182 self.id
1183 }
1184
1185 pub(crate) fn set_id(&mut self, id: ConfigId) {
1186 self.id = Some(id);
1187 }
1188}
1189
1190#[derive(Clone, Debug)]
1192pub(crate) struct EvalContext<'a> {
1193 pub(crate) app_context: &'a ApplicationContext,
1194 pub(crate) config: &'a Configuration,
1195 pub(crate) graft_config: Option<&'a Configuration>,
1196 pub(crate) tree_context: &'a TreeContext,
1197}
1198
1199impl EvalContext<'_> {
1200 pub(crate) fn new<'a>(
1202 app_context: &'a ApplicationContext,
1203 config: &'a Configuration,
1204 graft_config: Option<&'a Configuration>,
1205 tree_context: &'a TreeContext,
1206 ) -> EvalContext<'a> {
1207 EvalContext {
1208 app_context,
1209 config,
1210 graft_config,
1211 tree_context,
1212 }
1213 }
1214
1215 pub(crate) fn from_app_context<'a>(
1217 app_context: &'a ApplicationContext,
1218 tree_context: &'a TreeContext,
1219 ) -> EvalContext<'a> {
1220 let config = app_context.get_root_config();
1221 let graft_config = tree_context
1222 .config
1223 .map(|config_id| app_context.get_config(config_id));
1224 EvalContext::new(app_context, config, graft_config, tree_context)
1225 }
1226
1227 pub(crate) fn tree_value(&self, value: &str) -> String {
1229 eval::tree_value(
1230 self.app_context,
1231 self.config,
1232 self.graft_config,
1233 value,
1234 &self.tree_context.tree,
1235 self.tree_context.garden.as_ref(),
1236 )
1237 }
1238
1239 pub(crate) fn tree_variable(&self, var: &Variable) -> String {
1241 eval::tree_variable(
1242 self.app_context,
1243 self.config,
1244 self.graft_config,
1245 &self.tree_context.tree,
1246 self.tree_context.garden.as_ref(),
1247 var,
1248 )
1249 }
1250}
1251
1252#[derive(Clone, Debug)]
1253pub struct TreeContext {
1254 pub tree: TreeName,
1255 pub config: Option<ConfigId>,
1256 pub garden: Option<GardenName>,
1257 pub group: Option<String>,
1258}
1259
1260impl TreeContext {
1261 pub fn new(
1263 tree: &str,
1264 config: Option<ConfigId>,
1265 garden: Option<GardenName>,
1266 group: Option<String>,
1267 ) -> Self {
1268 TreeContext {
1269 tree: TreeName::from(tree),
1270 config,
1271 garden,
1272 group,
1273 }
1274 }
1275}
1276
1277#[derive(Debug, Default)]
1278pub struct TreeQuery {
1279 pub query: String,
1280 pub pattern: glob::Pattern,
1281 pub is_default: bool,
1282 pub is_garden: bool,
1283 pub is_group: bool,
1284 pub is_tree: bool,
1285 pub include_gardens: bool,
1286 pub include_groups: bool,
1287 pub include_trees: bool,
1288}
1289
1290impl TreeQuery {
1291 pub fn new(query: &str) -> Self {
1292 let mut is_default = false;
1293 let mut is_tree = false;
1294 let mut is_garden = false;
1295 let mut is_group = false;
1296 let mut include_gardens = true;
1297 let mut include_groups = true;
1298 let mut include_trees = true;
1299
1300 if syntax::is_garden(query) {
1301 is_garden = true;
1302 include_groups = false;
1303 include_trees = false;
1304 } else if syntax::is_group(query) {
1305 is_group = true;
1306 include_gardens = false;
1307 include_trees = false;
1308 } else if syntax::is_tree(query) {
1309 is_tree = true;
1310 include_gardens = false;
1311 include_groups = false;
1312 } else {
1313 is_default = true;
1314 }
1315 let glob_pattern = syntax::trim(query);
1316 let pattern = glob::Pattern::new(glob_pattern).unwrap_or_default();
1317
1318 TreeQuery {
1319 query: query.into(),
1320 is_default,
1321 is_garden,
1322 is_group,
1323 is_tree,
1324 include_gardens,
1325 include_groups,
1326 include_trees,
1327 pattern,
1328 }
1329 }
1330}
1331
1332#[derive(
1333 Clone,
1334 Debug,
1335 Default,
1336 PartialEq,
1337 Eq,
1338 strum_macros::EnumString,
1339 strum_macros::Display,
1340 strum_macros::VariantNames,
1341)]
1342#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
1343pub enum ColorMode {
1344 #[default]
1346 Auto,
1347 #[strum(
1349 serialize = "1",
1350 serialize = "on",
1351 serialize = "true",
1352 serialize = "always",
1353 serialize = "y",
1354 serialize = "yes"
1355 )]
1356 On,
1357 #[strum(
1359 serialize = "0",
1360 serialize = "off",
1361 serialize = "false",
1362 serialize = "never",
1363 serialize = "n",
1364 serialize = "no"
1365 )]
1366 Off,
1367}
1368
1369impl ColorMode {
1370 pub fn parse_from_str(string: &str) -> Result<ColorMode, String> {
1372 ColorMode::from_str(string).map_err(|_| format!("choices are {:?}", Self::VARIANTS))
1373 }
1374
1375 pub fn is_enabled(&self) -> bool {
1376 match self {
1377 ColorMode::Auto => std::io::stdout().is_terminal(),
1378 ColorMode::Off => false,
1379 ColorMode::On => true,
1380 }
1381 }
1382
1383 pub fn update(&mut self) {
1384 if *self == ColorMode::Auto {
1385 if self.is_enabled() {
1388 *self = ColorMode::On;
1389 } else {
1390 *self = ColorMode::Off;
1391 }
1392 }
1393
1394 if *self == ColorMode::Off {
1395 yansi::disable();
1396 }
1397 }
1398}
1399
1400#[derive(
1401 Clone,
1402 Debug,
1403 Default,
1404 PartialEq,
1405 Eq,
1406 strum_macros::EnumString,
1407 strum_macros::Display,
1408 strum_macros::VariantNames,
1409)]
1410#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
1411pub enum TreeSortMode {
1412 #[default]
1413 None,
1414 Name,
1415 Time,
1416}
1417
1418impl TreeSortMode {
1419 pub(crate) fn parse_from_str(string: &str) -> Result<TreeSortMode, String> {
1421 TreeSortMode::from_str(string).map_err(|_| format!("choices are {:?}", Self::VARIANTS))
1422 }
1423}
1424
1425#[derive(Debug)]
1426pub struct ApplicationContext {
1427 pub options: cli::MainOptions,
1428 arena: UnsafeCell<Arena<Configuration>>,
1429 root_id: ConfigId,
1430}
1431
1432unsafe impl Sync for ApplicationContext {}
1448
1449impl Clone for ApplicationContext {
1452 fn clone(&self) -> Self {
1453 let mut arena: Arena<Configuration> = Arena::new();
1454 if let Some(self_arena) = unsafe { self.arena.get().as_ref() } {
1455 for node in self_arena.iter() {
1456 arena.new_node(node.get().clone());
1457 }
1458 }
1459 Self {
1460 arena: UnsafeCell::new(arena),
1461 options: self.options.clone(),
1462 root_id: self.root_id,
1463 }
1464 }
1465}
1466
1467impl ApplicationContext {
1468 pub fn new(options: cli::MainOptions) -> Self {
1470 let mut arena = Arena::new();
1471 let config = Configuration::new();
1472 let root_id = arena.new_node(config);
1473
1474 let app_context = ApplicationContext {
1475 arena: UnsafeCell::new(arena),
1476 root_id,
1477 options,
1478 };
1479 let config = app_context.get_root_config_mut();
1481 config.set_id(root_id);
1482
1483 app_context
1484 }
1485
1486 pub fn from_options(options: &cli::MainOptions) -> Result<Self, errors::GardenError> {
1488 let app_context = Self::new(options.clone());
1489 let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
1490
1491 app_context.get_root_config_mut().update(
1492 &app_context,
1493 options.config.as_deref(),
1494 options.root.as_deref(),
1495 config_verbose,
1496 None,
1497 )?;
1498 app_context.get_root_config_mut().update_options(options)?;
1499 config::read_grafts(&app_context)?;
1500
1501 Ok(app_context)
1502 }
1503
1504 pub fn from_path_and_root(
1506 path: &dyn AsRef<std::path::Path>,
1507 root: Option<&std::path::Path>,
1508 ) -> Result<Self, errors::GardenError> {
1509 let options = cli::MainOptions::new();
1510 let app_context = Self::new(options.clone());
1511 let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
1512 app_context.get_root_config_mut().update(
1513 &app_context,
1514 Some(path.as_ref()),
1515 root,
1516 config_verbose,
1517 None,
1518 )?;
1519 config::read_grafts(&app_context)?;
1521
1522 Ok(app_context)
1523 }
1524
1525 pub fn from_path(path: &dyn AsRef<std::path::Path>) -> Result<Self, errors::GardenError> {
1527 if let Some(root_dir) = path.as_ref().parent().map(std::path::Path::to_owned) {
1528 Self::from_path_and_root(path, Some(&root_dir))
1529 } else {
1530 Self::from_path_and_root(path, None)
1531 }
1532 }
1533
1534 pub fn from_path_string(path: &str) -> Result<Self, errors::GardenError> {
1536 Self::from_path(&std::path::PathBuf::from(path))
1537 }
1538
1539 pub fn from_string(string: &str) -> Result<Self, errors::GardenError> {
1541 let options = cli::MainOptions::new();
1542 let app_context = Self::new(options);
1543
1544 config::parse(&app_context, string, 0, app_context.get_root_config_mut())?;
1545 config::read_grafts(&app_context)?;
1546
1547 Ok(app_context)
1548 }
1549
1550 pub fn get_config(&self, id: ConfigId) -> &Configuration {
1551 unsafe { (*self.arena.get()).get(id).unwrap().get() }
1552 }
1553
1554 #[allow(clippy::mut_from_ref)]
1555 pub(crate) fn get_config_mut(&self, id: ConfigId) -> &mut Configuration {
1556 unsafe { (*self.arena.get()).get_mut(id).unwrap().get_mut() }
1557 }
1558
1559 pub fn get_root_id(&self) -> ConfigId {
1560 self.root_id
1561 }
1562
1563 pub fn get_root_config(&self) -> &Configuration {
1564 self.get_config(self.get_root_id())
1565 }
1566
1567 pub fn get_root_config_mut(&self) -> &mut Configuration {
1568 self.get_config_mut(self.get_root_id())
1569 }
1570
1571 pub(crate) fn add_graft(&self, parent: ConfigId, config: Configuration) -> ConfigId {
1573 let graft_id = unsafe { (*self.arena.get()).new_node(config) }; if let Some(arena) = unsafe { self.arena.get().as_mut() } {
1575 parent.append(graft_id, arena);
1576 }
1577
1578 self.get_config_mut(graft_id).set_id(graft_id);
1579
1580 graft_id
1581 }
1582
1583 pub(crate) fn add_graft_config(
1585 &self,
1586 config_id: ConfigId,
1587 graft_name: &str,
1588 path: &std::path::Path,
1589 root: Option<&std::path::Path>,
1590 ) -> Result<(), errors::GardenError> {
1591 let path = path.to_path_buf();
1592 let config_verbose = self.options.debug_level(constants::DEBUG_LEVEL_CONFIG);
1593 let mut graft_config = Configuration::new();
1594 graft_config.tree_branches = self.get_config(config_id).tree_branches;
1596 graft_config.shell_exit_on_error = self.get_config(config_id).shell_exit_on_error;
1597 graft_config.shell_word_split = self.get_config(config_id).shell_word_split;
1598 graft_config.update(self, Some(&path), root, config_verbose, Some(config_id))?;
1600
1601 let graft_id = self.add_graft(config_id, graft_config);
1603 if let Some(graft_config) = self.get_config_mut(config_id).grafts.get_mut(graft_name) {
1605 graft_config.set_id(graft_id);
1606 }
1607 config::read_grafts_recursive(self, graft_id)?;
1609
1610 Ok(())
1611 }
1612}
1613
1614#[derive(Clone, Debug, PartialEq, Eq)]
1616pub enum GitTreeType {
1617 Parent,
1619 Worktree(std::path::PathBuf),
1621 Tree,
1623 Bare,
1625}
1626
1627#[derive(Clone, Debug)]
1629pub struct GitTreeDetails {
1630 pub branch: String,
1631 pub tree_type: GitTreeType,
1632}