1#![recursion_limit = "128"]
2use proc_macro2::*;
3use quote::*;
4use regex::Regex;
5use std::iter::once;
6
7#[proc_macro_attribute]
14pub fn template(
15 attr: proc_macro::TokenStream,
16 item: proc_macro::TokenStream,
17) -> proc_macro::TokenStream {
18 let attr = proc_macro2::TokenStream::from(attr);
19 let item = proc_macro2::TokenStream::from(item);
20 let mut file_ = String::new();
21 let mut attr = attr.into_iter();
22 let mut re = regex::Regex::new(r#"(\\%)|(%([^\n]*)\n)|(\{\{([^\}]*)\}\})"#).expect("regex");
23 let mut error = "anyhow::Error".to_string();
24 while let Some(a) = attr.next() {
25 if format!("{}", a) == "path" {
26 if let (Some(a), Some(b)) = (attr.next(), attr.next()) {
27 if format!("{}", a) == "=" {
28 file_ = format!("{}", b)
29 }
30 }
31 } else if format!("{}", a) == "regex" {
32 if let (Some(a), Some(b)) = (attr.next(), attr.next()) {
33 if format!("{}", a) == "=" {
34 re = regex::Regex::new(&format!("{}", b)).expect("regex");
35 }
36 }
37 } else if format!("{}", a) == "error" {
38 if let (Some(a), Some(b)) = (attr.next(), attr.next()) {
39 if format!("{}", a) == "=" {
40 let e = format!("{}", b);
41 error.clear();
42 error.push_str(e.trim_matches('"'));
43 }
44 }
45 } else if format!("{}", a) == "," {
46 continue
47 } else {
48 println!("unknown attribute {:?}", a);
49 }
50 }
51 let cargo_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
52 let mut file = std::path::Path::new(&cargo_dir).join("templates");
53 file.push(file_.trim_matches('"'));
54 let template = std::fs::read_to_string(&file).unwrap();
55
56 let mut name = None;
67 let mut item = item.into_iter();
68 let mut item_ = Vec::new();
69 let mut spec = proc_macro2::TokenStream::new();
70 let mut spec2 = proc_macro2::TokenStream::new();
71 let mut is_name = true;
72 let mut last_was_name = false;
73 loop {
74 match item.next() {
75 Some(TokenTree::Ident(id)) => {
76 if id.to_string() == "struct" || id.to_string() == "enum" {
77 let it = item.next().unwrap();
78 name = Some(syn::Ident::new(&format!("{}", it), it.span()));
79 item_.push(TokenTree::Ident(id));
80 item_.push(it);
81 last_was_name = true;
82 } else {
83 item_.push(TokenTree::Ident(id));
84 }
85 }
86 None => break,
87 Some(TokenTree::Punct(p)) => {
88 if last_was_name {
90 if p.to_string() == "<" {
91 let mut level = 1;
92 spec.extend(once(TokenTree::Punct(p.clone())));
93 spec2.extend(once(TokenTree::Punct(p.clone())));
94 item_.push(TokenTree::Punct(p));
95 loop {
96 match item.next() {
97 Some(TokenTree::Punct(p)) => {
98 let pp = p.to_string();
99 spec.extend(once(TokenTree::Punct(p.clone())));
100 item_.push(TokenTree::Punct(p.clone()));
101 if pp == ">" {
102 level -= 1;
103 if level <= 0 {
104 spec2.extend(once(TokenTree::Punct(p.clone())));
105 break;
106 }
107 } else if pp == "<" {
108 level += 1;
109 } else if pp == ":" {
110 is_name = false;
111 } else if pp == "," && level == 1 {
112 spec2.extend(once(TokenTree::Punct(p.clone())));
113 is_name = true;
114 } else if is_name {
115 spec2.extend(once(TokenTree::Punct(p.clone())));
116 }
117 }
118 Some(it) => {
119 spec.extend(once(it.clone()));
120 if is_name {
121 spec2.extend(once(it.clone()));
122 }
123 item_.push(it)
124 }
125 None => break,
126 }
127 }
128 } else {
129 item_.push(TokenTree::Punct(p));
130 }
131 } else {
132 item_.push(TokenTree::Punct(p))
133 }
134 }
135 Some(it) => item_.push(it),
136 }
137 }
138 let name = name.unwrap();
139
140 use std::iter::FromIterator;
141 let item = proc_macro2::TokenStream::from_iter(item_);
142
143 let tokens = walk(re, &template);
144 let tok: TokenStream = tokens.parse().unwrap();
145 let file = file.to_str().unwrap();
146 let error: TokenStream = error.parse().unwrap();
147 let tokens = quote! {
148 impl #spec cuach_tex::Render for #name #spec2 {
149 type Error = #error;
150 fn render_into<W: std::fmt::Write>(&self, w: &mut W) -> Result<(), Self::Error> {
151 let _ = include_bytes!(#file);
152 use std::fmt::Write;
153 use cuach_tex::Render;
154 #tok
155 Ok(())
156 }
157 }
158 #item
159 };
160 proc_macro::TokenStream::from(tokens)
161}
162
163fn walk(re: Regex, mut input: &str) -> String {
164 use std::fmt::Write;
165 let mut s = String::new();
166 while let Some(f) = re.captures(input) {
167 let range = f.get(0).unwrap().range();
168 let (a, b) = input.split_at(range.end);
169 let (a, _) = a.split_at(range.start);
170 if !a.is_empty() {
171 writeln!(s, "w.write_str(r#\"{}\"#)?;", a).unwrap();
172 }
173 input = b;
174
175 if f.get(1).is_some() {
176 writeln!(s, "w.write_str(\"\\\\%\")?;").unwrap();
177 } else if let Some(needle) = f.get(3) {
178 s.push_str(needle.as_str());
179 } else if let Some(needle) = f.get(5) {
180 writeln!(s, "({}).render_into(w)?;", needle.as_str()).unwrap();
181 }
182 }
183 if !input.is_empty() {
184 writeln!(s, "w.write_str(r#\"{}\"#)?;", input).unwrap()
185 }
186 s
187}