1use std::io::{Read, Write};
2
3use proc_macro2::TokenStream;
4use quote::TokenStreamExt;
5
6const DEFAULT_HEADER_LENGTH: usize = 50;
7const DEFAULT_HEADER: &[u8; DEFAULT_HEADER_LENGTH] =
8 b"// PLACEHOLDER FILE DEFAULT HEADER, DO NOT CHANGE\n";
9
10fn read_and_write<P: AsRef<std::path::Path> + std::fmt::Display>(
11 span: proc_macro2::Span,
12 filename: &P,
13 input: TokenStream,
14) -> syn::Result<()> {
15 #[cfg(debug_assertions)]
16 eprintln!("[{}] Checking {filename}", env!("CARGO_PKG_NAME"));
17
18 if filename.as_ref().exists() {
19 let mut file = std::fs::File::open(filename)
20 .map_err(|e| syn::Error::new(span, format!("Open {filename} error: {e:?}")))?;
21
22 let mut buffer: [u8; DEFAULT_HEADER_LENGTH] = [0; DEFAULT_HEADER_LENGTH];
23
24 let read_size = file
25 .read(&mut buffer)
26 .map_err(|e| syn::Error::new(span, format!("Read {filename} header error: {e:?}")))?;
27
28 if read_size != DEFAULT_HEADER_LENGTH || !buffer.eq(DEFAULT_HEADER) {
29 #[cfg(debug_assertions)]
30 eprintln!(
31 "[{}] {filename} file not managed, keep unchanged",
32 env!("CARGO_PKG_NAME")
33 );
34 return Ok(());
35 }
36 }
37
38 let mut file = std::fs::OpenOptions::new()
39 .write(true)
40 .truncate(true)
41 .create(true)
42 .open(filename)
43 .map_err(|e| syn::Error::new(span, format!("Open {filename} error: {e:?}")))?;
44
45 file.write_all(DEFAULT_HEADER)
46 .map_err(|e| syn::Error::new(span, format!("Write {filename} header error: {e:?}")))?;
47
48 file.write_all(input.to_string().as_bytes())
49 .map_err(|e| syn::Error::new(span, format!("Write {filename} body error: {e:?}")))?;
50
51 Ok(())
52}
53
54fn process(input: TokenStream) -> syn::Result<TokenStream> {
55 let mut v: Vec<_> = input.clone().into_iter().collect();
56
57 if v.len() < 3 {
58 return Err(syn::Error::new_spanned(input, "Usage: (macro_name)! (<$filename:literal> <punctuate> <$content:others...>)\nSample: (macro_name)! { \"src/foo.rs\"; pub mod Foo {} }"));
59 }
60
61 let file_name = v.get(0).unwrap();
62
63 let span = file_name.span();
64 let file_name = match file_name {
65 proc_macro2::TokenTree::Literal(f) => {
66 let s = f.to_string();
67 if !(s.starts_with('"') && (s.ends_with('"'))) {
68 return Err(syn::Error::new_spanned(
69 file_name,
70 "Must be string in index 0",
71 ));
72 }
73 s[1..s.len() - 1].to_string()
74 }
75 _ => {
76 return Err(syn::Error::new_spanned(
77 file_name,
78 "Must be literal in index 0",
79 ))
80 }
81 };
82
83 let punctuate = v.get(1).unwrap();
84
85 match punctuate {
86 proc_macro2::TokenTree::Punct(_) => {}
87 _ => {
88 return Err(syn::Error::new_spanned(
89 punctuate,
90 "Must be punctuate in index 1",
91 ))
92 }
93 }
94 v.drain(..2);
95
96 let ret = v.iter().fold(TokenStream::new(), |mut acc, x| {
97 acc.append(x.clone());
98 acc
99 });
100
101 read_and_write(span, &file_name, ret)?;
102
103 println!("cargo:rerun-if-changed={}", file_name);
104
105 Ok(Default::default())
106}
107
108#[proc_macro]
109pub fn placeholder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
110 process(input.into())
111 .unwrap_or_else(syn::Error::into_compile_error)
112 .into()
113}