diff --git a/README.md b/README.md index 06be106026..70eb7facf4 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ let lib_rs = PROJECT_DIR.get_file("src/lib.rs").unwrap(); let body = lib_rs.contents_utf8().unwrap(); assert!(body.contains("SOME_INTERESTING_STRING")); +// you can filter which files are included using a glob pattern +static RUST_FILES: Dir = include_dir!("$CARGO_MANIFEST_DIR", "**/*.rs"); + // you can search for files (and directories) using glob patterns #[cfg(feature = "glob")] { diff --git a/include_dir/src/lib.rs b/include_dir/src/lib.rs index b9380b931c..e2524b2f00 100644 --- a/include_dir/src/lib.rs +++ b/include_dir/src/lib.rs @@ -33,7 +33,10 @@ //! let body = lib_rs.contents_utf8().unwrap(); //! assert!(body.contains("SOME_INTERESTING_STRING")); //! -//! // if you enable the `glob` feature, you can for files (and directories) using glob patterns +//! // you can filter which files are included using a glob pattern +//! static RUST_FILES: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR", "**/*.rs"); +//! +//! // or search for files (and directories) using glob patterns after inclusion //! #[cfg(feature = "glob")] //! { //! let glob = "**/*.rs"; diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 8990fbc7e4..2df8188cdb 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -17,6 +17,7 @@ proc-macro = true [dependencies] proc-macro2 = "1" quote = "1" +glob = "0.3" [features] nightly = [] diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 4a881f029b..16d93f58d7 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -12,20 +12,25 @@ use std::{ path::{Path, PathBuf}, time::SystemTime, }; +use glob::Pattern; /// Embed the contents of a directory in your crate. #[proc_macro] pub fn include_dir(input: TokenStream) -> TokenStream { let tokens: Vec<_> = input.into_iter().collect(); - let path = match tokens.as_slice() { - [TokenTree::Literal(lit)] => unwrap_string_literal(lit), - _ => panic!("This macro only accepts a single, non-empty string argument"), + let (path, glob_pattern) = match tokens.as_slice() { + [TokenTree::Literal(lit)] => (unwrap_string_literal(lit), None), + [TokenTree::Literal(lit1), TokenTree::Punct(_), TokenTree::Literal(lit2)] => { + (unwrap_string_literal(lit1), Some(unwrap_string_literal(lit2))) + } + _ => panic!("This macro accepts either a single string argument (directory path) or two string arguments (directory path and glob pattern)"), }; let path = resolve_path(&path, get_env).unwrap(); + let glob_pattern = glob_pattern.map(|p| Pattern::new(&p).expect("Invalid glob pattern")); - expand_dir(&path, &path).into() + expand_dir(&path, &path, glob_pattern.as_ref()).into() } fn unwrap_string_literal(lit: &proc_macro::Literal) -> String { @@ -40,7 +45,7 @@ fn unwrap_string_literal(lit: &proc_macro::Literal) -> String { repr } -fn expand_dir(root: &Path, path: &Path) -> proc_macro2::TokenStream { +fn expand_dir(root: &Path, path: &Path, glob_pattern: Option<&Pattern>) -> proc_macro2::TokenStream { let children = read_dir(path).unwrap_or_else(|e| { panic!( "Unable to read the entries in \"{}\": {}", @@ -53,15 +58,18 @@ fn expand_dir(root: &Path, path: &Path) -> proc_macro2::TokenStream { for child in children { if child.is_dir() { - let tokens = expand_dir(root, &child); + let tokens = expand_dir(root, &child, glob_pattern); child_tokens.push(quote! { include_dir::DirEntry::Dir(#tokens) }); } else if child.is_file() { - let tokens = expand_file(root, &child); - child_tokens.push(quote! { - include_dir::DirEntry::File(#tokens) - }); + let normalized_path = normalize_path(root, &child); + if glob_pattern.map_or(true, |p| p.matches(&normalized_path)) { + let tokens = expand_file(root, &child); + child_tokens.push(quote! { + include_dir::DirEntry::File(#tokens) + }); + } } else { panic!("\"{}\" is neither a file nor a directory", child.display()); }