use std::error::Error;
use std::fs;
use std::path::Path;
#[allow(dead_code)]
use std::{env, path};
use log::warn;
use serde::{Deserialize, Serialize};
use crate::constants::default_constants::{EDITOR, GIT_EXECUTABLE, PGP_EXECUTABLE};
#[derive(Debug, Serialize, Deserialize, Default, Eq, PartialEq)]
#[serde(default)]
pub struct ParsConfig {
#[serde(default = "PrintConfig::default")]
pub print_config: PrintConfig,
#[serde(default = "PathConfig::default")]
pub path_config: PathConfig,
#[serde(default = "ExecutableConfig::default")]
pub executable_config: ExecutableConfig,
#[serde(default = "FeatureConfig::default")]
pub feature_config: FeatureConfig,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct PrintConfig {
pub dir_color: String,
pub file_color: String,
pub symbol_color: String,
pub tree_color: String,
pub grep_pass_color: String,
pub grep_match_color: String,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct PathConfig {
pub default_repo: String,
pub repos: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct ExecutableConfig {
pub pgp_executable: String,
pub editor_executable: String,
pub git_executable: String,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct FeatureConfig {
pub clip_time: Option<usize>,
pub fuzzy_search: bool,
}
impl Default for PrintConfig {
fn default() -> Self {
Self {
dir_color: "cyan".into(),
file_color: String::new(),
symbol_color: "bright green".into(),
tree_color: String::new(),
grep_pass_color: "bright green".into(),
grep_match_color: "bright red".into(),
}
}
}
impl AsRef<PrintConfig> for PrintConfig {
fn as_ref(&self) -> &PrintConfig {
self
}
}
impl PrintConfig {
pub fn none() -> Self {
Self {
dir_color: String::new(),
file_color: String::new(),
symbol_color: String::new(),
tree_color: String::new(),
grep_pass_color: String::new(),
grep_match_color: String::new(),
}
}
}
impl Default for ExecutableConfig {
fn default() -> Self {
Self {
pgp_executable: PGP_EXECUTABLE.into(),
editor_executable: EDITOR.into(),
git_executable: GIT_EXECUTABLE.into(),
}
}
}
impl Default for PathConfig {
fn default() -> Self {
let default_path = match dirs::home_dir() {
Some(path) => {
format!("{}{}.password-store", path.display(), path::MAIN_SEPARATOR)
}
None => {
format!(
"{}{}.password-store",
env::var(
#[cfg(unix)]
{
"HOME"
},
#[cfg(windows)]
{
"USERPROFILE"
}
)
.unwrap_or("~".into()),
path::MAIN_SEPARATOR
)
}
};
PathConfig { default_repo: default_path.clone(), repos: vec![default_path] }
}
}
impl Default for FeatureConfig {
fn default() -> Self {
FeatureConfig { clip_time: Some(45), fuzzy_search: true }
}
}
pub fn load_config<P: AsRef<Path>>(path: P) -> Result<ParsConfig, Box<dyn Error>> {
let content = fs::read_to_string(path)?;
let config: ParsConfig = toml::from_str(&content)?;
Ok(config)
}
pub fn save_config<P: AsRef<Path>>(config: &ParsConfig, path: P) -> Result<(), Box<dyn Error>> {
let toml_str = toml::to_string_pretty(config)?;
fs::write(path, toml_str)?;
Ok(())
}
pub fn handle_env_config(config: ParsConfig) -> ParsConfig {
use env_var_handler::*;
let mut new_conf = config;
let config = &mut new_conf;
handle_clip_time(config);
handle_fuzzy(config);
new_conf
}
mod env_var_handler {
use super::*;
pub(super) fn handle_clip_time(config: &mut ParsConfig) {
if let Ok(sec_str) = env::var("PARS_CLIP_TIME") {
match sec_str.parse::<usize>() {
Ok(sec) => {
config.feature_config.clip_time = {
if sec == 0 {
None
} else {
Some(sec)
}
}
}
Err(e) => {
warn!("Parse env variable 'PARS_CLIP_TIME' met error {e}");
}
}
}
}
pub(super) fn handle_fuzzy(config: &mut ParsConfig) {
if env::var("PARS_NO_FUZZY").is_ok() {
config.feature_config.fuzzy_search = false;
}
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::util::test_util::gen_unique_temp_dir;
#[test]
fn load_save_test() {
let (_temp_dir, root) = gen_unique_temp_dir();
let config_path = root.join("config.toml");
let test_config = ParsConfig::default();
save_config(&test_config, &config_path).unwrap();
let loaded_config = load_config(&config_path).unwrap();
assert_eq!(test_config, loaded_config);
}
#[test]
fn generate_default_config_test() {
let mut default_config = ParsConfig::default();
default_config.path_config.default_repo = "<Your Home>/.password-store".into();
default_config.path_config.repos = vec!["<Your Home>/.password-store".into()];
let root = env!("CARGO_MANIFEST_DIR");
let save_path = Path::new(root).parent().unwrap().join("config").join("cli");
if !save_path.exists() {
fs::create_dir_all(&save_path).unwrap();
}
save_config(&default_config, save_path.join("pars_config.toml"))
.expect("Failed to save default config");
}
#[test]
fn invalid_path_test() {
let test_config = ParsConfig::default();
let result = if cfg!(unix) {
save_config(&test_config, "/home/user/\0file.txt")
} else if cfg!(windows) {
save_config(&test_config, "C:\\<illegal>\\invalid.toml")
} else {
Err(Box::from("Unsupported OS"))
};
assert!(result.is_err());
}
}