use std::env::var;
use std::fs::read_to_string;
use std::io::Error as IoError;
use std::sync::OnceLock;
use strum::EnumString;
use thiserror::Error;
use toml::Table;
use toml::de::Error as TomlError;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("IO error reading configuration: {0:#}")]
Io(#[from] IoError),
#[error(
"TOML parse error '{}'{}",
.0.message(),
.0.span()
.map(|l| format!(" in ./translatable.toml:{}:{}", l.start, l.end))
.unwrap_or_else(|| "".into())
)]
ParseToml(#[from] TomlError),
#[error("Couldn't parse configuration entry '{1}' for '{0}'")]
InvalidValue(String, String),
}
#[derive(Default, Clone, Copy, EnumString)]
pub enum SeekMode {
#[default]
Alphabetical,
Unalphabetical,
}
#[derive(Default, Clone, Copy, EnumString)]
pub enum TranslationOverlap {
#[default]
Overwrite,
Ignore,
}
pub struct MacroConfig {
path: String,
seek_mode: SeekMode,
overlap: TranslationOverlap,
}
impl MacroConfig {
pub fn path(&self) -> &str {
&self.path
}
pub fn seek_mode(&self) -> SeekMode {
self.seek_mode
}
pub fn overlap(&self) -> TranslationOverlap {
self.overlap
}
}
static TRANSLATABLE_CONFIG: OnceLock<MacroConfig> = OnceLock::new();
pub fn load_config() -> Result<&'static MacroConfig, ConfigError> {
if let Some(config) = TRANSLATABLE_CONFIG.get() {
return Ok(config);
}
let toml_content = read_to_string("./translatable.toml")
.unwrap_or_default()
.parse::<Table>()?;
macro_rules! config_value {
($env_var:expr, $key:expr, $default:expr) => {
var($env_var)
.ok()
.or_else(|| {
toml_content
.get($key)
.and_then(|v| v.as_str())
.map(|v| v.to_string())
})
.unwrap_or_else(|| $default.into())
};
(parse($env_var:expr, $key:expr, $default:expr)) => {{
let value = var($env_var)
.ok()
.or_else(|| {
toml_content
.get($key)
.and_then(|v| v.as_str())
.map(|v| v.to_string())
});
if let Some(value) = value {
value
.parse()
.map_err(|_| ConfigError::InvalidValue($key.into(), value.into()))
} else {
Ok($default)
}
}};
}
let config = MacroConfig {
path: config_value!("TRANSLATABLE_LOCALES_PATH", "path", "./translations"),
overlap: config_value!(parse(
"TRANSLATABLE_OVERLAP",
"overlap",
TranslationOverlap::Ignore
))?,
seek_mode: config_value!(parse(
"TRANSLATABLE_SEEK_MODE",
"seek_mode",
SeekMode::Alphabetical
))?,
};
Ok(TRANSLATABLE_CONFIG.get_or_init(|| config))
}