include_flate_codegen/
lib.rs1extern crate proc_macro;
17
18use std::fs::{self, File};
19use std::io::{Read, Seek};
20use std::path::PathBuf;
21use std::str::{from_utf8, FromStr};
22
23use include_flate_compress::{apply_compression, CompressionMethod};
24use proc_macro::TokenStream;
25use proc_macro2::Span;
26use proc_macro_error::{emit_warning, proc_macro_error};
27use quote::quote;
28use syn::{Error, LitByteStr};
29
30#[proc_macro]
47#[proc_macro_error]
48pub fn deflate_file(ts: TokenStream) -> TokenStream {
49 match inner(ts, false) {
50 Ok(ts) => ts.into(),
51 Err(err) => err.to_compile_error().into(),
52 }
53}
54
55#[proc_macro]
61#[proc_macro_error]
62pub fn deflate_utf8_file(ts: TokenStream) -> TokenStream {
63 match inner(ts, true) {
64 Ok(ts) => ts.into(),
65 Err(err) => err.to_compile_error().into(),
66 }
67}
68
69struct FlateArgs {
77 path: syn::LitStr,
78 algorithm: Option<CompressionMethodTy>,
79}
80
81impl syn::parse::Parse for FlateArgs {
82 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
83 let path = input.parse()?;
84
85 let algorithm = if input.is_empty() {
86 None
87 } else {
88 let lookahead = input.lookahead1();
89 if lookahead.peek(kw::deflate) {
90 input.parse::<kw::deflate>()?;
91 Some(CompressionMethodTy(CompressionMethod::Deflate))
92 } else if lookahead.peek(kw::zstd) {
93 input.parse::<kw::zstd>()?;
94 Some(CompressionMethodTy(CompressionMethod::Zstd))
95 } else {
96 return Err(lookahead.error());
97 }
98 };
99
100 Ok(Self { path, algorithm })
101 }
102}
103
104mod kw {
105 syn::custom_keyword!(deflate);
106 syn::custom_keyword!(zstd);
107}
108
109#[derive(Debug)]
110struct CompressionMethodTy(CompressionMethod);
111
112fn compression_ratio(original_size: u64, compressed_size: u64) -> f64 {
113 (compressed_size as f64 / original_size as f64) * 100.0
114}
115
116fn inner(ts: TokenStream, utf8: bool) -> syn::Result<impl Into<TokenStream>> {
117 fn emap<E: std::fmt::Display>(error: E) -> Error {
118 Error::new(Span::call_site(), error)
119 }
120
121 let dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").map_err(emap)?);
122
123 let args: FlateArgs = syn::parse2::<FlateArgs>(ts.to_owned().into())?;
124 let path = PathBuf::from_str(&args.path.value()).map_err(emap)?;
125 let algo = args
126 .algorithm
127 .unwrap_or(CompressionMethodTy(CompressionMethod::Deflate));
128
129 if path.is_absolute() {
130 Err(emap("absolute paths are not supported"))?;
131 }
132
133 let target = dir.join(&path);
134
135 let mut file = File::open(&target).map_err(emap)?;
136
137 let mut vec = Vec::<u8>::new();
138 if utf8 {
139 std::io::copy(&mut file, &mut vec).map_err(emap)?;
140 from_utf8(&vec).map_err(emap)?;
141 }
142
143 let mut compressed_buffer = Vec::<u8>::new();
144
145 {
146 let mut compressed_cursor = std::io::Cursor::new(&mut compressed_buffer);
147 let mut source: Box<dyn Read> = if utf8 {
148 Box::new(std::io::Cursor::new(vec))
149 } else {
150 file.seek(std::io::SeekFrom::Start(0)).map_err(emap)?;
151 Box::new(&file)
152 };
153
154 apply_compression(&mut source, &mut compressed_cursor, algo.0).map_err(emap)?;
155 }
156
157 let bytes = LitByteStr::new(&compressed_buffer, Span::call_site());
158 let result = quote!(#bytes);
159
160 #[cfg(not(feature = "no-compression-warnings"))]
161 {
162 let compression_ratio = compression_ratio(
163 fs::metadata(&target).map_err(emap)?.len(),
164 compressed_buffer.len() as u64,
165 );
166
167 if compression_ratio < 10.0f64 {
168 emit_warning!(
169 &args.path,
170 "Detected low compression ratio ({:.2}%) for file {:?} with `{:?}`. Consider using other compression methods.",
171 compression_ratio,
172 path.display(),
173 algo.0,
174 );
175 }
176 }
177
178 Ok(result)
179}