1515
1616extern crate proc_macro;
1717
18- use std:: fs:: File ;
18+ use std:: fs:: { self , File } ;
19+ use std:: io:: { Read , Seek } ;
1920use std:: path:: PathBuf ;
20- use std:: str:: from_utf8;
21+ use std:: str:: { from_utf8, FromStr } ;
2122
22- use libflate :: deflate :: Encoder ;
23+ use include_flate_compress :: { apply_compression , CompressionMethod } ;
2324use proc_macro:: TokenStream ;
2425use proc_macro2:: Span ;
26+ use proc_macro_error:: { emit_warning, proc_macro_error} ;
2527use quote:: quote;
26- use syn:: { Error , LitByteStr , LitStr , Result } ;
28+ use syn:: { Error , LitByteStr } ;
2729
2830/// `deflate_file!("file")` is equivalent to `include_bytes!("file.gz")`.
2931///
@@ -42,6 +44,7 @@ use syn::{Error, LitByteStr, LitStr, Result};
4244/// - If the argument is not a single literal
4345/// - If the referenced file does not exist or is not readable
4446#[ proc_macro]
47+ #[ proc_macro_error]
4548pub fn deflate_file ( ts : TokenStream ) -> TokenStream {
4649 match inner ( ts, false ) {
4750 Ok ( ts) => ts. into ( ) ,
@@ -55,47 +58,122 @@ pub fn deflate_file(ts: TokenStream) -> TokenStream {
5558/// - The compile errors in `deflate_file!`
5659/// - If the file contents are not all valid UTF-8
5760#[ proc_macro]
61+ #[ proc_macro_error]
5862pub fn deflate_utf8_file ( ts : TokenStream ) -> TokenStream {
5963 match inner ( ts, true ) {
6064 Ok ( ts) => ts. into ( ) ,
6165 Err ( err) => err. to_compile_error ( ) . into ( ) ,
6266 }
6367}
6468
65- fn inner ( ts : TokenStream , utf8 : bool ) -> Result < impl Into < TokenStream > > {
69+ /// An arguments expected provided by the proc-macro.
70+ ///
71+ /// ```ignore
72+ /// flate!(pub static DATA: [u8] from "assets/009f.dat"); // default, DEFLATE
73+ /// flate!(pub static DATA: [u8] from "assets/009f.dat" with zstd); // Use Zstd for this file spcifically
74+ /// flate!(pub static DATA: [u8] from "assets/009f.dat" with deflate); // Explicitly use DEFLATE.
75+ /// ```
76+ struct FlateArgs {
77+ path : syn:: LitStr ,
78+ algorithm : Option < CompressionMethodTy > ,
79+ }
80+
81+ impl 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+
104+ mod kw {
105+ syn:: custom_keyword!( deflate) ;
106+ syn:: custom_keyword!( zstd) ;
107+ }
108+
109+ #[ derive( Debug ) ]
110+ struct CompressionMethodTy ( CompressionMethod ) ;
111+
112+ fn compression_ratio ( original_size : u64 , compressed_size : u64 ) -> f64 {
113+ ( compressed_size as f64 / original_size as f64 ) * 100.0
114+ }
115+
116+ fn inner ( ts : TokenStream , utf8 : bool ) -> syn:: Result < impl Into < TokenStream > > {
66117 fn emap < E : std:: fmt:: Display > ( error : E ) -> Error {
67118 Error :: new ( Span :: call_site ( ) , error)
68119 }
69120
70- let dir = PathBuf :: from ( std:: env:: var ( "CARGO_MANIFEST_DIR" ) . unwrap ( ) ) ;
121+ let dir = PathBuf :: from ( std:: env:: var ( "CARGO_MANIFEST_DIR" ) . map_err ( emap ) ? ) ;
71122
72- let lit = syn:: parse :: < LitStr > ( ts) ?;
73- let path = PathBuf :: from ( lit. value ( ) ) ;
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 ) ) ;
74128
75129 if path. is_absolute ( ) {
76130 Err ( emap ( "absolute paths are not supported" ) ) ?;
77131 }
78132
79- let target = dir. join ( path) ;
133+ let target = dir. join ( & path) ;
80134
81- let mut file = File :: open ( target) . map_err ( emap) ?;
135+ let mut file = File :: open ( & target) . map_err ( emap) ?;
82136
83- let mut encoder = Encoder :: new ( Vec :: < u8 > :: new ( ) ) ;
137+ let mut vec = Vec :: < u8 > :: new ( ) ;
84138 if utf8 {
85- use std:: io:: Write ;
86-
87- let mut vec = Vec :: < u8 > :: new ( ) ;
88139 std:: io:: copy ( & mut file, & mut vec) . map_err ( emap) ?;
89140 from_utf8 ( & vec) . map_err ( emap) ?;
90- encoder. write_all ( & vec) . map_err ( emap) ?;
91- } else {
92- // no need to store the raw buffer; let's avoid storing two buffers
93- std:: io:: copy ( & mut file, & mut encoder) . map_err ( emap) ?;
94141 }
95- let bytes = encoder. finish ( ) . into_result ( ) . map_err ( emap) ?;
96142
97- let bytes = LitByteStr :: new ( & bytes, Span :: call_site ( ) ) ;
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 ( ) ) ;
98158 let result = quote ! ( #bytes) ;
99159
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+
100178 Ok ( result)
101179}
0 commit comments