From 0576afc70f9689620ec53eb64fd5837e87c63041 Mon Sep 17 00:00:00 2001 From: Simon Gerber Date: Tue, 29 Aug 2023 18:41:27 +0200 Subject: [PATCH 1/4] Add custom list types which are suitable for Reclass's `classes` and `applications` arrays --- Cargo.toml | 2 + src/lib.rs | 1 + src/list/mod.rs | 18 +++ src/list/removable.rs | 344 ++++++++++++++++++++++++++++++++++++++++++ src/list/unique.rs | 160 ++++++++++++++++++++ 5 files changed, 525 insertions(+) create mode 100644 src/list/mod.rs create mode 100644 src/list/removable.rs create mode 100644 src/list/unique.rs 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..6d76bf3 --- /dev/null +++ b/src/list/mod.rs @@ -0,0 +1,18 @@ +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 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..edebd27 --- /dev/null +++ b/src/list/removable.rs @@ -0,0 +1,344 @@ +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 { + 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 { + fn from(l: RemovableList) -> Self { + drop(l.negations); + l.items + } +} + +impl List for RemovableList { + /// 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..38276d8 --- /dev/null +++ b/src/list/unique.rs @@ -0,0 +1,160 @@ +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 { + 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 { + fn from(l: UniqueList) -> Self { + l.items + } +} + +impl List for UniqueList { + /// 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()]); + } +} From d4425593377fa1e3a6e423bf14c4770e6324f85f Mon Sep 17 00:00:00 2001 From: Simon Gerber Date: Wed, 30 Aug 2023 13:03:46 +0200 Subject: [PATCH 2/4] Annotate List `From` impls with `#[inline]` --- src/list/removable.rs | 2 ++ src/list/unique.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/list/removable.rs b/src/list/removable.rs index edebd27..ea46d02 100644 --- a/src/list/removable.rs +++ b/src/list/removable.rs @@ -45,6 +45,7 @@ impl RemovableList { } impl From> for RemovableList { + #[inline] fn from(item: Vec) -> Self { let mut res = RemovableList { items: vec![], @@ -58,6 +59,7 @@ impl From> for RemovableList { } impl From for Vec { + #[inline] fn from(l: RemovableList) -> Self { drop(l.negations); l.items diff --git a/src/list/unique.rs b/src/list/unique.rs index 38276d8..4de8e9d 100644 --- a/src/list/unique.rs +++ b/src/list/unique.rs @@ -26,6 +26,7 @@ impl UniqueList { } impl From> for UniqueList { + #[inline] fn from(item: Vec) -> Self { let mut res = Self { items: vec![] }; for it in item { @@ -36,6 +37,7 @@ impl From> for UniqueList { } impl From for Vec { + #[inline] fn from(l: UniqueList) -> Self { l.items } From 1796f033480fac61a063caae1c8f2cbe9f233df2 Mon Sep 17 00:00:00 2001 From: Simon Gerber Date: Wed, 30 Aug 2023 13:04:41 +0200 Subject: [PATCH 3/4] Add methods `new()`, `with_capacity()`, `len()` and `shrink_to_fit()` to `List` trait Also implement the methods for `UniqueList` and `RemovableList` --- src/list/mod.rs | 4 ++++ src/list/removable.rs | 24 ++++++++++++++++++++++++ src/list/unique.rs | 22 ++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/list/mod.rs b/src/list/mod.rs index 6d76bf3..264c5b5 100644 --- a/src/list/mod.rs +++ b/src/list/mod.rs @@ -4,6 +4,10 @@ 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); diff --git a/src/list/removable.rs b/src/list/removable.rs index ea46d02..e23bb95 100644 --- a/src/list/removable.rs +++ b/src/list/removable.rs @@ -67,6 +67,30 @@ impl From for Vec { } 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 diff --git a/src/list/unique.rs b/src/list/unique.rs index 4de8e9d..8f57e04 100644 --- a/src/list/unique.rs +++ b/src/list/unique.rs @@ -44,6 +44,28 @@ impl From for Vec { } 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() { From 441ebb3d8eeac1efb84ec31c7629b67f9bac3891 Mon Sep 17 00:00:00 2001 From: Simon Gerber Date: Wed, 30 Aug 2023 16:06:25 +0200 Subject: [PATCH 4/4] Remove unnecessary explicit `drop()` --- src/list/removable.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/list/removable.rs b/src/list/removable.rs index e23bb95..9662fe0 100644 --- a/src/list/removable.rs +++ b/src/list/removable.rs @@ -61,7 +61,6 @@ impl From> for RemovableList { impl From for Vec { #[inline] fn from(l: RemovableList) -> Self { - drop(l.negations); l.items } }