1use std::path::PathBuf;
2use std::sync::RwLock;
3use dirs;
4use toml;
5use serde::Deserialize;
6use lazy_static::lazy_static;
7
8use crate::util::*;
9
10const DEF_CONFIG_PATH : &str = ".config/todor/todor.toml";
11const DATA_BASE : &str = ".local/share/todor";
12const DEF_CONFIG_CONTENT: &str = r#"# config for todor in toml
13
14## base directory for todor data
15basedir = "~/.local/share/todor"
16
17## blink the icons of items or not
18blink = true
19"#;
20
21lazy_static! {
22 pub static ref CONFIG: RwLock<Config> = RwLock::new(Config::load(None));
23}
24
25pub fn get_default_basedir() -> String {
26 let rel_base :PathBuf = DATA_BASE.split("/").collect();
28 dirs::home_dir()
29 .expect("cannot get home dir")
30 .join(rel_base)
31 .to_str()
32 .expect("cannot convert path to string")
33 .to_string()
34}
35
36#[derive(Deserialize, Debug)]
37pub struct Config {
38 pub basedir: Option<String>,
40
41 pub blink: Option<bool>,
43}
44
45impl Default for Config {
46 fn default() -> Self {
47 Config {
48 basedir: Some(get_default_basedir()),
49 blink: Some(true),
50 }
51 }
52}
53
54impl Config {
55 pub fn update_with(&mut self, aconf: &Config) {
56 if let Some(basedir) = &aconf.basedir {
57 self.basedir = Some(basedir.clone());
58 }
59
60 if let Some(blink) = aconf.blink {
61 self.blink = Some(blink);
62 }
63 }
64
65 pub fn load(path_str: Option<String>) -> Self {
66 let mut work_conf = Config::default();
67
68 let confp;
69 if let Some(path_str) = path_str {
70 confp = PathBuf::from(util::path_normalize(&path_str));
71 if !confp.exists() {
72 eprintln!("config file not found, ignore and use defaults");
73 return work_conf;
74 }
75 } else {
76 let rel_base :PathBuf = DEF_CONFIG_PATH.split("/").collect();
77 confp = dirs::home_dir()
78 .expect("cannot get home dir")
79 .join(rel_base);
80
81 if !confp.exists() {
82 std::fs::create_dir_all(confp.parent().unwrap())
83 .expect("Failed to create base directory");
84
85 std::fs::write(confp.clone(), DEF_CONFIG_CONTENT)
86 .expect("cannot create config file");
87 }
88 }
89
90 let mut conf :Config = toml::from_str(
91 &std::fs::read_to_string(&confp)
92 .expect("cannot read config file"))
93 .expect("cannot parse config file");
94 if let Some(basedir) = conf.basedir {
95 conf.basedir = Some(util::path_normalize(&basedir))
96 }
97
98 work_conf.update_with(&conf);
99 work_conf
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_get_default_basedir() {
109 assert!(get_default_basedir().contains(".local/share/todor"));
110 }
111
112 #[test]
113 fn test_config_load() {
114 let temp_dir = tempfile::tempdir().unwrap();
115 let testtoml = temp_dir.path().join("config.toml");
116 let testcontent = r#"basedir = "/tmp/.todor-test/"
117 blink = false
118 "#;
119 std::fs::write(&testtoml, testcontent).expect("write err");
120 let conf = Config::load(Some(testtoml.to_str().unwrap().into()));
121 assert_eq!(conf.basedir, Some("/tmp/.todor-test/".into()));
122 assert_eq!(conf.blink, Some(false));
123 }
124
125 #[test]
126 fn test_config_default() {
127 let conf = Config::default();
128 assert_eq!(conf.basedir, Some(get_default_basedir()));
129 assert_eq!(conf.blink, Some(true));
130 }
131
132 #[test]
133 fn test_config_update() {
134 let mut conf = Config::default();
135 let aconf = Config {
136 basedir: Some("/nowhere".into()),
137 blink: Some(false),
138 };
139 conf.update_with(&aconf);
140 assert_eq!(conf.basedir, Some("/nowhere".into()));
141 assert_eq!(conf.blink, Some(false));
142 }
143}