diff options
| -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()      }  } | 
