Skip to content

Commit 232fedb

Browse files
authored
Merge pull request #25 from kkent030315/zstd
Add Zstd support as optional and compile-time low ratio report
2 parents cee7272 + 2abab4b commit 232fedb

File tree

18 files changed

+502
-59
lines changed

18 files changed

+502
-59
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ jobs:
2525
stability:
2626
- ""
2727
- "--release"
28+
feature:
29+
- ""
30+
- "--no-default-features --features deflate"
31+
- "--no-default-features --features zstd"
2832
steps:
2933
- uses: actions/checkout@v2
3034
- uses: actions-rs/toolchain@v1
@@ -33,4 +37,4 @@ jobs:
3337
profile: default
3438
default: true
3539
- name: cargo clippy
36-
run: "cargo clippy --all ${{matrix.stability}}"
40+
run: "cargo clippy --all ${{matrix.feature}} ${{matrix.stability}}"

Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = [".", "codegen"]
2+
members = [".", "codegen", "compress"]
33

44
[package]
55
name = "include-flate"
@@ -15,5 +15,13 @@ keywords = ["compression", "deflate", "macro", "include", "assets"]
1515

1616
[dependencies]
1717
include-flate-codegen = { version = "0.2.0", path = "codegen" }
18+
include-flate-compress = { version = "0.1.0", path = "compress" }
1819
once_cell = "1.18.0"
1920
libflate = "2.0.0"
21+
zstd = "0.13.0"
22+
23+
[features]
24+
default = ["deflate", "zstd"]
25+
deflate = ["include-flate-compress/deflate"]
26+
zstd = ["include-flate-compress/zstd"]
27+
no-compression-warnings = ["include-flate-codegen/no-compression-warnings"]

codegen/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,10 @@ libflate = "2.0.0"
1616
proc-macro2 = "1.0.9"
1717
quote = "1.0.2"
1818
syn = { version = "2.0.2", features = ["full"] }
19+
zstd = "0.13.0"
20+
include-flate-compress = { version = "0.1.0", path = "../compress" }
21+
proc-macro-error = "1.0.4"
22+
23+
[features]
24+
default = []
25+
no-compression-warnings = []

codegen/src/lib.rs

Lines changed: 98 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@
1515

1616
extern crate proc_macro;
1717

18-
use std::fs::File;
18+
use std::fs::{self, File};
19+
use std::io::{Read, Seek};
1920
use 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};
2324
use proc_macro::TokenStream;
2425
use proc_macro2::Span;
26+
use proc_macro_error::{emit_warning, proc_macro_error};
2527
use 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]
4548
pub 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]
5862
pub 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
}

compress/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "include-flate-compress"
3+
version = "0.1.0"
4+
authors = ["SOFe <sofe2038@gmail.com>", "Kento Oki <hrn832@protonmail.com>"]
5+
edition = "2021"
6+
license = "Apache-2.0"
7+
repository = "https://github.com/SOF3/include-flate.git"
8+
homepage = "https://github.com/SOF3/include-flate"
9+
description = "Compression algorithm provider"
10+
11+
[dependencies]
12+
libflate = { version = "2.0.0", optional = true }
13+
zstd = { version = "0.13.0", optional = true }
14+
15+
[features]
16+
default = ["deflate", "zstd"]
17+
deflate = ["dep:libflate"]
18+
zstd = ["dep:zstd"]

0 commit comments

Comments
 (0)