aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/deck.rs145
-rw-r--r--src/model/mod.rs2
2 files changed, 112 insertions, 35 deletions
diff --git a/src/deck.rs b/src/deck.rs
index 0c302e1..4491d9b 100644
--- a/src/deck.rs
+++ b/src/deck.rs
@@ -23,51 +23,57 @@ impl std::error::Error for ParseError {
}
}
-pub fn read(deck: &str) -> Result<Vec<Line>> {
- let file = File::open(deck)?;
+pub fn read_file(path: &str) -> Result<Vec<Line>> {
+ let file = File::open(path)?;
let reader = BufReader::new(file);
let mut entries: Vec<Line> = 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 {
+ if let Some(line) = read_line(index, &line)? {
+ entries.push(line)
+ }
+ }
+
+ Ok(entries)
+}
+
+fn read_line(index: usize, line: &str) -> Result<Option<Line>> {
+ let line = line.trim();
+
+ if line.starts_with('#') || line.is_empty() {
+ Ok(None)
+ } else if !line.starts_with('-') {
+ Err(Error::from(ParseError {
+ line: index + 1,
+ message: "an entry should starts with “-”.".to_string(),
+ }))
+ } else {
+ let without_minus = line.split('-').skip(1).collect::<Vec<&str>>().join("-");
+ let without_comment = without_minus.split('#').collect::<Vec<&str>>()[0].trim();
+ let translation = without_comment.split(':').collect::<Vec<&str>>();
+ if translation.len() != 2 {
+ 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() {
+ Err(Error::from(ParseError {
line: index + 1,
- message: "an entry should starts with “-”.".to_string(),
- }));
+ message: "an entry should contain two parts separated by “:”.".to_string(),
+ }))
} else {
- let without_minus = line.split('-').skip(1).collect::<Vec<&str>>().join("-");
- let without_comment = without_minus.split('#').collect::<Vec<&str>>()[0].trim();
- let translation = without_comment.split(':').collect::<Vec<&str>>();
- 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(Line {
- part_1: serialization::line_to_words(t1),
- part_2: serialization::line_to_words(t2),
- })
- }
- }
+ Ok(Some(Line {
+ part_1: serialization::line_to_words(t1),
+ part_2: serialization::line_to_words(t2),
+ }))
}
}
}
-
- Ok(entries)
}
pub fn pp_from_path(path: &str) -> Option<String> {
@@ -83,3 +89,74 @@ fn capitalize(s: &str) -> String {
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
}
+
+#[cfg(test)]
+pub mod tests {
+
+ use crate::model::Line;
+ use anyhow::Result;
+
+ #[test]
+ fn errors() {
+ is_error("A : a");
+ 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\n- B : 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::<Vec<_>>(),
+ part_2: part_2.iter().map(|x| x.to_string()).collect::<Vec<_>>()
+ })
+ .collect::<Vec<_>>()
+ )
+ }
+
+ pub fn read_string(content: &str) -> Result<Vec<Line>> {
+ let mut entries: Vec<Line> = Vec::new();
+
+ for (index, line) in content.lines().enumerate() {
+ if let Some(line) = super::read_line(index, line)? {
+ entries.push(line)
+ }
+ }
+
+ Ok(entries)
+ }
+}
diff --git a/src/model/mod.rs b/src/model/mod.rs
index 2dc1ab5..4df4c49 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -2,7 +2,7 @@ use crate::space_repetition;
pub mod difficulty;
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
pub struct Line {
pub part_1: Vec<String>,
pub part_2: Vec<String>,