use std::path::Path;
pub struct Dictionary {
chars: Vec<String>,
}
impl Dictionary {
pub fn from_file(path: &Path) -> Result<Self, DictionaryError> {
let data = std::fs::read(path)
.map_err(|e| DictionaryError::Io(format!("{}: {e}", path.display())))?;
Self::from_bytes(&data)
}
pub fn from_bytes(data: &[u8]) -> Result<Self, DictionaryError> {
let text = std::str::from_utf8(data)
.map_err(|e| DictionaryError::Parse(format!("invalid UTF-8: {e}")))?;
let mut chars = vec![String::new()]; for line in text.lines() {
if !line.is_empty() {
chars.push(line.to_string());
}
}
if chars.len() < 2 {
return Err(DictionaryError::Parse(
"dictionary must contain at least one character".into(),
));
}
Ok(Self { chars })
}
pub fn len(&self) -> usize {
self.chars.len()
}
pub fn is_empty(&self) -> bool {
self.chars.len() <= 1
}
pub fn get(&self, index: usize) -> Option<&str> {
if index == 0 {
return None; }
self.chars.get(index).map(|s| s.as_str())
}
}
#[derive(Debug)]
pub enum DictionaryError {
Io(String),
Parse(String),
}
impl std::fmt::Display for DictionaryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(msg) => write!(f, "dictionary I/O error: {msg}"),
Self::Parse(msg) => write!(f, "dictionary parse error: {msg}"),
}
}
}
impl std::error::Error for DictionaryError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn load_and_lookup() {
let dict = Dictionary::from_bytes(b"a\nb\nc\nd\n").unwrap();
assert_eq!(dict.len(), 5); assert_eq!(dict.get(0), None); assert_eq!(dict.get(1), Some("a"));
assert_eq!(dict.get(2), Some("b"));
assert_eq!(dict.get(3), Some("c"));
assert_eq!(dict.get(4), Some("d"));
assert_eq!(dict.get(5), None); }
#[test]
fn empty_dict_is_error() {
let result = Dictionary::from_bytes(b"");
assert!(result.is_err());
}
#[test]
fn dict_with_unicode() {
let dict = Dictionary::from_bytes("日\n本\n語\n".as_bytes()).unwrap();
assert_eq!(dict.len(), 4);
assert_eq!(dict.get(1), Some("日"));
assert_eq!(dict.get(2), Some("本"));
assert_eq!(dict.get(3), Some("語"));
}
#[test]
fn is_empty() {
let dict = Dictionary::from_bytes(b"a\n").unwrap();
assert!(!dict.is_empty());
}
#[test]
fn skips_empty_lines() {
let dict = Dictionary::from_bytes(b"a\n\nb\n").unwrap();
assert_eq!(dict.len(), 3); assert_eq!(dict.get(1), Some("a"));
assert_eq!(dict.get(2), Some("b"));
}
}