diff --git a/Cargo.toml b/Cargo.toml index 11b68a5..1396507 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,5 @@ crate-type = ["cdylib", "rlib"] anyhow = "1.0.75" nom = "7.1.3" pyo3 = "0.19.2" +serde = { version = "1.0.188", features = ["derive"] } +serde_yaml = "0.9.25" diff --git a/src/lib.rs b/src/lib.rs index 336d944..2924921 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ #![warn(clippy::redundant_closure_for_method_calls)] #![warn(let_underscore_drop)] +mod list; mod refs; use pyo3::prelude::*; diff --git a/src/list/mod.rs b/src/list/mod.rs new file mode 100644 index 0000000..264c5b5 --- /dev/null +++ b/src/list/mod.rs @@ -0,0 +1,22 @@ +mod removable; +mod unique; + +/// Defines the shared interface between the unique list (which is effectively an insert-ordered +/// Set) and the unique list which supports removals. +pub trait List { + fn new() -> Self; + fn with_capacity(capacity: usize) -> Self; + fn len(&self) -> usize; + fn shrink_to_fit(&mut self); + fn append_if_new(&mut self, item: String); + fn merge(&mut self, other: Self); + fn merge_from(&mut self, other: &Self); +} + +/// Returns the 0-indexed position of the item in the list, if it's found +fn item_pos(items: &[String], item: &String) -> Option { + items.iter().position(|v| v == item) +} + +pub use removable::*; +pub use unique::*; diff --git a/src/list/removable.rs b/src/list/removable.rs new file mode 100644 index 0000000..9662fe0 --- /dev/null +++ b/src/list/removable.rs @@ -0,0 +1,369 @@ +use serde::Deserialize; + +use super::{item_pos, List}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] +#[serde(from = "Vec")] +pub struct RemovableList { + items: Vec, + #[serde(skip)] + negations: Vec, +} + +impl RemovableList { + /// Handles negating the provided item + /// + /// Assumes that the negation prefix is already stripped from `negitem` + fn handle_negation(&mut self, negitem: String) { + if let Some(itpos) = item_pos(&self.items, &negitem) { + // ...remove item from our list if it's negated in other + self.items.remove(itpos); + } else if item_pos(&self.negations, &negitem).is_none() { + // ...remember negations which we haven't processed yet and + // which aren't present in self. + self.negations.push(negitem); + } + } + + /// Internal merge implementation which takes negations into account + /// + /// Used by List::merge/List::merge_from + fn merge_impl( + &mut self, + itemiter: impl Iterator, + negiter: impl Iterator, + ) { + // merge negations first... + for n in negiter { + self.handle_negation(n); + } + // take items from other and append them using append_if_new + for it in itemiter { + self.append_if_new(it); + } + } +} + +impl From> for RemovableList { + #[inline] + fn from(item: Vec) -> Self { + let mut res = RemovableList { + items: vec![], + negations: vec![], + }; + for it in item { + res.append_if_new(it); + } + res + } +} + +impl From for Vec { + #[inline] + fn from(l: RemovableList) -> Self { + l.items + } +} + +impl List for RemovableList { + #[inline] + fn new() -> Self { + Self::default() + } + + #[inline] + fn with_capacity(capacity: usize) -> Self { + Self { + items: Vec::with_capacity(capacity), + negations: vec![], + } + } + + #[inline] + fn len(&self) -> usize { + self.items.len() + } + + #[inline] + fn shrink_to_fit(&mut self) { + self.items.shrink_to_fit(); + self.negations.shrink_to_fit(); + } + + /// Appends or removes item from list + /// + /// Regular strings are inserted in the list if they're not present yet. When `item` is + /// prefixed with ~ it's removed from the list if present. Negated items which can't be + /// removed immediately are stored as negations, for later processing. + fn append_if_new(&mut self, item: String) { + if let Some(neg) = item.strip_prefix('~') { + // handle negation + self.handle_negation(neg.to_string()); + } else if let Some(negpos) = item_pos(&self.negations, &item) { + // Remove previously negated item from negations list instead of + // inserting it into the list. + self.negations.remove(negpos); + } else if item_pos(&self.items, &item).is_none() { + // Finally, insert item if neither condition applies and the item + // isn't present in the list yet. + self.items.push(item); + }; + } + + /// Merges other into self, consuming other + /// + /// Negations from other are processed first, removing items which are already present from our + /// list. Negations which weren't processed are kept and merged into the list's negations. + /// Afterwards all items in other are taken and appended if they're not present in our list. + fn merge(&mut self, other: Self) { + self.merge_impl(other.items.into_iter(), other.negations.into_iter()); + } + + /// Merges other into self, creating a clone of other + fn merge_from(&mut self, other: &Self) { + self.merge_impl(other.items.iter().cloned(), other.negations.iter().cloned()); + } +} + +#[cfg(test)] +mod removable_list_tests { + use super::*; + + fn make_abc() -> RemovableList { + vec!["a".into(), "b".into(), "c".into()].into() + } + + fn make_def() -> RemovableList { + vec!["d".into(), "e".into(), "f".into()].into() + } + + #[test] + fn test_list_to_vec() { + let mut list = RemovableList::default(); + list.append_if_new("a".into()); + list.append_if_new("b".into()); + list.append_if_new("c".into()); + list.append_if_new("d".into()); + list.append_if_new("b".into()); + list.append_if_new("~d".into()); + + let vec: Vec = vec!["a".into(), "b".into(), "c".into()]; + + let intoed: Vec = list.clone().into(); + + assert_eq!(intoed, vec); + assert_eq!(Vec::from(list), vec); + } + + #[test] + fn test_vec_to_list() { + let vec: Vec = vec![ + "a".into(), + "b".into(), + "c".into(), + "d".into(), + "b".into(), + "~d".into(), + "~e".into(), + ]; + let mut list = RemovableList::default(); + list.append_if_new("a".into()); + list.append_if_new("b".into()); + list.append_if_new("c".into()); + list.append_if_new("d".into()); + list.append_if_new("b".into()); + list.append_if_new("~d".into()); + list.append_if_new("~e".into()); + + let intoed: RemovableList = vec.clone().into(); + + assert_eq!(intoed, list); + assert_eq!(RemovableList::from(vec), list); + } + + #[test] + fn test_list_add_new() { + let mut l = make_abc(); + l.append_if_new("d".into()); + let expected: Vec = vec!["a".into(), "b".into(), "c".into(), "d".into()]; + assert_eq!(l.items, expected); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_list_add_existing() { + let mut l = make_abc(); + l.append_if_new("c".into()); + let expected: Vec = vec!["a".into(), "b".into(), "c".into()]; + assert_eq!(l.items, expected); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_list_remove_nonexisting() { + let mut l = make_abc(); + l.append_if_new("~d".into()); + let expected: Vec = vec!["a".into(), "b".into(), "c".into()]; + assert_eq!(l.items, expected); + assert_eq!(l.negations, vec!["d".to_string()]); + } + + #[test] + fn test_list_remove_existing() { + let mut l = make_abc(); + l.append_if_new("~b".into()); + let expected: Vec = vec!["a".into(), "c".into()]; + assert_eq!(l.items, expected); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_list_negate_then_add() { + let mut l = make_abc(); + l.append_if_new("~d".into()); + l.append_if_new("d".into()); + let expected: Vec = vec!["a".into(), "b".into(), "c".into()]; + assert_eq!(l.items, expected); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_list_negate_then_negate() { + let mut l = make_abc(); + l.append_if_new("~d".into()); + l.append_if_new("~d".into()); + let expected: Vec = vec!["a".into(), "b".into(), "c".into()]; + assert_eq!(l.items, expected); + assert_eq!(l.negations, vec!["d".to_string()]); + } + + #[test] + fn test_merge() { + let mut l = make_abc(); + let o = make_def(); + l.merge(o); + + assert_eq!( + l.items, + vec![ + "a".to_string(), + "b".to_string(), + "c".to_string(), + "d".to_string(), + "e".to_string(), + "f".to_string() + ] + ); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_merge_same() { + let mut l = make_abc(); + let o = make_abc(); + l.merge(o); + + assert_eq!( + l.items, + vec!["a".to_string(), "b".to_string(), "c".to_string(),] + ); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_merge_add_and_remove() { + let mut l = make_abc(); + let mut o: RemovableList = vec!["d".into()].into(); + o.append_if_new("~c".into()); + l.merge(o); + + assert_eq!( + l.items, + vec!["a".to_string(), "b".to_string(), "d".to_string()] + ); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_merge_add_store_removal() { + let mut l = make_abc(); + let mut o: RemovableList = vec!["d".into()].into(); + o.append_if_new("~c".into()); + o.append_if_new("~e".into()); + l.merge(o); + + assert_eq!( + l.items, + vec!["a".to_string(), "b".to_string(), "d".to_string()] + ); + assert_eq!(l.negations, vec!["e".to_string()]); + } + + #[test] + fn test_merge_add_apply_removal() { + let mut l = make_abc(); + l.append_if_new("~d".into()); + let o: RemovableList = vec!["d".into()].into(); + l.merge(o); + + assert_eq!( + l.items, + vec!["a".to_string(), "b".to_string(), "c".to_string()] + ); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_merge_add_store_unique() { + let mut l = make_abc(); + l.append_if_new("~d".into()); + let mut o = RemovableList::default(); + o.append_if_new("~d".into()); + l.merge(o); + + assert_eq!( + l.items, + vec!["a".to_string(), "b".to_string(), "c".to_string()] + ); + assert_eq!(l.negations, vec!["d".to_string()]); + } + + #[test] + fn test_deserialize_process_negations() { + let yaml = r#" + - a + - b + - ~b + "#; + let l: RemovableList = serde_yaml::from_str(yaml).unwrap(); + + assert_eq!(l.items, vec!["a".to_string()]); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_deserialize_remove_duplicates() { + let yaml = r#" + - a + - a + - b + "#; + let l: RemovableList = serde_yaml::from_str(yaml).unwrap(); + + assert_eq!(l.items, vec!["a".to_string(), "b".to_string()]); + assert_eq!(l.negations, Vec::::new()); + } + + #[test] + fn test_deserialize_store_negations() { + let yaml = r#" + - a + - b + - ~c + "#; + let l: RemovableList = serde_yaml::from_str(yaml).unwrap(); + + assert_eq!(l.items, vec!["a".to_string(), "b".to_string()]); + assert_eq!(l.negations, vec!["c".to_string()]); + } +} diff --git a/src/list/unique.rs b/src/list/unique.rs new file mode 100644 index 0000000..8f57e04 --- /dev/null +++ b/src/list/unique.rs @@ -0,0 +1,184 @@ +use serde::Deserialize; + +use super::{item_pos, List}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] +#[serde(from = "Vec")] +pub struct UniqueList { + items: Vec, +} + +impl UniqueList { + pub fn items_iter(&self) -> impl Iterator { + self.items.iter() + } + + #[cfg(test)] + pub fn get_items(&self) -> &Vec { + &self.items + } + + fn merge_impl(&mut self, itemiter: impl Iterator) { + for it in itemiter { + self.append_if_new(it); + } + } +} + +impl From> for UniqueList { + #[inline] + fn from(item: Vec) -> Self { + let mut res = Self { items: vec![] }; + for it in item { + res.append_if_new(it); + } + res + } +} + +impl From for Vec { + #[inline] + fn from(l: UniqueList) -> Self { + l.items + } +} + +impl List for UniqueList { + #[inline] + fn new() -> Self { + Self::default() + } + + #[inline] + fn with_capacity(capacity: usize) -> Self { + Self { + items: Vec::with_capacity(capacity), + } + } + + #[inline] + fn len(&self) -> usize { + self.items.len() + } + + #[inline] + fn shrink_to_fit(&mut self) { + self.items.shrink_to_fit(); + } + + /// Appends item to list if it's not present yet + fn append_if_new(&mut self, item: String) { + if item_pos(&self.items, &item).is_none() { + self.items.push(item); + } + } + + /// Merges other into self, consuming other + fn merge(&mut self, other: Self) { + self.merge_impl(other.items.into_iter()); + } + + /// Merges other into self, creating a clone of other + fn merge_from(&mut self, other: &Self) { + self.merge_impl(other.items.iter().cloned()); + } +} + +#[cfg(test)] +mod unique_list_tests { + use super::*; + + #[test] + fn test_list_to_vec() { + let mut list = UniqueList::default(); + list.append_if_new("a".into()); + list.append_if_new("b".into()); + list.append_if_new("c".into()); + list.append_if_new("b".into()); + + let vec: Vec = vec!["a".into(), "b".into(), "c".into()]; + + let intoed: Vec = list.clone().into(); + + assert_eq!(intoed, vec); + assert_eq!(Vec::from(list), vec); + } + + #[test] + fn test_vec_to_list() { + let vec: Vec = vec!["a".into(), "b".into(), "c".into(), "d".into(), "b".into()]; + let mut list = UniqueList::default(); + list.append_if_new("a".into()); + list.append_if_new("b".into()); + list.append_if_new("c".into()); + list.append_if_new("d".into()); + list.append_if_new("b".into()); + + let intoed: UniqueList = vec.clone().into(); + + assert_eq!(intoed, list); + assert_eq!(UniqueList::from(vec), list); + } + + #[test] + fn test_add_new() { + let mut l = UniqueList::default(); + l.append_if_new("a".into()); + let r: Vec = l.into(); + assert_eq!(r, vec!["a".to_string()]); + } + + #[test] + fn test_add_unique() { + let mut l = UniqueList::default(); + l.append_if_new("a".into()); + l.append_if_new("a".into()); + let r: Vec = l.into(); + assert_eq!(r, vec!["a".to_string()]); + } + + #[test] + fn test_merge() { + let mut a: UniqueList = vec!["a".into()].into(); + let b: UniqueList = vec!["b".into()].into(); + + a.merge(b); + + let r: Vec = a.into(); + assert_eq!(r, vec!["a".to_string(), "b".to_string()]); + } + + #[test] + fn test_merge_unique_append() { + let mut a: UniqueList = vec!["b".into(), "a".into()].into(); + let b: UniqueList = vec!["b".into()].into(); + + a.merge(b); + + let r: Vec = a.into(); + assert_eq!(r, vec!["b".to_string(), "a".to_string()]); + } + + #[test] + fn test_deserialize() { + let yaml = r#" + - a + - b + "#; + let l: UniqueList = serde_yaml::from_str(yaml).unwrap(); + + assert_eq!(l.items, vec!["a".to_string(), "b".to_string()]); + } + + #[test] + fn test_deserialize_unique() { + let yaml = r#" + - a + - b + - a + "#; + let l: UniqueList = serde_yaml::from_str(yaml).unwrap(); + + assert_eq!(l.items, vec!["a".to_string(), "b".to_string()]); + } +}