From a76d9199c0562b4c1640fb4668401ad8c1f3c011 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Thu, 21 Dec 2023 13:00:33 +0300 Subject: [PATCH] Initial commit --- .gitignore | 11 +++++ Cargo.lock | 53 ++++++++++++++++++++ Cargo.toml | 13 +++++ LICENSE.md | 21 ++++++++ README.md | 45 +++++++++++++++++ src/dir.rs | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/file.rs | 67 +++++++++++++++++++++++++ src/lib.rs | 5 ++ 8 files changed, 353 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 src/dir.rs create mode 100644 src/file.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3fa25b --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +* + +!src/ + +!.gitignore + +!Cargo.lock +!Cargo.toml + +!*.md +!*.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..91d7f8c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,53 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "embed" +version = "0.1.0" +dependencies = [ + "include_dir", +] + +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..58a7d7d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "embed" +description = "Read files or directories from the filesystem at runtime on debug, embed on release." +repository = "https://github.com/RGBCube/embed-rs" +license = "MIT" +keywords = [ "embedding", "files", "debug-optimization", "bundling" ] +cateories = [ "filesystem" ] +authors = [ "RGBCube" ] +version = "0.1.0" +edition = "2021" + +[dependencies] +include_dir = { version = "^0.7", features = [ "metadata" ] } diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..14b90d4 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2023-present RGBCube + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f3b3d5 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# embed-rs + +A super simple file and directory embedding crate, +that loads files from the filesystem in debug mode, +allowing for quick edit-and-test cycles without compilation. + +On release mode it falls back to `include_str!`, `include_bytes!` and `include_dir!`. + +## Usage + +After running `cargo add embed`, you can do: + +```rs +let contents: String = embed::string!("path/to/file.txt"); +let bytes: Vec = embed::bytes!("path/to/image.png"); + +let dir: embed::Dir = embed::dir!("path/to/dir"); + +``` + +## License + +``` +MIT License + +Copyright (c) 2023-present RGBCube + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/src/dir.rs b/src/dir.rs new file mode 100644 index 0000000..a3b938a --- /dev/null +++ b/src/dir.rs @@ -0,0 +1,138 @@ +use std::{ + fs, + path::PathBuf, + time::SystemTime, +}; + +use include_dir::{ + Dir, + DirEntry, + File, +}; + +fn read_dir(dir: &PathBuf) -> Vec { + if !dir.is_dir() { + panic!("embed: {path} is not a directory", path = dir.display()); + } + + let mut paths = Vec::new(); + + for entry in dir.read_dir().unwrap_or_else(|error| { + panic!( + "embed: failed to read directory {dir}: {error}", + dir = dir.display() + ) + }) { + paths.push( + entry + .unwrap_or_else(|error| panic!("embed: failed to resolve entry: {error}")) + .path(), + ); + } + + paths.sort(); + paths +} + +fn file_to_entry<'a>(path: &'a PathBuf) -> DirEntry<'a> { + let abs = path.canonicalize().unwrap_or_else(|error| { + panic!( + "embed: failed to resolve path {path}: {error}", + path = path.display() + ) + }); + + let contents = fs::read(&path).unwrap_or_else(|error| { + panic!( + "embed: failed to read file {path}: {error}", + path = path.display() + ) + }); + + let mut entry = File::new(abs.to_str().unwrap(), contents.as_slice()); + + if let Ok(metadata) = path.metadata() { + entry = entry.with_metadata(include_dir::Metadata::new( + metadata + .accessed() + .unwrap_or_else(|error| { + panic!("embed: failed to read metadata.accessed of {metadata:?}: {error}"); + }) + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_else(|error| { + panic!("embed: failed to calculate time difference: {error}") + }), + metadata + .created() + .unwrap_or_else(|error| { + panic!("embed: failed to read metadata.created of {metadata:?}: {error}"); + }) + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_else(|error| { + panic!("embed: failed to calculate time difference: {error}") + }), + metadata + .modified() + .unwrap_or_else(|error| { + panic!("embed: failed to read metadata.modified of {metadata:?}: {error}"); + }) + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_else(|error| { + panic!("embed: failed to calculate time difference: {error}") + }), + )); + } + + DirEntry::File(entry) +} + +fn dir_to_entries<'a>(root: &'a PathBuf, path: &'a PathBuf) -> Vec> { + let mut entries = Vec::new(); + + for child in read_dir(path) { + if child.is_dir() { + entries.push(DirEntry::Dir(Dir::new( + root.as_os_str().to_str().unwrap_or_else(|| { + panic!( + "embed: failed to convert {lossy} to &str as it was not valid unicode", + lossy = root.to_string_lossy() + ) + }), + &dir_to_entries(root, &child), + ))); + } + else if child.is_file() { + entries.push(file_to_entry(&child)) + } + else { + panic!( + "{child} is neither a file or directory", + child = child.display() + ) + } + } + + entries +} + +fn include_dir<'a>(path_str: &'a str) -> Dir<'a> { + let path = PathBuf::from(path_str); + + let entries = dir_to_entries(&path, &path); + + Dir::new(path_str, entries.as_slice()) +} + +#[macro_export] +macro_rules! dir { + ($path:literal) => {{ + #[cfg(debug_assertions)] + { + include_dir($path) + } + #[cfg(not(debug_assertions))] + { + include_dir!($path) + } + }}; +} diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..3bf1e6b --- /dev/null +++ b/src/file.rs @@ -0,0 +1,67 @@ +#[macro_export] +macro_rules! string { + ($path:literal) => {{ + use std::borrow::Cow; + + #[cfg(debug_assertions)] + { + use std::{ + fs, + path::Path, + }; + + let file = Path::new(file!()) + .parent() + .expect("embed: file has no parent") + .join($path); + + Cow::<'static, str>::Owned(fs::read_to_string(&file).unwrap_or_else(|error| { + panic!( + "embed: failed to read file {file}: {error}", + file = file.display() + ) + })) + } + #[cfg(not(debug_assertions))] + { + Cow::Borrowed(include_str!($path)) + } + }}; +} + +#[macro_export] +macro_rules! bytes { + ($path:literal) => {{ + use std::borrow::Cow; + + #[cfg(debug_assertions)] + { + use std::{ + fs, + path::Path, + }; + + let file = Path::new(file!()) + .parent() + .expect("embed: file has no parent") + .join($path); + + Cow::<'static, str>::Owned(fs::read_to_string(&file).unwrap_or_else(|error| { + panic!( + "embed: failed to read file {file}: {error}", + file = file.display() + ) + })) + } + #[cfg(not(debug_assertions))] + { + Cow::Borrowed(include_bytes!($path)) + } + }}; +} + +#[allow(dead_code)] +fn check_validity() { + string!("test.txt"); + bytes!("test.txt"); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..54d51d6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +mod dir; +pub use dir::*; + +mod file; +pub use file::*;