Skip to main content

encre_css/plugins/
mod.rs

1//! A [`Plugin`] is a handler used to convert utility classes into CSS declarations.
2//!
3//! A lot of plugins are built in (like the ones from Tailwind CSS) and some others live in
4//! their own crates and need to be imported manually. They usually define a `register` function taking
5//! a mutable reference to a [`Config`] structure.
6//!
7//! # Example (with `encre-css-typography`)
8//!
9//! ```ignore
10//! use encre_css::Config;
11//!
12//! # fn main() -> encre_css::Result<()> {
13//! let mut config = Config::from_file("encre-css.toml")?;
14//! // Or let mut config = Config::default();
15//!
16//! encre_css_typography::register(&mut config);
17//!
18//! let _css = encre_css::generate(
19//!     [r#"<div class="prose prose-headings:text-blue-500 prose-slate lg:prose-lg dark:prose-invert"></div>"#],
20//!     &config,
21//! );
22//! // Do something with the CSS
23//! # Ok(())
24//! # }
25//! ```
26//!
27//! # Official plugins
28//!
29//! - [`encre-css-typography`](https://gitlab.com/encre-org/encre-css/tree/main/crates/encre-css-typography): used to define beautiful typographic defaults for HTML you don't control.
30//! - [`encre-css-icons`](https://gitlab.com/encre-org/encre-css/tree/main/crates/encre-css-icons): used to quickly add pure CSS icons to your website.
31//!
32//! If you want to write your own plugins, see [`Plugin`].
33//!
34//! [`Config`]: crate::Config
35
36use std::collections::HashMap;
37
38use serde::{Deserialize, Serialize};
39
40use crate::{generator::{ContextCanHandle, ContextHandle}, selector::CssType};
41
42pub mod accessibility;
43pub mod background;
44pub mod border;
45pub mod css_property;
46pub mod effect;
47pub mod filter;
48pub mod flexbox;
49pub mod grid;
50pub mod interactivity;
51pub mod layout;
52pub mod sizing;
53pub mod spacing;
54pub mod svg;
55pub mod table;
56pub mod transform;
57pub mod transition;
58pub mod typography;
59
60/// An alias to a [`Plugin`] which can easily be defined in Rust, e.g in const environments.
61///
62/// It requires using `&'static str` for all configuration. If you need to use `String`s for some
63/// dynamic configuration, use [`DynamicPlugin`] instead.
64pub type StaticPlugin = Plugin<
65    &'static str,
66    &'static [&'static str],
67    phf::Map<&'static str, &'static str>,
68    phf::Map<&'static str, &'static [&'static str]>,
69    &'static [CssType],
70>;
71
72/// An alias to a [`Plugin`] which contain configuration defined using `String`s instead of static
73/// string references, likely deserialized from a configuration file.
74///
75/// If you need to define a plugin using Rust without needing any heap-allocated `String`s, use
76/// [`StaticPlugin`] instead.
77pub type DynamicPlugin = Plugin<
78    String,
79    Vec<String>,
80    HashMap<String, String>,
81    HashMap<String, Vec<String>>,
82    Vec<CssType>,
83>;
84
85/// An alias to a [`PropertyName`] which is defined using `&'static str`, adapted for use in const
86/// environments.
87pub type StaticPropertyName = PropertyName<&'static str, &'static [&'static str]>;
88
89/// An alias to a [`PropertyName`] which is defined using `String`, adapted for use when a name
90/// needs to be dynamic or deserialized.
91pub type DynamicPropertyName = PropertyName<String, Vec<String>>;
92
93fn can_handle_nop(_: &ContextCanHandle) -> bool { false }
94fn handle_nop(_: &mut ContextHandle) {}
95
96#[derive(Debug, Clone, Serialize)]
97pub(crate) enum CustomPlugin {
98    #[serde(skip_serializing)]
99    Static(&'static StaticPlugin),
100    Dynamic(DynamicPlugin),
101}
102
103impl<'de> Deserialize<'de> for CustomPlugin {
104    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105    where
106        D: serde::Deserializer<'de>,
107    {
108        Ok(Self::Dynamic(DynamicPlugin::deserialize(deserializer)?))
109    }
110}
111
112/// Either a single or several CSS property names.
113///
114/// This enumeration is used when defining plugins to specify which CSS property name should be
115/// generated. In the case of several property names (i.e [`MultipleProps`]), the
116/// CSS value will be copied to all the properties.
117///
118/// When using [the `build_plugin` prelude](`crate::prelude::build_plugin`), the variants of this
119/// enumeration are reexported so that you can simply write [`SingleProp`] and [`MultipleProps`]
120/// without having to prefix them with `PropertyName::`.
121///
122/// When [defining a plugin using TOML](Plugin#define-a-plugin-in-toml), if you use a string, the
123/// [`SingleProp`] variant will automatically be used, and if you use an array, the [`MultipleProps`]
124/// variants will be used.
125///
126/// [`SingleProp`]: PropertyName::SingleProp
127/// [`MultipleProps`]: PropertyName::MultipleProps
128#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
129#[serde(untagged)]
130pub enum PropertyName<Str, ArrayStr> {
131    /// A single CSS property name.
132    SingleProp(Str),
133
134    /// Several CSS property names in the order they will be generated.
135    ///
136    /// The CSS value defined by the plugin will be copied to each of the properties.
137    ///
138    /// ### Example
139    ///
140    /// ```
141    /// use encre_css::{Config, generate};
142    /// use encre_css::prelude::build_plugin::*;
143    ///
144    /// const PLUGIN: StaticPlugin = Plugin::Color(Color {
145    ///     namespace: "custom-decoration",
146    ///     prop: MultipleProps(&["-webkit-text-decoration-color", "text-decoration-color"]),
147    ///     ..Color::default()
148    /// });
149    ///
150    /// let mut config = Config::default();
151    /// config.register_plugin(&PLUGIN);
152    ///
153    /// let generated = generate(["custom-decoration-red-200"], &config);
154    ///
155    /// assert!(generated.ends_with(r".custom-decoration-red-200 {
156    ///   -webkit-text-decoration-color: oklch(88.5% .062 18.334);
157    ///   text-decoration-color: oklch(88.5% .062 18.334);
158    /// }"));
159    /// ```
160    MultipleProps(ArrayStr),
161}
162
163/// When defining a [`PluginArbitraryMatcher`] for an [`Arbitrary`] kind, defines how values
164/// are separated.
165///
166/// A lot of CSS properties allow specifying several values of a single type separated by a
167/// character, e.g `margin` allows [`<length>`](crate::utils::value_matchers::is_matching_length`)
168/// or [`<percentage>`](crate::utils::value_matchers::is_matching_percentage`)
169/// values separated by spaces, to define a specific margin for each side of the CSS layout box.
170///
171/// This enumeration helps matching these values when using a [`PluginArbitraryMatcher`], e.g for
172/// the `margin` example, you would use
173///
174/// ```ignore
175/// Plugin::new(...)
176///     .matchers(&[Length, Percentage], PluginArbitraryMatcherSeparation::Space)
177/// ```
178#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
179pub enum ArbitraryDisambiguateSeparation {
180    /// No separation, a single value is matched.
181    None,
182
183    /// Separated by commas (`,`).
184    ///
185    /// Example: `33px, 42%, 6em`.
186    Comma,
187
188    /// Separated by spaces (` `).
189    ///
190    /// Example: `left top`.
191    Space,
192
193    /// Separated by commas (`,`) then spaces (` `).
194    ///
195    /// Example: `left, 12% 33px, right center`.
196    Both,
197}
198
199#[doc = include_str!("./doc_extra_slash.md")]
200#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
201pub struct ExtraSlash<Str, MapStr> {
202    /// The mapping between the string parsed after the slash (`/`) and the actual values generated
203    /// in the CSS value.
204    pub values: MapStr,
205
206    /// The key in [`ExtraSlash::values`] which is chosen by default when no slash is present in the
207    /// utility class.
208    pub default: Str,
209}
210
211#[doc = include_str!("./doc_arbitrary_disambiguate.md")]
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct ArbitraryDisambiguate<ArrayMatched> {
214    /// The list of CSS types which are accepted as arbitrary value by this plugin.
215    pub matched: ArrayMatched,
216
217    /// Define how values are specified inside the CSS value.
218    ///
219    /// In practice, you can check the values accepted by the CSS property and set this field to
220    ///
221    /// - [`ArbitraryDisambiguateSeparation::Space`] if it accepts several values separated by spaces
222    /// - [`ArbitraryDisambiguateSeparation::Comma`] if it accepts several values separated by commas
223    /// - [`ArbitraryDisambiguateSeparation::Both`] if it accepts several values separated by commas
224    /// which themselves accept several values separated by spaces
225    /// - [`ArbitraryDisambiguateSeparation::None`] otherwise
226    pub separation: ArbitraryDisambiguateSeparation,
227}
228
229/// Define a plugin using a map between utility classes and raw CSS lines.
230///
231/// It directly generates the CSS of the map value if the utility class as map key is scanned.
232///
233/// Map values are arrays which represent individual lines of the CSS so that each line can be
234/// correctly indented.
235///
236/// If a utility class maps to an empty array, no class will be generated at all. This behavior can
237/// be combined with [`ListProperties::extra_css`] to generate root-level CSS blocks (like
238/// `@keyframe` animations).
239///
240/// If you instead need to map CSS property values to a single CSS property, use [`ListValues`].
241///
242/// ### Example
243///
244/// ```
245/// use encre_css::{Config, generate};
246/// use encre_css::prelude::build_plugin::*;
247///
248/// const PLUGIN: StaticPlugin = Plugin::ListProperties(ListProperties {
249///     props: map! {
250///         "overflow-visible" => &["overflow: visible;"],
251///         "overflow-hidden" => &["overflow: hidden;"],
252///         "overflow-clip" => &["overflow: clip;"],
253///         "overflow-scroll" => &["overflow: scroll;"],
254///         "overflow-auto" => &["overflow: auto;"],
255///     },
256///     ..ListProperties::default()
257/// });
258///
259/// let mut config = Config::default();
260/// config.register_plugin(&PLUGIN);
261///
262/// let generated = generate(["overflow-scroll"], &config);
263///
264/// assert!(generated.ends_with(r".overflow-scroll {
265///   overflow: scroll;
266/// }"));
267/// ```
268///
269/// ### Example in TOML
270///
271/// ```toml
272/// [[custom_plugins]]
273///
274/// [custom_plugins.ListProperties]
275///
276/// [custom_plugins.ListProperties.props]
277/// overflow-visible = ["overflow: visible;"]
278/// overflow-hidden = ["overflow: hidden;"]
279/// overflow-clip = ["overflow: clip;"]
280/// overflow-scroll = ["overflow: scroll;"]
281/// overflow-auto = ["overflow: auto;"]
282/// ```
283#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct ListProperties<Str, ArrayStr, MapStr, MapArrayStr> {
285    /// The map between utility classes and raw CSS lines.
286    ///
287    /// This field should be assigned separately after calling [`ListProperties::default`] (or
288    /// [`ListProperties::default_dynamic`]).
289    pub props: MapArrayStr,
290
291    /// Define a [namespace](crate::selector) (i.e a prefix) common to all utility classes declared in the map keys.
292    ///
293    /// The last dash character (`-`) should be omitted due to the way the parsing of utility classes work
294    /// (e.g in the example below, `overflow` is correct while `overflow-` is **incorrect**).
295    ///
296    /// ### Example
297    ///
298    /// ```
299    /// use encre_css::{Config, generate};
300    /// use encre_css::prelude::build_plugin::*;
301    ///
302    /// const PLUGIN: StaticPlugin = Plugin::ListProperties(ListProperties {
303    ///     namespace: Some("overflow"),
304    ///     props: map! {
305    ///         "visible" => &["overflow: visible;"],
306    ///         "hidden" => &["overflow: hidden;"],
307    ///         "clip" => &["overflow: clip;"],
308    ///         "scroll" => &["overflow: scroll;"],
309    ///         "auto" => &["overflow: auto;"],
310    ///     },
311    ///     ..ListProperties::default()
312    /// });
313    ///
314    /// let mut config = Config::default();
315    /// config.register_plugin(&PLUGIN);
316    ///
317    /// let generated = generate(["overflow-scroll"], &config);
318    ///
319    /// assert!(generated.ends_with(r".overflow-scroll {
320    ///   overflow: scroll;
321    /// }"));
322    /// ```
323    pub namespace: Option<Str>,
324
325    #[doc = include_str!("./doc_extra_rule_css.md")]
326    pub extra_rule_css: Option<ArrayStr>,
327
328    #[doc = include_str!("./doc_extra_css.md")]
329    pub extra_css: Option<MapStr>,
330
331    #[doc = include_str!("./doc_extra_class.md")]
332    pub extra_class: Option<Str>,
333}
334
335impl<Str, ArrayStr, MapStr> ListProperties<Str, ArrayStr, MapStr, phf::Map<&'static str, &'static [&'static str]>> {
336    /// Make a default [`ListProperties`] plugin kind.
337    ///
338    /// All required fields are initialized with empty values and optional fields are initialized
339    /// with `None`.
340    ///
341    /// You should at least set [`ListProperties::props`] after calling this function.
342    ///
343    /// This function is intended to be used as an automatic filler for default values using the
344    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
345    ///
346    /// The difference with [`ListProperties::default_dynamic`] is that this function can only be used to
347    /// build a plugin using static structures like `&[]`s, `&'static str`s.
348    ///
349    /// ### Example
350    ///
351    /// ```
352    /// use encre_css::prelude::build_plugin::*;
353    ///
354    /// const PLUGIN: StaticPlugin = Plugin::ListProperties(ListProperties {
355    ///     props: map! {
356    ///         "overflow-visible" => &["overflow: visible;"],
357    ///         "overflow-hidden" => &["overflow: hidden;"],
358    ///         "overflow-clip" => &["overflow: clip;"],
359    ///         "overflow-scroll" => &["overflow: scroll;"],
360    ///         "overflow-auto" => &["overflow: auto;"],
361    ///     },
362    ///     ..ListProperties::default()
363    /// });
364    /// ```
365    pub const fn default() -> Self {
366        Self {
367            props: phf::Map::new(),
368            namespace: None,
369            extra_rule_css: None,
370            extra_css: None,
371            extra_class: None,
372        }
373    }
374}
375
376impl<Str, ArrayStr, MapStr> ListProperties<Str, ArrayStr, MapStr, HashMap<String, Vec<String>>> {
377    /// Make a default [`ListProperties`] plugin kind.
378    ///
379    /// All required fields are initialized with empty values and optional fields are initialized
380    /// with `None`.
381    ///
382    /// You should at least set [`ListProperties::props`] after calling this function.
383    ///
384    /// This function is intended to be used as an automatic filler for default values using the
385    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
386    ///
387    /// The difference with [`ListProperties::default`] is that this function can only be used to
388    /// build a plugin using heap-allocated structures like `String`s, `Vec`s.
389    ///
390    /// ### Example
391    ///
392    /// ```
393    /// use std::collections::HashMap;
394    /// use encre_css::prelude::build_plugin::*;
395    ///
396    /// fn main() {
397    ///     let props = HashMap::from_iter(
398    ///         ["visible", "hidden", "clip", "scroll", "auto"].iter().map(|v| {
399    ///             (format!("overflow-{v}"), vec![format!("overflow: {v};")])
400    ///         })
401    ///     );
402    ///
403    ///     // Note: the DynamicPlugin type hint is required to help the compiler
404    ///     // find the concrete types of type parameters
405    ///     let _plugin: DynamicPlugin = Plugin::ListProperties(ListProperties {
406    ///         props,
407    ///         ..ListProperties::default_dynamic()
408    ///     });
409    /// }
410    /// ```
411    ///
412    /// This example is equivalent to the one of [`ListProperties::default`].
413    pub fn default_dynamic() -> Self {
414        Self {
415            props: HashMap::new(),
416            namespace: None,
417            extra_rule_css: None,
418            extra_css: None,
419            extra_class: None,
420        }
421    }
422}
423
424/// Define a plugin using a map between utility classes and the values of a single CSS property.
425///
426/// If you instead need to generate several CSS properties or to have more control on the CSS lines
427/// generated, use [`ListProperties`].
428///
429/// ### Example
430///
431/// ```
432/// use encre_css::{Config, generate};
433/// use encre_css::prelude::build_plugin::*;
434///
435/// const PLUGIN: StaticPlugin = Plugin::ListValues(ListValues {
436///     prop: SingleProp("width"),
437///     values: map! {
438///         "w-fit" => "fit-content",
439///         "w-max" => "max-content",
440///         "w-min" => "min-content",
441///     },
442///     ..ListValues::default()
443/// });
444///
445/// let mut config = Config::default();
446/// config.register_plugin(&PLUGIN);
447///
448/// let generated = generate(["w-max"], &config);
449///
450/// assert!(generated.ends_with(r".w-max {
451///   width: max-content;
452/// }"));
453/// ```
454///
455/// ### Example in TOML
456///
457/// ```toml
458/// [[custom_plugins]]
459///
460/// [custom_plugins.ListValues]
461/// prop = "width"
462///
463/// [custom_plugins.ListValues.values]
464/// w-fit = "fit-content"
465/// w-max = "max-content"
466/// w-min = "min-content"
467/// ```
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct ListValues<Str, ArrayStr, MapStr> {
470    /// The CSS property name of the generated CSS rule.
471    ///
472    /// It can be a single property using [`PropertyName::SingleProp`] or a list of properties
473    /// using [`PropertyName::MultipleProps`], in which case the value will be copied for all
474    /// properties.
475    ///
476    /// This field should be assigned separately after calling [`ListValues::default`] (or
477    /// [`ListValues::default_dynamic`]).
478    pub prop: PropertyName<Str, ArrayStr>,
479
480    /// The map between utility classes and the CSS values of the property [`ListValues::prop`].
481    ///
482    /// This field should be assigned separately after calling [`ListValues::default`] (or
483    /// [`ListValues::default_dynamic`]).
484    pub values: MapStr,
485
486    /// Define a [namespace](crate::selector) (i.e a prefix) common to all utility classes declared in the map keys.
487    ///
488    /// The last dash character (`-`) should be omitted due to the way the parsing of utility classes work
489    /// (e.g in the example below, `overflow` is correct while `overflow-` is **incorrect**).
490    ///
491    /// ### Example
492    ///
493    /// ```
494    /// use encre_css::{Config, generate};
495    /// use encre_css::prelude::build_plugin::*;
496    ///
497    /// const PLUGIN: StaticPlugin = Plugin::ListValues(ListValues {
498    ///     namespace: Some("w"),
499    ///     prop: SingleProp("width"),
500    ///     values: map! {
501    ///         "fit" => "fit-content",
502    ///         "max" => "max-content",
503    ///         "min" => "min-content",
504    ///     },
505    ///     ..ListValues::default()
506    /// });
507    ///
508    /// let mut config = Config::default();
509    /// config.register_plugin(&PLUGIN);
510    ///
511    /// let generated = generate(["w-max"], &config);
512    ///
513    /// assert!(generated.ends_with(r".w-max {
514    ///   width: max-content;
515    /// }"));
516    /// ```
517    pub namespace: Option<Str>,
518
519    #[doc = include_str!("./doc_extra_rule_css.md")]
520    pub extra_rule_css: Option<ArrayStr>,
521
522    #[doc = include_str!("./doc_extra_css.md")]
523    pub extra_css: Option<MapStr>,
524
525    #[doc = include_str!("./doc_extra_class.md")]
526    pub extra_class: Option<Str>,
527
528    #[doc = include_str!("./doc_extra_slash.md")]
529    pub extra_slash: Option<ExtraSlash<Str, MapStr>>,
530}
531
532impl<ArrayStr> ListValues<&'static str, ArrayStr, phf::Map<&'static str, &'static str>> {
533    /// Make a default [`ListValues`] plugin kind.
534    ///
535    /// All required fields are initialized with empty values and optional fields are initialized
536    /// with `None`.
537    ///
538    /// You should at least set [`ListValues::prop`] and [`ListValues::values`] after calling this function.
539    ///
540    /// This function is intended to be used as an automatic filler for default values using the
541    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
542    ///
543    /// The difference with [`ListValues::default_dynamic`] is that this function can only be used to
544    /// build a plugin using static structures like `&[]`s, `&'static str`s.
545    ///
546    /// ### Example
547    ///
548    /// ```
549    /// use encre_css::prelude::build_plugin::*;
550    ///
551    /// const PLUGIN: StaticPlugin = Plugin::ListValues(ListValues {
552    ///     prop: SingleProp("width"),
553    ///     values: map! {
554    ///         "w-fit" => "fit-content",
555    ///         "w-max" => "max-content",
556    ///         "w-min" => "min-content",
557    ///     },
558    ///     ..ListValues::default()
559    /// });
560    /// ```
561    pub const fn default() -> Self {
562        Self {
563            prop: PropertyName::SingleProp(""),
564            values: phf::Map::new(),
565            namespace: None,
566            extra_rule_css: None,
567            extra_css: None,
568            extra_class: None,
569            extra_slash: None,
570        }
571    }
572}
573
574impl<ArrayStr> ListValues<String, ArrayStr, HashMap<String, String>> {
575    /// Make a default [`ListValues`] plugin kind.
576    ///
577    /// All required fields are initialized with empty values and optional fields are initialized
578    /// with `None`.
579    ///
580    /// You should at least set [`ListValues::prop`] and [`ListValues::values`] after calling this function.
581    ///
582    /// This function is intended to be used as an automatic filler for default values using the
583    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
584    ///
585    /// The difference with [`ListProperties::default`] is that this function can only be used to
586    /// build a plugin using heap-allocated structures like `String`s, `Vec`s.
587    ///
588    /// ### Example
589    ///
590    /// ```
591    /// use std::collections::HashMap;
592    /// use encre_css::prelude::build_plugin::*;
593    ///
594    /// fn main() {
595    ///     let values = HashMap::from_iter(
596    ///         ["fit", "max", "min"].iter().map(|v| {
597    ///             (format!("w-{v}"), format!("width: {v}-content;"))
598    ///         })
599    ///     );
600    ///
601    ///     // Note: the DynamicPlugin type hint is required to help the compiler
602    ///     // find the concrete types of type parameters
603    ///     let _plugin: DynamicPlugin = Plugin::ListValues(ListValues {
604    ///         prop: SingleProp("width".to_string()),
605    ///         values,
606    ///         ..ListValues::default_dynamic()
607    ///     });
608    /// }
609    /// ```
610    ///
611    /// This example is equivalent to the one of [`ListValues::default`].
612    pub fn default_dynamic() -> Self {
613        Self {
614            prop: PropertyName::SingleProp(String::new()),
615            values: HashMap::new(),
616            namespace: None,
617            extra_rule_css: None,
618            extra_css: None,
619            extra_class: None,
620            extra_slash: None,
621        }
622    }
623}
624
625/// Define a plugin which supports all spacing modifiers,
626/// that is a (potentially floating) number, a fraction (e.g `3/4`) or `px`.
627///
628/// This plugin kind can also support the `auto` and `full` modifiers by setting [`Spacing::has_auto`] and [`Spacing::has_full`].
629///
630/// ### Example
631///
632/// ```
633/// use encre_css::{Config, generate};
634/// use encre_css::prelude::build_plugin::*;
635///
636/// const PLUGIN: StaticPlugin = Plugin::Spacing(Spacing {
637///     namespace: "h",
638///     prop: SingleProp("height"),
639///     has_auto: Some(true),
640///     has_full: Some(true),
641///     ..Spacing::default()
642/// });
643///
644/// let mut config = Config::default();
645/// config.register_plugin(&PLUGIN);
646///
647/// let generated = generate(["h-2", "h-3/4", "h-px", "h-auto", "h-full"], &config);
648///
649/// assert!(generated.ends_with(r".h-2 {
650///   height: 0.5rem;
651/// }
652///
653/// .h-3\/4 {
654///   height: 75%;
655/// }
656///
657/// .h-auto {
658///   height: auto;
659/// }
660///
661/// .h-full {
662///   height: 100%;
663/// }
664///
665/// .h-px {
666///   height: 1px;
667/// }"));
668/// ```
669///
670/// ### Example in TOML
671///
672/// ```toml
673/// [[custom_plugins]]
674///
675/// [custom_plugins.Spacing]
676/// namespace = "h"
677/// prop = "height"
678/// has_auto = true
679/// has_full = true
680/// ```
681#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct Spacing<Str, ArrayStr, MapStr> {
683    /// The namespace (i.e common prefix) that all classes need to start with in order to be
684    /// matched by this plugin.
685    pub namespace: Str,
686
687    /// The CSS property name of the generated CSS rule.
688    ///
689    /// It can be a single property using [`PropertyName::SingleProp`] or a list of properties
690    /// using [`PropertyName::MultipleProps`], in which case the value will be copied for all
691    /// properties.
692    pub prop: PropertyName<Str, ArrayStr>,
693
694    /// Automatically add support for the `auto` modifier.
695    ///
696    /// If this method is called, an `auto` modifier will generate an `auto` CSS property value.
697    pub has_auto: Option<bool>,
698
699    /// Automatically add support for the `full` modifier.
700    ///
701    /// If this method is called, a `full` modifier will generate a `100%` CSS property value.
702    pub has_full: Option<bool>,
703
704    #[doc = include_str!("./doc_template.md")]
705    pub template: Option<PropertyName<Str, ArrayStr>>,
706
707    #[doc = include_str!("./doc_extra_rule_css.md")]
708    pub extra_rule_css: Option<ArrayStr>,
709
710    #[doc = include_str!("./doc_extra_css.md")]
711    pub extra_css: Option<MapStr>,
712
713    #[doc = include_str!("./doc_extra_class.md")]
714    pub extra_class: Option<Str>,
715
716    #[doc = include_str!("./doc_extra_slash.md")]
717    pub extra_slash: Option<ExtraSlash<Str, MapStr>>,
718}
719
720impl<ArrayStr, MapStr> Spacing<&'static str, ArrayStr, MapStr> {
721    /// Make a default [`Spacing`] plugin kind.
722    ///
723    /// All required fields are initialized with empty values and optional fields are initialized
724    /// with `None`.
725    ///
726    /// You should at least set [`Spacing::namespace`] and [`Spacing::prop`] after calling this function.
727    ///
728    /// This function is intended to be used as an automatic filler for default values using the
729    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
730    ///
731    /// The difference with [`Spacing::default_dynamic`] is that this function can only be used to
732    /// build a plugin using static structures like `&[]`s, `&'static str`s.
733    ///
734    /// ### Example
735    ///
736    /// ```
737    /// use encre_css::prelude::build_plugin::*;
738    ///
739    /// const PLUGIN: StaticPlugin = Plugin::Spacing(Spacing {
740    ///     namespace: "h",
741    ///     prop: SingleProp("height"),
742    ///     ..Spacing::default()
743    /// });
744    /// ```
745    pub const fn default() -> Self {
746        Self {
747            namespace: "",
748            prop: PropertyName::SingleProp(""),
749            has_auto: None,
750            has_full: None,
751            template: None,
752            extra_rule_css: None,
753            extra_css: None,
754            extra_class: None,
755            extra_slash: None,
756        }
757    }
758}
759
760impl<ArrayStr, MapStr> Spacing<String, ArrayStr, MapStr> {
761    /// Make a default [`Spacing`] plugin kind.
762    ///
763    /// All required fields are initialized with empty values and optional fields are initialized
764    /// with `None`.
765    ///
766    /// You should at least set [`Spacing::namespace`] and [`Spacing::prop`] after calling this function.
767    ///
768    /// This function is intended to be used as an automatic filler for default values using the
769    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
770    ///
771    /// The difference with [`Spacing::default`] is that this function can only be used to
772    /// build a plugin using heap-allocated structures like `String`s, `Vec`s.
773    ///
774    /// ### Example
775    ///
776    /// ```
777    /// use encre_css::prelude::build_plugin::*;
778    ///
779    /// fn main() {
780    ///     // Note: the DynamicPlugin type hint is required to help the compiler
781    ///     // find the concrete types of type parameters
782    ///     let _plugin: DynamicPlugin = Plugin::Spacing(Spacing {
783    ///         namespace: "h".to_string(),
784    ///         prop: SingleProp("height".to_string()),
785    ///         ..Spacing::default_dynamic()
786    ///     });
787    /// }
788    /// ```
789    ///
790    /// This example is equivalent to the one of [`Spacing::default`].
791    pub fn default_dynamic() -> Self {
792        Self {
793            namespace: String::new(),
794            prop: PropertyName::SingleProp(String::new()),
795            has_auto: None,
796            has_full: None,
797            template: None,
798            extra_rule_css: None,
799            extra_css: None,
800            extra_class: None,
801            extra_slash: None,
802        }
803    }
804}
805
806/// Define a plugin which supports all color modifiers, e.g `red-200` (the list of colors is based
807/// on [`BUILTIN_COLORS`] and [`Theme::colors`] which is defined by the [`Config`]).
808///
809/// [`BUILTIN_COLORS`]: crate::config::BUILTIN_COLORS
810/// [`Theme::colors`]: crate::config::Theme::colors
811/// [`Config`]: crate::config::Config
812///
813/// ### Example
814///
815/// ```
816/// use encre_css::{Config, generate};
817/// use encre_css::prelude::build_plugin::*;
818///
819/// const PLUGIN: StaticPlugin = Plugin::Color(Color {
820///     namespace: "bg",
821///     prop: SingleProp("background-color"),
822///     ..Color::default()
823/// });
824///
825/// let mut config = Config::default();
826/// config.register_plugin(&PLUGIN);
827///
828/// let generated = generate(["bg-red-200", "bg-black", "bg-inherit"], &config);
829///
830/// assert!(generated.ends_with(r".bg-black {
831///   background-color: #000;
832/// }
833///
834/// .bg-inherit {
835///   background-color: inherit;
836/// }
837///
838/// .bg-red-200 {
839///   background-color: oklch(88.5% .062 18.334);
840/// }"));
841/// ```
842///
843/// ### Example in TOML
844///
845/// ```toml
846/// [[custom_plugins]]
847///
848/// [custom_plugins.Color]
849/// namespace = "bg"
850/// prop = "background-color"
851/// ```
852#[derive(Debug, Clone, Serialize, Deserialize)]
853pub struct Color<Str, ArrayStr, MapStr> {
854    /// The namespace (i.e common prefix) that all classes need to start with in order to be
855    /// matched by this plugin.
856    pub namespace: Str,
857
858    /// The CSS property name of the generated CSS rule.
859    ///
860    /// It can be a single property using [`PropertyName::SingleProp`] or a list of properties
861    /// using [`PropertyName::MultipleProps`], in which case the value will be copied for all
862    /// properties.
863    pub prop: PropertyName<Str, ArrayStr>,
864
865    #[doc = include_str!("./doc_template.md")]
866    pub template: Option<PropertyName<Str, ArrayStr>>,
867
868    #[doc = include_str!("./doc_extra_rule_css.md")]
869    pub extra_rule_css: Option<ArrayStr>,
870
871    #[doc = include_str!("./doc_extra_css.md")]
872    pub extra_css: Option<MapStr>,
873
874    #[doc = include_str!("./doc_extra_class.md")]
875    pub extra_class: Option<Str>,
876}
877
878impl<ArrayStr, MapStr> Color<&'static str, ArrayStr, MapStr> {
879    /// Make a default [`Color`] plugin kind.
880    ///
881    /// All required fields are initialized with empty values and optional fields are initialized
882    /// with `None`.
883    ///
884    /// You should at least set [`Color::namespace`] and [`Color::prop`] after calling this function.
885    ///
886    /// This function is intended to be used as an automatic filler for default values using the
887    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
888    ///
889    /// The difference with [`Color::default_dynamic`] is that this function can only be used to
890    /// build a plugin using static structures like `&[]`s, `&'static str`s.
891    ///
892    /// ### Example
893    ///
894    /// ```
895    /// use encre_css::prelude::build_plugin::*;
896    ///
897    /// const PLUGIN: StaticPlugin = Plugin::Color(Color {
898    ///     namespace: "bg",
899    ///     prop: SingleProp("background-color"),
900    ///     ..Color::default()
901    /// });
902    /// ```
903    pub const fn default() -> Self {
904        Self {
905            namespace: "",
906            prop: PropertyName::SingleProp(""),
907            template: None,
908            extra_rule_css: None,
909            extra_css: None,
910            extra_class: None,
911        }
912    }
913}
914
915impl<ArrayStr, MapStr> Color<String, ArrayStr, MapStr> {
916    /// Make a default [`Color`] plugin kind.
917    ///
918    /// All required fields are initialized with empty values and optional fields are initialized
919    /// with `None`.
920    ///
921    /// You should at least set [`Color::namespace`] and [`Color::prop`] after calling this function.
922    ///
923    /// This function is intended to be used as an automatic filler for default values using the
924    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
925    ///
926    /// The difference with [`Color::default`] is that this function can only be used to
927    /// build a plugin using heap-allocated structures like `String`s, `Vec`s.
928    ///
929    /// ### Example
930    ///
931    /// ```
932    /// use encre_css::prelude::build_plugin::*;
933    ///
934    /// fn main() {
935    ///     // Note: the DynamicPlugin type hint is required to help the compiler
936    ///     // find the concrete types of type parameters
937    ///     let _plugin: DynamicPlugin = Plugin::Color(Color {
938    ///         namespace: "bg".to_string(),
939    ///         prop: SingleProp("background-color".to_string()),
940    ///         ..Color::default_dynamic()
941    ///     });
942    /// }
943    /// ```
944    ///
945    /// This example is equivalent to the one of [`Color::default`].
946    pub fn default_dynamic() -> Self {
947        Self {
948            namespace: String::new(),
949            prop: PropertyName::SingleProp(String::new()),
950            template: None,
951            extra_rule_css: None,
952            extra_css: None,
953            extra_class: None,
954        }
955    }
956}
957
958/// Define a plugin which supports any number as modifier.
959///
960/// The number must be an integer (signed integers can be supported by enabling
961/// [`Number::has_negative`]).
962///
963/// ### Example
964///
965/// ```
966/// use encre_css::{Config, generate};
967/// use encre_css::prelude::build_plugin::*;
968///
969/// const PLUGIN: StaticPlugin = Plugin::Number(Number {
970///    namespace: "z",
971///    prop: SingleProp("z-index"),
972///    has_negative: Some(true),
973///    has_auto: Some(true),
974///    ..Number::default()
975/// });
976///
977/// let mut config = Config::default();
978/// config.register_plugin(&PLUGIN);
979///
980/// let generated = generate(["z-20", "-z-5", "z-auto"], &config);
981///
982/// assert!(generated.ends_with(r".-z-5 {
983///   z-index: -5;
984/// }
985///
986/// .z-20 {
987///   z-index: 20;
988/// }
989///
990/// .z-auto {
991///   z-index: auto;
992/// }"));
993/// ```
994///
995/// ### Example in TOML
996///
997/// ```toml
998/// [[custom_plugins]]
999///
1000/// [custom_plugins.Number]
1001/// namespace = "z"
1002/// prop = "z-index"
1003/// has_negative = true
1004/// has_auto = true
1005/// ```
1006#[derive(Debug, Clone, Serialize, Deserialize)]
1007pub struct Number<Str, ArrayStr, MapStr> {
1008    /// The namespace (i.e common prefix) that all classes need to start with in order to be
1009    /// matched by this plugin.
1010    pub namespace: Str,
1011
1012    /// The CSS property name of the generated CSS rule.
1013    ///
1014    /// It can be a single property using [`PropertyName::SingleProp`] or a list of properties
1015    /// using [`PropertyName::MultipleProps`], in which case the value will be copied for all
1016    /// properties.
1017    pub prop: PropertyName<Str, ArrayStr>,
1018
1019    /// A float by which to divide the number given in the utility class.
1020    ///
1021    /// It can for example be used to support classes having a percentage between 1-100 but which
1022    /// need to generate a CSS property value between 0-1.
1023    ///
1024    /// ### Example
1025    ///
1026    /// ```
1027    /// use encre_css::{Config, generate};
1028    /// use encre_css::prelude::build_plugin::*;
1029    ///
1030    /// const PLUGIN: StaticPlugin = Plugin::Number(Number {
1031    ///     namespace: "custom-opacity",
1032    ///     prop: SingleProp("opacity"),
1033    ///     divide_by: Some(100.0),
1034    ///     ..Number::default()
1035    /// });
1036    ///
1037    /// let mut config = Config::default();
1038    /// config.register_plugin(&PLUGIN);
1039    ///
1040    /// let generated = generate(["custom-opacity-80", "custom-opacity-2"], &config);
1041    ///
1042    /// assert!(generated.ends_with(r".custom-opacity-2 {
1043    ///   opacity: 0.02;
1044    /// }
1045    ///
1046    /// .custom-opacity-80 {
1047    ///   opacity: 0.8;
1048    /// }"));
1049    /// ```
1050    pub divide_by: Option<f32>,
1051
1052    /// Automatically add support for the `auto` modifier.
1053    ///
1054    /// If this method is called, an `auto` modifier will generate an `auto` CSS property value.
1055    pub has_auto: Option<bool>,
1056
1057    /// Automatically add support for an empty modifier.
1058    ///
1059    /// If this method is called, an empty modifier will generate a `1` CSS property value.
1060    pub has_empty: Option<bool>,
1061
1062    /// Automatically add support for negative modifiers.
1063    pub has_negative: Option<bool>,
1064
1065    #[doc = include_str!("./doc_template.md")]
1066    pub template: Option<PropertyName<Str, ArrayStr>>,
1067
1068    #[doc = include_str!("./doc_extra_rule_css.md")]
1069    pub extra_rule_css: Option<ArrayStr>,
1070
1071    #[doc = include_str!("./doc_extra_css.md")]
1072    pub extra_css: Option<MapStr>,
1073
1074    #[doc = include_str!("./doc_extra_class.md")]
1075    pub extra_class: Option<Str>,
1076
1077    #[doc = include_str!("./doc_extra_slash.md")]
1078    pub extra_slash: Option<ExtraSlash<Str, MapStr>>,
1079}
1080
1081impl<ArrayStr, MapStr> Number<&'static str, ArrayStr, MapStr> {
1082    /// Make a default [`Number`] plugin kind.
1083    ///
1084    /// All required fields are initialized with empty values and optional fields are initialized
1085    /// with `None`.
1086    ///
1087    /// You should at least set [`Number::namespace`] and [`Number::prop`] after calling this function.
1088    ///
1089    /// This function is intended to be used as an automatic filler for default values using the
1090    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
1091    ///
1092    /// The difference with [`Number::default_dynamic`] is that this function can only be used to
1093    /// build a plugin using static structures like `&[]`s, `&'static str`s.
1094    ///
1095    /// ### Example
1096    ///
1097    /// ```
1098    /// use encre_css::prelude::build_plugin::*;
1099    ///
1100    /// const PLUGIN: StaticPlugin = Plugin::Number(Number {
1101    ///     namespace: "z",
1102    ///     prop: SingleProp("z-index"),
1103    ///     ..Number::default()
1104    /// });
1105    /// ```
1106    pub const fn default() -> Self {
1107        Self {
1108            namespace: "",
1109            prop: PropertyName::SingleProp(""),
1110            divide_by: None,
1111            has_auto: None,
1112            has_empty: None,
1113            has_negative: None,
1114            template: None,
1115            extra_rule_css: None,
1116            extra_css: None,
1117            extra_class: None,
1118            extra_slash: None,
1119        }
1120    }
1121}
1122
1123impl<ArrayStr, MapStr> Number<String, ArrayStr, MapStr> {
1124    /// Make a default [`Number`] plugin kind.
1125    ///
1126    /// All required fields are initialized with empty values and optional fields are initialized
1127    /// with `None`.
1128    ///
1129    /// You should at least set [`Number::namespace`] and [`Number::prop`] after calling this function.
1130    ///
1131    /// This function is intended to be used as an automatic filler for default values using the
1132    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
1133    ///
1134    /// The difference with [`Number::default`] is that this function can only be used to
1135    /// build a plugin using heap-allocated structures like `String`s, `Vec`s.
1136    ///
1137    /// ### Example
1138    ///
1139    /// ```
1140    /// use encre_css::prelude::build_plugin::*;
1141    ///
1142    /// fn main() {
1143    ///     // Note: the DynamicPlugin type hint is required to help the compiler
1144    ///     // find the concrete types of type parameters
1145    ///     let _plugin: DynamicPlugin = Plugin::Number(Number {
1146    ///         namespace: "z".to_string(),
1147    ///         prop: SingleProp("z-index".to_string()),
1148    ///         ..Number::default_dynamic()
1149    ///     });
1150    /// }
1151    /// ```
1152    ///
1153    /// This example is equivalent to the one of [`Number::default`].
1154    pub fn default_dynamic() -> Self {
1155        Self {
1156            namespace: String::new(),
1157            prop: PropertyName::SingleProp(String::new()),
1158            divide_by: None,
1159            has_auto: None,
1160            has_empty: None,
1161            has_negative: None,
1162            template: None,
1163            extra_rule_css: None,
1164            extra_css: None,
1165            extra_class: None,
1166            extra_slash: None,
1167        }
1168    }
1169}
1170
1171/// Define a plugin supporting [`arbitrary values`], i.e all selectors in the form
1172/// `<namespace>-[...]` (i.e the modifier is wrapped in square brackets).
1173///
1174/// It directly copies the contents given inside brackets as the value of the `<prop>` CSS
1175/// propertie(s).
1176///
1177/// By default, all values are allowed by the plugin and it's up to the final user to only use
1178/// valid CSS values for the property. However, if several [`Arbitrary`] plugins
1179/// share the same namespace, it's *required* to disambiguate which plugins should handle the
1180/// selector. In this case, [`Arbitrary::disambiguate`] should be used to
1181/// only handle the selector if the arbitrary CSS value has a specific CSS type or a specific manual hint.
1182///
1183/// ### Example
1184///
1185/// ```
1186/// use encre_css::{Config, generate};
1187/// use encre_css::prelude::build_plugin::*;
1188///
1189/// const PLUGIN: StaticPlugin = Plugin::Arbitrary(Arbitrary {
1190///     namespace: "mask",
1191///     prop: SingleProp("mask-position"),
1192///     ..Arbitrary::default()
1193/// });
1194///
1195/// let mut config = Config::default();
1196/// config.register_plugin(&PLUGIN);
1197///
1198/// let generated = generate(["mask-[25%]", "mask-[left_center]"], &config);
1199///
1200/// assert!(generated.ends_with(r".mask-\[25\%\] {
1201///   mask-position: 25%;
1202/// }
1203///
1204/// .mask-\[left_center\] {
1205///   mask-position: left center;
1206/// }"));
1207/// ```
1208///
1209/// ### Example in TOML
1210///
1211/// ```toml
1212/// [[custom_plugins]]
1213///
1214/// [custom_plugins.Arbitrary]
1215/// namespace = "mask"
1216/// prop = "mask-position"
1217/// ```
1218///
1219/// [`arbitrary values`]: crate::selector
1220#[derive(Debug, Clone, Serialize, Deserialize)]
1221pub struct Arbitrary<Str, ArrayStr, MapStr, ArrayMatched> {
1222    /// The namespace (i.e common prefix) that all classes need to start with in order to be
1223    /// matched by this plugin.
1224    pub namespace: Str,
1225
1226    /// The CSS property name of the generated CSS rule.
1227    ///
1228    /// It can be a single property using [`PropertyName::SingleProp`] or a list of properties
1229    /// using [`PropertyName::MultipleProps`], in which case the value will be copied for all
1230    /// properties.
1231    pub prop: PropertyName<Str, ArrayStr>,
1232
1233    /// If the arbitrary value is a shadow, replace all the colors used by a single CSS variable
1234    /// given as string.
1235    ///
1236    /// This field should only be used for shadows that need to have their colors separately set
1237    /// using a dedicated utility class.
1238    ///
1239    /// If the value contains a placeholder `{}`, it will be replaced by the previous color value.
1240    ///
1241    /// ### Example
1242    ///
1243    /// ```
1244    /// use encre_css::{Config, generate};
1245    /// use encre_css::prelude::build_plugin::*;
1246    ///
1247    /// const PLUGIN_SHADOW: StaticPlugin = Plugin::Arbitrary(Arbitrary {
1248    ///     namespace: "custom-shadow",
1249    ///     prop: SingleProp("box-shadow"),
1250    ///     shadow_color_replacement: Some("var(--shadow-color, {})"),
1251    ///     ..Arbitrary::default()
1252    /// });
1253    ///
1254    /// const PLUGIN_SHADOW_COLOR: StaticPlugin = Plugin::Color(Color {
1255    ///     namespace: "custom-shadow-color",
1256    ///     prop: SingleProp("--shadow-color"),
1257    ///     ..Color::default()
1258    /// });
1259    ///
1260    /// let mut config = Config::default();
1261    /// config.register_plugin(&PLUGIN_SHADOW);
1262    /// config.register_plugin(&PLUGIN_SHADOW_COLOR);
1263    ///
1264    /// let generated = generate(["custom-shadow-[10px_5px_5px_red]", "custom-shadow-color-blue-100"], &config);
1265    ///
1266    /// assert!(generated.ends_with(r"
1267    /// .custom-shadow-\[10px_5px_5px_red\] {
1268    ///   box-shadow: 10px 5px 5px var(--shadow-color, red);
1269    /// }
1270    ///
1271    /// .custom-shadow-color-blue-100 {
1272    ///   --shadow-color: oklch(93.2% .032 255.585);
1273    /// }"));
1274    /// ```
1275    ///
1276    ///
1277    pub shadow_color_replacement: Option<Str>,
1278
1279    /// See [`ArbitraryDisambiguate`].
1280    pub disambiguate: Option<ArbitraryDisambiguate<ArrayMatched>>,
1281
1282    #[doc = include_str!("./doc_template.md")]
1283    pub template: Option<PropertyName<Str, ArrayStr>>,
1284
1285    #[doc = include_str!("./doc_extra_rule_css.md")]
1286    pub extra_rule_css: Option<ArrayStr>,
1287
1288    #[doc = include_str!("./doc_extra_css.md")]
1289    pub extra_css: Option<MapStr>,
1290
1291    #[doc = include_str!("./doc_extra_class.md")]
1292    pub extra_class: Option<Str>,
1293}
1294
1295impl<ArrayStr, MapStr, ArrayMatched> Arbitrary<&'static str, ArrayStr, MapStr, ArrayMatched> {
1296    /// Make a default [`Arbitrary`] plugin kind.
1297    ///
1298    /// All required fields are initialized with empty values and optional fields are initialized
1299    /// with `None`.
1300    ///
1301    /// You should at least set [`Arbitrary::namespace`] and [`Arbitrary::prop`] after calling this function.
1302    ///
1303    /// This function is intended to be used as an automatic filler for default values using the
1304    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
1305    ///
1306    /// The difference with [`Arbitrary::default_dynamic`] is that this function can only be used to
1307    /// build a plugin using static structures like `&[]`s, `&'static str`s.
1308    ///
1309    /// ### Example
1310    ///
1311    /// ```
1312    /// use encre_css::prelude::build_plugin::*;
1313    ///
1314    /// const PLUGIN: StaticPlugin = Plugin::Arbitrary(Arbitrary {
1315    ///     namespace: "gap",
1316    ///     prop: SingleProp("gap"),
1317    ///     ..Arbitrary::default()
1318    /// });
1319    /// ```
1320    pub const fn default() -> Self {
1321        Self {
1322            namespace: "",
1323            prop: PropertyName::SingleProp(""),
1324            shadow_color_replacement: None,
1325            disambiguate: None,
1326            template: None,
1327            extra_rule_css: None,
1328            extra_css: None,
1329            extra_class: None,
1330
1331        }
1332    }
1333}
1334
1335impl<ArrayStr, MapStr, ArrayMatched> Arbitrary<String, ArrayStr, MapStr, ArrayMatched> {
1336    /// Make a default [`Arbitrary`] plugin kind.
1337    ///
1338    /// All required fields are initialized with empty values and optional fields are initialized
1339    /// with `None`.
1340    ///
1341    /// You should at least set [`Arbitrary::namespace`] and [`Arbitrary::prop`] after calling this function.
1342    ///
1343    /// This function is intended to be used as an automatic filler for default values using the
1344    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
1345    ///
1346    /// The difference with [`Arbitrary::default`] is that this function can only be used to
1347    /// build a plugin using heap-allocated structures like `String`s, `Vec`s.
1348    ///
1349    /// ### Example
1350    ///
1351    /// ```
1352    /// use encre_css::prelude::build_plugin::*;
1353    ///
1354    /// fn main() {
1355    ///     // Note: the DynamicPlugin type hint is required to help the compiler
1356    ///     // find the concrete types of type parameters
1357    ///     let _plugin: DynamicPlugin = Plugin::Arbitrary(Arbitrary {
1358    ///         namespace: "gap".to_string(),
1359    ///         prop: SingleProp("gap".to_string()),
1360    ///         ..Arbitrary::default_dynamic()
1361    ///     });
1362    /// }
1363    /// ```
1364    ///
1365    /// This example is equivalent to the one of [`Arbitrary::default`].
1366    pub fn default_dynamic() -> Self {
1367        Self {
1368            namespace: String::new(),
1369            prop: PropertyName::SingleProp(String::new()),
1370            shadow_color_replacement: None,
1371            disambiguate: None,
1372            template: None,
1373            extra_rule_css: None,
1374            extra_css: None,
1375            extra_class: None,
1376        }
1377    }
1378}
1379
1380/// A powerful kind allowing the use a Rust function to handle all selectors in the form
1381/// `<namespace>-...`.
1382///
1383/// This plugin kind is (of course) not serializable.
1384///
1385/// The [`can_handle`] field function takes a [`ContextCanHandle`] structure and returns whether
1386/// the plugin is capable of handling the utility class given in the context.
1387///
1388/// The [`handle`] field function takes a [`ContextHandle`] structure containing the modifier, the current
1389/// configuration and a buffer containing the whole CSS currently generated. You can use the
1390/// [`Buffer`] structure (especially the [`Buffer::line`] and [`Buffer::lines`] functions) to
1391/// push CSS declarations to it, they will be automatically indented.
1392///
1393/// [`generate_wrapper`] (and the more powerful [`generate_at_rules`] and [`generate_class`])
1394/// should be called to generate the CSS rule wrapping.
1395///
1396/// ### Example
1397///
1398/// ```
1399/// use encre_css::{Config, generate};
1400/// use encre_css::prelude::build_plugin::*;
1401/// use std::collections::HashMap;
1402///
1403/// /// Reads the `emoji` extra field of the configuration to find the replacement emoji.
1404/// fn extract_emoji_value<'a>(config: &'a Config, value: &str) -> Option<&'a str> {
1405///     config.extra.get("emoji")
1406///         .and_then(|r| r.as_table())
1407///         .and_then(|r| r.get(value))
1408///         .and_then(|r| r.as_str())
1409/// }
1410///
1411/// const PLUGIN: StaticPlugin = Plugin::Functional(Functional {
1412///     namespace: "emoji",
1413///     can_handle: |context| matches!(context.modifier, Modifier::Builtin {
1414///         value,
1415///         ..
1416///     } if extract_emoji_value(context.config, value).is_some()),
1417///     handle: |context| {
1418///         // Only accept static modifiers, and dynamically fetch them from the
1419///         // `emoji` extra field of the configuration
1420///         if let Modifier::Builtin { value, .. } = context.modifier
1421///         && let Some(value) = extract_emoji_value(&context.config, value) {
1422///             generate_wrapper(context, |context| {
1423///                 context.buffer.line(format_args!("content: \"{value}\";"));
1424///             });
1425///         }
1426///     },
1427/// });
1428///
1429/// let mut config = Config::default();
1430/// config.extra.add(
1431///     "emoji",
1432///     HashMap::from_iter([("tada", "\u{1f389}"), ("rocket", "\u{1f680}")]),
1433/// );
1434/// config.register_plugin(&PLUGIN);
1435///
1436/// let generated = generate(["emoji-tada", "emoji-rocket"], &config);
1437///
1438/// assert!(generated.ends_with(".emoji-rocket {
1439///   content: \"\u{1f680}\";
1440/// }
1441///
1442/// .emoji-tada {
1443///   content: \"\u{1f389}\";
1444/// }"));
1445/// ```
1446///
1447/// [`Buffer`]: crate::utils::buffer::Buffer
1448/// [`Buffer::line`]: crate::utils::buffer::Buffer::line
1449/// [`Buffer::lines`]: crate::utils::buffer::Buffer::lines
1450/// [`can_handle`]: Functional::can_handle
1451/// [`handle`]: Functional::handle
1452/// [`generate_at_rules`]: crate::generator::generate_at_rules
1453/// [`generate_class`]: crate::generator::generate_class
1454/// [`generate_wrapper`]: crate::generator::generate_wrapper
1455#[derive(Debug, Clone)]
1456pub struct Functional<Str> {
1457    /// The namespace (i.e common prefix) that all classes need to start with in order to be
1458    /// matched by this plugin.
1459    pub namespace: Str,
1460
1461    /// A function returning whether a specific class (passed inside the context) is matched by
1462    /// this plugin.
1463    pub can_handle: fn(&ContextCanHandle) -> bool,
1464
1465    /// A function called to generate the CSS of a matched class.
1466    ///
1467    /// It should use [`generate_wrapper`] (and the more powerful [`generate_at_rules`] and [`generate_class`])
1468    /// to generate the CSS rule wrapping.
1469    ///
1470    /// Various notes:
1471    ///
1472    /// - The CSS written should end with a newline
1473    /// - Arbitrary values are already normalized (e.g. underscores are replaced by spaces)
1474    /// - This function is guaranteed to be called only once per selector
1475    ///
1476    /// [`generate_wrapper`]: crate::generator::generate_wrapper
1477    /// [`generate_at_rules`]: crate::generator::generate_at_rules
1478    /// [`generate_class`]: crate::generator::generate_class
1479    pub handle: fn(&mut ContextHandle),
1480}
1481
1482impl Functional<&'static str> {
1483    /// Make a default [`Functional`] plugin kind.
1484    ///
1485    /// All required fields are initialized with empty values and optional fields are initialized
1486    /// with `None`.
1487    ///
1488    /// You should at least set [`Functional::namespace`], [`Functional::can_handle`] and [`Functional::handle`] after calling this function.
1489    ///
1490    /// This function is intended to be used as an automatic filler for default values using the
1491    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
1492    ///
1493    /// The difference with [`Functional::default_dynamic`] is that this function can only be used to
1494    /// build a plugin using static structures like `&[]`s, `&'static str`s.
1495    ///
1496    /// ### Example
1497    ///
1498    /// ```
1499    /// use encre_css::prelude::build_plugin::*;
1500    ///
1501    /// const PLUGIN: StaticPlugin = Plugin::Functional(Functional {
1502    ///     namespace: "emoji",
1503    ///     can_handle: |context| matches!(context.modifier, Modifier::Builtin { value: "tada", .. }),
1504    ///     handle: |context| {
1505    ///         generate_wrapper(context, |context| {
1506    ///             context.buffer.line(format_args!("content: \"\u{1f389}\";"));
1507    ///         });
1508    ///     },
1509    ///     ..Functional::default()
1510    /// });
1511    /// ```
1512    pub const fn default() -> Self {
1513        Self {
1514            namespace: "",
1515            can_handle: can_handle_nop,
1516            handle: handle_nop,
1517        }
1518    }
1519}
1520
1521impl Functional<String> {
1522    /// Make a default [`Functional`] plugin kind.
1523    ///
1524    /// All required fields are initialized with empty values and optional fields are initialized
1525    /// with `None`.
1526    ///
1527    /// You should at least set [`Functional::namespace`], [`Functional::can_handle`] and [`Functional::handle`] after calling this function.
1528    ///
1529    /// This function is intended to be used as an automatic filler for default values using the
1530    /// [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
1531    ///
1532    /// The difference with [`Functional::default`] is that this function can only be used to
1533    /// build a plugin using heap-allocated structures like `String`s, `Vec`s.
1534    ///
1535    /// ### Example
1536    ///
1537    /// ```
1538    /// use encre_css::prelude::build_plugin::*;
1539    ///
1540    /// fn main() {
1541    ///     // Note: the DynamicPlugin type hint is required to help the compiler
1542    ///     // find the concrete types of type parameters
1543    ///     let _plugin: DynamicPlugin = Plugin::Functional(Functional {
1544    ///         namespace: "emoji".to_string(),
1545    ///         can_handle: |context| matches!(context.modifier, Modifier::Builtin { value: "tada", .. }),
1546    ///         handle: |context| {
1547    ///             generate_wrapper(context, |context| {
1548    ///                 context.buffer.line(format_args!("content: \"\u{1f389}\";"));
1549    ///             });
1550    ///         },
1551    ///         ..Functional::default_dynamic()
1552    ///     });
1553    /// }
1554    /// ```
1555    ///
1556    /// This example is equivalent to the one of [`Functional::default`].
1557    pub fn default_dynamic() -> Self {
1558        Self {
1559            namespace: String::new(),
1560            can_handle: can_handle_nop,
1561            handle: handle_nop,
1562        }
1563    }
1564}
1565
1566/// A plugin is a structure capable of generating CSS styles from a CSS selector.
1567///
1568/// Several kinds of plugins exist and define what values are accepted as selector or modifier and
1569/// what CSS is generated based on the input selector. The API is designed to be fully declarative
1570/// (so that plugin declarations are serializable), except for the
1571/// [functional kind](Plugin::Functional).
1572///
1573/// Each plugin kind has a set of required parameters and a set of default parameters which can be
1574/// automatically filled in Rust using the [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-with-struct-update-syntax).
1575///
1576/// It's common to define several plugins to handle a single utility class, and to define static
1577/// plugins as constants (the `default` function on each plugin kind is a `const fn`).
1578///
1579/// After you have defined a plugin, you need to register it in the [`Config`] structure by calling
1580/// [`Config::register_plugin`].
1581///
1582/// # Simple example (defines the static values of the `font-family` plugin)
1583///
1584/// ```
1585/// use encre_css::prelude::build_plugin::*;
1586///
1587/// const PLUGIN: StaticPlugin = Plugin::ListValues(ListValues {
1588///     prop: SingleProp("font-family"),
1589///     values: map! {
1590///         "font-sans" => r#"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont"#,
1591///         "font-serif" => r#"Georgia, Cambria, "Times New Roman", Times, serif"#,
1592///         "font-mono" => r#"Menlo, Monaco, Consolas, "Liberation Mono", monospace"#,
1593///     },
1594///     ..ListValues::default()
1595/// });
1596/// ```
1597///
1598/// # More advanced example (defines the `stroke-width` plugin)
1599///
1600/// ```
1601/// use encre_css::prelude::build_plugin::*;
1602///
1603/// const PLUGIN: StaticPlugin = Plugin::Number(Number {
1604///     namespace: "stroke",
1605///     prop: SingleProp("stroke-width"),
1606///     template: Some(SingleProp("{}px")),
1607///     ..Number::default()
1608/// });
1609///
1610/// // There's also a plugin sharing the same `stroke` namespace (which helps changing the
1611/// // stroke color, e.g `stroke-red-500`), so it's required to define `hints` and `matchers`
1612/// const PLUGIN_ARBITRARY: StaticPlugin = Plugin::Arbitrary(Arbitrary {
1613///     namespace: "stroke",
1614///     prop: SingleProp("stroke-width"),
1615///     disambiguate: Some(ArbitraryDisambiguate {
1616///         matched: &[CssType::Length, CssType::Percentage, CssType::LineWidth, CssType::Number],
1617///         separation: ArbitraryDisambiguateSeparation::None,
1618///     }),
1619///     ..Arbitrary::default()
1620/// });
1621/// ```
1622///
1623/// # More powerful usage
1624///
1625/// If you need to have full control over the CSS **rule** generated, you can use the [`Functional`]
1626/// plugin kind. It allows executing a full-blown Rust function for each selector having a specific
1627/// namespace. However, it's (of course) not serializable, and thus cannot be used in, e.g a TOML
1628/// configuration.
1629///
1630/// ### Example
1631///
1632/// ```
1633/// use encre_css::Config;
1634/// use encre_css::prelude::build_plugin::*;
1635///
1636/// /// Reads the `emoji` extra field of the configuration to find the replacement emoji.
1637/// fn extract_emoji_value<'a>(config: &'a Config, value: &str) -> Option<&'a str> {
1638///     config.extra.get("emoji")
1639///         .and_then(|r| r.as_table())
1640///         .and_then(|r| r.get(value))
1641///         .and_then(|r| r.as_str())
1642/// }
1643///
1644/// const PLUGIN: StaticPlugin = Plugin::Functional(Functional {
1645///     namespace: "emoji",
1646///     can_handle: |context| matches!(context.modifier, Modifier::Builtin {
1647///         value,
1648///         ..
1649///     } if extract_emoji_value(context.config, value).is_some()),
1650///     handle: |context| {
1651///         // Only accept static modifiers, and dynamically fetch them from the
1652///         // `emoji` extra field of the configuration
1653///         if let Modifier::Builtin { value, .. } = context.modifier
1654///         && let Some(value) = extract_emoji_value(&context.config, value) {
1655///             generate_at_rules(context, |context| {
1656///                 generate_class(
1657///                     context,
1658///                     |context| {
1659///                         context.buffer.line(format_args!("content: \"{value}\";"));
1660///                     },
1661///                     "",
1662///                 );
1663///             });
1664///         }
1665///     },
1666/// });
1667/// ```
1668///
1669/// Have a look at <https://gitlab.com/encre-org/encre-css/tree/main/crates/encre-css/src/plugins>
1670/// for more examples.
1671///
1672/// # Define a plugin in TOML
1673///
1674/// Instead of defining plugins in Rust, you can also define them in `encre-css`'s TOML configuration
1675/// (or every other language that uses a `serde` deserializer).
1676/// The sole exception is plugins using the [`Functional`] kind which are not serializable.
1677///
1678/// To do that, you need to add a new entry in the `custom_plugins` list of the configuration.
1679/// You can then define plugins as you would do in Rust.
1680///
1681/// ### Example
1682///
1683/// ```toml
1684/// [[custom_plugins]]
1685///
1686/// [custom_plugins.Number]
1687/// namespace = "stroke"
1688/// prop = "stroke-width"
1689/// template = "{}px"
1690///
1691/// [[custom_plugins]]
1692///
1693/// [custom_plugins.Arbitrary]
1694/// namespace = "stroke"
1695/// prop = "stroke-width"
1696/// hints = ["Length", "Percentage"]
1697/// matchers = [["Length", "Percentage", "LineWidth", "Number"], "None"]
1698/// ```
1699///
1700/// # Advice
1701///
1702/// `encre-css` builds a [trie structure](https://en.wikipedia.org/wiki/Trie) based on the
1703/// namespace of the plugins to optimize matching a utility class to a specific plugin, so it's
1704/// **highly discouraged to leave the namespace of a plugin empty**, otherwise the performances will
1705/// decrease heavily.
1706///
1707/// # Release a plugin as a crate
1708///
1709/// If you want to release your custom plugins as a crate, you can export a `register` function
1710/// taking a mutable reference to a [`Config`] structure and use the [`Config::register_plugin`]
1711/// function to register them.
1712///
1713/// ```ignore
1714/// pub fn register(config: &mut Config) {
1715///     config.register_plugin(&PLUGIN);
1716///     config.register_plugin(&PLUGIN_ARBITRARY);
1717/// }
1718/// ```
1719///
1720/// [`Config::register_plugin`]: crate::Config::register_plugin
1721/// [`Config`]: crate::Config
1722#[derive(Debug, Clone, Serialize, Deserialize)]
1723pub enum Plugin<Str, ArrayStr, MapStr, MapArrayStr, ArrayMatched> {
1724    /// See [`ListProperties`].
1725    ListProperties(ListProperties<Str, ArrayStr, MapStr, MapArrayStr>),
1726
1727    /// See [`ListValues`].
1728    ListValues(ListValues<Str, ArrayStr, MapStr>),
1729
1730    /// See [`Spacing`].
1731    Spacing(Spacing<Str, ArrayStr, MapStr>),
1732
1733    /// See [`Color`].
1734    Color(Color<Str, ArrayStr, MapStr>),
1735
1736    /// See [`Number`].
1737    Number(Number<Str, ArrayStr, MapStr>),
1738
1739    /// See [`Arbitrary`].
1740    Arbitrary(Arbitrary<Str, ArrayStr, MapStr, ArrayMatched>),
1741
1742    /// See [`Functional`].
1743    ///
1744    /// Not serializable.
1745    #[serde(skip)]
1746    Functional(Functional<Str>),
1747}