use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use std::sync::OnceLock;
static MAPPINGS: OnceLock<Mappings> = OnceLock::new();
pub struct Mappings {
pub expand: HashMap<String, String>,
pub compress: HashMap<String, String>,
}
impl Mappings {
pub fn load() -> Result<Self, String> {
let mapping_path = Self::find_mappings_file()?;
let content = fs::read_to_string(&mapping_path)
.map_err(|e| format!("Failed to read mappings file: {}", e))?;
Self::parse(&content)
}
fn parse(content: &str) -> Result<Self, String> {
let mut expand = HashMap::new();
let mut compress = HashMap::new();
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((short, full)) = line.split_once('=') {
let short = short.trim().to_string();
let full = full.trim().to_string();
expand.insert(short.clone(), full.clone());
compress.insert(full, short);
}
}
Ok(Self { expand, compress })
}
fn find_mappings_file() -> Result<PathBuf, String> {
let mut current = std::env::current_dir()
.map_err(|e| format!("Failed to get current directory: {}", e))?;
loop {
let mappings_path = current.join(".dx").join("serializer").join("mappings.dx");
if mappings_path.exists() {
return Ok(mappings_path);
}
if !current.pop() {
break;
}
}
Err("Could not find .dx/serializer/mappings.dx in current directory or parents".to_string())
}
pub fn get() -> &'static Mappings {
MAPPINGS.get_or_init(|| {
Self::load().unwrap_or_else(|e| {
eprintln!("Warning: Failed to load mappings: {}. Using defaults.", e);
Self::default()
})
})
}
#[inline]
pub fn expand_key(&self, key: &str) -> String {
self.expand
.get(key)
.cloned()
.unwrap_or_else(|| key.to_string())
}
#[inline]
pub fn compress_key(&self, key: &str) -> String {
self.compress
.get(key)
.cloned()
.unwrap_or_else(|| key.to_string())
}
}
impl Default for Mappings {
fn default() -> Self {
let mut expand = HashMap::new();
let mut compress = HashMap::new();
let defaults = [
("n", "name"),
("v", "version"),
("t", "title"),
("d", "description"),
("a", "author"),
("c", "context"),
("l", "languages"),
("f", "forge"),
("s", "style"),
("m", "media"),
("i", "i18n"),
("u", "ui"),
];
for (short, full) in defaults {
expand.insert(short.to_string(), full.to_string());
compress.insert(full.to_string(), short.to_string());
}
Self { expand, compress }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_mappings() {
let content = r#"
# Comment
n=name
v=version
c=context
"#;
let mappings = Mappings::parse(content).unwrap();
assert_eq!(mappings.expand_key("n"), "name");
assert_eq!(mappings.expand_key("v"), "version");
assert_eq!(mappings.compress_key("name"), "n");
assert_eq!(mappings.compress_key("version"), "v");
}
#[test]
fn test_roundtrip() {
let mappings = Mappings::default();
let short = "n";
let full = mappings.expand_key(short);
let back = mappings.compress_key(&full);
assert_eq!(short, back);
}
}