include_flate_codegen/
lib.rsextern crate proc_macro;
use std::fs::{self, File};
use std::io::{Read, Seek};
use std::path::PathBuf;
use std::str::{from_utf8, FromStr};
use include_flate_compress::{apply_compression, CompressionMethod};
use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro_error::{emit_warning, proc_macro_error};
use quote::quote;
use syn::{Error, LitByteStr};
#[proc_macro]
#[proc_macro_error]
pub fn deflate_file(ts: TokenStream) -> TokenStream {
match inner(ts, false) {
Ok(ts) => ts.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro]
#[proc_macro_error]
pub fn deflate_utf8_file(ts: TokenStream) -> TokenStream {
match inner(ts, true) {
Ok(ts) => ts.into(),
Err(err) => err.to_compile_error().into(),
}
}
struct FlateArgs {
path: syn::LitStr,
algorithm: Option<CompressionMethodTy>,
}
impl syn::parse::Parse for FlateArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let path = input.parse()?;
let algorithm = if input.is_empty() {
None
} else {
let lookahead = input.lookahead1();
if lookahead.peek(kw::deflate) {
input.parse::<kw::deflate>()?;
Some(CompressionMethodTy(CompressionMethod::Deflate))
} else if lookahead.peek(kw::zstd) {
input.parse::<kw::zstd>()?;
Some(CompressionMethodTy(CompressionMethod::Zstd))
} else {
return Err(lookahead.error());
}
};
Ok(Self { path, algorithm })
}
}
mod kw {
syn::custom_keyword!(deflate);
syn::custom_keyword!(zstd);
}
#[derive(Debug)]
struct CompressionMethodTy(CompressionMethod);
fn compression_ratio(original_size: u64, compressed_size: u64) -> f64 {
(compressed_size as f64 / original_size as f64) * 100.0
}
fn inner(ts: TokenStream, utf8: bool) -> syn::Result<impl Into<TokenStream>> {
fn emap<E: std::fmt::Display>(error: E) -> Error {
Error::new(Span::call_site(), error)
}
let dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").map_err(emap)?);
let args: FlateArgs = syn::parse2::<FlateArgs>(ts.to_owned().into())?;
let path = PathBuf::from_str(&args.path.value()).map_err(emap)?;
let algo = args
.algorithm
.unwrap_or(CompressionMethodTy(CompressionMethod::Deflate));
if path.is_absolute() {
Err(emap("absolute paths are not supported"))?;
}
let target = dir.join(&path);
let mut file = File::open(&target).map_err(emap)?;
let mut vec = Vec::<u8>::new();
if utf8 {
std::io::copy(&mut file, &mut vec).map_err(emap)?;
from_utf8(&vec).map_err(emap)?;
}
let mut compressed_buffer = Vec::<u8>::new();
{
let mut compressed_cursor = std::io::Cursor::new(&mut compressed_buffer);
let mut source: Box<dyn Read> = if utf8 {
Box::new(std::io::Cursor::new(vec))
} else {
file.seek(std::io::SeekFrom::Start(0)).map_err(emap)?;
Box::new(&file)
};
apply_compression(&mut source, &mut compressed_cursor, algo.0).map_err(emap)?;
}
let bytes = LitByteStr::new(&compressed_buffer, Span::call_site());
let result = quote!(#bytes);
#[cfg(not(feature = "no-compression-warnings"))]
{
let compression_ratio = compression_ratio(
fs::metadata(&target).map_err(emap)?.len(),
compressed_buffer.len() as u64,
);
if compression_ratio < 10.0f64 {
emit_warning!(
&args.path,
"Detected low compression ratio ({:.2}%) for file {:?} with `{:?}`. Consider using other compression methods.",
compression_ratio,
path.display(),
algo.0,
);
}
}
Ok(result)
}