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}