include_flate/lib.rs
1// include-flate
2// Copyright (C) SOFe
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! A variant of `include_bytes!`/`include_str!` with compile-time deflation and runtime lazy inflation.
17//!
18//! ## Why?
19//! `include_bytes!`/`include_str!` are great for embedding resources into an executable/library
20//! without involving the complex logistics of maintaining an assets manager.
21//! However, they are copied as-is into the artifact, leading to unnecessarily large binary size.
22//! This library automatically compresses the resources and lazily decompresses them at runtime,
23//! allowing smaller binary sizes.
24//!
25//! Nevertheless, this inevitably leads to wasting RAM to store both the compressed and decompressed data,
26//! which might be undesirable if the data are too large.
27//! An actual installer is still required if the binary involves too many resources that do not need to be kept in RAM all time.
28
29use libflate::deflate;
30
31/// The low-level macros used by this crate.
32pub use include_flate_codegen as codegen;
33#[doc(hidden)]
34pub use lazy_static::lazy_static;
35
36/// This macro is like [`include_bytes!`][1] or [`include_str!`][2], but compresses at compile time
37/// and lazily decompresses at runtime.
38///
39/// # Parameters
40/// The macro can be used like this:
41/// ```ignore
42/// flate!($meta $vis static $name: $type from $file);
43/// ```
44///
45/// - `$meta` is zero or more `#[...]` attributes that can be applied on the static parameters of
46/// `lazy_static`. For the actual semantics of the meta attributes, please refer to
47/// [`lazy_static`][3] documentation.
48/// - `$vis` is a visibility modifier (e.g. `pub`, `pub(crate)`) or empty.
49/// - `$name` is the name of the static variable..
50/// - `$type` can be either `[u8]` or `str`. However, the actual type created would dereference
51/// into `Vec<u8>` and `String` (although they are `AsRef<[u8]>` and `AsRef<str>`) respectively.
52/// - `$file` is a path relative to the current [`CARGO_MANIFEST_DIR`][4]. Absolute paths are not supported.
53/// Note that **this is distinct from the behaviour of the builtin `include_bytes!`/`include_str!`
54/// macros** — `includle_bytes!`/`include_str!` paths are relative to the current source file,
55/// while `flate!` paths are relative to `CARGO_MANIFEST_DIR`.
56///
57/// # Returns
58/// The macro expands to a [`lazy_static`][3] call, which lazily inflates the compressed bytes.
59///
60/// # Compile errors
61/// - If the input format is incorrect
62/// - If the referenced file does not exist or is not readable
63/// - If `$type` is `str` but the file is not fully valid UTF-8
64///
65/// # Algorithm
66/// Compression and decompression use the DEFLATE algorithm from [`libflate`][5].
67///
68/// # Examples
69/// Below are some basic examples. For actual compiled examples, see the [`tests`][6] directory.
70///
71/// ```ignore
72/// // This declares a `static VAR_NAME: impl Deref<Vec<u8>>`
73/// flate!(static VAR_NAME: [u8] from "binary-file.dat");
74///
75/// // This declares a `static VAR_NAME: impl Deref<String>`
76/// flate!(static VAR_NAME: str from "text-file.txt");
77///
78/// // Visibility modifiers can be added in the front
79/// flate!(pub static VAR_NAME: str from "public-file.txt");
80///
81/// // Meta attributes can also be added
82/// flate!(#[allow(unused)]
83/// #[doc = "Example const"]
84/// pub static VAR_NAME: str from "file.txt");
85/// ```
86///
87/// [1]: https://doc.rust-lang.org/std/macro.include_bytes.html
88/// [2]: https://doc.rust-lang.org/std/macro.include_str.html
89/// [3]: https://docs.rs/lazy_static/1.3.0/lazy_static/
90/// [4]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
91/// [5]: https://docs.rs/libflate/0.1.26/libflate/
92/// [6]: https://github.com/SOF3/include-flate/tree/master/tests
93#[macro_export]
94macro_rules! flate {
95 ($(#[$meta:meta])*
96 $(pub $(($($vis:tt)+))?)? static $name:ident: [u8] from $path:literal) => {
97 // HACK: workaround to make cargo auto rebuild on modification of source file
98 const _: &'static [u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path));
99
100 $crate::lazy_static! {
101 $(#[$meta])*
102 $(pub $(($($vis)+))?)? static ref $name: ::std::vec::Vec<u8> = $crate::decode($crate::codegen::deflate_file!($path));
103 }
104 };
105 ($(#[$meta:meta])*
106 $(pub $(($($vis:tt)+))?)? static $name:ident: str from $path:literal) => {
107 // HACK: workaround to make cargo auto rebuild on modification of source file
108 const _: &'static str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path));
109
110 $crate::lazy_static! {
111 $(#[$meta])*
112 $(pub $(($($vis)+))?)? static ref $name: ::std::string::String = $crate::decode_string($crate::codegen::deflate_utf8_file!($path));
113 }
114 };
115}
116
117#[doc(hidden)]
118pub fn decode(bytes: &[u8]) -> Vec<u8> {
119 use std::io::{Cursor, Read};
120
121 let mut dec = deflate::Decoder::new(Cursor::new(bytes));
122 let mut ret = Vec::new();
123 dec.read_to_end(&mut ret)
124 .expect("Compiled DEFLATE buffer was corrupted");
125 ret
126}
127
128#[doc(hidden)]
129pub fn decode_string(bytes: &[u8]) -> String {
130 // We should have checked for utf8 correctness in encode_utf8_file!
131 String::from_utf8(decode(bytes))
132 .expect("flate_str has malformed UTF-8 despite checked at compile time")
133}