use crate::prelude::*;
use std::fs;
pub const CONFIG_FILENAME: &str = "ncmr.toml";
pub const DEFAULT_TEMPLATES_GLOB: &str = "./tmpls/**/*.md";
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Config {
pub path: PathBuf,
pub user: ConfigUser,
}
impl Config {
pub fn new(path: PathBuf, user: ConfigUser) -> Self {
Self { path, user }
}
pub fn resolve_path<P: AsRef<Path>>(path: P) -> PathBuf {
if path.as_ref().ends_with(CONFIG_FILENAME) {
return path.as_ref().to_path_buf();
}
Self::join_path(path)
}
pub fn join_path<P: AsRef<Path>>(path: P) -> PathBuf {
path.as_ref().join(CONFIG_FILENAME)
}
pub fn init<P: AsRef<Path>>(path: P, force: bool) -> Result<Self> {
let path = Self::resolve_path(path);
if path.exists() && !force {
bail!("Config file already exists at {}", path.display());
}
Ok(Self::new(path, Default::default()))
}
pub fn find<P: AsRef<Path>>(path: Option<P>) -> Result<Config> {
match path {
Some(path) => {
let path = if path.as_ref().is_dir() {
Self::join_path(path)
} else {
path.as_ref().to_path_buf()
};
Self::read(path.clone())
}
None => {
let initial: PathBuf = ".".into();
let mut cur_path: Option<&Path> = Some(&initial);
while let Some(path) = cur_path {
let file = Self::join_path(path);
if file.is_file() {
return Self::read(file);
}
cur_path = path.parent();
}
Err(anyhow!(
"No config file found in current or parent directories"
))
}
}
}
fn read(path: PathBuf) -> Result<Config> {
let settings = config::Config::builder()
.add_source(config::File::from(path.clone()))
.add_source(config::Environment::with_prefix("NMCR"))
.build()?;
let user = settings.try_deserialize::<ConfigUser>()?;
Ok(Self::new(path, user))
}
pub fn write(&self) -> Result<()> {
let parent = self
.path
.parent()
.map(|p| p.to_path_buf())
.with_context(|| {
format!("Failed to get parent directory of {}", self.path.display())
})?;
if !parent.exists() {
fs::create_dir_all(&parent).with_context(|| {
format!("Failed to create directories for {}", parent.display())
})?;
}
let toml =
toml::to_string_pretty(&self.user).with_context(|| "Failed to serialize config")?;
fs::write(&self.path, toml)
.with_context(|| format!("Failed to write config at {}", self.path.display()))?;
Ok(())
}
pub fn default_templates_glob() -> String {
DEFAULT_TEMPLATES_GLOB.to_string()
}
pub fn default_path() -> PathBuf {
PathBuf::from(CONFIG_FILENAME)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ConfigUser {
#[serde(default = "Config::default_templates_glob")]
pub templates: String,
}
impl Default for ConfigUser {
fn default() -> Self {
Self {
templates: Config::default_templates_glob(),
}
}
}
impl ConfigUser {
pub fn normalized_templates(&self) -> String {
let mut pattern = self.templates.trim().to_string();
if let Some(stripped) = pattern
.strip_prefix("./")
.or_else(|| pattern.strip_prefix(".\\"))
{
pattern = stripped.to_string();
}
pattern
}
}