#![feature(proc_macro_value)] use std::{fs::File, io::Read}; use proc_macro::{Delimiter, Group, Literal, Punct, TokenStream, TokenTree}; use sha2::Digest; fn read_path_literal(input: TokenStream) -> String { let mut input_iter = input.into_iter(); let path_token = input_iter.next().expect("expected a path"); assert!(input_iter.next().is_none(), "only expected one argument"); let path_literal = match path_token { TokenTree::Group(path_group) => { let mut path_stream = path_group.stream().into_iter(); let path_literal = path_stream.next().expect("expected a str literal"); assert!(path_stream.next().is_none(), "expected a str literal"); let TokenTree::Literal(path_literal) = path_literal else { panic!("expected a str literal"); }; path_literal } TokenTree::Literal(path_literal) => path_literal, _ => panic!("expected a str literal"), }; // eprintln!("{path_literal:?}"); path_literal.str_value().expect("expected a str literal") } fn extend_grouped( ts: &mut TokenStream, delim: Delimiter, items: impl Iterator, ) { let mut comma_ts = TokenStream::new(); comma_ts.extend(std::iter::once(TokenTree::Punct(Punct::new( ',', proc_macro::Spacing::Alone, )))); let mut tuple_content = TokenStream::new(); tuple_content.extend(items.flat_map(|token| [token, comma_ts.clone()])); ts.extend(std::iter::once(TokenTree::Group(Group::new( delim, tuple_content, )))); } fn extend_array(ts: &mut TokenStream, array: &[u8]) { extend_grouped( ts, Delimiter::Bracket, array.iter().map(|&b| { let mut ts = TokenStream::new(); ts.extend(std::iter::once(TokenTree::Literal(Literal::u8_unsuffixed( b, )))); ts }), ); } fn extend_asset_tuple(ts: &mut TokenStream, buf: &[u8], mime: &str, sha256sum: [u8; 32]) { extend_grouped( ts, Delimiter::Parenthesis, [ { let mut ts = TokenStream::new(); ts.extend(std::iter::once(TokenTree::Punct(Punct::new( '&', proc_macro::Spacing::Joint, )))); extend_array(&mut ts, buf); ts }, { let mut ts = TokenStream::new(); ts.extend(std::iter::once(Literal::string(mime))); ts }, { let mut ts = TokenStream::new(); extend_array(&mut ts, &sha256sum); ts }, ] .into_iter(), ); } fn get_mime(buf: &[u8]) -> String { let cookie = magic::Cookie::open(magic::cookie::Flags::MIME).expect("failure opening mime cookie"); let cookie = cookie .load(&magic::cookie::DatabasePaths::default()) .expect("failure loading magic db"); cookie.buffer(buf).expect("error getting file mime") } fn get_sha256sum(buf: &[u8]) -> [u8; 32] { sha2::Sha256::digest(buf) .as_slice() .try_into() .expect("failed to sha256sum file") } #[proc_macro] #[allow(clippy::missing_panics_doc)] pub fn include_asset(input: TokenStream) -> TokenStream { let path = read_path_literal(input); let mut buf = Vec::new(); File::open(path) .expect("couldn't open file") .read_to_end(&mut buf) .expect("error reading file"); let mut ts = TokenStream::new(); extend_asset_tuple(&mut ts, &buf, &get_mime(&buf), get_sha256sum(&buf)); ts }