tailwindcss_to_rust_macros/
lib.rs

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.
17
18pub mod to_option_vec_string;
19pub use to_option_vec_string::ToOptionVecString;
20
21/// 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(" ")
48            let 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}
56
57/// 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        {
76            let mut all_classes = vec![];
77            $(
78                $crate::_push_all_strings(&mut all_classes, $class.to_option_vec_string());
79            )*
80            format_args!("{}", all_classes.join(" "))
81        }
82    };
83}
84
85/// 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        {
101            let 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}
109
110/// 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>>) {
112    if 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}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test() {
128        assert_eq!(C![""], "");
129        assert_eq!(C!["x"], "x");
130
131        let x = "x";
132        let y = "y";
133        assert_eq!(C![x, y], "x y");
134        assert_eq!(C![x, y, "z"], "x y z");
135
136        assert_eq!(C![M!["md", "x"]], "md:x");
137        assert_eq!(C![M!["md", "hover", "x"]], "md:hover:x");
138        assert_eq!(C![M!["md", "x"], M!["hover", "y"]], "md:x hover:y");
139        assert_eq!(C![x, M!["md", y], M!["hover", "z"]], "x md:y hover:z");
140
141        let z = "z".to_string();
142        assert_eq!(C![x, y, z, "foo"], "x y z foo");
143
144        let z = "z".to_string();
145        assert_eq!(C![M![x, y, z, "foo"]], "x:y:z:foo");
146    }
147
148    // 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.
154
155    // --- Texts ---
156
157    #[test]
158    fn to_option_vec_string_ref_str() {
159        let text: &str = "foo";
160        assert_eq!(C![text], "foo");
161        assert_eq!(M![text], "foo");
162    }
163
164    #[test]
165    fn to_option_vec_string_ref_str_empty() {
166        let text: &str = "";
167        assert!(C![text].is_empty());
168        assert!(M![text].is_empty());
169    }
170
171    #[test]
172    fn to_option_vec_string_string() {
173        let text = String::from("bar");
174        assert_eq!(C![text], "bar");
175        let text = String::from("bar");
176        assert_eq!(M![text], "bar");
177    }
178
179    #[test]
180    fn to_option_vec_string_ref_string() {
181        let text = &String::from("ref_bar");
182        assert_eq!(C![text], "ref_bar");
183        let text = &String::from("ref_bar");
184        assert_eq!(M![text], "ref_bar");
185    }
186
187    // --- Containers ---
188
189    #[test]
190    fn to_option_vec_string_vec() {
191        let vec: Vec<&str> = vec!["foo_1", "foo_2"];
192        assert_eq!(C![vec], "foo_1 foo_2");
193        let vec: Vec<&str> = vec!["foo_1", "foo_2"];
194        assert_eq!(M![vec], "foo_1:foo_2");
195    }
196
197    #[test]
198    fn to_option_vec_string_ref_vec() {
199        let vec: &Vec<&str> = &vec!["foo_1", "foo_2"];
200        assert_eq!(C![vec], "foo_1 foo_2");
201        let vec: &Vec<&str> = &vec!["foo_1", "foo_2"];
202        assert_eq!(M![vec], "foo_1:foo_2");
203    }
204
205    #[test]
206    fn to_option_vec_string_slice() {
207        let slice: &[&str] = &["foo_1", "foo_2"];
208        assert_eq!(C![slice], "foo_1 foo_2");
209        assert_eq!(M![slice], "foo_1:foo_2");
210    }
211
212    #[test]
213    fn to_option_vec_string_option_some() {
214        let option: Option<&str> = Some("foo_opt");
215        assert_eq!(C![option], "foo_opt");
216        assert_eq!(M![option], "foo_opt");
217    }
218
219    #[test]
220    fn to_option_vec_string_ref_option_some() {
221        let option: &Option<&str> = &Some("foo_opt");
222        assert_eq!(C![option], "foo_opt");
223        assert_eq!(M![option], "foo_opt");
224    }
225
226    #[test]
227    fn to_option_vec_string_option_none() {
228        let option: Option<&str> = None;
229        assert!(C![option].is_empty());
230        assert!(M![option].is_empty());
231    }
232
233    #[test]
234    fn to_option_vec_string_option_vec() {
235        let option_vec: Option<Vec<&str>> = Some(vec!["foo_1", "foo_2"]);
236        assert_eq!(C![option_vec], "foo_1 foo_2");
237        let option_vec: Option<Vec<&str>> = Some(vec!["foo_1", "foo_2"]);
238        assert_eq!(M![option_vec], "foo_1:foo_2");
239    }
240
241    // 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]
244    fn with_fmt() {
245        struct Classes {
246            classes: String,
247        }
248
249        impl std::fmt::Display for Classes {
250            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251                write!(f, "{} {}", self.classes, C!["foo", "bar" M!["md", "baz"]],)
252            }
253        }
254
255        let classes = Classes {
256            classes: "x y z".to_string(),
257        };
258        assert_eq!(format!("{classes}"), "x y z foo bar md:baz");
259    }
260}