use crate::display::DisplayConfig;
use crate::fs_wrappers;
use crate::mkdev_error::{Context, Error};
use std::collections::HashMap;
use std::default::Default;
use std::path::PathBuf;
use std::sync::OnceLock;
use confique::Config as Confique;
use serde::{Deserialize, Serialize};
static CONFIG: OnceLock<Config> = OnceLock::new();
static CONFIG_PATH_OVERRIDE: OnceLock<PathBuf> = OnceLock::new();
#[derive(Confique, Serialize, Deserialize, Debug)]
pub struct Config {
pub recipe_dir: Option<PathBuf>,
#[serde(default = "default_vim")]
pub vim: bool,
#[serde(default = "default_subs")]
pub subs: HashMap<String, String>,
#[serde(default)]
#[config(nested)]
pub recipe_fmt: DisplayConfig,
}
impl Config {
pub fn get() -> Result<&'static Config, Error> {
if CONFIG.get().is_none() {
let config = Config::load()?;
CONFIG.set(config).unwrap_or_else(|_| unreachable!());
}
Ok(CONFIG.get().unwrap())
}
pub fn override_path(path: PathBuf) {
CONFIG_PATH_OVERRIDE
.set(path)
.expect("double initiaisation of config path override");
}
fn load() -> Result<Config, Error> {
let config_file = match CONFIG_PATH_OVERRIDE.get() {
Some(path) => path.clone(),
None => dirs::config_dir()
.expect("$HOME is not set; cannot determine config directory.")
.join("mkdev")
.join("config.toml"),
};
if let Some(dir) = config_file.parent()
&& !dir.is_dir()
{
fs_wrappers::create_dir_all(dir, Context::Config)?;
}
if !config_file.is_file() {
let cfg = Config::default();
let serialized_default =
toml::to_string(&cfg).expect("default `Config` is always serialisable.");
fs_wrappers::write(config_file, serialized_default, Context::Config)?;
Ok(cfg)
} else {
let cfg_contents = fs_wrappers::read_to_string(&config_file, Context::Config)?;
let cfg: Config =
toml::from_str(&cfg_contents).map_err(|e| Error::Deserialisation {
which: config_file,
cause: e.to_string(),
context: Context::Config,
})?;
Ok(cfg)
}
}
}
fn default_subs() -> HashMap<String, String> {
HashMap::from_iter(
[
("dir", "mk::dir"),
("name", "mk::name"),
("user", "whoami"),
("day", "date +%d"),
("month", "date +%m"),
("year", "date +%Y"),
]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string())),
)
}
fn default_vim() -> bool {
false
}
impl Default for Config {
fn default() -> Self {
let recipe_dir = None;
let vim = default_vim();
let subs = default_subs();
let recipe_fmt = DisplayConfig::default();
Self {
recipe_dir,
vim,
subs,
recipe_fmt,
}
}
}