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}