diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/sync.rs | 206 |
1 files changed, 122 insertions, 84 deletions
diff --git a/src/sync.rs b/src/sync.rs index 1635ad6..6e3d84b 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -4,11 +4,12 @@ use crate::{ }; use anyhow::Result; use rusqlite::Connection; +use std::collections::HashMap; use std::collections::HashSet; pub fn run(conn: &mut Connection, deck_path: &str) -> Result<()> { let db_entries = db::all(conn)?; - let lines = deck::read(deck_path)?; + let lines = deck::read_file(deck_path)?; let Diff { new, deleted, @@ -29,29 +30,23 @@ struct Diff { } fn diff(db_entries: Vec<DbEntry>, lines: Vec<Line>) -> Diff { - let mut file_questions: HashSet<Question> = HashSet::new(); - let mut db_questions_not_deleted: HashSet<Question> = HashSet::new(); - let mut db_questions_deleted: HashSet<Question> = HashSet::new(); + let mut file_questions = HashMap::<String, Vec<String>>::new(); + let mut db_questions_not_deleted = HashSet::<Question>::new(); + let mut db_questions_deleted = HashSet::<Question>::new(); for Line { part_1, part_2 } in lines { - for question in part_1.clone() { - let mut responses = part_2.clone(); - responses.sort(); - file_questions.insert(Question { - question, - responses, - }); - } - for question in part_2 { - let mut responses = part_1.clone(); - responses.sort(); - file_questions.insert(Question { - question, - responses, - }); - } + insert(&mut file_questions, part_1.clone(), part_2.clone()); + insert(&mut file_questions, part_2, part_1); } + let file_questions: HashSet<Question> = file_questions + .iter() + .map(|(question, responses)| Question { + question: question.to_string(), + responses: responses.to_vec(), + }) + .collect(); + for DbEntry { question, mut responses, @@ -97,77 +92,120 @@ fn diff(db_entries: Vec<DbEntry>, lines: Vec<Line>) -> Diff { } } +fn insert(map: &mut HashMap<String, Vec<String>>, questions: Vec<String>, responses: Vec<String>) { + for question in questions { + let mut responses = responses.clone(); + responses.sort(); + match map.get_mut(&question) { + Some(existing_responses) => existing_responses.append(&mut responses), + None => { + map.insert(question, responses); + } + }; + } +} + #[cfg(test)] mod tests { - use super::*; + use super::{deck, DbEntry, Diff, Question}; + use std::collections::HashSet; #[test] - fn test_diff() { - let db_entries = vec![ - DbEntry { - question: "A".to_string(), - responses: vec!["A".to_string()], - deleted: None, - }, - DbEntry { - question: "B".to_string(), - responses: vec!["B".to_string()], - deleted: None, - }, - DbEntry { - question: "C".to_string(), - responses: vec!["C".to_string()], - deleted: Some(0), - }, - DbEntry { - question: "D".to_string(), - responses: vec!["D".to_string()], - deleted: Some(0), - }, - ]; - - let lines = vec![ - Line { - part_1: vec!["A".to_string()], - part_2: vec!["A".to_string()], - }, - Line { - part_1: vec!["C".to_string()], - part_2: vec!["C".to_string()], - }, - Line { - part_1: vec!["E".to_string()], - part_2: vec!["E".to_string()], - }, - ]; - - let Diff { - new, - deleted, - undeleted, - } = diff(db_entries, lines); + fn test_added() { + let diff = deck_diff("- A : a", "- A : a\n- B : b"); - assert_eq!( - new, - vec!(Question { - question: "E".to_string(), - responses: vec!("E".to_string()) - }) - ); - assert_eq!( - deleted, - vec!(Question { - question: "B".to_string(), - responses: vec!("B".to_string()) - }) + has_questions(diff.new, vec![("B", vec!["b"]), ("b", vec!["B"])]); + assert!(diff.deleted.is_empty()); + assert!(diff.undeleted.is_empty()); + } + + #[test] + fn test_updated() { + let diff = deck_diff("- A : a1", "- A : a2"); + + has_questions(diff.new, vec![("A", vec!["a2"]), ("a2", vec!["A"])]); + has_questions(diff.deleted, vec![("A", vec!["a1"]), ("a1", vec!["A"])]); + assert!(diff.undeleted.is_empty()); + } + + #[test] + fn test_deleted() { + let diff = deck_diff("- A : a", ""); + + assert!(diff.new.is_empty()); + has_questions(diff.deleted, vec![("A", vec!["a"]), ("a", vec!["A"])]); + assert!(diff.undeleted.is_empty()); + } + + #[test] + fn test_undeleted() { + let db_entries = vec![DbEntry { + question: "A".to_string(), + responses: vec!["a".to_string()], + deleted: Some(0), + }]; + + let diff = super::diff(db_entries, deck::tests::read_string("- A : a").unwrap()); + + has_questions(diff.new, vec![("a", vec!["A"])]); + assert!(diff.deleted.is_empty()); + has_questions(diff.undeleted, vec![("A", vec!["a"])]); + } + #[test] + fn regroup_same_question() { + let diff = deck_diff("", "- A : a\n- A | B : b"); + + has_questions( + diff.new, + vec![ + ("A", vec!["a", "b"]), + ("B", vec!["b"]), + ("a", vec!["A"]), + ("b", vec!["A", "B"]), + ], ); + assert!(diff.deleted.is_empty()); + assert!(diff.undeleted.is_empty()); + } + + fn deck_diff(from: &str, to: &str) -> Diff { + super::diff(db_entries(from), deck::tests::read_string(to).unwrap()) + } + + fn has_questions(questions: Vec<Question>, xs: Vec<(&str, Vec<&str>)>) { assert_eq!( - undeleted, - vec!(Question { - question: "C".to_string(), - responses: vec!("C".to_string()) - }) - ); + to_set(questions), + HashSet::from_iter( + xs.iter() + .map(|(y, ys)| Question { + question: y.to_string(), + responses: ys.iter().map(|z| z.to_string()).collect::<Vec<_>>() + }) + .collect::<Vec<_>>() + ) + ) + } + + fn to_set<A: std::cmp::Eq + std::hash::Hash + std::clone::Clone>(xs: Vec<A>) -> HashSet<A> { + xs.iter().cloned().collect() + } + + fn db_entries(deck: &str) -> Vec<DbEntry> { + let lines = deck::tests::read_string(deck).unwrap(); + let diff = super::diff(vec![], lines); + diff.new + .iter() + .map( + |Question { + question, + responses, + }| DbEntry { + question: question.to_string(), + responses: responses.to_vec(), + deleted: None, + }, + ) + .collect() } } |