cuach_derive_tex/
lib.rs

1#![recursion_limit = "128"]
2use proc_macro2::*;
3use quote::*;
4use regex::Regex;
5use std::iter::once;
6
7// use html5ever::tendril::*;
8// use html5ever::tokenizer::BufferQueue;
9// use html5ever::tokenizer::{
10//     CharacterTokens, CommentToken, EndTag, NullCharacterToken, StartTag, TagToken, EOFToken, ParseError, Token, TokenSink, TokenSinkResult, Tokenizer, TokenizerOpts, DoctypeToken,
11// };
12
13#[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    // Replacing all & (except in "&blabla;") with &
57    // let re_amp = regex::Regex::new(r"(&[a-zA-Z]+;)|&").unwrap();
58    // let template = re_amp.replace_all(&template, |cap: &regex::Captures| {
59    //     if let Some(c) = cap.get(1) {
60    //         c.as_str().to_string()
61    //     } else {
62    //         "&".to_string()
63    //     }
64    // });
65
66    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                // println!("punct {:?} {:?}", p, last_was_name);
89                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}