1extern crate proc_macro;
4#[macro_use]
6extern crate quote;
7extern crate glob;
8extern crate proc_macro2;
9
10use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
11use std::fmt::Write;
12use std::fs::File;
13use std::io::Read;
14use std::path::{Path, PathBuf};
15
16fn find_template_file(path: &str) -> PathBuf {
17 let sourcedirs: std::collections::HashSet<_> = glob::glob("**/*.rs").unwrap()
18 .flat_map(|x| x.ok())
19 .filter(|x| !x.starts_with("target/"))
20 .map(|x| PathBuf::from(x.clone().parent().unwrap()))
21 .collect();
22 let paths: Vec<_> = sourcedirs.into_iter()
23 .filter(|d| d.join(path).exists())
24 .collect();
25 if paths.len() == 0 {
26 panic!("No template file named {:?} exists.", path);
27 } else if paths.len() > 1 {
28 panic!(r"Multiple files named {:?} exist. Eventually display-as will
29 support this, but for now each template file must have a unique name.", path);
30 }
31 paths.into_iter().next().unwrap()
32}
33
34fn proc_to_two(i: TokenStream) -> proc_macro2::TokenStream {
35 i.into()
36}
37fn two_to_proc(i: proc_macro2::TokenStream) -> TokenStream {
38 i.into()
39}
40
41fn is_str(x: &TokenTree) -> bool {
42 match x {
43 TokenTree::Literal(_) => {
44 let s = x.to_string();
45 s.len() > 0 && s.contains("\"") && s.chars().next() != Some('b')
46 }
47 _ => false,
48 }
49}
50
51fn to_tokens(s: &str) -> impl Iterator<Item = TokenTree> {
52 let ts: TokenStream = s.parse().unwrap();
53 ts.into_iter()
54}
55
56fn count_pounds(x: &str) -> &'static str {
57 for pounds in &["#######", "######", "#####", "####", "###", "##", "#", ""] {
58 if x.contains(pounds) {
59 return pounds;
60 }
61 }
62 ""
63}
64
65#[proc_macro]
69pub fn format_as(input: TokenStream) -> TokenStream {
70 let mut tokens = input.into_iter();
71 let format = if let Some(format) = tokens.next() {
72 proc_to_two(format.into())
73 } else {
74 panic!("format_as! needs a Format as its first argument")
75 };
76 if let Some(comma) = tokens.next() {
77 if &comma.to_string() != "," {
78 panic!(
79 "format_as! needs a Format followed by a comma, not {}",
80 comma.to_string()
81 );
82 }
83 } else {
84 panic!("format_as! needs a Format followed by a comma");
85 }
86
87 let statements = proc_to_two(template_to_statements(
88 "templates".as_ref(),
89 &format,
90 tokens.collect(), "", ""
91 ));
92
93 quote!(
94 {
95 use std::fmt::Write;
96 use display_as::DisplayAs;
97 let doit = || -> Result<String, std::fmt::Error> {
98 let mut __f = String::with_capacity(32);
99 #statements
100 Ok(__f)
101 };
102 display_as::FormattedString::<#format>::from_formatted(doit().expect("trouble writing to String??!"))
103 }
104 )
105 .into()
106}
107
108#[proc_macro]
112pub fn write_as(input: TokenStream) -> TokenStream {
113 let mut tokens = input.into_iter();
114 let format = if let Some(format) = tokens.next() {
115 proc_to_two(format.into())
116 } else {
117 panic!("write_as! needs a Format as its first argument")
118 };
119 if let Some(comma) = tokens.next() {
120 if &comma.to_string() != "," {
121 panic!(
122 "write_as! needs a Format followed by a comma, not {}",
123 comma.to_string()
124 );
125 }
126 } else {
127 panic!("write_as! needs a Format followed by a comma");
128 }
129
130 let mut writer: Vec<TokenTree> = Vec::new();
131 while let Some(tok) = tokens.next() {
132 if &tok.to_string() == "," {
133 break;
134 } else {
135 writer.push(tok);
136 }
137 }
138 if writer.len() == 0 {
139 panic!("write_as! needs a Writer as its second argument followed by comma.")
140 }
141 let writer = proc_to_two(writer.into_iter().collect());
142
143 let statements = proc_to_two(template_to_statements(
144 "templates".as_ref(),
145 &format,
146 tokens.collect(), "", ""
147 ));
148
149 quote!(
150 {
151 use std::fmt::Write;
152 use display_as::DisplayAs;
153 let __f = &mut #writer;
154 let mut doit = || -> Result<(), std::fmt::Error> {
155 #statements
156 Ok(())
157 };
158 doit()
159 }
160 )
161 .into()
162}
163
164fn expr_toks_to_stmt(
165 format: &proc_macro2::TokenStream,
166 expr: &mut Vec<TokenTree>,
167) -> impl Iterator<Item = TokenTree> {
168 let len = expr.len();
169
170 let to_display_as = {
171 use rand::distributions::Alphanumeric;
177 use rand::{thread_rng, Rng};
178 use std::iter;
179
180 let mut rng = thread_rng();
181 let rand_chars: String = iter::repeat(())
182 .map(|()| rng.sample(Alphanumeric))
183 .map(char::from)
184 .take(13)
185 .collect();
186 proc_macro2::Ident::new(
187 &format!("ToDisplayAs{}xxx{}", format, rand_chars),
188 proc_macro2::Span::call_site(),
189 )
190 };
191 if len > 2 && expr[len - 2].to_string() == "as" {
192 let format = proc_to_two(expr.pop().unwrap().into());
193 expr.pop();
194 let expr = proc_to_two(expr.drain(..).collect());
195 two_to_proc(quote! {
196 {
197 trait ToDisplayAs {
198 fn #to_display_as(&self) -> &Self;
199 }
200 impl<T: DisplayAs<#format>> ToDisplayAs for T {
201 fn #to_display_as(&self) -> &Self { self }
202 }
203 __f.write_fmt(format_args!("{}", <_ as DisplayAs<#format>>::display((#expr).#to_display_as())))?;
204 }
205 })
206 .into_iter()
207 } else if expr.len() > 0 {
208 let expr = proc_to_two(expr.drain(..).collect());
209 let format = format.clone();
210 two_to_proc(quote! {
211 {
212 trait ToDisplayAs {
213 fn #to_display_as(&self) -> &Self;
214 }
215 impl<T: DisplayAs<#format>> ToDisplayAs for T {
216 fn #to_display_as(&self) -> &Self { self }
217 }
218 __f.write_fmt(format_args!("{}", <_ as DisplayAs<#format>>::display((#expr).#to_display_as())))?;
219 }
220 })
221 .into_iter()
222 } else {
223 two_to_proc(quote! {}).into_iter()
224 }
225}
226fn expr_toks_to_conditional(expr: &mut Vec<TokenTree>) -> TokenStream {
227 expr.drain(..).collect()
228}
229
230fn read_template_file(dirname: &Path, pathname: &str,
231 left_delim: &str, right_delim: &str) -> TokenStream {
232 let path = dirname.join(&pathname);
233 if let Ok(mut f) = File::open(&path) {
234 let mut contents = String::new();
235 f.read_to_string(&mut contents)
236 .expect("something went wrong reading the file");
237 let raw_template_len = contents.len();
238 let pounds: String = if left_delim == "" {
239 count_pounds(&contents).to_string()
240 } else {
241 let mut pounds = count_pounds(&contents).to_string();
242 pounds.write_str("#").unwrap();
243 contents = contents.replace(left_delim, &format!(r#""{}"#, pounds));
244 contents = contents.replace(right_delim, &format!(r#"r{}""#, pounds));
245 pounds
246 };
247 contents.write_str("\"").unwrap();
248 contents.write_str(£s).unwrap();
249 let mut template = "r".to_string();
250 template.write_str(£s).unwrap();
251 template.write_str("\"").unwrap();
252 template.write_str(&contents).unwrap();
253 template
254 .write_str(" ({ assert_eq!(include_str!(\"")
255 .unwrap();
256 template.write_str(&pathname).unwrap();
257 write!(template, "\").len(), {}); \"\"}}); ", raw_template_len).unwrap();
258 template.parse().expect("trouble parsing file")
259 } else {
260 panic!("No such file: {}", path.display())
261 }
262}
263
264fn template_to_statements(
265 dir: &Path,
266 format: &proc_macro2::TokenStream,
267 template: TokenStream,
268 left_delim: &str,
269 right_delim: &str) -> TokenStream
270{
271 let mut toks: Vec<TokenTree> = Vec::new();
272 let mut next_expr: Vec<TokenTree> = Vec::new();
273 for t in template.into_iter() {
274 if let TokenTree::Group(g) = t.clone() {
275 let next_expr_len = next_expr.len();
276 if g.delimiter() == Delimiter::Brace {
277 if next_expr_len > 2
278 && !next_expr.iter().any(|x| x.to_string() == "=")
279 && &next_expr[0].to_string() == "if"
280 && &next_expr[1].to_string() == "let"
281 {
282 next_expr.push(t);
285 } else if next_expr_len > 1
286 && &next_expr[next_expr_len - 1].to_string() != "="
287 && &next_expr[0].to_string() == "let"
288 {
289 next_expr.push(t);
292 } else if next_expr_len > 2
293 && &next_expr[next_expr_len - 1].to_string() == "="
294 && &next_expr[0].to_string() == "let"
295 {
296 toks.extend(expr_toks_to_conditional(&mut next_expr).into_iter());
300 let actions = proc_to_two(template_to_statements(dir, format, g.stream(),
301 left_delim, right_delim));
302 toks.extend(
303 two_to_proc(quote! {
304 display_as::display_closure_as(#format, |__f: &mut ::std::fmt::Formatter|
305 -> Result<(), ::std::fmt::Error> {
306 { #actions };
307 Ok(())
308 })
309 })
315 .into_iter(),
316 );
317 } else if next_expr_len > 0 && &next_expr[0].to_string() == "match" {
318 toks.extend(expr_toks_to_conditional(&mut next_expr).into_iter());
319 let mut interior_toks: Vec<TokenTree> = Vec::new();
320 for x in g.stream() {
321 if let TokenTree::Group(g) = x.clone() {
322 if g.delimiter() == Delimiter::Brace {
323 interior_toks.push(TokenTree::Group(Group::new(
324 Delimiter::Brace,
325 template_to_statements(dir, format, g.stream(),
326 left_delim, right_delim),
327 )));
328 } else {
329 interior_toks.push(x);
330 }
331 } else {
332 interior_toks.push(x);
333 }
334 }
335 toks.push(TokenTree::Group(Group::new(Delimiter::Brace,
336 interior_toks.into_iter().collect())));
337 } else {
338 toks.extend(expr_toks_to_conditional(&mut next_expr).into_iter());
339 toks.push(TokenTree::Group(Group::new(
340 Delimiter::Brace,
341 template_to_statements(dir, format, g.stream(),
342 left_delim, right_delim),
343 )));
344 }
345 } else if g.delimiter() == Delimiter::Parenthesis
346 && next_expr.len() >= 2
347 && &next_expr[next_expr_len - 1].to_string() == "!"
348 && &next_expr[next_expr_len - 2].to_string() == "include"
349 {
350 next_expr.pop();
351 next_expr.pop(); let filenames: Vec<_> = g.stream().into_iter().collect();
353 if filenames.len() != 1 {
354 panic!(
355 "include! macro within a template must have one argument, a string literal"
356 );
357 }
358 let filename = filenames[0].to_string().replace("\"", "");
359 let templ = read_template_file(dir, &filename, left_delim, right_delim);
360 let statements = template_to_statements(dir, format, templ,
361 left_delim, right_delim);
362 next_expr.extend(statements.into_iter());
363 next_expr.extend(to_tokens(";").into_iter());
364 toks.extend(expr_toks_to_conditional(&mut next_expr).into_iter());
365 toks.push(t);
366 } else {
367 next_expr.push(t);
368 }
369 } else if t.to_string() == ";" {
370 toks.extend(expr_toks_to_conditional(&mut next_expr).into_iter());
371 toks.push(t);
372 } else if is_str(&t) {
373 toks.extend(expr_toks_to_stmt(&format, &mut next_expr));
375 toks.extend(to_tokens("__f.write_str"));
377 toks.push(TokenTree::Group(Group::new(
378 Delimiter::Parenthesis,
379 TokenStream::from(t),
380 )));
381 toks.extend(to_tokens("?;"));
382 } else {
383 next_expr.push(t);
384 }
385 }
386 toks.extend(expr_toks_to_stmt(&format, &mut next_expr));
388 TokenTree::Group(Group::new(Delimiter::Brace, toks.into_iter().collect())).into()
389}
390
391#[proc_macro_attribute]
428pub fn with_template(input: TokenStream, my_impl: TokenStream) -> TokenStream {
429 let mut sourcedir = PathBuf::from(".");
430
431 let mut impl_toks: Vec<_> = my_impl.into_iter().collect();
432 if &impl_toks[0].to_string() != "impl" || impl_toks.len() < 3 {
433 panic!("with_template can only be applied to an impl of DisplayAs");
434 }
435 let mut my_format: proc_macro2::TokenStream = quote!();
436 for i in 0..impl_toks.len() - 2 {
437 if impl_toks[i].to_string() == "DisplayAs" && impl_toks[i + 1].to_string() == "<" {
438 my_format = proc_to_two(impl_toks[i + 2].clone().into());
439 break;
440 }
441 }
442 let last = impl_toks.pop().unwrap();
443 match last.to_string().as_ref() {
444 "{ }" | "{ }" | "{}" => (), s => panic!(
446 "with_template must be applied to an impl that ends in '{{}}', not {}",
447 s
448 ),
449 };
450 let my_format = my_format; let input_vec: Vec<_> = input.clone().into_iter().collect();
453 let mut left_delim = "".to_string();
454 let mut right_delim = "".to_string();
455 let input = if input_vec.len() == 1 {
456 let pathname = input_vec[0].to_string().replace("\"", "");
457 sourcedir = find_template_file(&pathname);
458 read_template_file(&sourcedir, &pathname, "", "")
459 } else if input_vec.len() == 3
460 && input_vec[0].to_string().contains("\"")
461 && input_vec[1].to_string().contains("\"")
462 && input_vec[2].to_string().contains("\"")
463 {
464 let pathname = input_vec[2].to_string().replace("\"", "");
467 sourcedir = find_template_file(&pathname);
468 left_delim = input_vec[0].to_string().replace("\"", "");
469 right_delim = input_vec[1].to_string().replace("\"", "");
470 read_template_file(&sourcedir, &pathname, &left_delim, &right_delim)
471 } else {
472 input
473 };
474 let statements = proc_to_two(template_to_statements(&sourcedir, &my_format, input,
475 &left_delim, &right_delim));
476
477 let out = quote! {
478 {
479 #statements
480 Ok(())
481 }
482 };
483 let mut new_impl: Vec<TokenTree> = Vec::new();
484 new_impl.extend(impl_toks.into_iter());
485 new_impl.extend(
486 two_to_proc(quote! {
487 {
488 fn fmt(&self, __f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
489 #out
490 }
491 }
492 })
493 .into_iter(),
494 );
495 let new_impl = new_impl.into_iter().collect();
496
497 new_impl
499}
500
501#[proc_macro_attribute]
504pub fn with_response_template(input: TokenStream, my_impl: TokenStream) -> TokenStream {
505 let displayas_impl = with_template(input, my_impl.clone());
506 displayas_impl
507}