rsass/sass/
selectors.rs

1//! This module contains types for the selectors of a rule.
2//!
3//! Basically, in a rule like `p.foo, .foo p { some: thing; }` there
4//! is a `Selectors` object which contains two `Selector` objects, one
5//! for `p.foo` and one for `.foo p`.
6//!
7//! This _may_ change to a something like a tree of operators with
8//! leafs of simple selectors in some future release.
9use crate::css::{self, parser::selector_set};
10use crate::parser::input_span;
11use crate::sass::SassString;
12use crate::{Error, ParseError, ScopeRef};
13use std::fmt::Write;
14
15/// A full set of selectors
16#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
17pub struct Selectors {
18    /// The actual selectors.
19    s: Vec<Selector>,
20}
21
22impl Selectors {
23    /// Create a root (empty) selector.
24    pub fn root() -> Self {
25        Self::new(vec![Selector::root()])
26    }
27    /// Return true if this is a root (empty) selector.
28    pub fn is_root(&self) -> bool {
29        self.s == [Selector::root()]
30    }
31    /// Create a new Selectors from a vec of selectors.
32    pub fn new(s: Vec<Selector>) -> Self {
33        Self { s }
34    }
35
36    /// Evaluate any interpolation in these Selectors.
37    pub fn eval(&self, scope: ScopeRef) -> Result<css::SelectorSet, Error> {
38        let mut s = Vec::new();
39        for sel in &self.s {
40            s.extend(sel.eval(scope.clone())?);
41        }
42        Ok(css::SelectorSet { s })
43    }
44    fn write_eval(
45        &self,
46        f: &mut String,
47        scope: ScopeRef,
48    ) -> Result<(), Error> {
49        if let Some((first, rest)) = self.s.split_first() {
50            first.write_eval(f, scope.clone())?;
51            for s in rest {
52                f.push_str(", ");
53                s.write_eval(f, scope.clone())?;
54            }
55        }
56        Ok(())
57    }
58}
59
60/// A css (or sass) selector.
61///
62/// A selector does not contain `,`.  If it does, it is a `Selectors`,
63/// where each of the parts separated by the comma is a `Selector`.
64#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
65pub struct Selector(Vec<SelectorPart>);
66
67impl Selector {
68    /// Get the root (empty) selector.
69    pub fn root() -> Self {
70        Self(vec![])
71    }
72    /// Create a new selector from parts.
73    pub fn new(s: Vec<SelectorPart>) -> Self {
74        Self(s)
75    }
76    fn eval(&self, scope: ScopeRef) -> Result<Vec<css::Selector>, Error> {
77        if self.0.is_empty() {
78            Ok(vec![css::Selector::default()])
79        } else {
80            let mut text = String::new();
81            self.write_eval(&mut text, scope)?;
82            Ok(ParseError::check(selector_set(input_span(text).borrow()))?.s)
83        }
84    }
85
86    fn write_eval(
87        &self,
88        f: &mut String,
89        scope: ScopeRef,
90    ) -> Result<(), Error> {
91        for p in &self.0 {
92            p.write_eval(f, scope.clone())?;
93        }
94        Ok(())
95    }
96}
97
98/// A selector consist of a sequence of these parts.
99#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
100pub enum SelectorPart {
101    /// A simple selector, eg a class, id or element name.
102    ///
103    /// Note that a Simple selector can hide a more complex selector
104    /// through string interpolation.
105    Simple(SassString),
106    /// The empty relational operator.
107    ///
108    /// The thing after this is a descendant of the thing before this.
109    Descendant,
110    /// A relational operator; `>`, `+`, `~`.
111    RelOp(u8),
112    /// An attribute selector
113    Attribute {
114        /// The attribute name
115        name: SassString,
116        /// An operator
117        op: String,
118        /// A value to match.
119        val: SassString,
120        /// Optional modifier.
121        modifier: Option<char>,
122    },
123    /// A css3 pseudo-element (::foo)
124    PseudoElement {
125        /// The name of the pseudo-element
126        name: SassString,
127        /// Arguments to the pseudo-element
128        arg: Option<Selectors>,
129    },
130    /// A pseudo-class or a css2 pseudo-element (:foo)
131    Pseudo {
132        /// The name of the pseudo-class
133        name: SassString,
134        /// Arguments to the pseudo-class
135        arg: Option<Selectors>,
136    },
137    /// A sass backref (`&`), to be replaced with outer selector.
138    BackRef,
139}
140
141impl SelectorPart {
142    fn write_eval(
143        &self,
144        f: &mut String,
145        scope: ScopeRef,
146    ) -> Result<(), Error> {
147        match self {
148            SelectorPart::Simple(s) => we(s, f, scope)?,
149            SelectorPart::Descendant => f.push(' '),
150            SelectorPart::RelOp(op) => {
151                if let Some(ch) = char::from_u32(u32::from(*op)) {
152                    f.push(' ');
153                    f.push(ch);
154                    f.push(' ');
155                }
156            }
157            SelectorPart::Attribute {
158                name,
159                op,
160                val,
161                modifier,
162            } => {
163                f.push('[');
164                we(name, f, scope.clone())?;
165                f.push_str(op);
166                let val = val.evaluate(scope)?.opt_unquote();
167                write!(f, "{val}")?;
168                if let Some(modifier) = modifier {
169                    if val.quotes().is_none() {
170                        f.push(' ');
171                    }
172                    f.push(*modifier);
173                }
174                f.push(']');
175            }
176            SelectorPart::PseudoElement { name, arg } => {
177                f.push_str("::");
178                we(name, f, scope.clone())?;
179                if let Some(arg) = arg {
180                    f.push('(');
181                    arg.write_eval(f, scope)?;
182                    f.push(')');
183                }
184            }
185            SelectorPart::Pseudo { name, arg } => {
186                f.push(':');
187                we(name, f, scope.clone())?;
188                if let Some(arg) = arg {
189                    f.push('(');
190                    arg.write_eval(f, scope)?;
191                    f.push(')');
192                }
193            }
194            SelectorPart::BackRef => f.push('&'),
195        }
196        Ok(())
197    }
198}
199
200fn we(s: &SassString, f: &mut String, scope: ScopeRef) -> Result<(), Error> {
201    write!(f, "{}", s.evaluate(scope)?)?;
202    Ok(())
203}