1use yaml_rust::{yaml, Yaml, YamlLoader};
2
3use crate::{
4 constants, errors, eval, model,
5 model::{IndexMap, StringSet},
6 path, syntax,
7};
8
9pub fn parse(
11 app_context: &model::ApplicationContext,
12 string: &str,
13 config_verbose: u8,
14 config: &mut model::Configuration,
15) -> Result<(), errors::GardenError> {
16 parse_recursive(app_context, string, config_verbose, config, None)
17}
18
19fn parse_recursive(
21 app_context: &model::ApplicationContext,
22 string: &str,
23 config_verbose: u8,
24 config: &mut model::Configuration,
25 current_include: Option<&std::path::Path>,
26) -> Result<(), errors::GardenError> {
27 let docs =
28 YamlLoader::load_from_str(string).map_err(|scan_err| errors::GardenError::ReadConfig {
29 err: scan_err,
30 path: config.get_path_for_display(),
31 })?;
32 if docs.is_empty() {
33 return Err(errors::GardenError::EmptyConfiguration {
34 path: config.get_path()?.into(),
35 });
36 }
37 let doc = &docs[0];
38
39 if config_verbose > 2 {
41 dump_node(doc, 1, "");
42 }
43
44 if config.root.is_empty()
49 && !config.root_is_dynamic
50 && get_raw_str(
51 &doc[constants::GARDEN][constants::ROOT],
52 config.root.get_expr_mut(),
53 )
54 {
55 if config.root.is_empty() {
56 config.root_is_dynamic = true;
59 }
60 if config_verbose > 0 {
61 debug!("config: garden.root = {}", config.root.get_expr());
62 }
63 }
64
65 if get_str(&doc[constants::GARDEN][constants::SHELL], &mut config.shell) && config_verbose > 0 {
67 debug!("config: {} = {}", constants::GARDEN_SHELL, config.shell);
68 }
69 if get_str(
71 &doc[constants::GARDEN][constants::INTERACTIVE_SHELL],
72 &mut config.interactive_shell,
73 ) && config_verbose > 0
74 {
75 debug!(
76 "config: {} = {}",
77 constants::GARDEN_INTERACTIVE_SHELL,
78 config.interactive_shell
79 );
80 }
81
82 if get_bool(
84 &doc[constants::GARDEN][constants::SHELL_ERREXIT],
85 &mut config.shell_exit_on_error,
86 ) && config_verbose > 0
87 {
88 debug!(
89 "config: {} = {}",
90 constants::GARDEN_SHELL_ERREXIT,
91 config.shell_exit_on_error
92 );
93 }
94 if get_bool(
96 &doc[constants::GARDEN][constants::SHELL_WORDSPLIT],
97 &mut config.shell_word_split,
98 ) && config_verbose > 0
99 {
100 debug!(
101 "config: {} = {}",
102 constants::GARDEN_SHELL_WORDSPLIT,
103 config.shell_word_split
104 );
105 }
106 if get_bool(
108 &doc[constants::GARDEN][constants::TREE_BRANCHES],
109 &mut config.tree_branches,
110 ) && config_verbose > 0
111 {
112 debug!(
113 "config: {} = {}",
114 constants::GARDEN_TREE_BRANCHES,
115 config.tree_branches
116 );
117 }
118
119 if config_verbose > 1 {
123 debug!("config: built-in variables");
124 }
125 config.variables.insert(
127 string!(constants::GARDEN_ROOT),
128 model::Variable::from_expr(
129 constants::GARDEN_ROOT.to_string(),
130 config.root.get_expr().to_string(),
131 ),
132 );
133
134 if let Some(config_path_raw) = config.dirname.as_ref() {
135 if let Ok(config_path) = path::canonicalize(config_path_raw) {
137 config.variables.insert(
138 string!(constants::GARDEN_CONFIG_DIR),
139 model::Variable::from_expr(
140 constants::GARDEN_CONFIG_DIR.to_string(),
141 config_path.to_string_lossy().to_string(),
142 ),
143 );
144 }
145 }
146
147 config.update_quiet_and_verbose_variables(config.quiet, 0);
152
153 if !get_variables_map(&doc[constants::VARIABLES], &mut config.variables) && config_verbose > 1 {
156 debug!("config: no variables");
157 }
158
159 let mut config_includes = Vec::new();
164 if get_vec_variables(
165 constants::INCLUDES,
166 &doc[constants::GARDEN][constants::INCLUDES],
167 &mut config_includes,
168 ) {
169 for garden_include in &config_includes {
170 let pathbuf = match config.eval_config_pathbuf_from_include(
171 app_context,
172 current_include,
173 garden_include.get_expr(),
174 ) {
175 Some(pathbuf) => pathbuf,
176 None => continue,
177 };
178 if !pathbuf.exists() {
179 if config_verbose > 0 {
180 debug!(
181 "warning: garden.includes entry not found: {:?} -> {:?}",
182 garden_include, pathbuf
183 );
184 }
185 continue;
186 }
187 if pathbuf.exists() {
188 if let Ok(content) = std::fs::read_to_string(&pathbuf) {
189 parse_recursive(
190 app_context,
191 &content,
192 config_verbose,
193 config,
194 Some(&pathbuf),
195 )
196 .unwrap_or(());
197 }
198 }
199 }
200
201 if !get_variables_map(&doc[constants::VARIABLES], &mut config.variables)
204 && config_verbose > 1
205 {
206 debug!("config: no reloaded variables");
207 }
208 }
209
210 if config_verbose > 1 {
212 debug!("config: grafts");
213 }
214 if !get_grafts(&doc[constants::GRAFTS], &mut config.grafts) && config_verbose > 1 {
215 debug!("config: no grafts");
216 }
217
218 get_multivariables(&doc[constants::ENVIRONMENT], &mut config.environment);
219
220 if config_verbose > 1 {
222 debug!("config: commands");
223 }
224 if !get_multivariables_map(&doc[constants::COMMANDS], &mut config.commands)
225 && config_verbose > 1
226 {
227 debug!("config: no commands");
228 }
229
230 if config_verbose > 1 {
232 debug!("config: templates");
233 }
234 if !get_templates(
235 &doc["templates"],
236 &config.templates.clone(),
237 &mut config.templates,
238 ) && config_verbose > 1
239 {
240 debug!("config: no templates");
241 }
242
243 if config_verbose > 1 {
245 debug!("config: trees");
246 }
247 if !get_trees(app_context, config, &doc[constants::TREES]) && config_verbose > 1 {
248 debug!("config: no trees");
249 }
250
251 if config_verbose > 1 {
253 debug!("config: groups");
254 }
255 if !get_groups(&doc[constants::GROUPS], &mut config.groups) && config_verbose > 1 {
256 debug!("config: no groups");
257 }
258
259 if config_verbose > 1 {
261 debug!("config: gardens");
262 }
263 if !get_gardens(&doc[constants::GARDENS], &mut config.gardens) && config_verbose > 1 {
264 debug!("config: no gardens");
265 }
266
267 Ok(())
268}
269
270fn print_indent(indent: usize) {
272 for _ in 0..indent {
273 print!(" ");
274 }
275}
276
277fn dump_node(yaml: &Yaml, indent: usize, prefix: &str) {
279 match yaml {
280 Yaml::String(value) => {
281 print_indent(indent);
282 println!("{prefix}\"{value}\"");
283 }
284 Yaml::Array(value) => {
285 for x in value {
286 dump_node(x, indent + 1, "- ");
287 }
288 }
289 Yaml::Hash(hash) => {
290 for (k, v) in hash {
291 print_indent(indent);
292 match k {
293 Yaml::String(x) => {
294 println!("{prefix}{x}:");
295 }
296 _ => {
297 println!("{prefix}{k:?}:");
298 }
299 }
300 dump_node(v, indent + 1, prefix);
301 }
302 }
303 _ => {
304 print_indent(indent);
305 println!("{yaml:?}");
306 }
307 }
308}
309
310fn get_raw_str(yaml: &Yaml, string: &mut String) -> bool {
313 match yaml {
314 Yaml::String(yaml_string) => {
315 string.clone_from(yaml_string);
316 true
317 }
318 _ => false,
319 }
320}
321
322fn get_str(yaml: &Yaml, string: &mut String) -> bool {
325 get_raw_str(yaml, string) && !string.is_empty()
326}
327
328fn get_str_trimmed(yaml: &Yaml, string: &mut String) -> bool {
331 match yaml {
332 Yaml::String(yaml_string) => {
333 *string = yaml_string.trim_end().to_string();
334 !string.is_empty()
335 }
336 _ => false,
337 }
338}
339
340fn get_i64(yaml: &Yaml, value: &mut i64) -> bool {
342 match yaml {
343 Yaml::Integer(yaml_integer) => {
344 *value = *yaml_integer;
345 true
346 }
347 _ => false,
348 }
349}
350
351fn get_bool(yaml: &Yaml, value: &mut bool) -> bool {
353 match yaml {
354 Yaml::Boolean(yaml_bool) => {
355 *value = *yaml_bool;
356 true
357 }
358 _ => false,
359 }
360}
361
362fn get_indexset_str(yaml: &Yaml, values: &mut StringSet) -> bool {
367 match yaml {
368 Yaml::String(yaml_string) => {
369 values.insert(yaml_string.clone());
370 true
371 }
372 Yaml::Array(yaml_vec) => {
373 for value in yaml_vec {
374 if let Yaml::String(value_str) = value {
375 values.insert(value_str.clone());
376 }
377 }
378 true
379 }
380 _ => false,
381 }
382}
383
384fn variable_from_yaml(name: String, yaml: &Yaml) -> Option<model::Variable> {
386 match yaml {
387 Yaml::String(yaml_str) => Some(model::Variable::from_expr(name, yaml_str.to_string())),
388 Yaml::Array(yaml_array) => {
389 yaml_array
391 .iter()
392 .next_back()
393 .and_then(|yaml_value| variable_from_yaml(name, yaml_value))
394 }
395 Yaml::Integer(yaml_int) => {
396 let int_value = yaml_int.to_string();
398
399 Some(model::Variable::from_resolved_expr(name, int_value))
400 }
401 Yaml::Boolean(yaml_bool) => {
402 let bool_value = syntax::bool_to_string(*yaml_bool);
404
405 Some(model::Variable::from_resolved_expr(name, bool_value))
406 }
407 Yaml::Hash(yaml_hash) => {
408 let expr = yaml_hash
409 .get(&Yaml::String(constants::VALUE.to_string()))
410 .and_then(|v| v.as_str())
411 .unwrap_or_default()
412 .to_string();
413 let required = yaml_hash
414 .get(&Yaml::String(constants::REQUIRED.to_string()))
415 .and_then(|v| v.as_bool())
416 .unwrap_or(false);
417
418 Some(model::Variable::new(name, expr, required))
419 }
420 _ => {
421 None
423 }
424 }
425}
426
427fn get_variable(name: String, yaml: &Yaml, value: &mut model::Variable) -> bool {
429 if let Some(variable) = variable_from_yaml(name, yaml) {
430 *value = variable;
431
432 true
433 } else {
434 false
435 }
436}
437
438fn get_vec_variables(name: &str, yaml: &Yaml, vec: &mut Vec<model::Variable>) -> bool {
440 if let Yaml::Array(yaml_array) = yaml {
441 for value in yaml_array {
442 if let Some(variable) = variable_from_yaml(name.to_string(), value) {
443 vec.push(variable);
444 }
445 }
446 return true;
447 }
448
449 if let Some(variable) = variable_from_yaml(name.to_string(), yaml) {
450 vec.push(variable);
451 return true;
452 }
453
454 false
455}
456
457fn get_variables_map(yaml: &Yaml, map: &mut model::VariableMap) -> bool {
460 match yaml {
461 Yaml::Hash(hash) => {
462 for (k, v) in hash {
463 let key = match k.as_str() {
464 Some(key_value) => key_value.to_string(),
465 None => {
466 continue;
467 }
468 };
469 if let Some(variable) = variable_from_yaml(key.to_string(), v) {
470 map.insert(key, variable);
471 }
472 }
473 true
474 }
475 _ => false,
476 }
477}
478
479fn get_multivariables(yaml: &Yaml, vec: &mut Vec<model::MultiVariable>) -> bool {
481 if let Yaml::Hash(hash) = yaml {
482 for (k, v) in hash {
483 let key = match k.as_str() {
484 Some(key_value) => key_value.to_string(),
485 None => continue,
486 };
487 if let Yaml::Array(yaml_array) = v {
489 let mut variables = Vec::new();
490 for value in yaml_array {
491 if let Some(variable) = variable_from_yaml(key.to_string(), value) {
492 variables.push(variable);
493 }
494 }
495 vec.push(model::MultiVariable::new(key, variables));
496 continue;
497 }
498
499 if let Some(variable) = variable_from_yaml(key.to_string(), v) {
500 let variables = vec![variable];
501 vec.push(model::MultiVariable::new(key, variables));
502 }
503 }
504
505 return true;
506 }
507
508 false
509}
510
511fn get_multivariables_map(yaml: &Yaml, multivariables: &mut model::MultiVariableMap) -> bool {
513 match yaml {
514 Yaml::Hash(hash) => {
515 for (k, v) in hash {
516 let key = match k.as_str() {
517 Some(key_value) => key_value.to_string(),
518 None => continue,
519 };
520 if let Yaml::Array(yaml_array) = v {
521 let mut variables = Vec::new();
522 for value in yaml_array {
523 if let Some(variable) = variable_from_yaml(key.to_string(), value) {
524 variables.push(variable);
525 }
526 }
527 multivariables.insert(key, variables);
528 continue;
529 }
530
531 if let Some(variable) = variable_from_yaml(key.to_string(), v) {
532 let variables = vec![variable];
533 multivariables.insert(key, variables);
534 }
535 }
536 true
537 }
538 _ => false,
539 }
540}
541
542fn get_templates(
544 yaml: &Yaml,
545 config_templates: &IndexMap<String, model::Template>,
546 templates: &mut IndexMap<String, model::Template>,
547) -> bool {
548 match yaml {
549 Yaml::Hash(hash) => {
550 for (name, value) in hash {
551 let template_name = match &name.as_str() {
552 Some(template_name) => template_name.to_string(),
553 None => continue,
554 };
555 templates.insert(
556 template_name,
557 get_template(name, value, config_templates, yaml),
558 );
559 }
560 true
561 }
562 _ => false,
563 }
564}
565
566fn get_template(
568 name: &Yaml,
569 value: &Yaml,
570 config_templates: &IndexMap<String, model::Template>,
571 templates: &Yaml,
572) -> model::Template {
573 let mut template = model::Template::default();
574 get_str(name, template.get_name_mut());
575
576 {
577 let mut url = String::new();
578 if get_str(value, &mut url) {
583 template.tree.remotes.insert(
584 constants::ORIGIN.to_string(),
585 model::Variable::from_expr(constants::ORIGIN.to_string(), url),
586 );
587 return template;
588 }
589 if get_str(&value[constants::URL], &mut url) {
592 template.tree.remotes.insert(
593 string!(constants::ORIGIN),
594 model::Variable::from_expr(constants::URL.to_string(), url),
595 );
596 }
597 }
598
599 get_indexset_str(&value[constants::EXTEND], &mut template.extend);
604 for template_name in &template.extend {
605 if let Yaml::Hash(_) = templates[template_name.as_ref()] {
608 let base = get_template(
609 &Yaml::String(template_name.clone()),
610 &templates[template_name.as_ref()],
611 config_templates,
612 templates,
613 );
614
615 base.apply(&mut template.tree);
616 } else {
617 if let Some(base) = config_templates.get(template_name) {
622 base.apply(&mut template.tree);
623 }
624 }
625 template.tree.templates.truncate(0);
627 }
628
629 get_tree_fields(value, &mut template.tree);
630
631 template
632}
633
634fn get_trees(
636 app_context: &model::ApplicationContext,
637 config: &mut model::Configuration,
638 yaml: &Yaml,
639) -> bool {
640 match yaml {
641 Yaml::Hash(hash) => {
642 for (name, value) in hash {
643 if let Yaml::String(url) = value {
644 let tree = get_tree_from_url(name, url);
646 if let Some(current_tree) = config.trees.get_mut(tree.get_name()) {
647 current_tree.clone_from_tree(&tree);
648 } else {
649 config.trees.insert(tree.get_name().to_string(), tree);
650 }
651 } else {
652 let tree = get_tree(app_context, config, name, value, hash, true);
653
654 let replace = match value[constants::REPLACE] {
657 Yaml::Boolean(value) => value,
658 _ => false,
659 };
660
661 let current_tree_opt = config.trees.get_mut(tree.get_name());
662 match current_tree_opt {
663 Some(current_tree) if !replace => {
664 current_tree.clone_from_tree(&tree);
665 }
666 _ => {
667 config.trees.insert(tree.get_name().to_string(), tree);
668 }
669 }
670 }
671 }
672 true
673 }
674 _ => false,
675 }
676}
677
678fn get_tree_from_url(name: &Yaml, url: &str) -> model::Tree {
680 let mut tree = model::Tree::default();
681
682 get_str(name, tree.get_name_mut());
684 let tree_name = tree.get_name().to_string();
686 tree.get_path_mut().set_expr(tree_name.to_string());
687 tree.get_path().set_value(tree_name);
688 tree.add_builtin_variables();
689 if syntax::is_git_dir(tree.get_path().get_expr()) {
690 tree.is_bare_repository = true;
691 }
692 tree.remotes.insert(
693 constants::ORIGIN.to_string(),
694 model::Variable::from_expr(constants::ORIGIN.to_string(), url.to_string()),
695 );
696
697 tree
698}
699
700#[inline]
702fn get_tree_fields(value: &Yaml, tree: &mut model::Tree) {
703 get_variables_map(&value[constants::VARIABLES], &mut tree.variables);
704 get_multivariables_map(&value[constants::GITCONFIG], &mut tree.gitconfig);
705 get_str(&value[constants::DEFAULT_REMOTE], &mut tree.default_remote);
706 get_str_trimmed(&value[constants::DESCRIPTION], &mut tree.description);
707 get_str_variables_map(&value[constants::REMOTES], &mut tree.remotes);
708 get_vec_variables(constants::LINKS, &value[constants::LINKS], &mut tree.links);
709
710 get_multivariables(&value[constants::ENVIRONMENT], &mut tree.environment);
711 get_multivariables_map(&value[constants::COMMANDS], &mut tree.commands);
712
713 get_variable(
714 constants::BRANCH.to_string(),
715 &value[constants::BRANCH],
716 &mut tree.branch,
717 );
718 get_variables_map(&value[constants::BRANCHES], &mut tree.branches);
719 get_variable(
720 constants::SYMLINK.to_string(),
721 &value[constants::SYMLINK],
722 &mut tree.symlink,
723 );
724 get_variable(
725 constants::WORKTREE.to_string(),
726 &value[constants::WORKTREE],
727 &mut tree.worktree,
728 );
729
730 get_i64(&value[constants::DEPTH], &mut tree.clone_depth);
731 get_bool(&value[constants::BARE], &mut tree.is_bare_repository);
732 get_bool(&value[constants::SINGLE_BRANCH], &mut tree.is_single_branch);
733
734 {
736 let mut url = String::new();
737 if get_str(&value[constants::URL], &mut url) {
738 tree.remotes.insert(
739 tree.default_remote.to_string(),
740 model::Variable::from_expr(constants::URL.to_string(), url),
741 );
742 }
743 }
744
745 tree.update_flags();
746}
747
748fn get_tree(
750 app_context: &model::ApplicationContext,
751 config: &mut model::Configuration,
752 name: &Yaml,
753 value: &Yaml,
754 trees: &yaml::Hash,
755 variables: bool,
756) -> model::Tree {
757 let mut tree = model::Tree::default();
759
760 let mut extend = String::new();
762 if get_str(&value[constants::EXTEND], &mut extend) {
763 let tree_name = Yaml::String(extend.clone());
765 if let Some(tree_values) = trees.get(&tree_name) {
766 let base_tree = get_tree(app_context, config, &tree_name, tree_values, trees, false);
767 tree.clone_from_tree(&base_tree);
768 } else {
769 if let Some(base) = config.get_tree(&extend) {
771 tree.clone_from_tree(base);
772 }
773 }
774 tree.templates.truncate(0); }
776
777 let mut parent_expr = String::new();
779 if get_str(&value[constants::WORKTREE], &mut parent_expr) {
780 let parent_name = eval::value(app_context, config, &parent_expr);
781 if !parent_expr.is_empty() {
782 let tree_name = Yaml::String(parent_name);
783 if let Some(tree_values) = trees.get(&tree_name) {
784 let base = get_tree(app_context, config, &tree_name, tree_values, trees, true);
785 tree.clone_from_tree(&base);
786 }
787 }
788 tree.templates.truncate(0); }
790
791 get_indexset_str(&value[constants::TEMPLATES], &mut tree.templates);
795 for template_name in &tree.templates.clone() {
796 if let Some(template) = config.templates.get(template_name) {
798 template.apply(&mut tree);
799 }
800 }
801
802 get_str(name, tree.get_name_mut());
804
805 if !get_str(&value[constants::PATH], tree.get_path_mut().get_expr_mut()) {
807 let tree_name = tree.get_name().to_string();
809 tree.get_path_mut().set_expr(tree_name.to_string());
810 tree.get_path().set_value(tree_name);
811 }
812
813 if syntax::is_git_dir(tree.get_path().get_expr()) {
815 tree.is_bare_repository = true;
816 }
817
818 if variables {
819 tree.add_builtin_variables();
820 }
821
822 get_tree_fields(value, &mut tree);
823
824 tree
825}
826
827fn get_str_variables_map(yaml: &Yaml, remotes: &mut model::VariableMap) {
829 let hash = match yaml {
830 Yaml::Hash(hash) => hash,
831 _ => return,
832 };
833 for (name, value) in hash {
834 if let (Some(name_str), Some(value_str)) = (name.as_str(), value.as_str()) {
835 remotes.insert(
836 name_str.to_string(),
837 model::Variable::from_expr(name_str.to_string(), value_str.to_string()),
838 );
839 }
840 }
841}
842
843fn get_groups(yaml: &Yaml, groups: &mut IndexMap<model::GroupName, model::Group>) -> bool {
845 match yaml {
846 Yaml::Hash(hash) => {
847 for (name, value) in hash {
848 let mut group = model::Group::default();
849 get_str(name, group.get_name_mut());
850 get_indexset_str(value, &mut group.members);
851 groups.insert(group.get_name_owned(), group);
852 }
853 true
854 }
855 _ => false,
856 }
857}
858
859fn get_gardens(yaml: &Yaml, gardens: &mut IndexMap<String, model::Garden>) -> bool {
861 match yaml {
862 Yaml::Hash(hash) => {
863 for (name, value) in hash {
864 let mut garden = model::Garden::default();
865 get_str(name, garden.get_name_mut());
866 get_indexset_str(&value[constants::GROUPS], &mut garden.groups);
867 get_indexset_str(&value[constants::TREES], &mut garden.trees);
868 get_multivariables_map(&value[constants::GITCONFIG], &mut garden.gitconfig);
869 get_variables_map(&value[constants::VARIABLES], &mut garden.variables);
870 get_multivariables(&value[constants::ENVIRONMENT], &mut garden.environment);
871 get_multivariables_map(&value[constants::COMMANDS], &mut garden.commands);
872 gardens.insert(garden.get_name().to_string(), garden);
873 }
874 true
875 }
876 _ => false,
877 }
878}
879
880fn get_grafts(yaml: &Yaml, grafts: &mut IndexMap<model::GardenName, model::Graft>) -> bool {
883 match yaml {
884 Yaml::Hash(yaml_hash) => {
885 for (name, value) in yaml_hash {
886 let graft = get_graft(name, value);
887 grafts.insert(graft.get_name().to_string(), graft);
888 }
889 true
890 }
891 _ => false,
892 }
893}
894
895fn get_graft(name: &Yaml, graft: &Yaml) -> model::Graft {
897 let mut graft_name = String::new();
898 let mut config = String::new();
899 let mut root = String::new();
900
901 get_str(name, &mut graft_name);
902
903 if !get_str(graft, &mut config) {
904 if let Yaml::Hash(_hash) = graft {
906 get_str(&graft[constants::CONFIG], &mut config);
908 get_str(&graft[constants::ROOT], &mut root);
909 }
910 }
911
912 model::Graft::new(graft_name, root, config)
913}
914
915pub fn read_yaml<P>(path: P) -> Result<Yaml, errors::GardenError>
917where
918 P: std::convert::AsRef<std::path::Path> + std::fmt::Debug,
919{
920 let string =
921 std::fs::read_to_string(&path).map_err(|io_err| errors::GardenError::ReadFile {
922 path: path.as_ref().into(),
923 err: io_err,
924 })?;
925
926 let docs =
927 YamlLoader::load_from_str(&string).map_err(|err| errors::GardenError::ReadConfig {
928 err,
929 path: path.as_ref().display().to_string(),
930 })?;
931 if docs.is_empty() {
932 return Err(errors::GardenError::EmptyConfiguration {
933 path: path.as_ref().into(),
934 });
935 }
936
937 Ok(docs[0].clone())
938}
939
940pub fn empty_doc() -> Yaml {
942 Yaml::Hash(yaml::Hash::new())
943}
944
945pub(crate) fn add_section(key: &str, doc: &mut Yaml) -> Result<(), errors::GardenError> {
947 let exists = doc[key].as_hash().is_some();
948 if !exists {
949 if let Yaml::Hash(doc_hash) = doc {
950 let key = Yaml::String(key.to_string());
951 doc_hash.insert(key, Yaml::Hash(yaml::Hash::new()));
952 } else {
953 return Err(errors::GardenError::InvalidConfiguration {
954 msg: "document is not a hash".into(),
955 });
956 }
957 }
958
959 Ok(())
960}