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