1use proc_macro2::TokenStream;
3use quote::{ToTokens, quote};
4use syn::*;
5
6use crate::{
7 common::{TemplWrite, path},
8 config::Config,
9 data::Template,
10 file::File,
11 metadata::{Metadata, TemplKind},
12};
13
14mod body;
15mod sizehint;
16
17pub fn derive(input: &DeriveInput) -> Result<TokenStream> {
19 let conf = Config::default();
20 let meta = Metadata::from_attrs(&input.attrs, &conf)?;
21 let file = File::from_meta(&meta)?;
22 let templ = Template::new(input.ident.clone(), meta, file)?;
23 let mut root = quote! { const _: () = };
24
25 brace(&mut root, |tokens| {
26 generate_templ(&templ, input, tokens);
27 });
28
29 <Token![;]>::default().to_tokens(&mut root);
30
31 Ok(root)
32}
33
34fn generate_templ(templ: &Template, input: &DeriveInput, root: &mut TokenStream) {
35 let ident = &input.ident;
36 let (g1, g2, g3) = input.generics.split_for_impl();
37
38 let cwd = templ.meta().path();
41 if std::path::Path::new(cwd).is_file() {
42 root.extend(quote! {
43 #[doc = concat!(" ",include_str!(#cwd))]
44 });
45 } else if let Some(src) = templ.meta().inline() {
46 root.extend(quote! {
47 #[doc = concat!(" ",#src)]
48 });
49 }
50
51 root.extend(quote! {
52 #[automatically_derived]
53 impl #g1 ::tour::Template for #ident #g2 #g3
54 });
55
56 brace(root, |trait_tokens| {
57
58 trait_tokens.extend(quote! {
61 fn render_into(&self, writer: &mut impl #TemplWrite) -> ::tour::Result<()>
62 });
63
64 brace(trait_tokens, |render_into| {
65 body::Visitor::generate(templ, input, render_into);
66 });
67
68 let blocks = templ.file().blocks();
71 let prefix = quote! {
72 fn render_block_into(&self, block: &str, writer: &mut impl #TemplWrite) -> ::tour::Result<()>
73 };
74
75 brace_if(!blocks.is_empty(), prefix, trait_tokens, |tokens| {
76 tokens.extend(quote! { match block });
77 brace(tokens, |tokens| {
78 for block in blocks {
79 let name = &block.templ.name;
80 let name_str = name.to_string();
81 tokens.extend(quote! { #name_str => });
82 brace(tokens, |tokens| {
83 body::Visitor::generate_block(templ, name, input, tokens);
84 });
85 }
86 tokens.extend(quote! { _ => Err(::tour::Error::NoBlock), });
87 });
88 });
89
90 let is_ok = matches!(templ.meta().kind(), TemplKind::Main) && !blocks.is_empty();
93 let prefix = quote! {
94 fn contains_block(&self, block: &str) -> bool
95 };
96
97 brace_if(is_ok, prefix, trait_tokens, |tokens| {
98 let blocks = blocks
99 .iter()
100 .map(|block|{
101 block.templ.name.to_string()
102 });
103
104 tokens.extend(quote! {
105 matches!(block, #(#blocks)|*)
106 });
107 });
108
109 let is_skip = !matches!(templ.meta().kind(), TemplKind::Main);
112 let size = if is_skip {
113 (0,None)
114 } else {
115 sizehint::Visitor::new(templ).calculate()
116 };
117
118 let is_sized = !sizehint::is_empty(size);
119 let prefix = quote! {
120 fn size_hint(&self) -> (usize,Option<usize>)
121 };
122
123 brace_if(is_sized, prefix, trait_tokens, |tokens| {
124 sizehint::generate(size, tokens);
125 });
126
127 let is_ok = matches!(templ.meta().kind(), TemplKind::Main) && !blocks.is_empty();
130 let blocks = if is_ok {
131 blocks
132 .iter()
133 .map(|block|{
134 let block_name = &block.templ.name;
135 (
136 sizehint::Visitor::new(templ).calculate_block(block_name),
137 block.templ.name.to_string()
138 )
139 })
140 .filter(|e|sizehint::is_empty(e.0))
141 .collect()
142
143 } else {
144 vec![]
145 };
146
147 let is_sized = !blocks.is_empty();
148 let prefix = quote! {
149 fn size_hint_block(&self, block: &str) -> (usize,Option<usize>)
150 };
151
152 brace_if(is_sized, prefix, trait_tokens, |tokens| {
153 tokens.extend(quote! { match block });
154 brace(tokens, |tokens| {
155 for (size,name) in blocks {
156 tokens.extend(quote! { #name => });
157 brace(tokens, |tokens| {
158 sizehint::generate(size, tokens);
159 });
160 }
161 tokens.extend(quote! { _ => (0,None), });
162 });
163 });
164 });
165
166 if matches!(templ.meta().kind(), TemplKind::Main) {
169 root.extend(quote! {
170 #[automatically_derived]
171 impl #g1 ::tour::TemplDisplay for #ident #g2 #g3 {
172 fn display(&self, f: &mut impl ::tour::TemplWrite) -> ::tour::Result<()> {
173 self.render_into(f)
174 }
175 }
176 });
177 }
178
179 for import in templ.file().imports() {
182 let name = import.alias();
183 let path = import
184 .templ()
185 .meta()
186 .path()
187 .trim_start_matches(path::cwd().to_str().unwrap_or(""))
188 .trim_start_matches("/");
189 let doc = if path.is_empty() {
190 quote! { }
191 } else {
192 quote! { #[doc = concat!(" ",#path)] }
193 };
194
195 let mut generics = input.generics.clone();
196 if !generics.lifetimes().any(|e|e.lifetime.ident=="tour_ref") {
197 generics.params.push(syn::parse_quote!('tour_ref));
198 }
199 let (t1,t2,_) = generics.split_for_impl();
200
201 let input: DeriveInput = syn::parse_quote! {
202 #doc
203 struct #name #t1 (&'tour_ref #ident #g2) #g3;
204 };
205 input.to_tokens(root);
206
207 generate_templ(import.templ(), &input, root);
208
209 root.extend(quote! {
210 #[automatically_derived]
211 impl #t1 ::std::ops::Deref for #name #t2 #g3 {
212 type Target = #ident #g2;
213 fn deref(&self) -> &Self::Target {
214 self.0
215 }
216 }
217 });
218 }
219}
220
221fn brace<F>(tokens: &mut TokenStream, call: F)
222where
223 F: FnOnce(&mut TokenStream)
224{
225 token::Brace::default().surround(tokens, call);
226}
227
228fn brace_if<F>(cond: bool, prefix: impl ToTokens, tokens: &mut TokenStream, call: F)
229where
230 F: FnOnce(&mut TokenStream)
231{
232 if cond {
233 prefix.to_tokens(tokens);
234 token::Brace::default().surround(tokens, call);
235 }
236}
237
238fn paren<F>(tokens: &mut TokenStream, call: F)
239where
240 F: FnOnce(&mut TokenStream)
241{
242 token::Paren::default().surround(tokens, call);
243}
244