Skip to main content

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}