pars_core/config/
cli.rs

1use std::error::Error;
2use std::fs;
3use std::path::Path;
4#[allow(dead_code)]
5use std::{env, path};
6
7use log::warn;
8use serde::{Deserialize, Serialize};
9
10use crate::constants::default_constants::{EDITOR, GIT_EXECUTABLE, PGP_EXECUTABLE};
11
12#[derive(Debug, Serialize, Deserialize, Default, Eq, PartialEq)]
13#[serde(default)]
14pub struct ParsConfig {
15    #[serde(default = "PrintConfig::default")]
16    pub print_config: PrintConfig,
17    #[serde(default = "PathConfig::default")]
18    pub path_config: PathConfig,
19    #[serde(default = "ExecutableConfig::default")]
20    pub executable_config: ExecutableConfig,
21    #[serde(default = "FeatureConfig::default")]
22    pub feature_config: FeatureConfig,
23}
24
25#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
26pub struct PrintConfig {
27    pub dir_color: String,
28    pub file_color: String,
29    pub symbol_color: String,
30    pub tree_color: String,
31    pub grep_pass_color: String,
32    pub grep_match_color: String,
33}
34
35#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
36pub struct PathConfig {
37    pub default_repo: String,
38    pub repos: Vec<String>,
39}
40
41#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
42pub struct ExecutableConfig {
43    pub pgp_executable: String,
44    pub editor_executable: String,
45    pub git_executable: String,
46}
47
48#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
49pub struct FeatureConfig {
50    pub clip_time: Option<usize>,
51    pub fuzzy_search: bool,
52}
53
54impl Default for PrintConfig {
55    fn default() -> Self {
56        Self {
57            dir_color: "cyan".into(),
58            file_color: String::new(),
59            symbol_color: "bright green".into(),
60            tree_color: String::new(),
61            grep_pass_color: "bright green".into(),
62            grep_match_color: "bright red".into(),
63        }
64    }
65}
66
67impl AsRef<PrintConfig> for PrintConfig {
68    fn as_ref(&self) -> &PrintConfig {
69        self
70    }
71}
72
73impl PrintConfig {
74    pub fn none() -> Self {
75        Self {
76            dir_color: String::new(),
77            file_color: String::new(),
78            symbol_color: String::new(),
79            tree_color: String::new(),
80            grep_pass_color: String::new(),
81            grep_match_color: String::new(),
82        }
83    }
84}
85
86impl Default for ExecutableConfig {
87    fn default() -> Self {
88        Self {
89            pgp_executable: PGP_EXECUTABLE.into(),
90            editor_executable: EDITOR.into(),
91            git_executable: GIT_EXECUTABLE.into(),
92        }
93    }
94}
95
96impl Default for PathConfig {
97    fn default() -> Self {
98        let default_path = match dirs::home_dir() {
99            Some(path) => {
100                format!("{}{}.password-store", path.display(), path::MAIN_SEPARATOR)
101            }
102            None => {
103                format!(
104                    "{}{}.password-store",
105                    env::var(
106                        #[cfg(unix)]
107                        {
108                            "HOME"
109                        },
110                        #[cfg(windows)]
111                        {
112                            "USERPROFILE"
113                        }
114                    )
115                    .unwrap_or("~".into()),
116                    path::MAIN_SEPARATOR
117                )
118            }
119        };
120        PathConfig { default_repo: default_path.clone(), repos: vec![default_path] }
121    }
122}
123
124impl Default for FeatureConfig {
125    fn default() -> Self {
126        FeatureConfig { clip_time: Some(45), fuzzy_search: true }
127    }
128}
129
130pub fn load_config<P: AsRef<Path>>(path: P) -> Result<ParsConfig, Box<dyn Error>> {
131    let content = fs::read_to_string(path)?;
132    let config: ParsConfig = toml::from_str(&content)?;
133    Ok(config)
134}
135
136pub fn save_config<P: AsRef<Path>>(config: &ParsConfig, path: P) -> Result<(), Box<dyn Error>> {
137    let toml_str = toml::to_string_pretty(config)?;
138    fs::write(path, toml_str)?;
139    Ok(())
140}
141
142pub fn handle_env_config(config: ParsConfig) -> ParsConfig {
143    use env_var_handler::*;
144
145    let mut new_conf = config;
146    let config = &mut new_conf;
147
148    handle_clip_time(config);
149    handle_fuzzy(config);
150
151    new_conf
152}
153
154mod env_var_handler {
155    use super::*;
156
157    pub(super) fn handle_clip_time(config: &mut ParsConfig) {
158        if let Ok(sec_str) = env::var("PARS_CLIP_TIME") {
159            match sec_str.parse::<usize>() {
160                Ok(sec) => {
161                    config.feature_config.clip_time = {
162                        if sec == 0 {
163                            None
164                        } else {
165                            Some(sec)
166                        }
167                    }
168                }
169                Err(e) => {
170                    warn!("Parse env variable 'PARS_CLIP_TIME' met error {e}");
171                }
172            }
173        }
174    }
175
176    pub(super) fn handle_fuzzy(config: &mut ParsConfig) {
177        if env::var("PARS_NO_FUZZY").is_ok() {
178            config.feature_config.fuzzy_search = false;
179        }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use pretty_assertions::assert_eq;
186
187    use super::*;
188    use crate::util::test_util::gen_unique_temp_dir;
189
190    #[test]
191    fn load_save_test() {
192        let (_temp_dir, root) = gen_unique_temp_dir();
193        let config_path = root.join("config.toml");
194
195        let test_config = ParsConfig::default();
196        save_config(&test_config, &config_path).unwrap();
197        let loaded_config = load_config(&config_path).unwrap();
198        assert_eq!(test_config, loaded_config);
199    }
200
201    #[test]
202    fn generate_default_config_test() {
203        let mut default_config = ParsConfig::default();
204        default_config.path_config.default_repo = "<Your Home>/.password-store".into();
205        default_config.path_config.repos = vec!["<Your Home>/.password-store".into()];
206        let root = env!("CARGO_MANIFEST_DIR");
207        let save_path = Path::new(root).parent().unwrap().join("config").join("cli");
208        if !save_path.exists() {
209            fs::create_dir_all(&save_path).unwrap();
210        }
211        save_config(&default_config, save_path.join("pars_config.toml"))
212            .expect("Failed to save default config");
213    }
214
215    #[test]
216    fn invalid_path_test() {
217        let test_config = ParsConfig::default();
218        let result = if cfg!(unix) {
219            save_config(&test_config, "/home/user/\0file.txt")
220        } else if cfg!(windows) {
221            save_config(&test_config, "C:\\<illegal>\\invalid.toml")
222        } else {
223            Err(Box::from("Unsupported OS"))
224        };
225
226        assert!(result.is_err());
227    }
228}