pub mod logger;
#[cfg(target_has_atomic = "ptr")]
use alloc::sync::Arc;
#[cfg(not(target_has_atomic = "ptr"))]
use portable_atomic_util::Arc;
use serde::Serialize;
use serde::de::DeserializeOwned;
pub trait RuntimeConfig:
Default + Clone + Serialize + DeserializeOwned + Send + Sync + 'static
{
fn storage() -> &'static spin::Mutex<Option<Arc<Self>>>;
fn file_names() -> &'static [&'static str];
fn section_file_names() -> &'static [(&'static str, &'static str)] {
&[]
}
#[cfg(std_io)]
fn override_from_env(self) -> Self {
self
}
fn get() -> Arc<Self> {
let mut state = Self::storage().lock();
if state.as_ref().is_none() {
cfg_if::cfg_if! {
if #[cfg(std_io)] {
let config = Self::from_current_dir();
let config = config.override_from_env();
} else {
let config = Self::default();
}
}
*state = Some(Arc::new(config));
}
state.as_ref().cloned().unwrap()
}
fn set(config: Self) {
let mut state = Self::storage().lock();
if state.is_some() {
panic!("Cannot set the configuration multiple times.");
}
*state = Some(Arc::new(config));
}
#[cfg(std_io)]
fn save_default<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<()> {
use std::io::Write;
let config = Self::get();
let content =
toml::to_string_pretty(config.as_ref()).expect("Default config should be serializable");
let mut file = std::fs::File::create(path)?;
file.write_all(content.as_bytes())?;
Ok(())
}
#[cfg(std_io)]
fn from_current_dir() -> Self {
let mut dir = std::env::current_dir().unwrap();
loop {
for name in Self::file_names() {
if let Ok(content) = Self::from_file_path(dir.join(name)) {
return content;
}
}
for (name, section) in Self::section_file_names() {
if let Ok(content) = Self::from_section_file_path(dir.join(name), section) {
return content;
}
}
if !dir.pop() {
break;
}
}
Self::default()
}
#[cfg(std_io)]
fn from_file_path<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: Self = match toml::from_str(&content) {
Ok(val) => val,
Err(err) => panic!("The file provided doesn't have the right format => {err:?}"),
};
Ok(config)
}
#[cfg(std_io)]
fn from_section_file_path<P: AsRef<std::path::Path>>(
path: P,
section: &str,
) -> std::io::Result<Self> {
let content = std::fs::read_to_string(path)?;
let mut table: toml::Table = match toml::from_str(&content) {
Ok(val) => val,
Err(err) => panic!("The file provided doesn't have the right format => {err:?}"),
};
let value = match table.remove(section) {
Some(val) => val,
None => {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
alloc::format!("Section '{section}' not found"),
));
}
};
let config: Self = match value.try_into() {
Ok(val) => val,
Err(err) => {
panic!("The section '{section}' doesn't have the right format => {err:?}")
}
};
Ok(config)
}
}