tbf/
pattern.rs

1use super::{Group, Tag};
2
3use alloc::boxed::Box;
4use alloc::vec::Vec;
5use alloc::string::{String, ToString};
6use core::borrow::Borrow;
7
8mod sealed {
9    use super::*;
10
11    pub trait Sealed {}
12
13    impl Sealed for Tag {}
14    impl Sealed for [Tag] {}
15    impl<const N: usize> Sealed for [Tag; N] {}
16    impl Sealed for TagPredicate {}
17}
18
19/// Any type that can be used to match a file's tags on
20pub trait TagPattern: sealed::Sealed {
21    /// Match this item against an iterator of tags
22    fn match_tags<T, I>(&self, tags: I) -> bool
23    where
24        T: Borrow<Tag>,
25        I: IntoIterator<Item = T>;
26}
27
28impl TagPattern for Tag {
29    fn match_tags<T, I>(&self, tags: I) -> bool
30    where
31        T: Borrow<Tag>,
32        I: IntoIterator<Item = T>,
33    {
34        tags.into_iter().any(|tag| tag.borrow() == self)
35    }
36}
37
38impl TagPattern for [Tag] {
39    fn match_tags<T, I>(&self, tags: I) -> bool
40    where
41        T: Borrow<Tag>,
42        I: IntoIterator<Item = T>,
43    {
44        let tags = tags.into_iter().collect::<Vec<_>>();
45        self.iter()
46            .all(|tag| tags.iter().any(|t| tag == t.borrow()))
47    }
48}
49
50impl<const N: usize> TagPattern for [Tag; N] {
51    fn match_tags<T, I>(&self, tags: I) -> bool
52    where
53        T: Borrow<Tag>,
54        I: IntoIterator<Item = T>,
55    {
56        <[Tag]>::match_tags(self, tags)
57    }
58}
59
60/// Complex support for matching binary expressions against tags
61#[derive(Debug, PartialEq)]
62pub enum TagPredicate {
63    /// And predicates together
64    And(Vec<TagPredicate>),
65    /// Or predicates together
66    Or(Vec<TagPredicate>),
67    /// Inverse a predicate
68    Not(Box<TagPredicate>),
69
70    /// Match just the group of a tag
71    Group(Group),
72    /// Match just the name of a tag
73    Name(String),
74    /// Match a tag exactly
75    Tag(Tag),
76}
77
78impl From<Tag> for TagPredicate {
79    fn from(tag: Tag) -> TagPredicate {
80        TagPredicate::Tag(tag)
81    }
82}
83
84impl From<Group> for TagPredicate {
85    fn from(group: Group) -> Self {
86        TagPredicate::Group(group)
87    }
88}
89
90impl TagPredicate {
91    /// Create an and predicate from an iterator of predicate items
92    pub fn and<T, I>(preds: I) -> TagPredicate
93    where
94        T: Into<TagPredicate>,
95        I: IntoIterator<Item = T>,
96    {
97        TagPredicate::And(preds.into_iter().map(T::into).collect())
98    }
99
100    /// Create an or predicate from an iterator of predicate items
101    pub fn or<T, I>(preds: I) -> TagPredicate
102    where
103        T: Into<TagPredicate>,
104        I: IntoIterator<Item = T>,
105    {
106        TagPredicate::Or(preds.into_iter().map(T::into).collect())
107    }
108
109    /// Create a not predicate from some other predicate item
110    pub fn not<T>(pred: T) -> TagPredicate
111    where
112        T: Into<TagPredicate>,
113    {
114        TagPredicate::Not(Box::new(pred.into()))
115    }
116
117    /// Create a predicate for a group
118    pub fn group(group: Group) -> TagPredicate {
119        TagPredicate::Group(group)
120    }
121
122    /// Create a predicate for a name
123    pub fn name(name: &str) -> TagPredicate {
124        TagPredicate::Name(name.to_string())
125    }
126
127    /// Create a predicate to match a tag exactly
128    pub fn tag(tag: Tag) -> TagPredicate {
129        TagPredicate::Tag(tag)
130    }
131}
132
133impl TagPattern for TagPredicate {
134    fn match_tags<T, I>(&self, tags: I) -> bool
135    where
136        T: Borrow<Tag>,
137        I: IntoIterator<Item = T>,
138    {
139        use TagPredicate::*;
140
141        let mut iter = tags.into_iter();
142        match self {
143            And(preds) => {
144                let tags = iter.collect::<Vec<_>>();
145                preds.iter().all(|pred| {
146                    pred.match_tags(tags.iter().map(Borrow::borrow))
147                })
148            }
149            Or(preds) => {
150                let tags = iter.collect::<Vec<_>>();
151                preds.iter().any(|pred| {
152                    pred.match_tags(tags.iter().map(Borrow::borrow))
153                })
154            }
155            Not(pred) => !pred.match_tags(iter),
156
157            Group(group) => iter.any(|tag| tag.borrow().group() == group),
158            Name(name) => iter.any(|tag| tag.borrow().name() == name),
159            Tag(tag) => tag.match_tags(iter),
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_tag() {
170        let tag_a = Tag::named("a");
171
172        assert!(tag_a.match_tags(&[Tag::named("a"), Tag::named("b"),]));
173        assert!(!tag_a.match_tags(&[Tag::named("b"), Tag::named("c"),]));
174    }
175
176    #[test]
177    fn test_tag_slice() {
178        let tag_slice = &[Tag::named("a"), Tag::named("b")];
179
180        assert!(tag_slice.match_tags(&[
181            Tag::named("b"),
182            Tag::named("c"),
183            Tag::named("d"),
184            Tag::named("a"),
185        ]));
186
187        assert!(!tag_slice.match_tags(&[Tag::named("c"), Tag::named("ab"), Tag::named("d"),]))
188    }
189
190    #[test]
191    fn test_pred_and() {
192        let pred = TagPredicate::and([Tag::named("a"), Tag::named("b")]);
193
194        assert!(pred.match_tags(&[
195            Tag::named("c"),
196            Tag::named("b"),
197            Tag::named("f"),
198            Tag::named("a"),
199        ]));
200        assert!(!pred.match_tags(&[Tag::named("c"), Tag::named("f"), Tag::named("a"),]));
201    }
202
203    #[test]
204    fn test_pred_or() {
205        let pred = TagPredicate::or([Tag::named("a"), Tag::named("b")]);
206
207        assert!(pred.match_tags(&[Tag::named("a"),]));
208        assert!(pred.match_tags(&[Tag::named("b"), Tag::named("c"),]));
209        assert!(!pred.match_tags(&[Tag::named("c"), Tag::named("d"),]));
210    }
211
212    #[test]
213    fn test_pred_not() {
214        let pred = TagPredicate::not(Tag::named("a"));
215
216        assert!(pred.match_tags(&[Tag::named("b"), Tag::named("c")]));
217        assert!(!pred.match_tags(&[Tag::named("a"), Tag::named("b")]));
218    }
219
220    #[test]
221    fn test_pred_group() {
222        let pred = TagPredicate::group(Group::Default);
223
224        assert!(pred.match_tags(&[Tag::named("a"), Tag::new(Group::custom("group"), "a")]));
225        assert!(!pred.match_tags(&[
226            Tag::new(Group::custom("group"), "a"),
227            Tag::new(Group::custom("group"), "b")
228        ]));
229    }
230
231    #[test]
232    fn test_pred_name() {
233        let pred = TagPredicate::name("a");
234
235        assert!(pred.match_tags(&[
236            Tag::new(Group::custom("group"), "a"),
237            Tag::new(Group::custom("group"), "b"),
238        ]));
239        assert!(pred.match_tags(&[Tag::named("a"), Tag::named("b"),]));
240        assert!(!pred.match_tags(&[Tag::new(Group::custom("group"), "b"), Tag::named("b"),]));
241    }
242
243    #[test]
244    fn test_pred_tag() {
245        let pred = TagPredicate::Tag(Tag::named("a"));
246
247        assert!(pred.match_tags(&[Tag::named("c"), Tag::named("a"),]));
248        assert!(!pred.match_tags(&[Tag::named("c"), Tag::named("f"),]));
249    }
250}