use serde_json::to_vec;
use std::{
collections::HashMap,
ffi::OsStr,
fs::{self, read_to_string},
path::PathBuf,
sync::OnceLock,
};
use crate::{config::DEFAULT_CONFIG_DIR, OpenDictionary};
pub(crate) const DEFAULT_ALIAS_FILE: &str = "aliases.json";
#[derive(Debug, Clone)]
pub struct AliasManager {
pub(crate) config_dir: Option<PathBuf>,
aliases: OnceLock<HashMap<String, String>>,
}
impl AsRef<AliasManager> for AliasManager {
fn as_ref(&self) -> &AliasManager {
self
}
}
impl AliasManager {
pub fn new<S: AsRef<OsStr> + ?Sized>(config_dir: &S) -> Self {
Self {
config_dir: Some(PathBuf::from(config_dir)),
aliases: OnceLock::new(),
}
}
}
impl Default for AliasManager {
fn default() -> Self {
Self {
config_dir: None,
aliases: OnceLock::new(),
}
}
}
impl AsMut<AliasManager> for AliasManager {
fn as_mut(&mut self) -> &mut AliasManager {
self
}
}
impl AliasManager {
fn save_to_disk(&mut self) -> crate::Result<()> {
let config_bytes = to_vec(&self.get_aliases()?.clone())?;
fs::write(self.path(), config_bytes)?;
Ok(())
}
fn path(&self) -> PathBuf {
self.config_dir
.clone()
.unwrap_or_else(|| DEFAULT_CONFIG_DIR.to_path_buf())
.join(DEFAULT_ALIAS_FILE)
}
fn get_aliases(&mut self) -> crate::Result<&mut HashMap<String, String>> {
let path = self.path();
if self.aliases.get().is_none() {
let aliases = if !path.exists() {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&path, "{}").unwrap();
HashMap::new()
} else {
let config = read_to_string(&path)?;
serde_json::from_str(&config)?
};
self.aliases
.set(aliases)
.map_err(|e| crate::Error::Other(format!("{:?}", e)))?;
}
Ok(self.aliases.get_mut().unwrap())
}
pub fn add(&mut self, alias: &str, file: &OpenDictionary) -> crate::Result<()> {
if self.get(alias)?.is_none() {
self.set(alias, file)
} else {
Err(crate::Error::AliasExists)
}
}
pub fn set(&mut self, alias: &str, file: &OpenDictionary) -> crate::Result<()> {
match &file.path() {
Some(path) => {
self.get_aliases()?
.insert(alias.to_string(), path.to_string_lossy().to_string());
self.save_to_disk()
}
None => Err(crate::Error::DictionaryMissingPath),
}
}
pub fn delete(&mut self, alias: &str) -> crate::Result<()> {
self.get_aliases()?.remove(alias);
self.save_to_disk()?;
Ok(())
}
pub fn get(&mut self, alias: &str) -> crate::Result<Option<&String>> {
Ok(self.get_aliases()?.get(alias))
}
}
impl OpenDictionary {
pub fn from_alias_with_manager<Options: AsMut<AliasManager>>(
alias: &str,
mut options: Options,
) -> crate::Result<OpenDictionary> {
options.as_mut().get(alias)?.map_or_else(
|| Err(crate::Error::AliasNotFound(alias.to_string())),
OpenDictionary::from_path,
)
}
pub fn alias_to_with_manager<Options: AsMut<AliasManager>>(
&self,
name: &str,
mut options: Options,
) -> crate::Result<()> {
options.as_mut().add(name, self)
}
pub fn alias_to(&self, name: &str) -> crate::Result<()> {
self.alias_to_with_manager(name, AliasManager::default())
}
pub fn from_alias(alias: &str) -> crate::Result<OpenDictionary> {
Self::from_alias_with_manager(alias, AliasManager::default())
}
}
#[cfg(test)]
mod tests {
use tempfile::TempDir;
use crate::{alias::DEFAULT_ALIAS_FILE, config::DEFAULT_CONFIG_DIR};
use super::AliasManager;
#[test]
fn test_default_config_dir() {
let alias_manager = AliasManager::default();
assert_eq!(
alias_manager.path(),
DEFAULT_CONFIG_DIR.join(DEFAULT_ALIAS_FILE)
);
}
#[test]
fn test_custom_config_dir() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().to_path_buf();
let alias_manager = AliasManager::new(&config_path);
assert_eq!(alias_manager.path(), config_path.join(DEFAULT_ALIAS_FILE));
}
}