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}