1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// include-flate
// Copyright (C) SOFe
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! A variant of `include_bytes!`/`include_str!` with compile-time deflation and runtime lazy inflation.
//!
//! ## Why?
//! `include_bytes!`/`include_str!` are great for embedding resources into an executable/library
//! without involving the complex logistics of maintaining an assets manager.
//! However, they are copied as-is into the artifact, leading to unnecessarily large binary size.
//! This library automatically compresses the resources and lazily decompresses them at runtime,
//! allowing smaller binary sizes.
//!
//! Nevertheless, this inevitably leads to wasting RAM to store both the compressed and decompressed data,
//! which might be undesirable if the data are too large.
//! An actual installer is still required if the binary involves too many resources that do not need to be kept in RAM all time.

use libflate::deflate;

/// The low-level macros used by this crate.
pub use include_flate_codegen_exports as codegen;
#[doc(hidden)]
pub use lazy_static::lazy_static;

/// This macro is like [`include_bytes!`][1] or [`include_str!`][2], but compresses at compile time
/// and lazily decompresses at runtime.
///
/// # Parameters
/// The macro can be used like this:
/// ```ignore
/// flate!($meta $vis static $name: $type from $file);
/// ```
///
/// - `$meta` is zero or more `#[...]` attributes that can be applied on the static parameters of
/// `lazy_static`. For the actual semantics of the meta attributes, please refer to
/// [`lazy_static`][3] documentation.
/// - `$vis` is a visibility modifier (e.g. `pub`, `pub(crate)`) or empty.
/// - `$name` is the name of the static variable..
/// - `$type` can be either `[u8]` or `str`. However, the actual type created would dereference
/// into `Vec<u8>` and `String` (although they are `AsRef<[u8]>` and `AsRef<str>`) respectively.
/// - `$file` is either an absolute path or a path relative to the current
/// [`CARGO_MANIFEST_DIR`][4]. Note that **this is distinct from the behaviour of the builtin
/// `include_bytes!`/`include_str!` macros** &mdash; `includle_bytes!`/`include_str!` paths are
/// relative to the current source file, while `flate!` paths are relative to `CARGO_MANIFEST_DIR`.
///
/// # Returns
/// The macro expands to a [`lazy_static`][3] call, which lazily inflates the compressed bytes.
///
/// # Compile errors
/// - If the input format is incorrect
/// - If the referenced file does not exist or is not readable
/// - If `$type` is `str` but the file is not fully valid UTF-8
///
/// # Algorithm
/// Compression and decompression use the DEFLATE algorithm from [`libflate`][5].
///
/// # Examples
/// Below are some basic examples. For actual compiled examples, see the [`tests`][6] directory.
///
/// ```ignore
/// // This declares a `static VAR_NAME: impl Deref<Vec<u8>>`
/// flate!(static VAR_NAME: [u8] from "binary-file.dat");
///
/// // This declares a `static VAR_NAME: impl Deref<String>`
/// flate!(static VAR_NAME: str from "text-file.txt");
///
/// // Visibility modifiers can be added in the front
/// flate!(pub static VAR_NAME: str from "public-file.txt");
///
/// // Meta attributes can also be added
/// flate!(#[allow(unused)]
///        #[doc = "Example const"]
///        pub static VAR_NAME: str from "file.txt");
/// ```
///
///   [1]: https://doc.rust-lang.org/std/macro.include_bytes.html
///   [2]: https://doc.rust-lang.org/std/macro.include_str.html
///   [3]: https://docs.rs/lazy_static/1.3.0/lazy_static/
///   [4]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
///   [5]: https://docs.rs/libflate/0.1.26/libflate/
///   [6]: https://github.com/SOF3/include-flate/tree/master/tests
#[macro_export]
macro_rules! flate {
    ($(#[$meta:meta])*
        $(pub $(($($vis:tt)+))?)? static $name:ident: [u8] from $path:literal) => {
        // HACK: workaround to make cargo auto rebuild on modification of source file
        const _: &'static [u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path));

        $crate::lazy_static! {
            $(#[$meta])*
            $(pub $(($($vis)+))?)? static ref $name: ::std::vec::Vec<u8> = $crate::decode($crate::codegen::deflate_file!($path));
        }
    };
    ($(#[$meta:meta])*
        $(pub $(($($vis:tt)+))?)? static $name:ident: str from $path:literal) => {
        // HACK: workaround to make cargo auto rebuild on modification of source file
        const _: &'static str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path));

        $crate::lazy_static! {
            $(#[$meta])*
            $(pub $(($($vis)+))?)? static ref $name: ::std::string::String = $crate::decode_string($crate::codegen::deflate_utf8_file!($path));
        }
    };
}

#[doc(hidden)]
pub fn decode(bytes: &[u8]) -> Vec<u8> {
    use std::io::{Cursor, Read};

    let mut dec = deflate::Decoder::new(Cursor::new(bytes));
    let mut ret = Vec::new();
    dec.read_to_end(&mut ret)
        .expect("Compiled DEFLATE buffer was corrupted");
    ret
}

#[doc(hidden)]
pub fn decode_string(bytes: &[u8]) -> String {
    // We should have checked for utf8 correctness in encode_utf8_file!
    String::from_utf8(decode(bytes))
        .expect("flate_str has malformed UTF-8 despite checked at compile time")
}