use std::{io::Write, path::PathBuf};
use serde::{de::DeserializeOwned, Serialize};
use tracing::info;
mod error;
mod traits;
mod utils;
pub use error::Error;
use traits::{Get, Set};
use utils::sanitize_name;
pub use utils::FileType;
pub struct Config {
path: PathBuf,
}
impl Config {
pub fn new(name: &str, version: u64, scope: Option<&str>) -> Result<Self, Error> {
let main_path = sanitize_name(name)?.join(format!("v{}", version));
let user_path = dirs::config_dir().ok_or(Error::NoConfigDirectory)?;
let config_path = if let Some(scope) = scope {
let scope = sanitize_name(scope)?;
user_path.join(main_path).join(scope)
} else {
user_path.join(main_path)
};
std::fs::create_dir_all(&config_path)?;
Ok(Self { path: config_path })
}
pub fn has_plain(&self, key: &str) -> bool {
self.path.join(key).exists()
}
#[cfg(feature = "toml")]
pub fn has_toml(&self, key: &str) -> bool {
self.path.join(format!("{key}.{}", FileType::Toml)).exists()
}
#[cfg(feature = "json")]
pub fn has_json(&self, key: &str) -> bool {
self.path.join(format!("{key}.{}", FileType::Json)).exists()
}
#[cfg(feature = "ron")]
pub fn has_ron(&self, key: &str) -> bool {
self.path.join(format!("{key}.{}", FileType::Ron)).exists()
}
#[cfg(feature = "toml")]
pub fn get_toml<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error> {
self.get(key, FileType::Toml)
}
#[cfg(feature = "json")]
pub fn get_json<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error> {
self.get(key, FileType::Json)
}
#[cfg(feature = "ron")]
pub fn get_ron<T: DeserializeOwned>(&self, key: &str) -> Result<T, Error> {
self.get(key, FileType::Ron)
}
pub fn get_plain(&self, key: &str) -> Result<String, Error> {
std::fs::read_to_string(self.path.join(key))
.map_err(|err| Error::GetKey(key.to_string(), err))
}
#[cfg(feature = "toml")]
pub fn set_toml<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> {
self.set(key, FileType::Toml, value)
}
#[cfg(feature = "json")]
pub fn set_json<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> {
self.set(key, FileType::Json, value)
}
#[cfg(feature = "ron")]
pub fn set_ron<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> {
self.set(key, FileType::Ron, value)
}
pub fn set_plain(&self, key: &str, value: impl ToString) -> Result<(), Error> {
let key_path = self.path.join(key);
atomicwrites::AtomicFile::new(&key_path, atomicwrites::OverwriteBehavior::AllowOverwrite)
.write(|file| file.write_all(value.to_string().as_bytes()))?;
Ok(())
}
pub fn path(&self, key: &str, file_type: FileType) -> Result<PathBuf, Error> {
let name = if FileType::Plain == file_type {
key.to_string()
} else {
format!("{key}.{file_type}")
};
let path = self.path.join(sanitize_name(&name)?);
info!("Found key {}.", key);
Ok(path)
}
pub fn clean(&self) -> Result<(), Error> {
std::fs::remove_dir_all(&self.path.parent().unwrap()).map_err(|err| Error::Io(err))
}
}
impl Get for Config {
fn get<T: DeserializeOwned>(&self, key: &str, file_type: FileType) -> Result<T, Error> {
let key_path = self.path(key, file_type)?;
let data = std::fs::read_to_string(&key_path)
.map_err(|err| Error::GetKey(key.to_string(), err))?;
let t = match file_type {
#[cfg(feature = "toml")]
FileType::Toml => toml::from_str(&data)?,
#[cfg(feature = "json")]
FileType::Json => serde_json::from_str(&data)?,
#[cfg(feature = "ron")]
FileType::Ron => ron::from_str(&data)?,
FileType::Plain => unreachable!("Never get plain text with get method."),
};
info!("Retrieved file from {}.", key_path.display());
Ok(t)
}
}
impl Set for Config {
fn set<T: Serialize>(&self, key: &str, file_type: FileType, value: T) -> Result<(), Error> {
let key_path = self.path(key, file_type)?;
let data = match file_type {
#[cfg(feature = "toml")]
FileType::Toml => toml::to_string_pretty(&value)?,
#[cfg(feature = "json")]
FileType::Json => serde_json::to_string_pretty(&value)?,
#[cfg(feature = "ron")]
FileType::Ron => ron::ser::to_string_pretty(&value, ron::ser::PrettyConfig::new())?,
FileType::Plain => unreachable!("Never get plain text with get method."),
};
atomicwrites::AtomicFile::new(&key_path, atomicwrites::OverwriteBehavior::AllowOverwrite)
.write(|file| file.write_all(data.as_bytes()))?;
info!("File written to {}.", key_path.display());
Ok(())
}
}