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}