1extern crate proc_macro;
93
94use std::collections::hash_map::DefaultHasher;
95use std::fs::File;
96use std::hash::Hasher;
97use std::io::prelude::*;
98use std::option::Option;
99use std::path::Path;
100use std::path::PathBuf;
101use std::result::Result;
102use std::vec::Vec;
103
104use nom::{
105 branch::alt,
106 bytes::complete::{
107 escaped_transform, is_not, tag, take, take_until, take_while,
108 },
109 character::complete::{alphanumeric1, line_ending, space0},
110 combinator::{map, not, opt, peek},
111 multi::many0,
112 sequence::tuple,
113 IResult,
114};
115use quote::quote;
116use syn::Meta::*;
117use syn::*;
118
119use crate::TemplatePart::*;
120
121macro_rules! dbg_println {
122 ($inf:ident) => { if $inf.debug_print { println!(); } };
123 ($inf:ident, $fmt:expr) => { if $inf.debug_print { println!($fmt); } };
124 ($inf:ident, $fmt:expr, $($arg:tt)*) => { if $inf.debug_print { println!($fmt, $($arg)*); } };
125}
126
127macro_rules! dbg_print {
128 ($inf:ident) => { if $inf.debug_print { print!(); } };
129 ($inf:ident, $fmt:expr) => { if $inf.debug_print { print!($fmt); } };
130 ($inf:ident, $fmt:expr, $($arg:tt)*) => { if $inf.debug_print { print!($fmt, $($arg)*); } };
131}
132
133const TEMPLATE_PATH_MACRO: &str = "TemplatePath";
134const TEMPLATE_DEBUG_MACRO: &str = "TemplateDebug";
135
136#[proc_macro_derive(Template, attributes(TemplatePath, TemplateDebug))]
137pub fn transform_template(
138 input: proc_macro::TokenStream,
139) -> proc_macro::TokenStream {
140 let macro_input = parse_macro_input!(input as DeriveInput);
141
142 let mut path: Option<String> = None;
143 let mut info = TemplateInfo::default();
144
145 for attr in ¯o_input.attrs {
146 match &attr.meta {
147 NameValue(MetaNameValue {
148 path: p,
149 value: syn::Expr::Lit(ExprLit {attrs: _, lit: Lit::Str(lit_str)}),
150 ..
151 }) => {
152 if p.get_ident().expect("Attribute with no name")
153 == TEMPLATE_PATH_MACRO
154 {
155 path = Some(lit_str.value());
156 }
157 }
158 Path(name) => {
159 if name.get_ident().expect("Attribute with no name")
160 == TEMPLATE_DEBUG_MACRO
161 {
162 info.debug_print = true;
163 }
164 }
165 _ => {}
166 }
167 }
168
169 let mut path_absolute =
171 PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
172 path_absolute.push(&path.unwrap_or_else(|| {
173 panic!(
174 "Please specify a #[{}=\"<path>\"] atribute with the template \
175 file path.",
176 TEMPLATE_PATH_MACRO
177 )
178 }));
179 let path =
180 &path_absolute.canonicalize().expect("Could not canonicalize path");
181 dbg_println!(
182 info,
183 "Looking for template in \"{}\"",
184 path.to_str().unwrap()
185 );
186
187 let read = read_from_file(path).expect("Could not read file");
189
190 let mut data = match parse_all(&mut info, &read) {
192 Ok(data) => data,
193 Err(e) => {
194 return syn::Error::new_spanned(macro_input, format!("Parse error: {}, reason: {}", e.index, e.reason))
195 .into_compile_error()
196 .into()
197 }
198 };
199
200 if info.debug_print {
201 debug_to_file(path, &data);
202 }
203
204 parse_postprocess(&mut data);
205
206 let data = parse_optimize(data);
207
208 info = TemplateInfo::default();
210 let mut builder = String::new();
211 for part in data {
212 match part {
213 Text(x) => {
214 builder.push_str(generate_save_str_print(&x).as_ref());
215 }
216 Code(x) => {
217 builder.push_str(x.as_ref());
218 }
219 Expr(x) => {
220 builder.push_str(generate_expression_print(&x, &info).as_ref());
221 }
222 Directive(dir) => {
223 apply_directive(&mut info, &dir);
224 }
225 }
226 }
227
228 dbg_println!(info, "Generated Code:\n{}", builder);
229
230 let tokens: proc_macro2::TokenStream =
231 builder.parse().expect("Parsing template code failed!");
232
233 let (impl_generics, ty_generics, where_clause) =
235 macro_input.generics.split_for_impl();
236 let name = ¯o_input.ident;
237 let path_str = path.to_str().expect("Invalid path");
238
239 let frame = quote! {
240 impl #impl_generics ::std::fmt::Display for #name #ty_generics #where_clause {
241 fn fmt(&self, _fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
242 let _ = include_bytes!(#path_str);
243 #tokens
244 Ok(())
245 }
246 }
247 };
248
249 if !info.debug_print {
254 proc_macro::TokenStream::from(frame)
255 } else {
256 let mut hasher = DefaultHasher::new();
261 hasher.write(path_str.as_bytes());
262
263 let out_dir = if let Ok(target_dir) = std::env::var("CARGO_TARGET_DIR")
264 {
265 PathBuf::from(target_dir)
266 } else {
267 let dir = std::env::var("CARGO_MANIFEST_DIR")
268 .expect("CARGO_MANIFEST_DIR not set");
269 PathBuf::from(dir).join("target")
270 };
271
272 let code_path = out_dir
273 .join("t4rust")
274 .join(&hasher.finish().to_string())
275 .with_extension("rs");
276
277 std::fs::create_dir_all(code_path.parent().unwrap())
278 .expect("Failed to create output path");
279
280 std::fs::write(&code_path, frame.to_string().as_bytes())
282 .expect("Failed to write compiled template");
283
284 let code_path_str = code_path.to_str();
285 proc_macro::TokenStream::from(quote! { include!(#code_path_str); })
286 }
287}
288
289fn generate_expression_print(print_expr: &str, info: &TemplateInfo) -> String {
290 if info.print_postprocessor.is_empty() {
291 format!("write!(_fmt, \"{{}}\", {})?;\n", print_expr)
292 } else {
293 format!(
294 "{{
295 let _s = format!(\"{{}}\", {});
296 let _s_transfomed = {}(&_s);
297 _fmt.write_str(&_s_transfomed)?;
298 }}\n",
299 print_expr, info.print_postprocessor
300 )
301 }
302}
303
304fn generate_save_str_print(print_str: &str) -> String {
305 let mut max_sharp_count = 0;
306 let mut cur_sharp_count = 0;
307
308 for c in print_str.chars() {
309 if c == '#' {
310 cur_sharp_count += 1;
311 max_sharp_count = std::cmp::max(max_sharp_count, cur_sharp_count);
312 } else {
313 cur_sharp_count = 0;
314 }
315 }
316
317 let sharps = "#".repeat(max_sharp_count + 1);
318 format!("_fmt.write_str(r{1}\"{0}\"{1})?;\n", print_str, sharps)
319}
320
321fn read_from_file(path: &Path) -> Result<String, std::io::Error> {
322 let mut file = File::open(path)?;
323 let mut contents = String::new();
324 file.read_to_string(&mut contents)?;
325 Ok(contents)
326}
327
328fn debug_to_file(path: &Path, data: &[TemplatePart]) {
329 let mut pathbuf = PathBuf::new();
330 pathbuf.push(path);
331 pathbuf.set_extension("tt.out");
332 let writepath = pathbuf.as_path();
333 if let Ok(mut file) = File::create(writepath) {
334 for var in data {
335 match *var {
336 Code(ref x) => {
337 write!(file, "Code:").unwrap();
338 file.write_all(x.as_bytes()).unwrap();
339 }
340 Text(ref x) => {
341 write!(file, "Text:").unwrap();
342 file.write_all(x.as_bytes()).unwrap();
343 }
344 Expr(ref x) => {
345 write!(file, "Expr:").unwrap();
346 file.write_all(x.as_bytes()).unwrap();
347 }
348 Directive(ref dir) => {
349 write!(file, "Dir:{:?}", dir).unwrap();
350 }
351 }
352 writeln!(file).unwrap();
353 }
354 }
355}
356
357fn parse_all(
359 info: &mut TemplateInfo,
360 input: &str,
361) -> Result<Vec<TemplatePart>, TemplateError>
362{
363 let mut builder: Vec<TemplatePart> = Vec::new();
364 let mut cur = input;
365
366 dbg_println!(info, "Reading template");
367
368 while !cur.is_empty() {
369 let (crest, content) = parse_text(info, cur)?;
370 builder.push(Text(content));
371 cur = crest;
372 dbg_println!(info, "");
373
374 if let Ok((rest, _)) = expression_start(cur) {
376 dbg_print!(info, " expression start");
377 let (crest, content) = parse_code(info, rest)?;
378 builder.push(Expr(content));
379 cur = crest;
380 } else if let Ok((rest, _)) = template_directive_start(cur) {
381 dbg_print!(info, " directive start");
382 let (crest, content) = parse_code(info, rest)?;
383 let dir = parse_directive(&content);
384 dbg_println!(info, " Directive: {:?}", dir);
385 match dir {
386 Ok((_, dir)) => {
387 apply_directive(info, &dir);
388 builder.push(Directive(dir));
389 }
390 Err(_) => {
391 println!("Malformed directive: {}", &content);
392 return Err(TemplateError {
393 index: 0,
394 reason: format!(
395 "Could not understand the directive: {}",
396 &content
397 ),
398 });
399 }
400 }
401 cur = crest;
402 } else if let Ok((rest, _)) = code_start(cur) {
403 dbg_print!(info, " code start");
404 let (crest, content) = parse_code(info, rest)?;
405 builder.push(Code(content));
406 cur = crest;
407 }
408
409 dbg_println!(info, " Rest: {:?}", &cur);
410 }
411
412 dbg_println!(info, "\nTemplate ok!");
413
414 Result::Ok(builder)
415}
416
417fn parse_text<'a>(
418 info: &TemplateInfo,
419 input: &'a str,
420) -> Result<(&'a str, String), TemplateError>
421{
422 let mut content = String::new();
423 let mut cur = input;
424
425 loop {
426 let read = read_text(cur);
427 match read {
428 Ok((rest, done)) => {
429 content.push_str(&done);
430 if rest.is_empty() {
431 return Ok((rest, content));
432 }
433 cur = rest;
434 dbg_print!(info, " take text: {:?}", &done);
435
436 if let Ok((rest, _)) = double_code_start(cur) {
437 dbg_print!(info, " double-escape");
438 content.push_str("<#");
439
440 if rest.is_empty() {
441 return Ok((rest, content));
442 }
443 cur = rest;
444 } else if done.is_empty() {
445 return Ok((rest, content));
446 }
447 }
448 Err(_) => {
449 if let Ok((rest, done)) = till_end(cur) {
450 if rest.is_empty() {
451 content.push_str(&done);
452 return Ok((rest, content));
453 }
454 }
455 panic!(
456 "Reached unknown parsing state (!read_text > !till_end)"
457 );
458 }
459 }
460
461 dbg_println!(info, " Rest: {:?}", &cur);
462 }
463}
464
465fn parse_code<'a>(
466 info: &TemplateInfo,
467 input: &'a str,
468) -> Result<(&'a str, String), TemplateError>
469{
470 let mut content = String::new();
471 let mut cur = input;
472
473 loop {
474 match read_code(cur) {
475 Ok((rest, done)) => {
476 dbg_print!(info, " take code: {:?}", &done);
477 content.push_str(&done);
478 cur = rest;
479
480 if let Ok((rest, _)) = code_end(cur) {
481 dbg_print!(info, " code end");
482 return Ok((rest, content));
483 } else if let Ok((rest, _)) = double_code_end(cur) {
484 dbg_print!(info, " double-escape");
485 content.push_str("#>");
486 cur = rest;
487 } else {
488 panic!("Nothing, i guess?");
489 }
490 }
491 Err(err) => {
492 dbg_println!(info, "Error at code {:?}", err);
493 return Err(TemplateError {
494 index: 0,
495 reason: "Unclosed code or expression block".into(),
496 });
497 }
498 }
499 }
500}
501
502fn parse_optimize(data: Vec<TemplatePart>) -> Vec<TemplatePart> {
504 let mut last_type = TemplatePartType::None;
505 let mut combined = Vec::<TemplatePart>::new();
506 let mut tmp_build = String::new();
507 for item in data {
508 match item {
509 Code(u) => {
510 if u.is_empty() {
511 continue;
512 }
513 if last_type != TemplatePartType::Code {
514 if !tmp_build.is_empty() {
515 match last_type {
516 TemplatePartType::None | TemplatePartType::Code => {
517 panic!()
518 }
519 TemplatePartType::Text => {
520 combined.push(Text(tmp_build))
521 }
522 TemplatePartType::Expr => {
523 combined.push(Expr(tmp_build))
524 }
525 }
526 }
527 tmp_build = String::new();
528 last_type = TemplatePartType::Code;
529 }
530 tmp_build.push_str(&u);
531 }
532 Text(u) => {
533 if u.is_empty() {
534 continue;
535 }
536 if last_type != TemplatePartType::Text {
537 if !tmp_build.is_empty() {
538 match last_type {
539 TemplatePartType::None | TemplatePartType::Text => {
540 panic!()
541 }
542 TemplatePartType::Code => {
543 combined.push(Code(tmp_build))
544 }
545 TemplatePartType::Expr => {
546 combined.push(Expr(tmp_build))
547 }
548 }
549 }
550 tmp_build = String::new();
551 last_type = TemplatePartType::Text;
552 }
553 tmp_build.push_str(&u);
554 }
555 Expr(u) => {
556 if !tmp_build.is_empty() {
557 match last_type {
558 TemplatePartType::None => panic!(),
559 TemplatePartType::Code => {
560 combined.push(Code(tmp_build))
561 }
562 TemplatePartType::Text => {
563 combined.push(Text(tmp_build))
564 }
565 TemplatePartType::Expr => {
566 combined.push(Expr(tmp_build))
567 }
568 }
569 }
570 tmp_build = String::new();
571 last_type = TemplatePartType::Expr;
572 tmp_build.push_str(&u);
573 }
574 Directive(d) => {
575 combined.push(Directive(d));
576 }
577 }
578 }
579 if !tmp_build.is_empty() {
580 match last_type {
581 TemplatePartType::None => {}
582 TemplatePartType::Code => combined.push(Code(tmp_build)),
583 TemplatePartType::Text => combined.push(Text(tmp_build)),
584 TemplatePartType::Expr => combined.push(Expr(tmp_build)),
585 }
586 }
587 combined
588}
589
590fn parse_postprocess(data: &mut Vec<TemplatePart>) {
593 let mut info = TemplateInfo::default();
594 let mut was_b_clean = None;
595 let mut clean_index = 0;
596
597 if data.len() < 3 {
599 return;
600 }
601
602 for i in 0..(data.len() - 2) {
603 let tri = data[i..(i + 3)].as_mut();
604 if let Directive(ref dir) = tri[1] {
605 apply_directive(&mut info, dir);
606 }
607
608 if !info.clean_whitespace
609 || !tri[0].is_text()
610 || !tri[1].should_trim_whitespace()
611 || !tri[2].is_text()
612 {
613 continue;
614 }
615
616 let mut res_a = None;
617 if clean_index == i && was_b_clean.is_some() {
618 res_a = was_b_clean;
619 } else if let Text(ref text_a) = tri[0] {
620 let rev_txt: String = text_a.chars().rev().collect();
621 if let Ok((_, a_len)) = is_ws_till_newline(&rev_txt) {
622 res_a = Some(a_len);
623 } else if i == 0 && text_a.is_empty() {
624 res_a = Some((0, 0));
626 } else {
627 continue;
628 }
629 }
630
631 let mut res_b = None;
632 if let Text(ref text_b) = tri[2] {
633 if let Ok((_, b_len)) = is_ws_till_newline(&text_b) {
634 res_b = Some(b_len);
635 } else {
636 continue;
637 }
638 }
639
640 if let Text(ref mut text_a) = tri[0] {
643 let res_a = res_a.unwrap();
644 let len = text_a.len();
645 text_a.drain((len - (res_a.0))..len);
646 }
647
648 if let Text(ref mut text_b) = tri[2] {
649 let rev_txt: String = text_b.chars().rev().collect();
650 if let Ok((_, b_len)) = is_ws_till_newline(&rev_txt) {
651 was_b_clean = Some(b_len);
652 clean_index = i + 2;
653 }
654
655 let res_b = res_b.unwrap();
656 text_b.drain(0..(res_b.0 + res_b.1));
657 }
658 }
659}
660
661fn apply_directive(info: &mut TemplateInfo, directive: &TemplateDirective) {
662 for (key, value) in directive
663 .params
664 .iter()
665 .map(|p| ((directive.name.as_str(), p.0.as_str()), p.1.as_str()))
666 {
667 match key {
668 ("template", "debug") => {
669 info.debug_print = value.parse::<bool>().unwrap()
670 }
671 ("template", "cleanws") | ("template", "clean_whitespace") => {
672 info.clean_whitespace = value.parse::<bool>().unwrap()
673 }
674 ("escape", "function") => {
675 info.print_postprocessor = value.to_string()
676 }
677 _ => println!(
678 "Unrecognized template parameter \"{}\" in \"{}\"",
679 key.0, key.1
680 ),
681 }
682 }
683}
684
685fn expression_start(s: &str) -> IResult<&str, &str> { tag("<#=")(s) }
688fn template_directive_start(s: &str) -> IResult<&str, &str> { tag("<#@")(s) }
689fn read_text(s: &str) -> IResult<&str, &str> { take_until("<#")(s) }
690
691fn code_start(s: &str) -> IResult<&str, &str> {
692 let (s, r) = tag("<#")(s)?;
693 not(tag("<#"))(s)?;
694 Ok((s, r))
695}
696fn double_code_start(s: &str) -> IResult<&str, &str> { tag("<#<#")(s) }
697
698fn code_end(s: &str) -> IResult<&str, &str> {
699 let (s, r) = tag("#>")(s)?;
700 not(tag("#>"))(s)?;
701 Ok((s, r))
702}
703fn double_code_end(s: &str) -> IResult<&str, &str> { tag("#>#>")(s) }
704
705fn read_code(s: &str) -> IResult<&str, &str> { take_until("#>")(s) }
706
707fn till_end(s: &str) -> IResult<&str, &str> { take_while(|_| true)(s) }
708
709fn parse_directive(s: &str) -> IResult<&str, TemplateDirective> {
710 map(
711 tuple((space0, alphanumeric1, many0(parse_directive_param), at_end)),
712 |t| TemplateDirective { name: t.1.to_string(), params: t.2 },
713 )(s)
714}
715
716fn at_end(s: &str) -> IResult<&str, ()> { not(peek(take(1usize)))(s) }
717
718fn parse_directive_param(s: &str) -> IResult<&str, (String, String)> {
719 map(
720 tuple((
721 space0,
722 alphanumeric1,
723 space0,
724 tag("="),
725 space0,
726 tag("\""),
727 opt(escaped_transform(
728 is_not("\\\""),
729 '\\',
730 alt((tag_transform("\\", "\\"), tag_transform("\"", "\""))),
731 )),
732 tag("\""),
733 space0,
734 )),
735 |t| (t.1.to_string(), t.6.unwrap_or_else(|| "".to_string())),
736 )(s)
737}
738
739fn is_ws_till_newline(s: &str) -> IResult<&str, (usize, usize)> {
740 map(
741 tuple((space0, line_ending)),
742 |t: (&str, &str)| (t.0.len(), t.1.len()),
743 )(s)
744}
745
746fn tag_transform<'a>(
747 s: &'a str,
748 t: &'a str,
749) -> impl Fn(&'a str) -> IResult<&str, &str>
750{
751 move |i: &'a str| {
752 let (r, _) = tag(s)(i)?;
753 Ok((r, t))
754 }
755}
756
757#[derive(Debug)]
760struct TemplateError {
761 reason: String,
762 index: usize,
763}
764
765#[derive(Debug)]
766struct TemplateDirective {
767 name: String,
768 params: Vec<(String, String)>,
769}
770
771#[derive(Debug)]
772enum TemplatePart {
773 Text(String),
774 Code(String),
775 Expr(String),
776 Directive(TemplateDirective),
777}
778
779impl TemplatePart {
780 fn is_text(&self) -> bool { matches!(self, Text(_)) }
781
782 fn should_trim_whitespace(&self) -> bool { matches!(self, Code(_) | Directive(_)) }
785}
786
787#[derive(PartialEq)]
788enum TemplatePartType {
789 None,
790 Code,
791 Text,
792 Expr,
793}
794
795#[derive(Debug)]
796struct TemplateInfo {
797 debug_print: bool,
798 clean_whitespace: bool,
799 print_postprocessor: String,
800}
801
802impl TemplateInfo {
803 fn default() -> Self {
804 Self {
805 debug_print: false,
806 clean_whitespace: false,
807 print_postprocessor: "".into(),
808 }
809 }
810}