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
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, TokenStreamExt};
use syn::{parse_macro_input, spanned::Spanned};
fn trait_check<'l, L>(lifetimes: L, type_: syn::Type, trait_: TokenStream2) -> TokenStream2
where
L: Iterator<Item = &'l syn::LifetimeDef>,
{
quote!(
const _: fn() = || {
fn declare_lifetime<#(#lifetimes),*>() {
fn assert_impl_all<T: ?Sized + #trait_>() {}
assert_impl_all::<#type_>();
}
};
)
}
#[proc_macro_derive(Template, attributes(raw))]
pub fn derive_template(item: TokenStream) -> TokenStream {
let item = parse_macro_input!(item as syn::DeriveInput);
let item_span = item.span();
let name = item.ident;
match item.data {
syn::Data::Struct(data) => {
let (impl_generics, ty_generics, _) = item.generics.split_for_impl();
let mut replacements = TokenStream2::new();
for field in data.fields {
match field.ident {
Some(ident) => {
let templated_field_name;
let lifetimes = item.generics.lifetimes();
let data = if field.attrs.iter().any(|attr| attr.path.is_ident("raw")) {
templated_field_name = format!("__RAW_{}__", ident);
let trait_check = trait_check(lifetimes,field.ty, quote!(::std::fmt::Display));
quote!(
#trait_check
let data: String = self.#ident.to_string();
)
} else {
templated_field_name = format!("__TEMPLATE_{}__", ident);
let trait_check = trait_check(lifetimes, field.ty, quote!(::serialize_to_javascript::private::Serialize));
quote!(
#trait_check
use ::std::convert::TryInto;
use ::serialize_to_javascript::{
private::{NotYetSerialized, SerializedOnce},
Serialized
};
let data: SerializedOnce = NotYetSerialized(&self.#ident).try_into()?;
let data: Serialized = data.into_javascript_string_literal(options);
let data: String = data.into_string();
)
};
replacements.append_all(quote!(
let template = {
#data
template.replace(
#templated_field_name,
&data
)
};
));
}
None => {
return syn::Error::new(
field.span(),
"Template fields must all have names",
)
.to_compile_error()
.into();
}
}
}
quote!(
impl #impl_generics ::serialize_to_javascript::private::Sealed for #name #ty_generics {}
impl #impl_generics ::serialize_to_javascript::Template for #name #ty_generics {
fn render(&self, template: &str, options: &::serialize_to_javascript::Options) -> ::serialize_to_javascript::Result<::serialize_to_javascript::Serialized> {
#replacements
Ok(unsafe {
::serialize_to_javascript::Serialized::from_string_unchecked(template.into())
})
}
}
)
}
_ => {
return syn::Error::new(
item_span,
"`Template` currently only supports data structs",
)
.to_compile_error()
.into();
}
}
.into()
}
#[proc_macro_attribute]
pub fn default_template(attr: TokenStream, item: TokenStream) -> TokenStream {
let path = parse_macro_input!(attr as syn::LitStr);
let item = parse_macro_input!(item as syn::DeriveInput);
let name = item.ident.clone();
let (impl_generics, ty_generics, _) = item.generics.split_for_impl();
quote!(
#item
impl #impl_generics ::serialize_to_javascript::DefaultTemplate for #name #ty_generics {
const RAW_TEMPLATE: &'static str = include_str!(#path);
}
)
.into()
}