mod appearance;
mod defaults;
pub mod errors;
pub use appearance::*;
pub use defaults::*;
use crate::{
configs::errors::{ConfigError, TomlError},
create_recursive,
};
use serde::Deserialize;
use std::{io::Read, ops::Range, sync::OnceLock};
pub static CONFIG: OnceLock<Config> = OnceLock::new();
#[derive(Default, Debug, Deserialize, PartialEq)]
pub struct Config {
#[serde(default)]
pub appearance: Appearance,
#[serde(default)]
pub defaults: Defaults,
}
impl Config {
fn apply_overrides(&mut self, overrides: ConfigOverride) {
if let Some(color) = overrides.color {
self.appearance.fg = color;
}
if let Some(dir) = overrides.out_dir {
self.defaults.out_dir = dir;
}
}
}
pub fn initialize_config(overrides: ConfigOverride) -> miette::Result<(), ConfigError> {
let mut config: Config = if let Ok(config_file) = get_config_file() {
let mut file = std::fs::File::open(config_file).expect("File should exist");
let mut contents = String::new();
file.read_to_string(&mut contents)?;
toml::from_str(&contents).map_err(|e| {
TomlError::new(
e.span().unwrap_or(Range { start: 0, end: 0 }),
contents,
e.message().to_string(),
)
})?
} else {
Config::default()
};
config.apply_overrides(overrides);
CONFIG
.set(config)
.map_err(|_| ConfigError::AlreadyInitialized)?;
Ok(())
}
pub fn get_config() -> &'static Config {
CONFIG.get().expect("Config not initialized")
}
#[derive(Debug)]
pub struct ConfigOverride {
pub color: Option<SeriColor>,
pub out_dir: Option<String>,
}
fn get_conf_dir() -> std::path::PathBuf {
let mut user_home_dir = std::env::home_dir().expect("Failed to get home directory");
if cfg!(target_os = "windows") {
user_home_dir.push(".config\\sericom");
} else {
user_home_dir.push(".config/sericom");
}
let user_conf_dir = user_home_dir;
create_recursive!(user_conf_dir.as_path());
user_conf_dir
}
fn get_config_file() -> miette::Result<std::path::PathBuf, ConfigError> {
let mut conf_dir = get_conf_dir();
conf_dir.push("config.toml");
let conf_file = conf_dir;
if conf_file.exists() && conf_file.is_file() {
Ok(conf_file)
} else {
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Could not find config file.").into())
}
}
#[test]
fn parse_test_config() {
let file: Config = toml::from_str(
r#"
[appearance]
fg = "dark-grey"
bg = "red"
hl_fg = "white"
hl_bg = "blue"
[defaults]
out_dir = "$HOME/.configs"
"#,
)
.unwrap();
let parsed_conf = Config {
appearance: Appearance {
fg: SeriColor::DarkGrey,
bg: SeriColor::Red,
hl_fg: SeriColor::White,
hl_bg: SeriColor::Blue,
},
defaults: Defaults {
out_dir: "$HOME/.configs".to_string(),
},
};
assert_eq!(file, parsed_conf)
}
#[test]
fn check_conf_dir_is_dir() {
let dir = get_conf_dir();
assert!(std::fs::metadata(dir).unwrap().is_dir())
}
#[test]
fn valid_conf_dir() {
let dir = get_conf_dir();
if cfg!(target_family = "windows") {
assert_eq!(dir.to_str().unwrap(), "C:\\Users\\Thomas\\.config\\sericom")
} else {
assert_eq!(dir.to_str().unwrap(), "/home/thomas/.config/sericom")
}
}