1use anyhow::Result;
2use clap::{Parser, ValueHint};
3use yaml_rust::{yaml, Yaml};
4
5use crate::{cli, cmds::plant, config, constants, errors, git, model, path};
6
7#[derive(Parser, Clone, Debug)]
8#[command(author, about, long_about)]
9pub struct InitOptions {
10 #[arg(long)]
12 pub empty: bool,
13 #[arg(long, short)]
15 pub force: bool,
16 #[arg(long)]
18 pub global: bool,
19 #[arg(long, default_value_t = string!(constants::GARDEN_CONFIG_DIR_EXPR), value_hint = ValueHint::DirPath)]
21 pub root: String,
22 #[arg(default_value = constants::GARDEN_CONFIG, value_hint = ValueHint::FilePath)]
24 pub filename: std::path::PathBuf,
25}
26
27pub fn main(options: &cli::MainOptions, init_options: &mut InitOptions) -> Result<()> {
28 let mut dirname = path::current_dir();
29 let file_path = &init_options.filename;
30 if file_path.is_absolute() {
31 if init_options.global {
32 return Err(errors::GardenError::Usage(
33 "'--global' cannot be used with an absolute path".into(),
34 )
35 .into());
36 }
37
38 dirname = file_path
39 .parent()
40 .ok_or_else(|| {
41 errors::GardenError::AssertionError(format!(
42 "unable to get parent(): {file_path:?}"
43 ))
44 })?
45 .to_path_buf();
46
47 init_options.filename =
48 std::path::PathBuf::from(file_path.file_name().ok_or_else(|| {
49 errors::GardenError::AssertionError(format!(
50 "unable to get file path: {file_path:?}"
51 ))
52 })?);
53 }
54 if init_options.global {
55 dirname = config::xdg_dir();
56 }
57
58 let mut config_path = dirname.clone();
59 config_path.push(&init_options.filename);
60
61 if !init_options.force && config_path.exists() {
62 let error_message = format!(
63 "{:?} already exists, use \"--force\" to overwrite",
64 config_path.to_string_lossy()
65 );
66 return Err(errors::GardenError::FileExists(error_message).into());
67 }
68
69 let parent = config_path
71 .parent()
72 .ok_or_else(|| {
73 errors::GardenError::AssertionError(format!("unable to get parent(): {config_path:?}"))
74 })?
75 .to_path_buf();
76
77 if !parent.exists() {
78 if let Err(err) = std::fs::create_dir_all(&parent) {
79 let error_message = format!("unable to create {parent:?}: {err}");
80 return Err(errors::GardenError::OSError(error_message).into());
81 }
82 }
83
84 let exists = config_path.exists();
86
87 let mut doc = if exists {
89 config::reader::read_yaml(&config_path)?
90 } else {
91 config::reader::empty_doc()
92 };
93
94 let mut config = model::Configuration::new();
95 config.root =
96 model::Variable::from_expr(constants::ROOT.to_string(), init_options.root.clone());
97 config.root_path.clone_from(&dirname);
98 config.path = Some(config_path.clone());
99
100 let mut done = false;
101 if !init_options.empty && init_options.root == constants::GARDEN_CONFIG_DIR_EXPR {
102 let git_worktree = git::current_worktree_path(&dirname);
103 if let Ok(worktree) = git_worktree {
104 config::reader::add_section(constants::TREES, &mut doc)?;
105 if let Yaml::Hash(ref mut doc_hash) = doc {
106 let trees_key = Yaml::String(constants::TREES.into());
107 if let Some(Yaml::Hash(trees)) = doc_hash.get_mut(&trees_key) {
108 if let Ok(tree_name) =
109 plant::plant_path(None, &config, options.verbose, &worktree, trees)
110 {
111 done = true;
112 if config.root_path.to_string_lossy() == worktree {
115 if let Some(Yaml::Hash(tree_entry)) = trees.get_mut(&tree_name) {
116 tree_entry.insert(
117 Yaml::String(constants::PATH.to_string()),
118 Yaml::String(constants::GARDEN_CONFIG_DIR_EXPR.to_string()),
119 );
120 }
121 }
122 }
123 }
124 }
125 }
126 }
127
128 if !done || init_options.root != constants::GARDEN_CONFIG_DIR_EXPR {
130 config::reader::add_section(constants::GARDEN, &mut doc)?;
131 if let Yaml::Hash(ref mut doc_hash) = doc {
132 let garden_key = Yaml::String(constants::GARDEN.into());
133 let garden: &mut yaml::Hash = match doc_hash.get_mut(&garden_key) {
134 Some(Yaml::Hash(ref mut hash)) => hash,
135 _ => {
136 return Err(errors::GardenError::InvalidConfiguration {
137 msg: "invalid configuration: 'garden' is not a hash".into(),
138 }
139 .into());
140 }
141 };
142
143 let root_key = Yaml::String(constants::ROOT.into());
144 garden.insert(root_key, Yaml::String(init_options.root.clone()));
145 }
146 }
147
148 config::writer::write_yaml(&doc, &config_path)?;
149
150 if !options.quiet {
151 if exists {
152 eprintln!("Reinitialized Garden configuration in {config_path:?}");
153 } else {
154 eprintln!("Initialized Garden configuration in {config_path:?}");
155 }
156 }
157
158 Ok(())
159}