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
use std::collections::HashSet;

/// YAML reader
pub mod reader;

/// YAML writer
pub mod writer;

use crate::{errors, model, path};

/// Search for configuration in the following locations:
///  .
///  ./garden
///  ./etc/garden
///  ~/.config/garden
///  ~/etc/garden
///  /etc/garden
///  ..
///
///  Traversal continues up file system until the root is reached.
///  GARDEN_CEILING_DIRS and GIT_CEILING_DIRS can be used to define
///  directories into which garden should not travrse.

pub(crate) 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.clone();
    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);
    }

    // Calculate ceiling directories above which no commands should be run.
    let mut ceiling_dirs: HashSet<String> = HashSet::new();
    // GARDEN_CEILING_DIRS completely overrides GIT_CEILING_DIRS.
    if let Ok(garden_ceiling_dirs) = std::env::var("GARDEN_CEILING_DIRS") {
        for dirname in garden_ceiling_dirs.split(':') {
            ceiling_dirs.insert(dirname.to_string());
        }
    } else if let Ok(git_ceiling_dirs) = std::env::var("GIT_CEILING_DIRS") {
        for dirname in git_ceiling_dirs.split(':') {
            ceiling_dirs.insert(dirname.to_string());
        }
    }
    current_dir.ancestors().for_each(|ancestor| {
        let ancestor_str = ancestor.to_string_lossy().to_string();
        if ceiling_dirs.contains(&ancestor_str) {
            return;
        }
        paths.push(ancestor.to_path_buf());
    });

    paths
}

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

    #[cfg(unix)]
    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")
    }
    #[cfg(not(unix))]
    {
        home_config_dir = path::home_dir();
        home_config_dir.push(".config")
    }

    home_config_dir.push("garden");

    home_config_dir
}

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

    Ok(())
}

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

/// Read grafts into the specified configuration
pub fn read_grafts_recursive(
    app: &model::ApplicationContext,
    id: model::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 (graft_name, graft) in &config.grafts {
            let path_str = config.eval_config_path(app, &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
                )));
            }
            let root = if graft.root.is_empty() {
                None
            } else {
                Some(std::path::PathBuf::from(graft.root.clone()))
            };

            details.push((graft_name.clone(), path, root));
        }
    }

    // Read child grafts recursively after the immutable scope has ended.
    for (graft_name, path, root) in details {
        app.add_graft_config(id, &graft_name, &path, root.as_deref())?;
    }

    Ok(())
}