1use proc_macro::{Delimiter, Group, Literal, TokenStream, TokenTree};
6use std::{env::current_dir, fs::read_to_string};
7
8#[proc_macro_attribute]
31pub fn include_first(_attr: TokenStream, item: TokenStream) -> TokenStream {
32 include_strings(item)
33}
34
35fn include_strings(stream: TokenStream) -> TokenStream {
36 let mut input = stream.into_iter().collect::<Vec<_>>();
37 let mut output = TokenStream::new();
38 let mut i = 0;
39 while i < input.len() {
40 if let TokenTree::Ident(ident) = &input[i]
41 && ident.span().source_text().unwrap_or_default() == "include_str"
42 && let Some(TokenTree::Punct(bang)) = input.get(i + 1)
43 && bang.as_char() == '!'
44 && let Some(TokenTree::Group(args)) = input.get(i + 2)
45 && args.delimiter() == Delimiter::Parenthesis
46 && let [TokenTree::Literal(filename)] =
47 args.stream().into_iter().collect::<Vec<_>>().as_slice()
48 {
49 let filename = filename.span().source_text().unwrap();
50 let filename = format!("src/{}", filename.trim_matches('"'));
51
52 let file_contents = match read_to_string(&filename) {
54 Err(e) => panic!(
55 "Error reading {filename} from {:?}/src: {e}",
56 current_dir().unwrap()
57 ),
58 Ok(contents) => contents,
59 };
60 output.extend(Some(TokenTree::Literal(Literal::string(&file_contents))));
61
62 i += 3;
64 continue;
65 }
66 if let TokenTree::Group(group) = &mut input[i] {
67 let stream = include_strings(group.stream());
68 output.extend(Some(TokenTree::Group(Group::new(
69 group.delimiter(),
70 stream,
71 ))));
72 } else {
73 output.extend(Some(input[i].clone()));
74 }
75 i += 1;
76 }
77 output
78}