libside_procmacro/
lib.rs

1use std::{fs, collections::HashSet};
2use proc_macro2::{TokenStream, Span};
3use quote::{ToTokens, quote};
4use syn::{parse_macro_input, Lit, Ident};
5
6enum Component {
7    Str(String),
8    Param {
9        name: String,
10    },
11}
12
13impl ToTokens for Component {
14    fn to_tokens(&self, tokens: &mut TokenStream) {
15        match self {
16            Component::Str(s) => {
17                tokens.extend(quote! {
18                    contents.push_str(&#s);
19                });
20            },
21            Component::Param { name } => {
22                let ident = Ident::new(name, Span::call_site());
23                tokens.extend(quote! {
24                    contents.push_str(&::libside::builder::AsParam::as_param(&p.#ident));
25                });
26            },
27        }
28    }
29}
30
31fn parse(mut s: &str) -> Vec<Component> {
32    let mut result = Vec::new();
33    loop {
34        if let Some(next) = s.find("{{") {
35            result.push(Component::Str(s[..next].to_string()));
36            s = s[next + 2..].trim_start();
37            let end = s.find("}}").expect("Can't find end of {{ tag");
38            let name = s[..end].trim();
39            s = &s[end + 2..];
40            result.push(Component::Param {
41                name: name.to_string(),
42            });
43        } else {
44            result.push(Component::Str(s.to_string()));
45            break result
46        }
47    }
48}
49
50#[proc_macro]
51pub fn config_file(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
52    let mut iter = tokens.into_iter();
53    let mut first_token = proc_macro::TokenStream::new();
54    first_token.extend([ iter.next().unwrap() ]);
55
56    let mut rest = proc_macro::TokenStream::new();
57    rest.extend(iter);
58    let rest: TokenStream = rest.into();
59
60    let lit = parse_macro_input!(first_token as Lit);
61    let path = if let Lit::Str(s) = lit {
62        s.value()
63    } else {
64        panic!();
65    };
66
67    let realpath = std::fs::canonicalize(&path).unwrap();
68    let realpath = realpath.display().to_string();
69
70    let contents = fs::read_to_string(&path).unwrap();
71    let components = parse(&contents);
72    let params = components.iter().map(|c| match c {
73        Component::Param { name } => Some(name.as_str()),
74        _ => None,
75    }).flatten().collect::<HashSet<_>>().into_iter().map(|n| Ident::new(n, Span::call_site())).collect::<Vec<_>>();
76
77    let mut ts = TokenStream::new();
78
79    ts.extend(quote! {
80        {
81            let _ = include_bytes!(#realpath);
82            struct Params<#(#params: libside::builder::AsParam,)*> {
83                #(#params: #params),*
84            }
85
86
87            let p = Params {
88                #rest
89            };
90
91            let mut contents = String::new();
92            #(
93                #components
94            )*
95
96            let contents = contents.as_bytes().to_vec();
97
98            ::libside::builder::fs::ConfigFileData {
99                path: std::path::PathBuf::from(#path).file_name().unwrap().into(),
100                contents,
101                path_dependency: None,
102                extra_dependencies: Vec::new(),
103            }
104        }
105    });
106
107    ts.into()
108}