1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
use super::errors;
use super::model;
use super::model::ConfigId;
use super::path;

/// YAML reader
pub mod reader;

/// YAML writer
pub mod writer;

// Search for configuration in the following locations:
//  .
//  ./garden
//  ./etc/garden
//  ~/.config/garden
//  ~/etc/garden
//  /etc/garden

fn search_path() -> Vec<std::path::PathBuf> {
    // Result: Vec<PathBufs> in priority order
    let mut paths: Vec<std::path::PathBuf> = Vec::new();

    let current_dir = path::current_dir();
    let home_dir = path::home_dir();

    // . Current directory
    paths.push(current_dir.clone());

    // ./garden
    let mut current_garden_dir = current_dir.clone();
    current_garden_dir.push("garden");
    if current_garden_dir.exists() {
        paths.push(current_garden_dir);
    }

    // ./etc/garden
    let mut current_etc_garden_dir = current_dir;
    current_etc_garden_dir.push("etc");
    current_etc_garden_dir.push("garden");
    if current_etc_garden_dir.exists() {
        paths.push(current_etc_garden_dir);
    }

    // $XDG_CONFIG_HOME/garden (typically ~/.config/garden)
    paths.push(xdg_dir());

    // ~/etc/garden
    let mut home_etc_dir = home_dir;
    home_etc_dir.push("etc");
    home_etc_dir.push("garden");
    if home_etc_dir.exists() {
        paths.push(home_etc_dir);
    }

    // /etc/garden
    let etc_garden = std::path::PathBuf::from("/etc/garden");
    if etc_garden.exists() {
        paths.push(etc_garden);
    }

    paths
}

/// $XDG_CONFIG_HOME/garden (typically ~/.config/garden)
pub fn xdg_dir() -> std::path::PathBuf {
    let mut home_config_dir;

    if let Ok(xdg_dirs) = xdg::BaseDirectories::new() {
        home_config_dir = xdg_dirs.get_config_home();
    } else {
        home_config_dir = path::home_dir();
        home_config_dir.push(".config")
    }
    home_config_dir.push("garden");

    home_config_dir
}

pub fn new(
    config: &Option<std::path::PathBuf>,
    root: &str,
    verbose: bool,
    parent: Option<ConfigId>,
) -> Result<model::Configuration, errors::GardenError> {
    let mut cfg = model::Configuration::new();
    if let Some(parent_id) = parent {
        cfg.set_parent(parent_id);
    }
    cfg.verbose = verbose;

    // Override the configured garden root
    if !root.is_empty() {
        cfg.root.set_expr(root.to_string());
    }

    let mut basename: String = "garden.yaml".into();

    // Find garden.yaml in the search path
    let mut found = false;
    if let Some(config_path) = config {
        if config_path.is_file() || config_path.is_absolute() {
            // If an absolute path was specified, or if the file exists,
            // short-circuit the search; the config file might be missing but
            // we shouldn't silently use a different config file.
            cfg.set_path(config_path.to_path_buf());
            found = true;
        } else {
            // The specified path is a basename or relative path to be found
            // in the config search path.
            basename = config_path.to_string_lossy().into();
        }
    }

    if !found {
        for entry in search_path() {
            let mut candidate = entry.to_path_buf();
            candidate.push(basename.clone());
            if candidate.exists() {
                cfg.set_path(candidate);
                found = true;
                break;
            }
        }
    }
    if verbose {
        debug!(
            "config: path: {:?}, root: {:?}, found: {}",
            cfg.path, cfg.root, found
        );
    }

    if found {
        // Read file contents.
        let config_path = cfg.get_path()?;
        let config_string = match std::fs::read_to_string(config_path) {
            Ok(content) => content,
            // Return a default Configuration If we are unable to read the file.
            Err(_) => return Ok(cfg),
        };

        parse(&config_string, verbose, &mut cfg)?;
    }

    // Default to the current directory when garden.root is unspecified
    if cfg.root.get_expr().is_empty() {
        cfg.root.set_expr(path::current_dir_string());
    }

    Ok(cfg)
}

/// Read configuration from a path.  Wraps new() to make the path required..
pub fn from_path(
    path: std::path::PathBuf,
    root: &str,
    verbose: bool,
    parent: Option<ConfigId>,
) -> Result<model::Configuration, errors::GardenError> {
    new(&Some(path), root, verbose, parent)
}

/// Read configuration from a path string.  Wraps from_path() to simplify usage.
pub fn from_path_string(
    path: &str,
    verbose: bool,
) -> Result<model::Configuration, errors::GardenError> {
    from_path(std::path::PathBuf::from(path), "", verbose, None)
}

/// Create a model::Configuration instance from model::CommandOptions
pub fn from_options(
    options: &model::CommandOptions,
) -> Result<model::Configuration, errors::GardenError> {
    let config_verbose = options.is_debug("config::new");
    let mut config = new(&options.filename, &options.root, config_verbose, None)?;

    if config.path.is_none() {
        error!("unable to find a configuration file -- use --config <path>");
    }
    if options.is_debug("config") {
        eprintln!("config: {:?}", config.get_path()?);
        debug!("{}", config);
    }

    for k_eq_v in &options.variables {
        let name: String;
        let expr: String;
        let values: Vec<&str> = k_eq_v.splitn(2, '=').collect();
        if values.len() == 1 {
            name = values[0].into();
            expr = "".into();
        } else if values.len() == 2 {
            name = values[0].into();
            expr = values[1].into();
        } else {
            error!("unable to split '{}'", k_eq_v);
        }
        config
            .variables
            .insert(0, model::NamedVariable::new(name, expr, None));
    }

    Ok(config)
}

/// Parse and apply configuration from a YAML/JSON string
pub fn parse(
    config_string: &str,
    verbose: bool,
    cfg: &mut model::Configuration,
) -> Result<(), errors::GardenError> {
    reader::parse(&config_string, verbose, cfg)?;
    // Initialize the configuration now that the values have been read.
    cfg.initialize();

    Ok(())
}

/// Read grafts into the root configuration on down.
pub fn read_grafts(app: &mut model::ApplicationContext) -> Result<(), errors::GardenError> {
    let root_id = app.get_root_id();
    read_grafts_recursive(app, root_id)
}

/// Read grafts into the specified configuration
fn read_grafts_recursive(
    app: &mut model::ApplicationContext,
    id: ConfigId,
) -> Result<(), errors::GardenError> {
    // Defer the recursive calls to avoid an immutable borrow from preventing us from
    // recursively taking an immutable borrow.
    //
    // We build a vector of paths inside an immutable scope and defer construction of
    // the graft Configuration since it requires a mutable borrow against app.
    let mut details = Vec::new();

    // Immutable scope for traversing the configuration.
    {
        let config = app.get_config(id); // Immutable borrow.
        for (idx, graft) in config.grafts.iter().enumerate() {
            let path_str = config.eval_config_path(&graft.config);
            let path = std::path::PathBuf::from(&path_str);
            if !path.exists() {
                let config_path = config.get_path()?;
                return Err(errors::GardenError::ConfigurationError(format!(
                    "{}: invalid graft in {:?}",
                    graft.get_name(),
                    config_path
                )));
            }
            details.push((idx, path, graft.root.to_string()));
        }
    }

    // Read child grafts recursively after the immutable scope has ended.
    let verbose = app.options.verbose;
    for (idx, path, root) in details {
        // Read the Configuration referenced by the graft.
        let graft_config = from_path(path, &root, verbose, Some(id))?;
        // The app Arena takes ownershp of the Configuration.
        let graft_id = app.add_graft(id, graft_config);
        // Record the config ID in the graft structure.
        app.get_config_mut(id).grafts[idx].set_id(graft_id);
        // Read child grafts recursively.
        read_grafts_recursive(app, graft_id)?;
    }

    Ok(())
}