Skip to main content

rama_http/protocols/html/selector/
builder.rs

1//! Infallible builders for [`Selector`] and [`Compound`].
2//!
3//! Unlike [`parse`](str::parse), these construct the AST directly from
4//! component values, so they can't fail — there is no grammar to violate.
5//! Inputs are taken *literally* (a *component value*, not a selector
6//! fragment): tag/attribute names are ASCII-lowercased per HTML, class/id
7//! and attribute values are kept verbatim. A value that can't occur in real
8//! markup (whitespace in a tag name, …) simply never matches; in debug
9//! builds such an almost-certainly-mistaken input trips a `debug_assert`.
10
11use super::ast::{
12    AttributeOperator, AttributeSelector, CaseSensitivity, Combinator, ComplexSelector, Compound,
13    LocalName, Nth, NthType, Selector, SelectorPart,
14};
15
16/// Debug-only guard for an ident-position input (tag / class / id / attribute
17/// name). These can't hold whitespace in real markup and are never empty, so
18/// such input is almost always a fragment that should have gone through
19/// `parse` instead. Released builds accept it (it just never matches).
20#[inline]
21fn debug_assert_ident(kind: &str, value: &str) {
22    debug_assert!(
23        !value.is_empty() && !value.bytes().any(|b| b.is_ascii_whitespace()),
24        "{kind} must be a non-empty literal without whitespace, got {value:?} \
25         (use `parse` for a selector fragment)"
26    );
27}
28
29impl Selector {
30    /// A type (tag) selector, e.g. `div`. Shorthand for
31    /// `Selector::of(Compound::tag(name))`.
32    #[must_use]
33    pub fn tag(name: impl AsRef<str>) -> Self {
34        Self::of(Compound::tag(name))
35    }
36
37    /// A class selector, e.g. `.menu`.
38    #[must_use]
39    pub fn class(name: impl AsRef<str>) -> Self {
40        Self::of(Compound::class(name))
41    }
42
43    /// An id selector, e.g. `#main`.
44    #[must_use]
45    pub fn id(name: impl AsRef<str>) -> Self {
46        Self::of(Compound::id(name))
47    }
48
49    /// The universal selector `*`.
50    #[must_use]
51    pub fn any() -> Self {
52        Self::of(Compound::any())
53    }
54
55    /// A selector from a single [`Compound`].
56    #[must_use]
57    pub fn of(compound: Compound) -> Self {
58        Self {
59            selectors: vec![ComplexSelector {
60                parts: vec![SelectorPart {
61                    combinator: None,
62                    compound,
63                }],
64            }],
65        }
66    }
67
68    /// Extends the current complex selector with a child combinator:
69    /// `self > compound`.
70    #[must_use]
71    pub fn child(self, compound: Compound) -> Self {
72        self.combine(Combinator::Child, compound)
73    }
74
75    /// Extends the current complex selector with a descendant combinator:
76    /// `self compound`.
77    #[must_use]
78    pub fn descendant(self, compound: Compound) -> Self {
79        self.combine(Combinator::Descendant, compound)
80    }
81
82    /// Adds `other`'s complex selectors as further alternatives (the comma
83    /// list): `self, other`.
84    #[must_use]
85    pub fn or(mut self, other: Self) -> Self {
86        self.selectors.extend(other.selectors);
87        self
88    }
89
90    fn combine(mut self, combinator: Combinator, compound: Compound) -> Self {
91        // `of`/`tag`/… always seed exactly one complex, and `or` only appends
92        // whole complexes, so there is always a last complex to extend.
93        if let Some(complex) = self.selectors.last_mut() {
94            complex.parts.push(SelectorPart {
95                combinator: Some(combinator),
96                compound,
97            });
98        }
99        self
100    }
101}
102
103impl Compound {
104    /// A type (tag) selector, e.g. `div` (ASCII-lowercased).
105    #[must_use]
106    pub fn tag(name: impl AsRef<str>) -> Self {
107        let name = name.as_ref();
108        debug_assert_ident("tag name", name);
109        Self {
110            name: Some(LocalName::new(name)),
111            ..Self::default()
112        }
113    }
114
115    /// The universal selector `*`.
116    #[must_use]
117    pub fn any() -> Self {
118        Self {
119            explicit_universal: true,
120            ..Self::default()
121        }
122    }
123
124    /// A class selector, e.g. `.menu`.
125    #[must_use]
126    pub fn class(name: impl AsRef<str>) -> Self {
127        Self::default().with_class(name)
128    }
129
130    /// An id selector, e.g. `#main`.
131    #[must_use]
132    pub fn id(name: impl AsRef<str>) -> Self {
133        Self::default().with_id(name)
134    }
135
136    /// Sets (replacing) the id, e.g. `#main`.
137    #[must_use]
138    pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
139        let id = id.as_ref();
140        debug_assert_ident("id", id);
141        self.id = Some(id.into());
142        self
143    }
144
145    /// Adds a class, e.g. `.menu`.
146    #[must_use]
147    pub fn with_class(mut self, class: impl AsRef<str>) -> Self {
148        let class = class.as_ref();
149        debug_assert_ident("class", class);
150        self.classes.push(class.into());
151        self
152    }
153
154    /// Adds an attribute-presence selector, e.g. `[disabled]`.
155    #[must_use]
156    pub fn with_attr(self, name: impl AsRef<str>) -> Self {
157        self.push_attr(name, None, "", CaseSensitivity::CaseSensitive)
158    }
159
160    /// Adds `[name="value"]` (exact match).
161    #[must_use]
162    pub fn with_attr_eq(self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
163        self.push_attr(
164            name,
165            Some(AttributeOperator::Equals),
166            value.as_ref(),
167            CaseSensitivity::CaseSensitive,
168        )
169    }
170
171    /// Adds `[name="value" i]` (exact, ASCII case-insensitive).
172    #[must_use]
173    pub fn with_attr_eq_ignore_case(self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
174        self.push_attr(
175            name,
176            Some(AttributeOperator::Equals),
177            value.as_ref(),
178            CaseSensitivity::AsciiCaseInsensitive,
179        )
180    }
181
182    /// Adds `[name~="value"]` (whitespace-separated list contains `value`).
183    #[must_use]
184    pub fn with_attr_includes(self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
185        self.push_attr(
186            name,
187            Some(AttributeOperator::Includes),
188            value.as_ref(),
189            CaseSensitivity::CaseSensitive,
190        )
191    }
192
193    /// Adds `[name|="value"]` (equals `value` or starts with `value-`).
194    #[must_use]
195    pub fn with_attr_dash_match(self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
196        self.push_attr(
197            name,
198            Some(AttributeOperator::DashMatch),
199            value.as_ref(),
200            CaseSensitivity::CaseSensitive,
201        )
202    }
203
204    /// Adds `[name^="value"]` (begins with `value`).
205    #[must_use]
206    pub fn with_attr_prefix(self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
207        self.push_attr(
208            name,
209            Some(AttributeOperator::Prefix),
210            value.as_ref(),
211            CaseSensitivity::CaseSensitive,
212        )
213    }
214
215    /// Adds `[name$="value"]` (ends with `value`).
216    #[must_use]
217    pub fn with_attr_suffix(self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
218        self.push_attr(
219            name,
220            Some(AttributeOperator::Suffix),
221            value.as_ref(),
222            CaseSensitivity::CaseSensitive,
223        )
224    }
225
226    /// Adds `[name*="value"]` (contains `value`).
227    #[must_use]
228    pub fn with_attr_substring(self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
229        self.push_attr(
230            name,
231            Some(AttributeOperator::Substring),
232            value.as_ref(),
233            CaseSensitivity::CaseSensitive,
234        )
235    }
236
237    /// Adds `:nth-child(an + b)`.
238    #[must_use]
239    pub fn with_nth_child(mut self, a: i32, b: i32) -> Self {
240        self.nth.push(Nth {
241            ty: NthType::Child,
242            a,
243            b,
244        });
245        self
246    }
247
248    /// Adds `:nth-of-type(an + b)`.
249    #[must_use]
250    pub fn with_nth_of_type(mut self, a: i32, b: i32) -> Self {
251        self.nth.push(Nth {
252            ty: NthType::OfType,
253            a,
254            b,
255        });
256        self
257    }
258
259    /// Adds `:first-child`.
260    #[must_use]
261    pub fn with_first_child(self) -> Self {
262        self.with_nth_child(0, 1)
263    }
264
265    /// Adds `:first-of-type`.
266    #[must_use]
267    pub fn with_first_of_type(self) -> Self {
268        self.with_nth_of_type(0, 1)
269    }
270
271    /// Adds `:not(compound)`.
272    #[must_use]
273    pub fn without(mut self, compound: Self) -> Self {
274        self.negations.push(compound);
275        self
276    }
277
278    fn push_attr(
279        mut self,
280        name: impl AsRef<str>,
281        operator: Option<AttributeOperator>,
282        value: &str,
283        case: CaseSensitivity,
284    ) -> Self {
285        let name = name.as_ref();
286        debug_assert_ident("attribute name", name);
287        self.attributes.push(AttributeSelector {
288            name: name.to_ascii_lowercase().into(),
289            operator,
290            value: value.into(),
291            case,
292        });
293        self
294    }
295}