use crate::model::Line; use crate::parser; 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: String, line_number: usize, } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Error parsing line {}:\n\n{}", self.line_number, self.line ) } } impl std::error::Error for ParseError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } pub fn read_file(path: &str) -> Result> { let file = File::open(path)?; let reader = BufReader::new(file); let mut entries: Vec = Vec::new(); for (index, line) in reader.lines().enumerate() { let line = line?; if let Some(line) = read_line(index, &line)? { entries.push(line) } } Ok(entries) } fn read_line(index: usize, line: &str) -> Result> { match parser::parse_line(line) { Ok(("", line)) => Ok(line), _ => Err(Error::from(ParseError { line_number: index + 1, line: line.to_string(), })), } } 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(), } } #[cfg(test)] pub mod tests { use crate::model::Line; use anyhow::Result; #[test] fn errors() { is_error("A"); is_error("A -> a"); is_error("A : B : C"); is_error(":"); is_error("A : a\n-") } #[test] fn ignored() { check("", &[]); check(" ", &[]); check(" \n \n ", &[]); check("# 1", &[]); check("# 1\n\n # 2", &[]); } #[test] fn card() { check("A : a", &[(&["A"], &["a"])]); } #[test] fn cards() { check("A : a\nB : b", &[(&["A"], &["a"]), (&["B"], &["b"])]); } #[test] fn alternatives() { check("A : a1 | a2", &[(&["A"], &["a1", "a2"])]); check("A1 | A2 : a", &[(&["A1", "A2"], &["a"])]); check("A1 | A2 : a1 | a2", &[(&["A1", "A2"], &["a1", "a2"])]); } fn is_error(content: &str) { assert!(read_string(content).is_err()) } fn check(content: &str, res: &[(&[&str], &[&str])]) { assert_eq!( read_string(content).unwrap(), res.iter() .map(|(part_1, part_2)| Line { part_1: part_1.iter().map(|x| x.to_string()).collect::>(), part_2: part_2.iter().map(|x| x.to_string()).collect::>() }) .collect::>() ) } pub fn read_string(content: &str) -> Result> { let mut entries: Vec = Vec::new(); for (index, line) in content.lines().enumerate() { if let Some(line) = super::read_line(index, line)? { entries.push(line) } } Ok(entries) } }