1use crate::store::Store;
5
6#[derive(Debug, Clone, PartialEq)]
7pub struct Config {
8 pub staleness_days: u64,
9 pub green_exit_code: i32,
10 pub staleness_ref: String, pub brief_limit: usize,
12}
13
14impl Default for Config {
15 fn default() -> Self {
16 Config {
17 staleness_days: 7,
18 green_exit_code: 0,
19 staleness_ref: "live-origin".into(),
20 brief_limit: 10,
21 }
22 }
23}
24
25fn value_of<'a>(text: &'a str, key: &str) -> Option<&'a str> {
27 text.lines().find_map(|line| {
28 let (k, v) = line.split_once('=')?;
29 (k.trim() == key).then_some(v.trim())
30 })
31}
32
33fn unquote(s: &str) -> &str {
34 s.strip_prefix('"')
35 .and_then(|x| x.strip_suffix('"'))
36 .unwrap_or(s)
37}
38
39pub fn schema_version(store: &Store) -> u64 {
43 let text = std::fs::read_to_string(store.config_path()).unwrap_or_default();
44 value_of(&text, "schema_version")
45 .and_then(|v| v.parse().ok())
46 .unwrap_or(1)
47}
48
49pub fn read(store: &Store) -> Config {
51 let text = std::fs::read_to_string(store.config_path()).unwrap_or_default();
52 let d = Config::default();
53 Config {
54 staleness_days: value_of(&text, "staleness_days")
55 .and_then(|v| v.parse().ok())
56 .unwrap_or(d.staleness_days),
57 green_exit_code: value_of(&text, "green_exit_code")
58 .and_then(|v| v.parse().ok())
59 .unwrap_or(d.green_exit_code),
60 staleness_ref: value_of(&text, "staleness_ref")
61 .map(|v| unquote(v).to_string())
62 .unwrap_or(d.staleness_ref),
63 brief_limit: value_of(&text, "brief_limit")
64 .and_then(|v| v.parse().ok())
65 .unwrap_or(d.brief_limit),
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::store::Store;
73
74 fn store() -> (std::path::PathBuf, Store) {
75 use std::sync::atomic::{AtomicU64, Ordering};
76 static N: AtomicU64 = AtomicU64::new(0);
77 let p = std::env::temp_dir().join(format!(
78 "ev-config-{}-{}",
79 std::process::id(),
80 N.fetch_add(1, Ordering::Relaxed)
81 ));
82 let _ = std::fs::remove_dir_all(&p);
83 std::fs::create_dir_all(&p).unwrap();
84 let s = Store::at(&p);
85 s.init().unwrap();
86 (p, s)
87 }
88
89 #[test]
90 fn read_should_parse_every_key_when_the_config_sets_them() {
91 let (_p, s) = store();
93 std::fs::write(
94 s.config_path(),
95 "[runner]\ngreen_exit_code = 1\n\n[liveness]\nstaleness_days = 3\nstaleness_ref = \"local-head\"\n",
96 )
97 .unwrap();
98
99 let c = read(&s);
101
102 assert_eq!(c.staleness_days, 3);
104 assert_eq!(c.green_exit_code, 1);
105 assert_eq!(c.staleness_ref, "local-head");
106 }
107
108 #[test]
109 fn read_should_parse_brief_limit_when_present() {
110 let (_p, s) = store();
112 std::fs::write(s.config_path(), "brief_limit = 5\n").unwrap();
113
114 let c = read(&s);
116
117 assert_eq!(c.brief_limit, 5);
119 }
120
121 #[test]
122 fn read_should_use_defaults_when_the_keys_are_absent() {
123 let (_p, s) = store();
125 std::fs::write(s.config_path(), "schema_version = 1\n").unwrap();
126
127 let c = read(&s);
129
130 assert_eq!(c, Config::default());
132 }
133
134 #[test]
135 fn read_should_not_match_a_longer_key_when_a_prefix_collides() {
136 let (_p, s) = store();
138 std::fs::write(s.config_path(), "staleness_days_extra = 99\n").unwrap();
139
140 let c = read(&s);
142
143 assert_eq!(c.staleness_days, 7);
145 }
146
147 #[test]
148 fn schema_version_should_read_the_declared_version_when_present() {
149 let (_p, s) = store();
151
152 let v = schema_version(&s);
154
155 assert_eq!(v, 1);
157 }
158
159 #[test]
160 fn schema_version_should_default_to_one_when_the_key_is_absent() {
161 let (_p, s) = store();
163 std::fs::write(s.config_path(), "brief_limit = 5\n").unwrap();
164
165 let v = schema_version(&s);
167
168 assert_eq!(v, 1);
170 }
171
172 #[test]
173 fn read_should_equal_the_defaults_for_a_freshly_initialized_store() {
174 let (_p, s) = store();
176
177 let c = read(&s);
179
180 assert_eq!(c, Config::default());
182 }
183}