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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
//! This crate provides macros to help conveniently load the contents of //! files during development. //! //! `load_str!` and `load_bytes!` are modeled after `include_str!` and //! `include_bytes!` from the standard library. The standard library macros //! are useful in many situations, one of which is quick-and-dirty loading of //! assets during a prototyping phase. (Examples of such assets are static web //! assets such as CSS or GLSL shaders for a game.) The `load_*` macros aim to //! offer a convenient way of loading the assets dynamically at run-time //! instead. This gets rid of the need to compile or even restart for every //! change while iterating on the assets. //! //! # Example //! Before: //! //! ``` //! fn main() { //! println!("{}", include_str!("greeting.txt")); //! } //! ``` //! //! After: //! //! ``` //! #[macro_use] //! extern crate load_file; //! //! fn main() { //! println!("{}", load_str!("greeting.txt")); //! } //! ``` use std::{ fs::File, io::Read, path::{Path, PathBuf}, str, }; #[doc(hidden)] pub fn resolve_path(base: &str, rel: &str) -> Result<PathBuf, &'static str> { Ok(Path::new(base) .parent() .ok_or("invalid source file path")? .join(rel)) } #[doc(hidden)] pub fn load_file_bytes(path: &Path) -> Result<&'static [u8], &'static str> { let mut f = File::open(path).map_err(|_| "file not found")?; let mut contents = Vec::new(); f.read_to_end(&mut contents) .map_err(|_| "unable to read the file")?; let contents = contents.into_boxed_slice(); Ok(Box::leak(contents)) } #[doc(hidden)] pub fn load_file_str(path: &Path) -> Result<&'static str, &'static str> { let bytes = load_file_bytes(path)?; let s = str::from_utf8(bytes).map_err(|_| "invalid utf8")?; Ok(s) } /// Load a file as a reference to a byte array at run-time. /// /// The file is located relative to the current source file, and the binary /// must be run with the crate root as the working directory. /// /// The resulting value is a `&'static [u8]` with the contents of the file. /// /// This macro can often be a drop-in replacement for `include_bytes!`, /// switching it to be a run-time rather than compile-time operation. /// /// Each time the macro is reached, the file is read into memory in its /// entirety and the memory is leaked, keeping the memory valid for the /// remainder of the program execution. /// /// # Compatibility with `include_bytes!` /// Apart from the semantic differences between `include_bytes!` and /// `load_bytes!` there are also some technical differences: /// /// * With `include_bytes!`, the length of the array is statically known, and /// is included in the type: `&'static [u8; N]`, vs `&'static [u8]` for /// `load_bytes!` /// * `include_bytes!` can appear in static contexts in the source code, /// while `load_bytes!` can not. It is possible to use the `lazy_static` /// crate to work around this. /// /// # Example /// ``` /// #[macro_use] /// extern crate load_file; /// /// fn main() { /// let greeting: &[u8] = load_bytes!("greeting.txt"); /// println!("{:?}", greeting); /// } /// ``` /// /// # Panics /// To facilitate using `load_bytes!` as a drop-in replacement for /// `include_bytes!`, all error situations cause panics: /// /// * File not found /// * Read errors #[macro_export] macro_rules! load_bytes { ($name:expr) => {{ let path = match $crate::resolve_path(file!(), $name) { Ok(x) => x, Err(msg) => { panic!("{} in load_bytes!({:?})", msg, $name); } }; match $crate::load_file_bytes(&path) { Ok(x) => x, Err(msg) => { panic!( "{} in load_bytes!({:?}) (resolved to: {:?})", msg, $name, path ); } } }}; } /// Load a utf8-encoded file as a string at run-time. /// /// The file is located relative to the current source file, and the binary /// must be run with the crate root as the working directory. /// /// The resulting value is a `&'static str` with the contents of the file. /// /// This macro can often be a drop-in replacement for `include_str!`, /// switching it to be a run-time rather than compile-time operation. /// /// Each time the macro is reached, the file is read into memory in its /// entirety and the memory is leaked, keeping the memory valid for the /// remainder of the program execution. /// /// # Compatibility with `include_str!` /// Apart from the semantic differences between `include_str!` and `load_str!` /// there are also a technical difference: /// /// `include_str!` can appear in static contexts in the source code, while /// `load_str!` can not. It is possible to use the `lazy_static` crate to work /// around this. /// /// # Example /// ``` /// #[macro_use] /// extern crate load_file; /// /// fn main() { /// let greeting: &str = load_str!("greeting.txt"); /// println!("{}", greeting); /// } /// ``` /// /// # Panics /// To facilitate using `load_str!` as a drop-in replacement for /// `include_str!`, all error situations cause panics: /// /// * File not found /// * Read errors /// * UTF-8 validation errors #[macro_export] macro_rules! load_str { ($name:expr) => {{ let path = match $crate::resolve_path(file!(), $name) { Ok(x) => x, Err(msg) => { panic!("{} in load_str!({:?})", msg, $name); } }; match $crate::load_file_str(&path) { Ok(x) => x, Err(msg) => { panic!( "{} in load_str!({:?}) (resolved to: {:?})", msg, $name, path ); } } }}; } #[cfg(test)] mod test { use super::load_str; #[test] fn basic_load_str() { let greeting = load_str!("greeting.txt"); assert_eq!(greeting, "Hello world!\n"); } #[test] #[should_panic] fn load_str_file_not_found() { load_str!("404.txt"); } }