micro_games_kit/
tag.rs

1use std::{
2    borrow::Cow,
3    hash::{Hash, Hasher},
4    ops::Range,
5    str::FromStr,
6};
7
8#[derive(Default, Clone)]
9pub struct Tag {
10    content: Cow<'static, str>,
11    parts: Vec<Range<usize>>,
12}
13
14impl Tag {
15    pub fn new(content: impl Into<Cow<'static, str>>) -> Self {
16        let content = content.into();
17        let mut range = 0..0;
18        let parts = content
19            .chars()
20            .chain(std::iter::once('.'))
21            .filter_map(|character| {
22                let result = range.clone();
23                range.end += character.len_utf8();
24                if character == '.' {
25                    range.start = range.end;
26                    Some(result)
27                } else {
28                    None
29                }
30            })
31            .collect();
32        Self { content, parts }
33    }
34
35    pub fn as_str(&self) -> &str {
36        &self.content
37    }
38
39    pub fn parts(&self) -> impl Iterator<Item = &str> {
40        self.parts.iter().map(|range| &self.content[range.clone()])
41    }
42
43    pub fn part(&self, index: usize) -> Option<&str> {
44        self.parts
45            .get(index)
46            .map(|range| &self.content[range.clone()])
47    }
48
49    pub fn len(&self) -> usize {
50        self.parts.len()
51    }
52
53    pub fn is_empty(&self) -> bool {
54        self.parts.is_empty()
55    }
56
57    pub fn fragment(&self, mut parts: usize) -> &str {
58        parts = parts.min(self.len()).saturating_sub(1);
59        &self.content[0..(self.parts[parts].end)]
60    }
61
62    pub fn parent_fragment(&self) -> &str {
63        self.fragment(self.len().saturating_sub(1))
64    }
65
66    pub fn sub_tag(&self, parts: usize) -> Self {
67        Self::new(self.fragment(parts).to_owned())
68    }
69
70    pub fn parent(&self) -> Self {
71        Self::new(self.parent_fragment().to_owned())
72    }
73
74    pub fn push(&self, part: &str) -> Self {
75        Self::new(format!("{}.{}", self.content, part))
76    }
77
78    pub fn shared_parts(&self, other: &Self) -> usize {
79        let mut result = 0;
80        for (a, b) in self.parts().zip(other.parts()) {
81            if a != b {
82                return result;
83            }
84            result += 1;
85        }
86        result
87    }
88
89    pub fn is_subset_of(&self, other: &Self) -> bool {
90        self.len() <= other.len() && self.shared_parts(other) == self.len()
91    }
92
93    pub fn is_superset_of(&self, other: &Self) -> bool {
94        self.len() >= other.len() && self.shared_parts(other) == other.len()
95    }
96
97    pub fn matches(&self, other: &Self) -> bool {
98        if self.len() > other.len() {
99            return false;
100        }
101        for (a, b) in self.parts().zip(other.parts()) {
102            if a != "*" && a != b {
103                return false;
104            }
105        }
106        true
107    }
108}
109
110impl Hash for Tag {
111    fn hash<H: Hasher>(&self, state: &mut H) {
112        self.content.hash(state);
113    }
114}
115
116impl PartialEq for Tag {
117    fn eq(&self, other: &Self) -> bool {
118        self.content == other.content
119    }
120}
121
122impl Eq for Tag {}
123
124impl std::fmt::Debug for Tag {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        f.debug_struct("Tag")
127            .field("content", &self.content)
128            .field(
129                "parts",
130                &self
131                    .parts
132                    .iter()
133                    .map(|range| &self.content[range.clone()])
134                    .collect::<Vec<_>>(),
135            )
136            .finish()
137    }
138}
139
140impl std::fmt::Display for Tag {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        write!(f, "{}", self.content)
143    }
144}
145
146impl FromStr for Tag {
147    type Err = ();
148
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        Ok(Self::new(s.to_owned()))
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::Tag;
157
158    #[test]
159    fn test_tag() {
160        let tag = Tag::new("abra.ca.dabra");
161        assert_eq!(tag.fragment(0), "abra");
162        assert_eq!(tag.fragment(1), "abra");
163        assert_eq!(tag.fragment(2), "abra.ca");
164        assert_eq!(tag.fragment(3), "abra.ca.dabra");
165        assert_eq!(tag.fragment(4), "abra.ca.dabra");
166        assert_eq!(tag.as_str(), "abra.ca.dabra");
167        assert_eq!(tag, tag.as_str().parse::<Tag>().unwrap());
168        assert_eq!(tag.parent_fragment(), "abra.ca");
169        assert_eq!(tag.parent(), Tag::new("abra.ca"));
170        assert_eq!(tag.parent().parent(), Tag::new("abra"));
171        assert_eq!(tag.parent().push("foo"), Tag::new("abra.ca.foo"));
172        assert_eq!(tag.shared_parts(&Tag::new("abra.ca.foo")), 2);
173        assert_eq!(tag.shared_parts(&Tag::new("abra.ca")), 2);
174        assert_eq!(tag.shared_parts(&Tag::new("abra.foo")), 1);
175        assert_eq!(tag.shared_parts(&Tag::new("foo")), 0);
176        assert_eq!(tag.shared_parts(&Tag::new("abra.ca.dabra.foo")), 3);
177        assert!(tag.is_subset_of(&Tag::new("abra.ca.dabra")));
178        assert!(tag.is_subset_of(&Tag::new("abra.ca.dabra.foo")));
179        assert!(!tag.is_subset_of(&Tag::new("abra.ca")));
180        assert!(!tag.is_subset_of(&Tag::new("abra.ca.foo")));
181        assert!(tag.is_superset_of(&Tag::new("abra.ca.dabra")));
182        assert!(!tag.is_superset_of(&Tag::new("abra.ca.dabra.foo")));
183        assert!(tag.is_superset_of(&Tag::new("abra.ca")));
184        assert!(!tag.is_superset_of(&Tag::new("abra.ca.foo")));
185        assert!(Tag::new("abra.ca.dabra").matches(&tag));
186        assert!(!Tag::new("abra.ca.dabra.foo").matches(&tag));
187        assert!(Tag::new("abra.ca").matches(&tag));
188        assert!(!Tag::new("abra.ca.foo").matches(&tag));
189        assert!(Tag::new("abra").matches(&tag));
190        assert!(!Tag::new("abra.foo").matches(&tag));
191        assert!(!Tag::new("foo").matches(&tag));
192        assert!(Tag::new("abra.ca.*").matches(&tag));
193        assert!(!Tag::new("foo.ca.*").matches(&tag));
194        assert!(Tag::new("abra.*.dabra").matches(&tag));
195        assert!(!Tag::new("abra.*.foo").matches(&tag));
196        assert!(Tag::new("*.ca.dabra").matches(&tag));
197        assert!(!Tag::new("*.foo.dabra").matches(&tag));
198    }
199}