zrx_id/id/specificity/convert.rs
1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Specificity computation.
27
28use crate::id::expression::{Expression, Operand, Operator, Term};
29use crate::id::selector::Selector;
30use crate::id::sequence::{Element, Sequence};
31use crate::id::Id;
32
33use super::segment::atom::{Character, Wildcard};
34use super::segment::{Atom, Segment, Segments, ToSegments};
35use super::Specificity;
36
37// ----------------------------------------------------------------------------
38// Traits
39// ----------------------------------------------------------------------------
40
41/// Computation of [`Specificity`].
42pub trait ToSpecificity {
43 /// Computes the specificity of the value.
44 fn to_specificity(&self) -> Specificity;
45}
46
47// ----------------------------------------------------------------------------
48// Trait implementations
49// ----------------------------------------------------------------------------
50
51impl ToSpecificity for Character<'_> {
52 /// Computes the specificity of the character class.
53 #[inline]
54 fn to_specificity(&self) -> Specificity {
55 Specificity(0, 1, 0, 1)
56 }
57}
58
59impl ToSpecificity for Wildcard {
60 /// Computes the specificity of the wildcard.
61 #[inline]
62 fn to_specificity(&self) -> Specificity {
63 match self {
64 Wildcard::Character => Specificity(0, 1, 0, 0),
65 Wildcard::Sequence => Specificity(0, 1, 0, 0),
66 Wildcard::Traversal => Specificity(0, 0, 1, 0),
67 }
68 }
69}
70
71impl ToSpecificity for Atom<'_> {
72 /// Computes the specificity of the atom.
73 #[inline]
74 fn to_specificity(&self) -> Specificity {
75 match self {
76 Atom::Literal(literal) => {
77 let len = u16::try_from(literal.len()).unwrap_or(u16::MAX);
78 Specificity(1, 0, 0, len)
79 }
80 Atom::Wildcard(wildcard) => wildcard.to_specificity(),
81 Atom::Character(character) => character.to_specificity(),
82 Atom::Group(data) => data
83 .iter()
84 .map(ToSpecificity::to_specificity)
85 .reduce(Specificity::min)
86 .unwrap_or_default(),
87 }
88 }
89}
90
91// ----------------------------------------------------------------------------
92
93impl ToSpecificity for Segment<'_> {
94 /// Computes the specificity of the segment.
95 #[inline]
96 fn to_specificity(&self) -> Specificity {
97 self.iter()
98 .map(ToSpecificity::to_specificity)
99 .reduce(Specificity::min_sum_len)
100 .unwrap_or_default()
101 }
102}
103
104impl ToSpecificity for Segments<'_> {
105 /// Computes the specificity of the segment set.
106 #[inline]
107 fn to_specificity(&self) -> Specificity {
108 self.iter()
109 .map(ToSpecificity::to_specificity)
110 .reduce(Specificity::sum)
111 .unwrap_or_default()
112 }
113}
114
115// ----------------------------------------------------------------------------
116
117impl ToSpecificity for Id {
118 /// Computes the specificity of the identifier.
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// # use std::error::Error;
124 /// # fn main() -> Result<(), Box<dyn Error>> {
125 /// use zrx_id::id;
126 /// use zrx_id::specificity::ToSpecificity;
127 ///
128 /// // Create identifier and compute specificity
129 /// let id = id!(provider = "file", context = ".", location = "index.md")?;
130 /// assert_eq!(id.to_specificity(), (3, 0, 0, 13).into());
131 /// # Ok(())
132 /// # }
133 /// ```
134 #[inline]
135 fn to_specificity(&self) -> Specificity {
136 let iter = 1..7;
137 iter.map(|index| self.as_ref().get(index).to_specificity())
138 .reduce(Specificity::sum)
139 .unwrap_or_default()
140 }
141}
142
143impl ToSpecificity for Selector {
144 /// Computes the specificity of the selector.
145 ///
146 /// # Examples
147 ///
148 /// ```
149 /// # use std::error::Error;
150 /// # fn main() -> Result<(), Box<dyn Error>> {
151 /// use zrx_id::selector;
152 /// use zrx_id::specificity::ToSpecificity;
153 ///
154 /// // Create selector and compute specificity
155 /// let selector = selector!(location = "**/*.md")?;
156 /// assert_eq!(selector.to_specificity(), (0, 1, 1, 3).into());
157 /// # Ok(())
158 /// # }
159 /// ```
160 #[inline]
161 fn to_specificity(&self) -> Specificity {
162 let iter = 1..7;
163 iter.map(|index| self.as_ref().get(index).to_specificity())
164 .reduce(Specificity::sum)
165 .unwrap_or_default()
166 }
167}
168
169// ----------------------------------------------------------------------------
170
171impl ToSpecificity for Operand {
172 /// Computes the specificity of the operand.
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// # use std::error::Error;
178 /// # fn main() -> Result<(), Box<dyn Error>> {
179 /// use zrx_id::expression::Operand;
180 /// use zrx_id::selector;
181 /// use zrx_id::specificity::ToSpecificity;
182 ///
183 /// // Create operand and compute specificity
184 /// let operand = Operand::from(selector!(location = "**/*.md")?);
185 /// assert_eq!(operand.to_specificity(), (0, 1, 1, 3).into());
186 /// # Ok(())
187 /// # }
188 /// ```
189 #[inline]
190 fn to_specificity(&self) -> Specificity {
191 match self {
192 Operand::Expression(expr) => expr.to_specificity(),
193 Operand::Term(term) => term.to_specificity(),
194 }
195 }
196}
197
198impl ToSpecificity for Term {
199 /// Computes the specificity of the term.
200 ///
201 /// # Examples
202 ///
203 /// ```
204 /// # use std::error::Error;
205 /// # fn main() -> Result<(), Box<dyn Error>> {
206 /// use zrx_id::expression::Term;
207 /// use zrx_id::selector;
208 /// use zrx_id::specificity::ToSpecificity;
209 ///
210 /// // Create term and compute specificity
211 /// let term = Term::from(selector!(location = "**/*.md")?);
212 /// assert_eq!(term.to_specificity(), (0, 1, 1, 3).into());
213 /// # Ok(())
214 /// # }
215 /// ```
216 #[inline]
217 fn to_specificity(&self) -> Specificity {
218 match self {
219 Term::Id(id) => id.to_specificity(),
220 Term::Selector(selector) => selector.to_specificity(),
221 }
222 }
223}
224
225impl ToSpecificity for Expression {
226 /// Computes the specificity of the expression.
227 ///
228 /// # Examples
229 ///
230 /// ```
231 /// # use std::error::Error;
232 /// # fn main() -> Result<(), Box<dyn Error>> {
233 /// use zrx_id::specificity::ToSpecificity;
234 /// use zrx_id::{selector, Expression};
235 ///
236 /// // Create expression and compute specificity
237 /// let expr = Expression::any(|expr| {
238 /// expr.with(selector!(location = "**/*.jpg")?)?
239 /// .with(selector!(location = "**/*.png")?)
240 /// })?;
241 /// assert_eq!(expr.to_specificity(), (0, 1, 1, 4).into());
242 /// # Ok(())
243 /// # }
244 /// ```
245 #[inline]
246 fn to_specificity(&self) -> Specificity {
247 let iter = self.operands().iter().map(ToSpecificity::to_specificity);
248 match self.operator() {
249 Operator::Any => iter.reduce(Specificity::min).unwrap_or_default(),
250 Operator::All => iter.reduce(Specificity::sum).unwrap_or_default(),
251 Operator::Not => Specificity::default(),
252 }
253 }
254}
255
256// ----------------------------------------------------------------------------
257
258impl ToSpecificity for Element {
259 /// Computes the specificity of the sequence element.
260 ///
261 /// # Examples
262 ///
263 /// ```
264 /// # use std::error::Error;
265 /// # fn main() -> Result<(), Box<dyn Error>> {
266 /// use zrx_id::selector;
267 /// use zrx_id::sequence::Element;
268 /// use zrx_id::specificity::ToSpecificity;
269 ///
270 /// // Create sequence and compute specificity
271 /// let element = Element::from(selector!(location = "**/*.md")?);
272 /// assert_eq!(element.to_specificity(), (0, 1, 1, 3).into());
273 /// # Ok(())
274 /// # }
275 /// ```
276 fn to_specificity(&self) -> Specificity {
277 match self {
278 Element::Expression(expr) => expr.to_specificity(),
279 Element::Gap => Specificity::default(),
280 }
281 }
282}
283
284impl ToSpecificity for Sequence {
285 /// Computes the specificity of the sequence.
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// # use std::error::Error;
291 /// # fn main() -> Result<(), Box<dyn Error>> {
292 /// use zrx_id::selector;
293 /// use zrx_id::sequence::Sequence;
294 /// use zrx_id::specificity::ToSpecificity;
295 ///
296 /// // Create sequence and compute specificity
297 /// let sequence = Sequence::from(selector!(location = "**/*.md")?);
298 /// assert_eq!(sequence.to_specificity(), (0, 1, 1, 3).into());
299 /// # Ok(())
300 /// # }
301 /// ```
302 fn to_specificity(&self) -> Specificity {
303 let iter = self.iter().map(ToSpecificity::to_specificity);
304 iter.reduce(Specificity::sum).unwrap_or_default()
305 }
306}
307
308// ----------------------------------------------------------------------------
309// Blanket implementations
310// ----------------------------------------------------------------------------
311
312impl<T> ToSpecificity for T
313where
314 T: ToSegments,
315{
316 #[inline]
317 fn to_specificity(&self) -> Specificity {
318 self.to_segments().to_specificity()
319 }
320}