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
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};

mod fmt;
mod html;
mod parse;

struct WriteInput {
    formatter: syn::Expr,
    template: html::Template,
}

impl Parse for WriteInput {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let formatter = input.parse()?;
        input.parse::<syn::Token![,]>()?;
        let template = input.parse()?;

        Ok(Self {
            formatter,
            template,
        })
    }
}

/// Writes formatted HTML to a buffer.
///
/// `fhtml::write!` works similar to [`std::write!`] with a few key differences:
/// - HTML can be written as-is without having to be inside a string literal.
/// - Expressions are written directly inside braces, compared to
///   [`std::write!`], where they are passed as separate parameters.
///
/// Formatting specifiers are written after expressions, denoted by
/// a colon `:`, similar to how they are written in [`std::write!`].
///
/// Values are not escaped implicitly, but are opt-in with an exclamation mark
/// `!` preceding any formatting specifiers:
/// `{[expr]:![specifiers]}`.
///
/// [`std::write!`]: std::write
///
/// # Examples
///
/// ## Simple usage
///
/// ```ignore
/// let mut buffer = String::new();
/// let _ = fhtml::write!(buffer, <div>"Hello, World!"</div>);
/// assert_eq!(buffer,  "<div>Hello, World!</div>");
/// ```
///
/// ## Escaping values
///
/// ```ignore
/// let mut buffer = String::new();
/// let user_input = "<b>Yay</b>";
/// let _ = fhtml::write!(buffer, <div>{user_input:!}</div>);
/// assert_eq!(buffer, "<div>&lt;b&gt;Yay&lt;/b&gt;</div>");
/// ```
#[proc_macro]
pub fn write(input: TokenStream) -> TokenStream {
    let WriteInput {
        formatter,
        template,
    } = syn::parse_macro_input!(input as WriteInput);

    let values = &template.values;
    let template = template.to_string();

    let output = quote! {
        ::std::write!(#formatter, #template, #(#values),*)
    };

    output.into()
}

/// Creates a compile time `&'static str` with formatted HTML.
///
/// Since there are no suitable `std` macros for compile-time string formatting,
/// `fhtml::formatcp!` is the only `fhtml` macro that uses a dependency:
/// [`const_format`]. `fhtml::formatcp!` has the same syntax as
/// [`fhtml::write!`], with the biggest difference being: values are limited by
/// what can be evaluated at compile-time.
///
/// Read about [limitations and behaviours here](https://docs.rs/const_format/latest/const_format/macro.formatcp.html).
///
/// [`fhtml::write!`]: crate::write!
/// [`const_format`]: https://docs.rs/const_format
/// [`const_format::formatcp!`]: https://docs.rs/const_format/latest/const_format/macro.formatcp.html
///
/// # Examples
///
/// ```ignore
/// const HTML: &str = fhtml::formatcp!(<div>"Hello, World!"</div>);
/// ```
#[proc_macro]
pub fn formatcp(input: TokenStream) -> TokenStream {
    let template = syn::parse_macro_input!(input as html::Template);

    let values = &template.values;
    let template = template.to_string();

    let output = quote! {
        ::fhtml::_internal::formatcp!(#template, #(#values),*)
    };

    output.into()
}