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
35use crate::generator::{ContextCanHandle, ContextHandle};
36
37use std::fmt;
38
39pub mod accessibility;
40pub mod background;
41pub mod border;
42pub mod css_property;
43pub mod effect;
44pub mod filter;
45pub mod flexbox;
46pub mod grid;
47pub mod interactivity;
48pub mod layout;
49pub mod sizing;
50pub mod spacing;
51pub mod svg;
52pub mod table;
53pub mod transform;
54pub mod transition;
55pub mod typography;
56
57/// A plugin is a structure capable of generating CSS styles from a modifier (contained in a
58/// context structure).
59///
60/// Each plugin consists of two methods:
61/// - [`Plugin::can_handle`] to check if it will be able to generate CSS for a specific modifier;
62/// - [`Plugin::handle`] to generate the CSS needed.
63///
64/// The [`Plugin::can_handle`] method takes a [`ContextCanHandle`] structure containing the
65/// modifier and the current configuration.
66///
67/// The [`Plugin::handle`] method takes a [`ContextHandle`] structure containing the modifier,
68/// the current configuration and a buffer containing the whole CSS
69/// currently generated. You can use the [`Buffer`] structure (especially the [`Buffer::line`]
70/// and [`Buffer::lines`] functions) to push CSS declarations to it, they will be automatically
71/// indented.
72///
73/// It is common to use the [`unreachable!`] macro if the [`Plugin::handle`] method cannot be
74/// called because you are sure that [`Plugin::can_handle`] returned `false`.
75///
76/// # Example (defines the `stroke-width` plugin)
77///
78/// ```
79/// use encre_css::prelude::build_plugin::*;
80///
81/// #[derive(Debug)]
82/// struct StrokeWidth;
83///
84/// impl Plugin for StrokeWidth {
85/// fn can_handle(&self, context: ContextCanHandle) -> bool {
86/// match context.modifier {
87/// Modifier::Builtin { value, .. } => value.parse::<usize>().is_ok(),
88/// Modifier::Arbitrary { hint, value, .. } => {
89/// *hint == "length"
90/// || *hint == "number"
91/// || *hint == "percentage"
92/// || (hint.is_empty()
93/// && (is_matching_length(value) || is_matching_percentage(value)))
94/// }
95/// }
96/// }
97///
98/// fn handle(&self, context: &mut ContextHandle) {
99/// match context.modifier {
100/// Modifier::Builtin { value, .. } => {
101/// context.buffer.line(format_args!("stroke-width: {value}px;"));
102/// }
103/// Modifier::Arbitrary { value, .. } => {
104/// context.buffer.line(format_args!("stroke-width: {value};"));
105/// }
106/// }
107/// }
108/// }
109/// ```
110///
111/// # Release a plugin as a crate
112///
113/// If you want to release your custom plugins as a crate, you can export a `register` function
114/// taking a mutable reference to a [`Config`] structure and use the [`Config::register_plugin`]
115/// function to register them. The first argument is the namespace prefixing all the
116/// utility classes handled by the plugin.
117///
118/// ```ignore
119/// pub fn register(config: &mut Config) {
120/// config.register_plugin("stroke", &StrokeWidth);
121/// }
122/// ```
123///
124/// # More powerful usage
125///
126/// If you need to have full control over the CSS **rule**, you can create a [`needs_wrapping`]
127/// method returning false and use [`generator::generate_at_rules`], [`generator::generate_class`]
128/// and [`generator::generate_wrapper`] to generate some CSS boilerplate.
129///
130/// ### Example (roughly defines the `animation` plugin)
131///
132/// ```
133/// use encre_css::prelude::build_plugin::*;
134///
135/// #[derive(Debug)]
136/// struct PluginDefinition;
137///
138/// impl Plugin for PluginDefinition {
139/// fn needs_wrapping(&self) -> bool {
140/// false
141/// }
142///
143/// fn can_handle(&self, context: ContextCanHandle) -> bool {
144/// match context.modifier {
145/// Modifier::Builtin { value, .. } => {
146/// ["spin", "ping", "pulse", "bounce", "none"].contains(value)
147/// }
148/// Modifier::Arbitrary { value, .. } => is_matching_all(value),
149/// }
150/// }
151///
152/// fn handle(&self, context: &mut ContextHandle) {
153/// match context.modifier {
154/// Modifier::Builtin { value, .. } => {
155/// let animation = match *value {
156/// "none" => "none",
157/// "spin" => {
158/// context.buffer.lines([
159/// "@keyframes spin",
160/// "...",
161/// ]);
162/// "spin 1s linear infinite"
163/// }
164/// "ping" => {
165/// context.buffer.lines([
166/// "@keyframes ping",
167/// "...",
168/// ]);
169/// "ping 1s cubic-bezier(0, 0, 0.2, 1) infinite"
170/// }
171/// "pulse" => {
172/// context.buffer.lines([
173/// "@keyframes pulse",
174/// "...",
175/// ]);
176/// "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"
177/// }
178/// "bounce" => {
179/// context.buffer.lines([
180/// "@keyframes bounce",
181/// "...",
182/// ]);
183/// "bounce 1s infinite"
184/// }
185/// _ => unreachable!(),
186/// };
187///
188/// generate_wrapper(context, |context| {
189/// context.buffer.line(format_args!("animation: {animation};"));
190/// })
191/// }
192/// Modifier::Arbitrary { value, .. } => generate_wrapper(context, |context| {
193/// context.buffer.line(format_args!("animation: {value};"));
194/// }),
195/// }
196/// }
197/// }
198/// ```
199///
200/// Have a look at <https://gitlab.com/encre-org/encre-css/tree/main/crates/encre-css/src/plugins>
201/// for more examples.
202///
203/// [`Buffer`]: crate::utils::buffer::Buffer
204/// [`Buffer::line`]: crate::utils::buffer::Buffer::line
205/// [`Buffer::lines`]: crate::utils::buffer::Buffer::lines
206/// [`Config::register_plugin`]: crate::Config::register_plugin
207/// [`Config`]: crate::Config
208/// [`needs_wrapping`]: Plugin::needs_wrapping
209/// [`generator::generate_at_rules`]: crate::generator::generate_at_rules
210/// [`generator::generate_class`]: crate::generator::generate_class
211/// [`generator::generate_wrapper`]: crate::generator::generate_wrapper
212pub trait Plugin: fmt::Debug {
213 /// Returns whether the plugin can handle a specific modifier.
214 fn can_handle(&self, _context: ContextCanHandle) -> bool;
215
216 /// Returns whether the plugin should be wrapped inside a CSS rule or if it will manually
217 /// generate it
218 fn needs_wrapping(&self) -> bool {
219 true
220 }
221
222 /// Get the CSS code from a modifier.
223 ///
224 ///
225 /// The [`Plugin::can_handle`] method **must be** called before to know if it can handle
226 /// the modifier, otherwise this function **will panic**.
227 ///
228 /// Various notes:
229 /// - The CSS returned should end with a newline;
230 /// - Arbitrary values are already normalized (e.g. underscores are replaced by spaces);
231 /// - This function is guaranteed to be called only once per selector.
232 fn handle(&self, _context: &mut ContextHandle);
233}