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::new(config.root.get_expr().to_string(), None),
129 );
130
131 if let Some(config_path_raw) = config.dirname.as_ref() {
132 if let Ok(config_path) = path::canonicalize(config_path_raw) {
134 config.variables.insert(
135 string!(constants::GARDEN_CONFIG_DIR),
136 model::Variable::new(config_path.to_string_lossy().to_string(), None),
137 );
138 }
139 }
140
141 config.update_quiet_and_verbose_variables(config.quiet, 0);
146
147 if !get_variables_map(&doc[constants::VARIABLES], &mut config.variables) && config_verbose > 1 {
150 debug!("config: no variables");
151 }
152
153 let mut config_includes = Vec::new();
158 if get_vec_variables(
159 &doc[constants::GARDEN][constants::INCLUDES],
160 &mut config_includes,
161 ) {
162 for garden_include in &config_includes {
163 let pathbuf = match config.eval_config_pathbuf_from_include(
164 app_context,
165 current_include,
166 garden_include.get_expr(),
167 ) {
168 Some(pathbuf) => pathbuf,
169 None => continue,
170 };
171 if !pathbuf.exists() {
172 if config_verbose > 0 {
173 debug!(
174 "warning: garden.includes entry not found: {:?} -> {:?}",
175 garden_include, pathbuf
176 );
177 }
178 continue;
179 }
180 if pathbuf.exists() {
181 if let Ok(content) = std::fs::read_to_string(&pathbuf) {
182 parse_recursive(
183 app_context,
184 &content,
185 config_verbose,
186 config,
187 Some(&pathbuf),
188 )
189 .unwrap_or(());
190 }
191 }
192 }
193
194 if !get_variables_map(&doc[constants::VARIABLES], &mut config.variables)
197 && config_verbose > 1
198 {
199 debug!("config: no reloaded variables");
200 }
201 }
202
203 if config_verbose > 1 {
205 debug!("config: grafts");
206 }
207 if !get_grafts(&doc[constants::GRAFTS], &mut config.grafts) && config_verbose > 1 {
208 debug!("config: no grafts");
209 }
210
211 get_multivariables(&doc[constants::ENVIRONMENT], &mut config.environment);
212
213 if config_verbose > 1 {
215 debug!("config: commands");
216 }
217 if !get_multivariables_map(&doc[constants::COMMANDS], &mut config.commands)
218 && config_verbose > 1
219 {
220 debug!("config: no commands");
221 }
222
223 if config_verbose > 1 {
225 debug!("config: templates");
226 }
227 if !get_templates(
228 &doc["templates"],
229 &config.templates.clone(),
230 &mut config.templates,
231 ) && config_verbose > 1
232 {
233 debug!("config: no templates");
234 }
235
236 if config_verbose > 1 {
238 debug!("config: trees");
239 }
240 if !get_trees(app_context, config, &doc[constants::TREES]) && config_verbose > 1 {
241 debug!("config: no trees");
242 }
243
244 if config_verbose > 1 {
246 debug!("config: groups");
247 }
248 if !get_groups(&doc[constants::GROUPS], &mut config.groups) && config_verbose > 1 {
249 debug!("config: no groups");
250 }
251
252 if config_verbose > 1 {
254 debug!("config: gardens");
255 }
256 if !get_gardens(&doc[constants::GARDENS], &mut config.gardens) && config_verbose > 1 {
257 debug!("config: no gardens");
258 }
259
260 Ok(())
261}
262
263fn print_indent(indent: usize) {
265 for _ in 0..indent {
266 print!(" ");
267 }
268}
269
270fn dump_node(yaml: &Yaml, indent: usize, prefix: &str) {
272 match yaml {
273 Yaml::String(value) => {
274 print_indent(indent);
275 println!("{prefix}\"{value}\"");
276 }
277 Yaml::Array(value) => {
278 for x in value {
279 dump_node(x, indent + 1, "- ");
280 }
281 }
282 Yaml::Hash(hash) => {
283 for (k, v) in hash {
284 print_indent(indent);
285 match k {
286 Yaml::String(x) => {
287 println!("{prefix}{x}:");
288 }
289 _ => {
290 println!("{prefix}{k:?}:");
291 }
292 }
293 dump_node(v, indent + 1, prefix);
294 }
295 }
296 _ => {
297 print_indent(indent);
298 println!("{yaml:?}");
299 }
300 }
301}
302
303fn get_raw_str(yaml: &Yaml, string: &mut String) -> bool {
306 match yaml {
307 Yaml::String(yaml_string) => {
308 string.clone_from(yaml_string);
309 true
310 }
311 _ => false,
312 }
313}
314
315fn get_str(yaml: &Yaml, string: &mut String) -> bool {
318 get_raw_str(yaml, string) && !string.is_empty()
319}
320
321fn get_str_trimmed(yaml: &Yaml, string: &mut String) -> bool {
324 match yaml {
325 Yaml::String(yaml_string) => {
326 *string = yaml_string.trim_end().to_string();
327 !string.is_empty()
328 }
329 _ => false,
330 }
331}
332
333fn get_i64(yaml: &Yaml, value: &mut i64) -> bool {
335 match yaml {
336 Yaml::Integer(yaml_integer) => {
337 *value = *yaml_integer;
338 true
339 }
340 _ => false,
341 }
342}
343
344fn get_bool(yaml: &Yaml, value: &mut bool) -> bool {
346 match yaml {
347 Yaml::Boolean(yaml_bool) => {
348 *value = *yaml_bool;
349 true
350 }
351 _ => false,
352 }
353}
354
355fn get_indexset_str(yaml: &Yaml, values: &mut StringSet) -> bool {
360 match yaml {
361 Yaml::String(yaml_string) => {
362 values.insert(yaml_string.clone());
363 true
364 }
365 Yaml::Array(yaml_vec) => {
366 for value in yaml_vec {
367 if let Yaml::String(value_str) = value {
368 values.insert(value_str.clone());
369 }
370 }
371 true
372 }
373 _ => false,
374 }
375}
376
377fn get_vec_variables(yaml: &Yaml, vec: &mut Vec<model::Variable>) -> bool {
379 match yaml {
380 Yaml::String(yaml_string) => {
381 vec.push(model::Variable::new(yaml_string.clone(), None));
382 true
383 }
384 Yaml::Array(yaml_vec) => {
385 for value in yaml_vec {
386 if let Yaml::String(value_str) = value {
387 vec.push(model::Variable::new(value_str.clone(), None));
388 }
389 }
390 true
391 }
392 _ => false,
393 }
394}
395
396fn get_variable(yaml: &Yaml, value: &mut model::Variable) -> bool {
398 match yaml {
399 Yaml::String(yaml_string) => {
400 value.set_expr(yaml_string.to_string());
401 true
402 }
403 _ => false,
404 }
405}
406
407fn get_variables_map(yaml: &Yaml, map: &mut model::VariableMap) -> bool {
410 match yaml {
411 Yaml::Hash(hash) => {
412 for (k, v) in hash {
413 let key = match k.as_str() {
414 Some(key_value) => key_value.to_string(),
415 None => {
416 continue;
417 }
418 };
419 match v {
420 Yaml::String(yaml_str) => {
421 map.insert(key, model::Variable::new(yaml_str.clone(), None));
422 }
423 Yaml::Array(yaml_array) => {
424 for value in yaml_array {
425 if let Yaml::String(yaml_str) = value {
426 map.insert(
427 key.to_owned(),
428 model::Variable::new(
429 yaml_str.clone(),
430 None, ),
432 );
433 }
434 }
435 }
436 Yaml::Integer(yaml_int) => {
437 let value = yaml_int.to_string();
438 map.insert(
439 key,
440 model::Variable::new(
441 value.clone(),
442 Some(value.clone()), ),
444 );
445 }
446 Yaml::Boolean(yaml_bool) => {
447 let value = syntax::bool_to_string(*yaml_bool);
448 map.insert(
449 key,
450 model::Variable::new(
451 value.clone(),
452 Some(value.clone()), ),
454 );
455 }
456 _ => {
457 dump_node(v, 1, "");
458 error!("invalid variables");
459 }
460 }
461 }
462 true
463 }
464 _ => false,
465 }
466}
467
468fn get_multivariables(yaml: &Yaml, vec: &mut Vec<model::MultiVariable>) -> bool {
470 if let Yaml::Hash(hash) = yaml {
471 for (k, v) in hash {
472 let key = match k.as_str() {
473 Some(key_value) => key_value.to_string(),
474 None => continue,
475 };
476 match v {
477 Yaml::String(yaml_str) => {
478 let variables = vec![model::Variable::new(yaml_str.to_string(), None)];
479 vec.push(model::MultiVariable::new(key, variables));
480 }
481 Yaml::Array(yaml_array) => {
482 let mut variables = Vec::new();
483 for value in yaml_array {
484 if let Yaml::String(yaml_str) = value {
485 variables.push(model::Variable::new(yaml_str.clone(), None));
486 }
487 }
488 vec.push(model::MultiVariable::new(key, variables));
489 }
490 Yaml::Integer(yaml_int) => {
491 let value = yaml_int.to_string();
492 let variables = vec![model::Variable::new(value.clone(), Some(value))];
493 vec.push(model::MultiVariable::new(key, variables));
494 }
495 _ => {
496 dump_node(v, 1, "");
497 error!("invalid configuration");
498 }
499 }
500 }
501
502 return true;
503 }
504
505 false
506}
507
508fn get_multivariables_map(yaml: &Yaml, multivariables: &mut model::MultiVariableMap) -> bool {
510 match yaml {
511 Yaml::Hash(hash) => {
512 for (k, v) in hash {
513 let key = match k.as_str() {
514 Some(key_value) => key_value.to_string(),
515 None => continue,
516 };
517 match v {
518 Yaml::String(yaml_str) => {
519 let variables = vec![model::Variable::new(yaml_str.to_string(), None)];
520 multivariables.insert(key, variables);
521 }
522 Yaml::Array(yaml_array) => {
523 let mut variables = Vec::new();
524 for value in yaml_array {
525 if let Yaml::String(yaml_str) = value {
526 variables.push(model::Variable::new(yaml_str.clone(), None));
527 }
528 }
529 multivariables.insert(key, variables);
530 }
531 Yaml::Integer(yaml_int) => {
532 let value = yaml_int.to_string();
534 let variables = vec![model::Variable::new(value.clone(), Some(value))];
535 multivariables.insert(key, variables);
536 }
537 Yaml::Boolean(yaml_bool) => {
538 let value = syntax::bool_to_string(*yaml_bool);
540 let variables = vec![model::Variable::new(value.clone(), Some(value))];
541 multivariables.insert(key, variables);
542 }
543 _ => {
544 dump_node(v, 1, "");
545 error!("invalid variables");
546 }
547 }
548 }
549
550 true
551 }
552 _ => false,
553 }
554}
555
556fn get_templates(
558 yaml: &Yaml,
559 config_templates: &IndexMap<String, model::Template>,
560 templates: &mut IndexMap<String, model::Template>,
561) -> bool {
562 match yaml {
563 Yaml::Hash(hash) => {
564 for (name, value) in hash {
565 let template_name = match &name.as_str() {
566 Some(template_name) => template_name.to_string(),
567 None => continue,
568 };
569 templates.insert(
570 template_name,
571 get_template(name, value, config_templates, yaml),
572 );
573 }
574 true
575 }
576 _ => false,
577 }
578}
579
580fn get_template(
582 name: &Yaml,
583 value: &Yaml,
584 config_templates: &IndexMap<String, model::Template>,
585 templates: &Yaml,
586) -> model::Template {
587 let mut template = model::Template::default();
588 get_str(name, template.get_name_mut());
589
590 {
591 let mut url = String::new();
592 if get_str(value, &mut url) {
597 template
598 .tree
599 .remotes
600 .insert(string!(constants::ORIGIN), model::Variable::new(url, None));
601 return template;
602 }
603 if get_str(&value[constants::URL], &mut url) {
606 template
607 .tree
608 .remotes
609 .insert(string!(constants::ORIGIN), model::Variable::new(url, None));
610 }
611 }
612
613 get_indexset_str(&value[constants::EXTEND], &mut template.extend);
618 for template_name in &template.extend {
619 if let Yaml::Hash(_) = templates[template_name.as_ref()] {
622 let base = get_template(
623 &Yaml::String(template_name.clone()),
624 &templates[template_name.as_ref()],
625 config_templates,
626 templates,
627 );
628
629 base.apply(&mut template.tree);
630 } else {
631 if let Some(base) = config_templates.get(template_name) {
636 base.apply(&mut template.tree);
637 }
638 }
639 template.tree.templates.truncate(0);
641 }
642
643 get_tree_fields(value, &mut template.tree);
644
645 template
646}
647
648fn get_trees(
650 app_context: &model::ApplicationContext,
651 config: &mut model::Configuration,
652 yaml: &Yaml,
653) -> bool {
654 match yaml {
655 Yaml::Hash(hash) => {
656 for (name, value) in hash {
657 if let Yaml::String(url) = value {
658 let tree = get_tree_from_url(name, url);
660 if let Some(current_tree) = config.trees.get_mut(tree.get_name()) {
661 current_tree.clone_from_tree(&tree);
662 } else {
663 config.trees.insert(tree.get_name().to_string(), tree);
664 }
665 } else {
666 let tree = get_tree(app_context, config, name, value, hash, true);
667
668 let replace = match value[constants::REPLACE] {
671 Yaml::Boolean(value) => value,
672 _ => false,
673 };
674
675 let current_tree_opt = config.trees.get_mut(tree.get_name());
676 match current_tree_opt {
677 Some(current_tree) if !replace => {
678 current_tree.clone_from_tree(&tree);
679 }
680 _ => {
681 config.trees.insert(tree.get_name().to_string(), tree);
682 }
683 }
684 }
685 }
686 true
687 }
688 _ => false,
689 }
690}
691
692fn get_tree_from_url(name: &Yaml, url: &str) -> model::Tree {
694 let mut tree = model::Tree::default();
695
696 get_str(name, tree.get_name_mut());
698 let tree_name = tree.get_name().to_string();
700 tree.get_path_mut().set_expr(tree_name.to_string());
701 tree.get_path().set_value(tree_name);
702 tree.add_builtin_variables();
703 if syntax::is_git_dir(tree.get_path().get_expr()) {
704 tree.is_bare_repository = true;
705 }
706 tree.remotes.insert(
707 string!(constants::ORIGIN),
708 model::Variable::new(url.to_string(), None),
709 );
710
711 tree
712}
713
714#[inline]
716fn get_tree_fields(value: &Yaml, tree: &mut model::Tree) {
717 get_variables_map(&value[constants::VARIABLES], &mut tree.variables);
718 get_multivariables_map(&value[constants::GITCONFIG], &mut tree.gitconfig);
719 get_str(&value[constants::DEFAULT_REMOTE], &mut tree.default_remote);
720 get_str_trimmed(&value[constants::DESCRIPTION], &mut tree.description);
721 get_str_variables_map(&value[constants::REMOTES], &mut tree.remotes);
722 get_vec_variables(&value[constants::LINKS], &mut tree.links);
723
724 get_multivariables(&value[constants::ENVIRONMENT], &mut tree.environment);
725 get_multivariables_map(&value[constants::COMMANDS], &mut tree.commands);
726
727 get_variable(&value[constants::BRANCH], &mut tree.branch);
728 get_variables_map(&value[constants::BRANCHES], &mut tree.branches);
729 get_variable(&value[constants::SYMLINK], &mut tree.symlink);
730 get_variable(&value[constants::WORKTREE], &mut tree.worktree);
731
732 get_i64(&value[constants::DEPTH], &mut tree.clone_depth);
733 get_bool(&value[constants::BARE], &mut tree.is_bare_repository);
734 get_bool(&value[constants::SINGLE_BRANCH], &mut tree.is_single_branch);
735
736 {
738 let mut url = String::new();
739 if get_str(&value[constants::URL], &mut url) {
740 tree.remotes.insert(
741 tree.default_remote.to_string(),
742 model::Variable::new(url, None),
743 );
744 }
745 }
746
747 tree.update_flags();
748}
749
750fn get_tree(
752 app_context: &model::ApplicationContext,
753 config: &mut model::Configuration,
754 name: &Yaml,
755 value: &Yaml,
756 trees: &yaml::Hash,
757 variables: bool,
758) -> model::Tree {
759 let mut tree = model::Tree::default();
761
762 let mut extend = String::new();
764 if get_str(&value[constants::EXTEND], &mut extend) {
765 let tree_name = Yaml::String(extend.clone());
767 if let Some(tree_values) = trees.get(&tree_name) {
768 let base_tree = get_tree(app_context, config, &tree_name, tree_values, trees, false);
769 tree.clone_from_tree(&base_tree);
770 } else {
771 if let Some(base) = config.get_tree(&extend) {
773 tree.clone_from_tree(base);
774 }
775 }
776 tree.templates.truncate(0); }
778
779 let mut parent_expr = String::new();
781 if get_str(&value[constants::WORKTREE], &mut parent_expr) {
782 let parent_name = eval::value(app_context, config, &parent_expr);
783 if !parent_expr.is_empty() {
784 let tree_name = Yaml::String(parent_name);
785 if let Some(tree_values) = trees.get(&tree_name) {
786 let base = get_tree(app_context, config, &tree_name, tree_values, trees, true);
787 tree.clone_from_tree(&base);
788 }
789 }
790 tree.templates.truncate(0); }
792
793 get_indexset_str(&value[constants::TEMPLATES], &mut tree.templates);
797 for template_name in &tree.templates.clone() {
798 if let Some(template) = config.templates.get(template_name) {
800 template.apply(&mut tree);
801 }
802 }
803
804 get_str(name, tree.get_name_mut());
806
807 if !get_str(&value[constants::PATH], tree.get_path_mut().get_expr_mut()) {
809 let tree_name = tree.get_name().to_string();
811 tree.get_path_mut().set_expr(tree_name.to_string());
812 tree.get_path().set_value(tree_name);
813 }
814
815 if syntax::is_git_dir(tree.get_path().get_expr()) {
817 tree.is_bare_repository = true;
818 }
819
820 if variables {
821 tree.add_builtin_variables();
822 }
823
824 get_tree_fields(value, &mut tree);
825
826 tree
827}
828
829fn get_str_variables_map(yaml: &Yaml, remotes: &mut model::VariableMap) {
831 let hash = match yaml {
832 Yaml::Hash(hash) => hash,
833 _ => return,
834 };
835 for (name, value) in hash {
836 if let (Some(name_str), Some(value_str)) = (name.as_str(), value.as_str()) {
837 remotes.insert(
838 name_str.to_string(),
839 model::Variable::new(value_str.to_string(), None),
840 );
841 }
842 }
843}
844
845fn get_groups(yaml: &Yaml, groups: &mut IndexMap<model::GroupName, model::Group>) -> bool {
847 match yaml {
848 Yaml::Hash(hash) => {
849 for (name, value) in hash {
850 let mut group = model::Group::default();
851 get_str(name, group.get_name_mut());
852 get_indexset_str(value, &mut group.members);
853 groups.insert(group.get_name_owned(), group);
854 }
855 true
856 }
857 _ => false,
858 }
859}
860
861fn get_gardens(yaml: &Yaml, gardens: &mut IndexMap<String, model::Garden>) -> bool {
863 match yaml {
864 Yaml::Hash(hash) => {
865 for (name, value) in hash {
866 let mut garden = model::Garden::default();
867 get_str(name, garden.get_name_mut());
868 get_indexset_str(&value[constants::GROUPS], &mut garden.groups);
869 get_indexset_str(&value[constants::TREES], &mut garden.trees);
870 get_multivariables_map(&value[constants::GITCONFIG], &mut garden.gitconfig);
871 get_variables_map(&value[constants::VARIABLES], &mut garden.variables);
872 get_multivariables(&value[constants::ENVIRONMENT], &mut garden.environment);
873 get_multivariables_map(&value[constants::COMMANDS], &mut garden.commands);
874 gardens.insert(garden.get_name().to_string(), garden);
875 }
876 true
877 }
878 _ => false,
879 }
880}
881
882fn get_grafts(yaml: &Yaml, grafts: &mut IndexMap<model::GardenName, model::Graft>) -> bool {
885 match yaml {
886 Yaml::Hash(yaml_hash) => {
887 for (name, value) in yaml_hash {
888 let graft = get_graft(name, value);
889 grafts.insert(graft.get_name().to_string(), graft);
890 }
891 true
892 }
893 _ => false,
894 }
895}
896
897fn get_graft(name: &Yaml, graft: &Yaml) -> model::Graft {
899 let mut graft_name = String::new();
900 let mut config = String::new();
901 let mut root = String::new();
902
903 get_str(name, &mut graft_name);
904
905 if !get_str(graft, &mut config) {
906 if let Yaml::Hash(_hash) = graft {
908 get_str(&graft[constants::CONFIG], &mut config);
910 get_str(&graft[constants::ROOT], &mut root);
911 }
912 }
913
914 model::Graft::new(graft_name, root, config)
915}
916
917pub fn read_yaml<P>(path: P) -> Result<Yaml, errors::GardenError>
919where
920 P: std::convert::AsRef<std::path::Path> + std::fmt::Debug,
921{
922 let string =
923 std::fs::read_to_string(&path).map_err(|io_err| errors::GardenError::ReadFile {
924 path: path.as_ref().into(),
925 err: io_err,
926 })?;
927
928 let docs =
929 YamlLoader::load_from_str(&string).map_err(|err| errors::GardenError::ReadConfig {
930 err,
931 path: path.as_ref().display().to_string(),
932 })?;
933 if docs.is_empty() {
934 return Err(errors::GardenError::EmptyConfiguration {
935 path: path.as_ref().into(),
936 });
937 }
938
939 Ok(docs[0].clone())
940}
941
942pub fn empty_doc() -> Yaml {
944 Yaml::Hash(yaml::Hash::new())
945}
946
947pub(crate) fn add_section(key: &str, doc: &mut Yaml) -> Result<(), errors::GardenError> {
949 let exists = doc[key].as_hash().is_some();
950 if !exists {
951 if let Yaml::Hash(doc_hash) = doc {
952 let key = Yaml::String(key.to_string());
953 doc_hash.insert(key, Yaml::Hash(yaml::Hash::new()));
954 } else {
955 return Err(errors::GardenError::InvalidConfiguration {
956 msg: "document is not a hash".into(),
957 });
958 }
959 }
960
961 Ok(())
962}