use crate::{model::entry::Entry, util::serialization}; use anyhow::{Error, Result}; use std::fmt; use std::fs::File; use std::io::{prelude::*, BufReader}; use std::path::Path; #[derive(Debug, Clone)] struct ParseError { line: usize, message: String, } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Line {}: {}", self.line, self.message) } } impl std::error::Error for ParseError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } pub fn read(deck: &str) -> Result> { let file = File::open(deck)?; let reader = BufReader::new(file); let mut entries: Vec = Vec::new(); for (index, line) in reader.lines().enumerate() { let line = line?; let line = line.trim(); if !line.starts_with('#') && !line.is_empty() { if !line.starts_with('-') { return Err(Error::from(ParseError { line: index + 1, message: "an entry should starts with “-”.".to_string(), })); } else { let without_minus = line.split('-').skip(1).collect::>().join("-"); let without_comment = without_minus.split('#').collect::>()[0].trim(); let translation = without_comment.split(':').collect::>(); if translation.len() != 2 { return Err(Error::from(ParseError { line: index + 1, message: "an entry should contain two parts separated by “:”.".to_string(), })); } else { let t1 = translation[0].trim(); let t2 = translation[1].trim(); if t1.is_empty() || t2.is_empty() { return Err(Error::from(ParseError { line: index + 1, message: "an entry should contain two parts separated by “:”." .to_string(), })); } else { entries.push(Entry { part_1: serialization::line_to_words(&t1.to_string()), part_2: serialization::line_to_words(&t2.to_string()), }) } } } } } Ok(entries) } pub fn pp_from_path(path: &str) -> Option { Some(capitalize( Path::new(&path).with_extension("").file_name()?.to_str()?, )) } fn capitalize(s: &str) -> String { let mut c = s.chars(); match c.next() { None => String::new(), Some(f) => f.to_uppercase().collect::() + c.as_str(), } }