ks_placeholder/
lib.rs

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}