Skip to main content

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    pub vim_mode: bool,
53    pub exit_on_copy: bool,
54}
55
56impl Default for PrintConfig {
57    fn default() -> Self {
58        Self {
59            dir_color: "cyan".into(),
60            file_color: String::new(),
61            symbol_color: "bright green".into(),
62            tree_color: String::new(),
63            grep_pass_color: "bright green".into(),
64            grep_match_color: "bright red".into(),
65        }
66    }
67}
68
69impl AsRef<PrintConfig> for PrintConfig {
70    fn as_ref(&self) -> &PrintConfig {
71        self
72    }
73}
74
75impl PrintConfig {
76    pub fn none() -> Self {
77        Self {
78            dir_color: String::new(),
79            file_color: String::new(),
80            symbol_color: String::new(),
81            tree_color: String::new(),
82            grep_pass_color: String::new(),
83            grep_match_color: String::new(),
84        }
85    }
86}
87
88impl Default for ExecutableConfig {
89    fn default() -> Self {
90        Self {
91            pgp_executable: PGP_EXECUTABLE.into(),
92            editor_executable: EDITOR.into(),
93            git_executable: GIT_EXECUTABLE.into(),
94        }
95    }
96}
97
98impl Default for PathConfig {
99    fn default() -> Self {
100        let default_path = match dirs::home_dir() {
101            Some(path) => {
102                format!("{}{}.password-store", path.display(), path::MAIN_SEPARATOR)
103            }
104            None => {
105                format!(
106                    "{}{}.password-store",
107                    env::var(
108                        #[cfg(unix)]
109                        {
110                            "HOME"
111                        },
112                        #[cfg(windows)]
113                        {
114                            "USERPROFILE"
115                        }
116                    )
117                    .unwrap_or("~".into()),
118                    path::MAIN_SEPARATOR
119                )
120            }
121        };
122        PathConfig { default_repo: default_path.clone(), repos: vec![default_path] }
123    }
124}
125
126impl Default for FeatureConfig {
127    fn default() -> Self {
128        FeatureConfig {
129            clip_time: Some(45),
130            fuzzy_search: true,
131            vim_mode: false,
132            exit_on_copy: false,
133        }
134    }
135}
136
137pub fn load_config<P: AsRef<Path>>(path: P) -> Result<ParsConfig, Box<dyn Error>> {
138    let content = fs::read_to_string(path)?;
139    let config: ParsConfig = toml::from_str(&content)?;
140    Ok(config)
141}
142
143pub fn save_config<P: AsRef<Path>>(config: &ParsConfig, path: P) -> Result<(), Box<dyn Error>> {
144    let toml_str = toml::to_string_pretty(config)?;
145    fs::write(path, toml_str)?;
146    Ok(())
147}
148
149pub fn handle_env_config(config: ParsConfig) -> ParsConfig {
150    use env_var_handler::*;
151
152    let mut new_conf = config;
153    let config = &mut new_conf;
154
155    handle_clip_time(config);
156    handle_fuzzy(config);
157    handle_vim_mode(config);
158
159    new_conf
160}
161
162mod env_var_handler {
163    use super::*;
164
165    pub(super) fn handle_clip_time(config: &mut ParsConfig) {
166        if let Ok(sec_str) = env::var("PARS_CLIP_TIME") {
167            match sec_str.parse::<usize>() {
168                Ok(sec) => {
169                    config.feature_config.clip_time = {
170                        if sec == 0 {
171                            None
172                        } else {
173                            Some(sec)
174                        }
175                    }
176                }
177                Err(e) => {
178                    warn!("Parse env variable 'PARS_CLIP_TIME' met error {e}");
179                }
180            }
181        }
182    }
183
184    pub(super) fn handle_fuzzy(config: &mut ParsConfig) {
185        if env::var("PARS_NO_FUZZY").is_ok() {
186            config.feature_config.fuzzy_search = false;
187        }
188    }
189
190    pub(super) fn handle_vim_mode(config: &mut ParsConfig) {
191        if env::var("PARS_VIM_MODE").is_ok() {
192            config.feature_config.vim_mode = true;
193        }
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use pretty_assertions::assert_eq;
200
201    use super::*;
202    use crate::util::test_util::gen_unique_temp_dir;
203
204    #[test]
205    fn load_save_test() {
206        let (_temp_dir, root) = gen_unique_temp_dir();
207        let config_path = root.join("config.toml");
208
209        let test_config = ParsConfig::default();
210        save_config(&test_config, &config_path).unwrap();
211        let loaded_config = load_config(&config_path).unwrap();
212        assert_eq!(test_config, loaded_config);
213    }
214
215    #[test]
216    fn generate_default_config_test() {
217        let mut default_config = ParsConfig::default();
218        default_config.path_config.default_repo = "<Your Home>/.password-store".into();
219        default_config.path_config.repos = vec!["<Your Home>/.password-store".into()];
220        let root = env!("CARGO_MANIFEST_DIR");
221        let save_path = Path::new(root).parent().unwrap().join("config").join("cli");
222        if !save_path.exists() {
223            fs::create_dir_all(&save_path).unwrap();
224        }
225        save_config(&default_config, save_path.join("pars_config.toml"))
226            .expect("Failed to save default config");
227    }
228
229    #[test]
230    fn invalid_path_test() {
231        let test_config = ParsConfig::default();
232        let result = if cfg!(unix) {
233            save_config(&test_config, "/home/user/\0file.txt")
234        } else if cfg!(windows) {
235            save_config(&test_config, "C:\\<illegal>\\invalid.toml")
236        } else {
237            Err(Box::from("Unsupported OS"))
238        };
239
240        assert!(result.is_err());
241    }
242}