git_wok/
config.rs

1use anyhow::*;
2use serde::{Deserialize, Serialize};
3use std::{fs, path};
4
5const CONFIG_CURRENT_VERSION: &str = "1.0-experimental";
6
7#[derive(Serialize, Deserialize, Debug, Clone)]
8#[serde(deny_unknown_fields)]
9pub struct Repo {
10    pub path: path::PathBuf,
11    pub head: String,
12    #[serde(default, skip_serializing_if = "Vec::is_empty")]
13    pub skip_for: Vec<String>,
14}
15
16/// Config schema for `wok.toml`
17///
18/// A repository containing `wok.toml` file serves as an "umbrella" repo for a
19/// workspace containing several repos.
20#[derive(Serialize, Deserialize, Debug)]
21#[serde(deny_unknown_fields)]
22pub struct Config {
23    pub version: String,
24    #[serde(rename = "repo")]
25    pub repos: Vec<Repo>,
26}
27
28impl Config {
29    pub fn new() -> Self {
30        Config {
31            version: String::from(CONFIG_CURRENT_VERSION),
32            repos: vec![],
33        }
34    }
35
36    pub fn add_repo(&mut self, path: &path::Path, head: &str) -> bool {
37        assert!(!path.is_absolute());
38
39        if self.has_repo_path(path) {
40            return false;
41        }
42
43        self.repos.push(Repo {
44            path: path::PathBuf::from(path),
45            head: String::from(head),
46            skip_for: vec![],
47        });
48        true
49    }
50
51    pub fn remove_repo(&mut self, path: &path::Path) -> bool {
52        assert!(!path.is_absolute());
53
54        let mut removed = false;
55        self.repos.retain(|r| {
56            if r.path != path {
57                return true;
58            }
59            removed = true;
60            false
61        });
62        removed
63    }
64
65    pub fn set_repo_head(&mut self, path: &path::Path, head: &String) -> bool {
66        assert!(!path.is_absolute());
67
68        let mut updated = false;
69        self.repos = self
70            .repos
71            .iter()
72            .map(|r| -> Repo {
73                let mut r = r.to_owned();
74                if r.path != path {
75                    return r;
76                }
77                if r.head == *head {
78                    return r;
79                }
80                r.head = head.to_owned();
81                updated = true;
82                r
83            })
84            .collect();
85        updated
86    }
87
88    /// Loads the workspace config from a file at the `config_path`.
89    pub fn load(config_path: &path::Path) -> Result<Config> {
90        let config = toml::from_str(&Self::read(config_path)?)
91            .context("Cannot parse the wok file")?;
92        Ok(config)
93    }
94
95    /// Reads the config file into a string (useful mainly for testing).
96    pub fn read(config_path: &path::Path) -> Result<String> {
97        fs::read_to_string(config_path).context("Cannot read the wok file")
98    }
99
100    /// Saves the workspace config to a file.
101    pub fn save(&self, config_path: &path::Path) -> Result<()> {
102        fs::write(config_path, self.dump()?).context("Cannot save the wok file")?;
103        Ok(())
104    }
105
106    /// Returns config as TOML string (useful mainly for testing).
107    pub fn dump(&self) -> Result<String> {
108        Ok(toml::to_string(self).context("Cannot serialize config")?)
109    }
110
111    fn has_repo_path(&self, path: &path::Path) -> bool {
112        assert!(!path.is_absolute());
113        self.repos.iter().any(|r| r.path == path)
114    }
115}
116
117impl Repo {
118    pub fn is_skipped_for(&self, command: &str) -> bool {
119        self.skip_for
120            .iter()
121            .any(|skip| skip.eq_ignore_ascii_case(command))
122    }
123}
124
125impl Default for Config {
126    fn default() -> Self {
127        Config::new()
128    }
129}