char-coal 0.1.0

A command line dictionary
Documentation
use std::{
    collections::hash_map::DefaultHasher,
    fs::{File, OpenOptions, self},
    hash::{Hash, Hasher},
    io,
    path::PathBuf,
};

#[derive(Clone)]
pub struct Cache {
    cache_dir: PathBuf,
    vault_dir: PathBuf,
}

enum CacheFile {
    Normal(u8, String),
    Absurd(u64),
}

impl CacheFile {
    fn str_hash(s: impl AsRef<str>) -> u64 {
        let mut hasher = DefaultHasher::new();
        s.as_ref().hash(&mut hasher);
        hasher.finish()
    }
    fn generate(s: String) -> Self {
        let hash_num = CacheFile::str_hash(&s);
        if s.contains(" ") || !s.is_ascii() {
            CacheFile::Absurd(hash_num)
        } else {
            CacheFile::Normal((hash_num % 256) as u8, s)
        }
    }
    fn consume(self, cache: &Cache, suffix: &'static str) -> io::Result<PathBuf> {
        match self {
            CacheFile::Normal(dir, file) => {
                let mut path = cache.cache_dir.clone();
                path.push(format!("{:02x}", dir));
                fs::create_dir_all(&path)?;
                path.push(format!("{}.{}", file, suffix));
                Ok(path)
            }
            CacheFile::Absurd(file) => {
                let mut path = cache.vault_dir.clone();
                path.push(format!("{:x}.{}", file, suffix));
                Ok(path)
            }
        }
    }
}

impl Cache {
    pub fn new(cache_dir: PathBuf, vault_dir: PathBuf) -> Self {
        Self {
            cache_dir,
            vault_dir,
        }
    }
    fn get_file_path(&self, word: impl AsRef<str>, suffix: &'static str) -> io::Result<PathBuf> {
        CacheFile::generate(word.as_ref().to_owned()).consume(&self, suffix)
    }
    pub fn query(&self, word: impl AsRef<str>, suffix: &'static str) -> io::Result<File> {
        let path = self.get_file_path(&word, suffix)?;
        let file = OpenOptions::new().read(true).open(path)?;
        Ok(file)
    }
    pub fn store(&self, word: impl AsRef<str>, suffix: &'static str) -> io::Result<File> {
        let path = self.get_file_path(&word, suffix)?;
        let file = OpenOptions::new().create(true).write(true).open(path)?;
        Ok(file)
    }

    pub fn clean(&self) -> io::Result<()> {
        fs::remove_dir_all(&self.cache_dir)?;
        fs::remove_dir_all(&self.vault_dir)?;

        Ok(())
    }
}