diff --git a/.gitignore b/.gitignore index d064094..240a47e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,14 @@ * -!src/ +!embed/ +!embed/src/ + +!macros/ +!macros/src/ !.gitignore -!Cargo.lock - +!*.lock !*.md !*.rs !*.toml diff --git a/Cargo.lock b/Cargo.lock index 91d7f8c..a07a683 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,46 +6,46 @@ version = 3 name = "embed" version = "0.1.0" dependencies = [ - "include_dir", + "embed_macros", ] [[package]] -name = "include_dir" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +name = "embed_macros" +version = "0.1.0" 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", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a" dependencies = [ "proc-macro2", ] +[[package]] +name = "syn" +version = "2.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d27c2c202598d05175a6dd3af46824b7f747f8d8e9b14c623f19fa5069735d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/Cargo.toml b/Cargo.toml index 58a7d7d..4862173 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,2 @@ -[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" ] } +[workspace] +members = [ "embed", "macros" ] diff --git a/README.md b/README.md index 017854a..85f49e8 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,16 @@ 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!`. +On release mode it falls back to `include_str!`, `include_bytes!` +and our own custom `include_dir!` implementation. ## Usage ```rs let contents: Cow<'_, str> = embed::string!("path/to/file.txt"); let bytes: Cow<'_, [u8]> = embed::bytes!("path/to/image.png"); + +let dir: embed::Dir = embed::dir!("path/to"); ``` ## License diff --git a/embed/Cargo.lock b/embed/Cargo.lock new file mode 100644 index 0000000..91d7f8c --- /dev/null +++ b/embed/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/embed/Cargo.toml b/embed/Cargo.toml new file mode 100644 index 0000000..75506f5 --- /dev/null +++ b/embed/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" ] +categories = [ "filesystem" ] +authors = [ "RGBCube" ] +version = "0.1.0" +edition = "2021" + +[dependencies] +embed_macros = { path = "../macros" } diff --git a/embed/README.md b/embed/README.md new file mode 100644 index 0000000..85f49e8 --- /dev/null +++ b/embed/README.md @@ -0,0 +1,43 @@ +# 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 our own custom `include_dir!` implementation. + +## Usage + +```rs +let contents: Cow<'_, str> = embed::string!("path/to/file.txt"); +let bytes: Cow<'_, [u8]> = embed::bytes!("path/to/image.png"); + +let dir: embed::Dir = embed::dir!("path/to"); +``` + +## 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/embed/src/dir.rs b/embed/src/dir.rs new file mode 100644 index 0000000..2730ecb --- /dev/null +++ b/embed/src/dir.rs @@ -0,0 +1,86 @@ +use std::{ + fs::{ + self, + }, + path::PathBuf, +}; + +pub enum DirEntry { + Dir(Dir), + File(File), +} + +pub struct Dir { + pub children: Vec, + pub path: PathBuf, +} + +impl Dir { + pub fn flatten(self) -> Vec { + let mut entries = Vec::new(); + + for child in self.children { + match child { + DirEntry::File(file) => entries.push(file), + DirEntry::Dir(dir) => entries.append(&mut dir.flatten()), + } + } + + entries + } +} + +pub struct File { + pub content: Vec, + pub path: PathBuf, +} + +fn read_dir(path: &PathBuf) -> Vec { + let mut entries = Vec::new(); + + for entry in fs::read_dir(path).expect("Failed to list directory contents") { + let entry = entry.expect("Failed to read entry"); + + let filetype = entry.file_type().expect("Failed to read entry filetype"); + let path = entry + .path() + .canonicalize() + .expect("Failed to get the canonical path of the DirEntry"); + + if filetype.is_dir() { + let children = read_dir(&path); + + entries.push(DirEntry::Dir(Dir { children, path })) + } else if filetype.is_file() { + let content = fs::read(&path).expect("Failed to read file contents"); + + entries.push(DirEntry::File(File { content, path })) + } + } + + entries +} + +pub fn __include_dir(path: &str) -> Dir { + let path = PathBuf::from(path) + .canonicalize() + .expect("Failed to get the canonical path of the DirEntry"); + + let children = read_dir(&path); + + Dir { children, path } +} + +#[macro_export] +macro_rules! dir { + ($path:literal) => {{ + #[cfg(debug_assertions)] + { + ::embed::__include_dir($path) + } + #[cfg(not(debug_assertions))] + { + ::embed_macros::__include_dir!($path) + } + }}; +} diff --git a/src/file.rs b/embed/src/file.rs similarity index 100% rename from src/file.rs rename to embed/src/file.rs diff --git a/embed/src/lib.rs b/embed/src/lib.rs new file mode 100644 index 0000000..54d51d6 --- /dev/null +++ b/embed/src/lib.rs @@ -0,0 +1,5 @@ +mod dir; +pub use dir::*; + +mod file; +pub use file::*; diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..9c4b391 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "embed_macros" +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" ] +categories = [ "filesystem" ] +authors = [ "RGBCube" ] +version = "0.1.0" +edition = "2021" + +[lib] +proc_macro = true + +[dependencies] +quote = "1" +syn = "2" diff --git a/macros/README.md b/macros/README.md new file mode 100644 index 0000000..85f49e8 --- /dev/null +++ b/macros/README.md @@ -0,0 +1,43 @@ +# 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 our own custom `include_dir!` implementation. + +## Usage + +```rs +let contents: Cow<'_, str> = embed::string!("path/to/file.txt"); +let bytes: Cow<'_, [u8]> = embed::bytes!("path/to/image.png"); + +let dir: embed::Dir = embed::dir!("path/to"); +``` + +## 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/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..b3d3b5a --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,80 @@ +use std::{ + fs, + path::PathBuf, +}; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{ + parse_macro_input, + LitStr, +}; + +#[proc_macro] +pub fn __include_dir(tokens: TokenStream) -> TokenStream { + let path = parse_macro_input!(tokens as LitStr).value(); + + let path = PathBuf::from(path) + .canonicalize() + .expect("Failed to get the canonical path of the DirEntry"); + + let path_str = path + .to_str() + .expect("Failed to get the string representation of PathBuf"); + + let children = read_dir(&path); + let children_tokens = quote! { + vec![#(#children),*] + }; + + TokenStream::from(quote! { + ::embed::Dir { + children: #children_tokens, + path: ::std::path::PathBuf::from(#path_str), + } + }) +} + +fn read_dir(path: &PathBuf) -> Vec { + let mut entries = Vec::new(); + + for entry in fs::read_dir(path).expect("Failed to list directory contents") { + let entry = entry.expect("Failed to read entry"); + + let path = entry + .path() + .canonicalize() + .expect("Failed to get the canonical path of the DirEntry"); + + let path_str = path + .to_str() + .expect("Failed to get the string representation of PathBuf"); + + let filetype = fs::metadata(path) + .expect("Failed to get file metadata") + .file_type(); + + if filetype.is_dir() { + let children = read_dir(&path); + let children_tokens = quote! { + vec![#(#children),*] + }; + + entries.push(quote! { + ::embed::DirEntry(::embed::Dir { + children: #children_tokens, + path: ::std::path::PathBuf::from(#path_str), + }) + }); + } else if filetype.is_file() { + entries.push(quote! { + ::embed::DirEntry(::embed::File { + content: ::include_bytes!(#path_str), + path: ::std::path::PathBuf::from(#path_str), + }) + }); + } + } + + entries +} diff --git a/src/dir.rs b/src/dir.rs deleted file mode 100644 index a3b938a..0000000 --- a/src/dir.rs +++ /dev/null @@ -1,138 +0,0 @@ -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/lib.rs b/src/lib.rs deleted file mode 100644 index 48a9329..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -// mod dir; -// pub use dir::*; - -mod file; -pub use file::*;