1use anyhow::*;
2use serde::{Deserialize, Serialize};
3use std::{fs, path};
4
5const CONFIG_CURRENT_VERSION: &str = "1.0";
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#[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 pub fn load(config_path: &path::Path) -> Result<Config> {
90 let mut config: Config = toml::from_str(&Self::read(config_path)?)
91 .context("Cannot parse the wok file")?;
92
93 if config.version == "1.0-experimental" {
95 config.version = String::from("1.0");
96 config
97 .save(config_path)
98 .context("Cannot save migrated wok file")?;
99 }
100
101 Ok(config)
102 }
103
104 pub fn read(config_path: &path::Path) -> Result<String> {
106 fs::read_to_string(config_path).context("Cannot read the wok file")
107 }
108
109 pub fn save(&self, config_path: &path::Path) -> Result<()> {
111 fs::write(config_path, self.dump()?).context("Cannot save the wok file")?;
112 Ok(())
113 }
114
115 pub fn dump(&self) -> Result<String> {
117 Ok(toml::to_string(self).context("Cannot serialize config")?)
118 }
119
120 fn has_repo_path(&self, path: &path::Path) -> bool {
121 assert!(!path.is_absolute());
122 self.repos.iter().any(|r| r.path == path)
123 }
124}
125
126impl Repo {
127 pub fn is_skipped_for(&self, command: &str) -> bool {
128 self.skip_for
129 .iter()
130 .any(|skip| skip.eq_ignore_ascii_case(command))
131 }
132}
133
134impl Default for Config {
135 fn default() -> Self {
136 Config::new()
137 }
138}