rswind/parsing/
mod.rs

1pub mod candidate;
2pub mod state;
3
4use std::{fmt::Debug, sync::Arc};
5
6use serde::Deserialize;
7use smallvec::{smallvec, SmallVec};
8use smol_str::{format_smolstr, SmolStr};
9
10use crate::{
11    common::MaybeArbitrary,
12    css::rule::RuleList,
13    ordering::OrderingKey,
14    process::{
15        ComposableHandler, RawValueRepr, RuleMatchingFn, ThemeParseError, Utility, UtilityGroup,
16        UtilityHandler, Variant, VariantHandlerExt,
17    },
18    theme::Theme,
19    types::TypeValidator,
20};
21
22#[derive(Debug, PartialEq, Clone, Copy, Default)]
23pub struct UtilityCandidate<'a> {
24    pub key: &'a str,
25    pub value: Option<MaybeArbitrary<'a>>,
26    pub modifier: Option<MaybeArbitrary<'a>>,
27    // fully arbitrary, e.g. [color:red] [text:--my-font-size]
28    pub arbitrary: bool,
29    pub important: bool,
30    pub negative: bool,
31}
32
33impl<'a> UtilityCandidate<'a> {
34    pub fn with_key(key: &'a str) -> Self {
35        Self { key, ..Default::default() }
36    }
37
38    // only if value and modifier are both named
39    pub fn take_fraction(&self) -> Option<SmolStr> {
40        match (self.value, self.modifier) {
41            (Some(MaybeArbitrary::Named(v)), Some(MaybeArbitrary::Named(m))) => {
42                Some(format_smolstr!("{v}/{m}",))
43            }
44            _ => None,
45        }
46    }
47}
48
49#[derive(Debug, Deserialize)]
50#[serde(deny_unknown_fields, rename_all = "camelCase")]
51#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
52pub struct UtilityBuilder {
53    /// The key of the utility, e.g. `bg`
54    pub key: SmolStr,
55
56    /// The css handler for the utility, e.g. `background-color: $1`
57    #[serde(rename = "css")]
58    pub handler: Option<UtilityHandler>,
59
60    /// The modifier for the utility, e.g. `bg-blue-500/50 <-`
61    #[serde(default)]
62    pub modifier: Option<RawValueRepr>,
63
64    /// The theme key for the utility, will read from `theme` by this key later, e.g. `colors`
65    #[serde(rename = "theme")]
66    pub theme_key: Option<SmolStr>,
67
68    /// The type validator for the utility, only used at `arbitrary values`
69    ///
70    /// e.g. `length-percentage` for `width`
71    #[serde(rename = "type")]
72    pub validator: Option<Box<dyn TypeValidator>>,
73
74    /// The wrapper selector for the utility
75    #[serde(default)]
76    pub wrapper: Option<SmolStr>,
77
78    /// Whether the utility supports negative values
79    #[serde(default)]
80    pub supports_negative: bool,
81
82    /// Whether the utility supports fraction values, e.g. `w-1/2`
83    #[serde(default)]
84    pub supports_fraction: bool,
85
86    #[serde(default)]
87    pub ordering_key: Option<OrderingKey>,
88
89    // TODO: add support for below fields
90    #[serde(skip_deserializing)]
91    pub additional_css: Option<Box<dyn AdditionalCssHandler>>,
92
93    #[serde(skip_deserializing)]
94    pub group: Option<UtilityGroup>,
95}
96
97pub trait AdditionalCssHandler: Sync + Send {
98    fn handle(&self, value: SmolStr) -> Option<Arc<RuleList>>;
99}
100
101impl<T: Fn(SmolStr) -> Option<RuleList> + Sync + Send> AdditionalCssHandler for T {
102    fn handle(&self, value: SmolStr) -> Option<Arc<RuleList>> {
103        self(value).map(Arc::new)
104    }
105}
106
107impl AdditionalCssHandler for Arc<RuleList> {
108    fn handle(&self, _value: SmolStr) -> Option<Arc<RuleList>> {
109        Some(self.clone())
110    }
111}
112
113impl Debug for dyn AdditionalCssHandler {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        f.debug_struct("AdditionalCssHandler").field("address", &format!("{:p}", self)).finish()
116    }
117}
118
119impl UtilityBuilder {
120    pub fn new(key: impl Into<SmolStr>, handler: impl RuleMatchingFn + 'static) -> Self {
121        Self {
122            key: key.into(),
123            handler: Some(UtilityHandler::new(handler)),
124            theme_key: None,
125            supports_negative: false,
126            supports_fraction: false,
127            modifier: None,
128            validator: None,
129            additional_css: None,
130            wrapper: None,
131            ordering_key: None,
132            group: None,
133        }
134    }
135
136    pub fn parse(self, theme: &Theme) -> Result<(SmolStr, Utility), ThemeParseError> {
137        Ok((
138            self.key,
139            Utility {
140                handler: self.handler.unwrap(),
141                supports_negative: self.supports_negative,
142                supports_fraction: self.supports_fraction,
143                value_repr: RawValueRepr { theme_key: self.theme_key, validator: self.validator }
144                    .parse(theme)?,
145                modifier: self.modifier.map(|m| m.parse(theme)).transpose()?,
146                wrapper: self.wrapper,
147                additional_css: self.additional_css,
148                ordering_key: self.ordering_key,
149                group: self.group,
150            },
151        ))
152    }
153
154    pub fn with_theme(&mut self, key: impl Into<SmolStr>) -> &mut Self {
155        self.theme_key = Some(key.into());
156        self
157    }
158
159    pub fn support_negative(&mut self) -> &mut Self {
160        self.supports_negative = true;
161        self
162    }
163
164    pub fn support_fraction(&mut self) -> &mut Self {
165        self.supports_fraction = true;
166        self
167    }
168
169    pub fn with_modifier(&mut self, modifier: RawValueRepr) -> &mut Self {
170        self.modifier = Some(modifier);
171        self
172    }
173
174    pub fn with_validator(&mut self, validator: impl TypeValidator + 'static) -> &mut Self {
175        self.validator = Some(Box::new(validator));
176        self
177    }
178
179    pub fn with_additional_css(&mut self, css: impl AdditionalCssHandler + 'static) -> &mut Self {
180        self.additional_css = Some(Box::new(css));
181        self
182    }
183
184    pub fn with_wrapper(&mut self, wrapper: &str) -> &mut Self {
185        self.wrapper = Some(wrapper.into());
186        self
187    }
188
189    pub fn with_ordering(&mut self, key: OrderingKey) -> &mut Self {
190        self.ordering_key = Some(key);
191        self
192    }
193
194    pub fn with_group(&mut self, group: UtilityGroup) -> &mut Self {
195        self.group = Some(group);
196        self
197    }
198}
199
200#[derive(Debug, Clone)]
201pub struct VariantCandidate<'a> {
202    pub key: &'a str,
203    pub value: Option<MaybeArbitrary<'a>>,
204    pub modifier: Option<MaybeArbitrary<'a>>,
205    // fully arbitrary, e.g. [@media(min-width:300px)] [&:nth-child(3)]
206    pub arbitrary: bool,
207    pub processor: Variant,
208    pub layers: SmallVec<[ComposableHandler; 1]>,
209}
210
211impl<'a> VariantCandidate<'a> {
212    pub fn new(processor: Variant, key: &'a str) -> Self {
213        Self { key, value: None, modifier: None, arbitrary: false, processor, layers: smallvec![] }
214    }
215
216    pub fn with_value(mut self, value: Option<MaybeArbitrary<'a>>) -> Self {
217        self.value = value;
218        self
219    }
220
221    pub fn with_modifier(mut self, modifier: Option<MaybeArbitrary<'a>>) -> Self {
222        self.modifier = modifier;
223        self
224    }
225
226    pub fn with_layers(mut self, layers: SmallVec<[ComposableHandler; 1]>) -> Self {
227        self.layers = layers;
228        self
229    }
230
231    pub fn arbitrary(mut self) -> Self {
232        self.arbitrary = true;
233        self
234    }
235
236    pub fn handle(&self, rule: RuleList) -> RuleList {
237        let rule = self.processor.handle(self, rule);
238        self.layers.iter().rev().fold(rule, |rule, handler| handler.handle(self, rule))
239    }
240}