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


/// includes the raw generated tailwind file like `include_str!`,
/// yields an empty file in debug
/// 
/// call `include_tailwind_raw!(always)` to always and include file
/// (also needs to be enabled in the build.rs config to work)
#[macro_export]
macro_rules! include_tailwind_raw {
    (always) => {
        ::core::include_str!(env!("INCLUDE_TAILWIND_PATH"))
    };
    () => {
        {
            #[cfg(not(debug_assertions))] {
                $crate::include_tailwind_raw!(always)
            }
            #[cfg(debug_assertions)] { "/* this is empty in debug */\n" }
        }
    };
}

/// includes the generated tailwind file wrapped that it can directly be used by
/// the supported web frameworks as a response
/// (the `Stylesheet` Type implements the corosponding trait if the feature is enabled)
/// see the `include_tailwind_raw` docs for the possible arguments
/// 
/// ```rust
/// // axum request handler as example
/// async fn style_css() -> include_tailwind::Stylesheet { include_tailwind!() }
/// ```
#[macro_export]
macro_rules! include_tailwind {
    ($($v:tt)*) => { $crate::Stylesheet($crate::include_tailwind_raw!($($v)*)) };
}

/// the wrapping type for the included tailwind source,
/// automaticly works as a response type for the web frameworks, 
/// enabled by the features
#[derive(Debug, Clone, Copy, Hash)]
pub struct Stylesheet(pub &'static str);

impl AsRef<str> for Stylesheet { fn as_ref(&self) -> &str { &self.0 } }

impl Stylesheet {
    /// returns the raw stylesheet `&str`
    pub fn as_str(&self) -> &'static str { &self.0 }
}

#[cfg(feature = "axum")]
mod axum_support {
    use crate::Stylesheet;
    use axum_core::response::{IntoResponse, Response};
    use http::{header, HeaderMap, HeaderValue};

    impl IntoResponse for Stylesheet {
        fn into_response(self) -> Response {
            (
                HeaderMap::from_iter([
                    (header::CONTENT_TYPE, HeaderValue::from_static("text/css; charset=utf-8"))
                ]),
                self.0,
            ).into_response()
        }
    }
}


/// the result of the `load_tailwind!` macro
#[derive(Debug)]
pub enum LoadTailwind {
    /// expands to
    /// ```html
    /// <style>
    ///     /* styles generated by tailwind */
    /// </style>
    /// ```
    Inline {
        css: &'static str
    },
    /// expands to
    /// ```html
    /// <link rel="stylesheet" type="text/css" href="{path}">
    /// ```
    Loaded {
        path: String,
    },
    /// expands to
    /// ```html
    /// <script src="{jit_url}"></script>
    /// <script>
    ///     tailwind.config = {
    ///         ...config
    ///     }
    /// </script>
    /// ```
    Jit {
        config: &'static str,
        jit_url: &'static str,
    },
}

#[cfg(feature = "maud")]
mod maud_support {
    use crate::LoadTailwind;
    use maud::Render;

    impl Render for LoadTailwind {
        #[inline]
        fn render(&self) -> maud::Markup {
            maud::PreEscaped(self.to_string())
        }
    }
}

impl std::fmt::Display for LoadTailwind {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LoadTailwind::Inline { css } => {
                write!(f, "
<!-- included inline by include-tailwind -->
<style>
{css}
</style>")?;
            },
            LoadTailwind::Loaded { path } => {
                write!(f, "<link rel=\"stylesheet\" type=\"text/css\" href=\"{path}\">")?;
            },
            LoadTailwind::Jit { config, jit_url } => {
                write!(f, "
<script src=\"{jit_url}\"></script>
<script>
{config}
</script>")?;
            },
        }
        Ok(())
    }
}

/// automaticly loads tailwind, depending on the config
/// the macro should be called in the head to the html doc:
///
/// ```rust
/// // example for maud
///
/// html! {
///     (DOCTYPE);
///     html {
///         head {
///             (load_tailwind!());
///         }
///         body {
///             div."w-prose mx-auto" { "Tailwind just works" }
///         }
///     }
/// }
/// ```
///
/// the actual way of loading the file depends on the compile mode (debug_assertions or not)
/// in debug the compilation is skipped as to not slow down the build
///
/// calling `load_tailwind!(jit)` forces jit
///
/// calling `load_tailwind!(always)` has the same behaviour as `include_html!(always)`
/// (never loads using `Jit`)
#[macro_export]
macro_rules! load_tailwind {
    (always) => {
        $crate::LoadTailwind::Inline { css: $crate::include_tailwind_raw!(always) }
    };
    (always, $path:literal) => {
        $crate::LoadTailwind::Loaded { path: String::from($path) }
    };
    (jit) => {
        $crate::LoadTailwind::Jit {
            config: include_str!(env!("INCLUDE_TAILWIND_JIT_CONFIG_PATH")),
            jit_url: env!("INCLUDE_TAILWIND_JIT_URL"),
        }
    };
    () => {
        {
            #[cfg(debug_assertions)] { $crate::load_tailwind!(jit) }
            #[cfg(not(debug_assertions))] { $crate::load_tailwind!(always) }
        }
    };
    ($path:expr) => {
        {
            #[cfg(debug_assertions)] { $crate::load_tailwind!(jit) }
            #[cfg(not(debug_assertions))] { $crate::load_tailwind!(always) }
        }
    };
}