Skip to main content

zrx_id/id/matcher/
builder.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//! Matcher builder.
27
28use globset::{Glob, GlobBuilder};
29
30use crate::id::selector::TryToSelector;
31
32use super::component;
33use super::error::Result;
34use super::Matcher;
35
36// ----------------------------------------------------------------------------
37// Structs
38// ----------------------------------------------------------------------------
39
40/// Matcher builder.
41#[derive(Clone, Debug, Default)]
42pub struct Builder {
43    /// Component builder for provider.
44    provider: component::Builder,
45    /// Component builder for resource.
46    resource: component::Builder,
47    /// Component builder for variant.
48    variant: component::Builder,
49    /// Component builder for context.
50    context: component::Builder,
51    /// Component builder for location.
52    location: component::Builder,
53    /// Component builder for fragment.
54    fragment: component::Builder,
55}
56
57// ----------------------------------------------------------------------------
58// Implementations
59// ----------------------------------------------------------------------------
60
61impl Matcher {
62    /// Creates a matcher builder.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use zrx_id::Matcher;
68    ///
69    /// // Create matcher builder
70    /// let mut builder = Matcher::builder();
71    /// ```
72    #[inline]
73    #[must_use]
74    pub fn builder() -> Builder {
75        Builder::default()
76    }
77}
78
79// ----------------------------------------------------------------------------
80
81impl Builder {
82    /// Extends the matcher with the given selector.
83    ///
84    /// This method adds a [`Selector`][] to the matcher, creating a [`Glob`]
85    /// for each component and adding it to a [`GlobSetBuilder`][].
86    ///
87    /// [`GlobSetBuilder`]: globset::GlobSetBuilder
88    /// [`Selector`]: crate::id::selector::Selector
89    ///
90    /// # Errors
91    ///
92    /// Returns [`Error::Id`] if the selector is invalid, and [`Error::Glob`]
93    /// if a component can't be successfully parsed.
94    ///
95    /// [`Error::Id`]: crate::id::matcher::error::Error::Id
96    /// [`Error::Glob`]: crate::id::matcher::error::Error::Glob
97    ///
98    /// # Examples
99    ///
100    /// ```
101    /// # use std::error::Error;
102    /// # fn main() -> Result<(), Box<dyn Error>> {
103    /// use zrx_id::Matcher;
104    ///
105    /// // Create matcher builder with selector
106    /// let mut builder = Matcher::builder().with(&"zrs:::::**/*.md:")?;
107    /// # Ok(())
108    /// # }
109    /// ```
110    #[inline]
111    pub fn with<T>(mut self, selector: &T) -> Result<Self>
112    where
113        T: TryToSelector,
114    {
115        self.add(selector)?;
116        Ok(self)
117    }
118
119    /// Adds a selector to the matcher.
120    ///
121    /// Note that [`Builder::with`] offers better ergonomics to create matchers
122    /// from fixed sets of selectors, as it simplifies construction by chaining.
123    /// However, sometimes matchers are owned by other data types, which makes
124    /// it necessary to provide this implementation as well.
125    ///
126    /// # Errors
127    ///
128    /// Returns [`Error::Id`] if the selector is invalid, and [`Error::Glob`]
129    /// if a component can't be successfully parsed.
130    ///
131    /// [`Error::Id`]: crate::id::matcher::error::Error::Id
132    /// [`Error::Glob`]: crate::id::matcher::error::Error::Glob
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// # use std::error::Error;
138    /// # fn main() -> Result<(), Box<dyn Error>> {
139    /// use zrx_id::Matcher;
140    ///
141    /// // Create matcher builder and add selector
142    /// let mut builder = Matcher::builder();
143    /// builder.add(&"zrs:::::**/*.md:")?;
144    /// # Ok(())
145    /// # }
146    /// ```
147    pub fn add<T>(&mut self, selector: &T) -> Result<&mut Self>
148    where
149        T: TryToSelector,
150    {
151        let selector = selector.try_to_selector()?;
152
153        // Compile and add each component of the given selector
154        self.provider.add(compile(selector.provider().as_deref())?);
155        self.resource.add(compile(selector.resource().as_deref())?);
156        self.variant.add(compile(selector.variant().as_deref())?);
157        self.context.add(compile(selector.context().as_deref())?);
158        self.location.add(compile(selector.location().as_deref())?);
159        self.fragment.add(compile(selector.fragment().as_deref())?);
160
161        // Return builder for chaining
162        Ok(self)
163    }
164
165    /// Builds the matcher.
166    ///
167    /// # Errors
168    ///
169    /// Returns [`Error::Glob`][] if a component's [`GlobSet`][] can't be built.
170    ///
171    /// [`Error::Glob`]: crate::id::matcher::error::Error::Glob
172    /// [`GlobSet`]: globset::GlobSet
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// # use std::error::Error;
178    /// # fn main() -> Result<(), Box<dyn Error>> {
179    /// use zrx_id::Matcher;
180    ///
181    /// // Create matcher builder and add selector
182    /// let mut builder = Matcher::builder();
183    /// builder.add(&"zrs:::::**/*.md:")?;
184    ///
185    /// // Create matcher from builder
186    /// let matcher = builder.build()?;
187    /// # Ok(())
188    /// # }
189    /// ```
190    pub fn build(self) -> Result<Matcher> {
191        Ok(Matcher {
192            provider: self.provider.build()?,
193            resource: self.resource.build()?,
194            variant: self.variant.build()?,
195            context: self.context.build()?,
196            location: self.location.build()?,
197            fragment: self.fragment.build()?,
198        })
199    }
200}
201
202// ----------------------------------------------------------------------------
203// Functions
204// ----------------------------------------------------------------------------
205
206/// Compiles a component for addition to the matcher.
207fn compile(opt: Option<&str>) -> Result<Option<Glob>> {
208    if let Some(pattern) = opt {
209        let mut builder = GlobBuilder::new(pattern);
210        // We enable empty alternates to support patterns like "{,**/}*.md",
211        // which is a sensible default as it makes glob patterns more flexible
212        Ok(Some(builder.empty_alternates(true).build()?))
213    } else {
214        Ok(None)
215    }
216}