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}