Skip to main content

garden/cmds/
init.rs

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    /// Do not add any trees when initializing
11    #[arg(long)]
12    pub empty: bool,
13    /// Overwrite existing config files
14    #[arg(long, short)]
15    pub force: bool,
16    /// Use the user-wide configuration directory (~/.config/garden/garden.yaml)
17    #[arg(long)]
18    pub global: bool,
19    /// Set the garden root path
20    #[arg(long, default_value_t = string!(constants::GARDEN_CONFIG_DIR_EXPR), value_hint = ValueHint::DirPath)]
21    pub root: String,
22    /// Config filename to write
23    #[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    // Create parent directories as needed
70    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    // Does the config file already exist?
85    let exists = config_path.exists();
86
87    // Read or create a new document
88    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 the config path is the same as the tree's worktree path then
113                        // set the tree's "path" field to ${GARDEN_CONFIG_DIR}.
114                        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    // Mutable scope
129    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}