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}