1#![doc = include_str!("../README.md")]
2//!
3//! It's convenient to have a single module in your codebase that exports the
4//! macros along with the generated structs.
5//!
6//! Let's assume that module will live at `src/css/mod.rs` and that the
7//! code generated by `tailwindcss-to-rust` lives at `src/css/generated.rs`. The
8//! contents of `mod.rs` will look like this:
9//!
10//! ```rust,ignore
11#![doc = include_str!("../examples/css/mod.rs")]
12//! ```
13//!
14//! See [the `examples` directory in the git
15//! repo](https://github.com/houseabsolute/tailwindcss-to-rust/tree/master/macros/examples)
16//! for all of the above example code.
1718pub mod to_option_vec_string;
19pub use to_option_vec_string::ToOptionVecString;
2021/// Takes one or more class names and returns a single space-separated string.
22///
23/// This macro provides a bit of sugar.
24///
25/// It frees you from having to write something like this:
26///
27/// ```rust,ignore
28/// let classes = [C.lay.flex, C.fg.flex_col].join(" ");
29/// ```
30///
31/// It also offers a lot of flexibility in what types it accepts, so you can
32/// use any of the following as arguments to `C!`:
33///
34/// * `&str`
35/// * `String`
36/// * `&String`
37/// * `Option<T>` and `&Option<T>` where `T` is any of the above.
38/// * `Vec<T>`, `&Vec<T>`, and `&[T]` where `T` is any of the above.
39#[macro_export]
40macro_rules! C {
41 ( $($class:expr $(,)?)+ ) => {
42 {
43// [
44 // $(
45 // $class.to_option_vec_string(),
46 // )*
47 // ].into_iter().filter_map(Option::is_some).flatten().join(" ")
48let mut all_classes = vec![];
49 $(
50$crate::_push_all_strings(&mut all_classes, $class.to_option_vec_string());
51 )*
52 all_classes.join(" ")
53 }
54 };
55}
5657/// Variant of the [`C!`] macro for use with Dioxus `class` attributes.
58///
59/// This works exactly like [`C!`] but it is designed to work with Dioxus's
60/// attributes.
61///
62/// If you want to import this as `C!` just write this:
63///
64/// ```rust,ignore
65/// use tailwindcss_to_rust_macros::DC as C;
66///
67/// div {
68/// class: DC![C.typ.text_lg],
69/// ...
70/// }
71/// ```
72#[macro_export]
73macro_rules! DC {
74 ( $($class:expr $(,)?)+ ) => {
75 {
76let mut all_classes = vec![];
77 $(
78$crate::_push_all_strings(&mut all_classes, $class.to_option_vec_string());
79 )*
80format_args!("{}", all_classes.join(" "))
81 }
82 };
83}
8485/// Takes one or more tailwind modifier names and a class name, returning a single colon-separated string.
86///
87/// This works exactly like [`C!`] but it expects one or more modifier names
88/// like "lg" or "hover", followed by a single class name.
89///
90/// ```rust,ignore
91/// let classes = [
92/// C.flex_and_grid.grid_cols_3,
93/// M![M.lg, C.fg.grid_cols_6],
94/// ].join(" ");
95/// // classes is "grid-cols-3 lg:grid-cols-6"
96/// ```
97#[macro_export]
98macro_rules! M {
99 ( $($modifier:expr $(,)?)* ) => {
100 {
101let mut all_modifiers = vec![];
102 $(
103$crate::_push_all_strings(&mut all_modifiers, $modifier.to_option_vec_string());
104 )*
105 all_modifiers.join(":")
106 }
107 };
108}
109110/// This is for use by the macros. Please don't use it yourself.
111pub fn _push_all_strings(all_strings: &mut Vec<String>, classes: Option<Vec<String>>) {
112if let Some(classes) = classes {
113 all_strings.append(
114&mut classes
115 .into_iter()
116 .filter(|c| !c.is_empty())
117 .collect::<Vec<_>>(),
118 );
119 }
120}
121122#[cfg(test)]
123mod tests {
124use super::*;
125126#[test]
127fn test() {
128assert_eq!(C![""], "");
129assert_eq!(C!["x"], "x");
130131let x = "x";
132let y = "y";
133assert_eq!(C![x, y], "x y");
134assert_eq!(C![x, y, "z"], "x y z");
135136assert_eq!(C![M!["md", "x"]], "md:x");
137assert_eq!(C![M!["md", "hover", "x"]], "md:hover:x");
138assert_eq!(C![M!["md", "x"], M!["hover", "y"]], "md:x hover:y");
139assert_eq!(C![x, M!["md", y], M!["hover", "z"]], "x md:y hover:z");
140141let z = "z".to_string();
142assert_eq!(C![x, y, z, "foo"], "x y z foo");
143144let z = "z".to_string();
145assert_eq!(C![M![x, y, z, "foo"]], "x:y:z:foo");
146 }
147148// These tests were copied from Seed (https://github.com/seed-rs/seed) and
149 // then modified.
150 //
151 // Copyright 2019 DavidOConnor <david.alan.oconnor@gmail.com>
152 //
153 // Licensed under the MIT license only.
154155 // --- Texts ---
156157#[test]
158fn to_option_vec_string_ref_str() {
159let text: &str = "foo";
160assert_eq!(C![text], "foo");
161assert_eq!(M![text], "foo");
162 }
163164#[test]
165fn to_option_vec_string_ref_str_empty() {
166let text: &str = "";
167assert!(C![text].is_empty());
168assert!(M![text].is_empty());
169 }
170171#[test]
172fn to_option_vec_string_string() {
173let text = String::from("bar");
174assert_eq!(C![text], "bar");
175let text = String::from("bar");
176assert_eq!(M![text], "bar");
177 }
178179#[test]
180fn to_option_vec_string_ref_string() {
181let text = &String::from("ref_bar");
182assert_eq!(C![text], "ref_bar");
183let text = &String::from("ref_bar");
184assert_eq!(M![text], "ref_bar");
185 }
186187// --- Containers ---
188189#[test]
190fn to_option_vec_string_vec() {
191let vec: Vec<&str> = vec!["foo_1", "foo_2"];
192assert_eq!(C![vec], "foo_1 foo_2");
193let vec: Vec<&str> = vec!["foo_1", "foo_2"];
194assert_eq!(M![vec], "foo_1:foo_2");
195 }
196197#[test]
198fn to_option_vec_string_ref_vec() {
199let vec: &Vec<&str> = &vec!["foo_1", "foo_2"];
200assert_eq!(C![vec], "foo_1 foo_2");
201let vec: &Vec<&str> = &vec!["foo_1", "foo_2"];
202assert_eq!(M![vec], "foo_1:foo_2");
203 }
204205#[test]
206fn to_option_vec_string_slice() {
207let slice: &[&str] = &["foo_1", "foo_2"];
208assert_eq!(C![slice], "foo_1 foo_2");
209assert_eq!(M![slice], "foo_1:foo_2");
210 }
211212#[test]
213fn to_option_vec_string_option_some() {
214let option: Option<&str> = Some("foo_opt");
215assert_eq!(C![option], "foo_opt");
216assert_eq!(M![option], "foo_opt");
217 }
218219#[test]
220fn to_option_vec_string_ref_option_some() {
221let option: &Option<&str> = &Some("foo_opt");
222assert_eq!(C![option], "foo_opt");
223assert_eq!(M![option], "foo_opt");
224 }
225226#[test]
227fn to_option_vec_string_option_none() {
228let option: Option<&str> = None;
229assert!(C![option].is_empty());
230assert!(M![option].is_empty());
231 }
232233#[test]
234fn to_option_vec_string_option_vec() {
235let option_vec: Option<Vec<&str>> = Some(vec!["foo_1", "foo_2"]);
236assert_eq!(C![option_vec], "foo_1 foo_2");
237let option_vec: Option<Vec<&str>> = Some(vec!["foo_1", "foo_2"]);
238assert_eq!(M![option_vec], "foo_1:foo_2");
239 }
240241// I wrote this to help debug an issue with a Dioxus application where
242 // similar code was leading to memory errors in the generated WASM.
243#[test]
244fn with_fmt() {
245struct Classes {
246 classes: String,
247 }
248249impl std::fmt::Display for Classes {
250fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251write!(f, "{} {}", self.classes, C!["foo", "bar" M!["md", "baz"]],)
252 }
253 }
254255let classes = Classes {
256 classes: "x y z".to_string(),
257 };
258assert_eq!(format!("{classes}"), "x y z foo bar md:baz");
259 }
260}