use crate::{error::Error, settings::Settings, store::Store};
use serde::Deserialize;
use std::{fs, path};
use toml;
pub const LOGFILE_NAME: &str = "jolly.toml";
pub struct Config {
pub settings: Settings,
pub store: Result<Store, Error>,
}
impl Default for Config {
fn default() -> Self {
Self {
settings: Settings::default(),
store: Err(Error::CustomError("".to_string())),
}
}
}
impl Config {
pub fn load() -> Self {
match get_logfile().map(load_path) {
Ok(config) => config.unwrap_or_else(|e| Self {
settings: Settings::default(),
store: Err(e),
}),
Err(e) => Self {
settings: Settings::default(),
store: Err(e),
},
}
}
}
fn get_logfile() -> Result<path::PathBuf, Error> {
let local_path = path::Path::new(LOGFILE_NAME);
if local_path.exists() {
return Ok(local_path.to_path_buf());
}
let config_dir = dirs::config_dir().ok_or(Error::CustomError(
"Cannot Determine Config Dir".to_string(),
))?;
let config_path = config_dir.join(LOGFILE_NAME);
if config_path.exists() {
Ok(config_path)
} else {
Err(Error::CustomError(format!("Cannot find {}", LOGFILE_NAME)))
}
}
fn load_path<P: AsRef<path::Path>>(path: P) -> Result<Config, Error> {
let txt = fs::read_to_string(path).map_err(Error::IoError)?;
load_txt(&txt)
}
fn load_txt(txt: &str) -> Result<Config, Error> {
let value: toml::Value = toml::from_str(txt).map_err(|e| Error::ParseError(e.to_string()))?;
let mut parsed_config = match value {
toml::Value::Table(t) => t,
_ => return Err(Error::ParseError("entry is not a Table".to_string())),
};
let mut settings = match parsed_config.remove("config") {
Some(config) => {
Settings::deserialize(config).map_err(|e| Error::ParseError(e.to_string()))?
}
None => Settings::default(),
};
settings.ui.propagate();
let store = Store::build(parsed_config.into_iter()).map_err(Error::StoreError);
Ok(Config { settings, store })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_file_is_valid() {
let config = load_txt("").unwrap();
assert_eq!(config.settings, Settings::default());
}
#[test]
fn partial_settings_uses_default() {
let toml = r#"[config]
ui = {width = 42 }"#;
let config = load_txt(toml).unwrap();
assert_eq!(
config.settings.ui.search.padding,
Settings::default().ui.search.padding
);
assert_ne!(config.settings.ui.width, Settings::default().ui.width);
}
#[test]
fn invalid_entry_keeps_non_default_settings() {
let toml = r#"a = 1
[config.ui]
width = 42"#;
let config = load_txt(toml).unwrap();
assert_ne!(config.settings, Settings::default());
assert!(matches!(config.store, Err(Error::StoreError(_))));
}
#[test]
fn extraneous_setting_allowed() {
let toml = r#"[config]
not_a_real_setting = 42"#;
let config = load_txt(toml).unwrap();
assert_eq!(config.settings, Settings::default());
}
#[test]
fn nonexistent_path() {
let result = load_path("nonexistentfile.toml");
assert!(matches!(result, Err(Error::IoError(_))));
}
#[test]
fn child_settings_override() {
let toml = r#"[config.ui.search]
text_size = 42"#;
let settings = load_txt(toml).unwrap().settings;
assert_eq!(settings.ui.common, Default::default());
assert_eq!(settings.ui.entry, Default::default());
assert_ne!(settings.ui.search, Default::default());
}
#[test]
fn parent_settings_inherit() {
let toml = r#"[config.ui]
text_size = 42"#;
let settings = load_txt(toml).unwrap().settings;
assert_ne!(settings.ui.common, Default::default());
assert_ne!(settings.ui.entry, Default::default());
assert_ne!(settings.ui.search, Default::default());
}
}