From c56274f50d2e842985d15718fa3e0fd5a724885f Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Fri, 1 Mar 2019 23:24:12 -0800 Subject: [PATCH 1/7] Update to proc-macro-hack 0.5 --- include_dir/Cargo.toml | 3 +-- include_dir/src/lib.rs | 16 ++----------- include_dir_impl/Cargo.toml | 4 ++-- include_dir_impl/src/dir.rs | 4 ++-- include_dir_impl/src/file.rs | 4 ++-- include_dir_impl/src/lib.rs | 44 ++++++++++++++++-------------------- 6 files changed, 29 insertions(+), 46 deletions(-) diff --git a/include_dir/Cargo.toml b/include_dir/Cargo.toml index e4f30c4579..2539ba998f 100644 --- a/include_dir/Cargo.toml +++ b/include_dir/Cargo.toml @@ -20,10 +20,9 @@ repository = "Michael-F-Bryan/include_dir" [dependencies] glob = "0.2.11" -proc-macro-hack = "0.4" +proc-macro-hack = "0.5" [dependencies.include_dir_impl] -#version = "0.2.1" path = "../include_dir_impl" [features] diff --git a/include_dir/src/lib.rs b/include_dir/src/lib.rs index 19e15ce34c..d1b2d54088 100644 --- a/include_dir/src/lib.rs +++ b/include_dir/src/lib.rs @@ -56,20 +56,8 @@ pub use dir::Dir; pub use file::File; #[doc(hidden)] -pub use include_dir_impl::*; - -#[macro_export] -#[doc(hidden)] -/// Hack used by `include_dir_impl` which can't access `$crate`. -macro_rules! __include_dir_use_everything { - () => { - pub use $crate::*; - }; -} - -proc_macro_expr_decl! { - include_dir! => include_dir_impl -} +#[proc_macro_hack] +pub use include_dir_impl::include_dir; /// Example the output generated when running `include_dir!()` on itself. #[cfg(feature = "example-output")] diff --git a/include_dir_impl/Cargo.toml b/include_dir_impl/Cargo.toml index 5a5a3f7124..30751eb0ea 100644 --- a/include_dir_impl/Cargo.toml +++ b/include_dir_impl/Cargo.toml @@ -17,8 +17,8 @@ branch = "master" repository = "Michael-F-Bryan/include_dir" [dependencies] -proc-macro-hack = "0.4" -syn = "0.14.1" +proc-macro-hack = "0.5" +syn = "0.15" quote = "0.6.3" failure = "0.1.1" proc-macro2 = "0.4.4" diff --git a/include_dir_impl/src/dir.rs b/include_dir_impl/src/dir.rs index 2a7ebcf9f1..7f2d604f1f 100644 --- a/include_dir_impl/src/dir.rs +++ b/include_dir_impl/src/dir.rs @@ -1,7 +1,7 @@ use failure::{self, Error, ResultExt}; use file::File; use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::{ToTokens, quote}; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, PartialEq)] @@ -47,7 +47,7 @@ impl ToTokens for Dir { let dirs = &self.dirs; let tok = quote!{ - Dir { + $crate::Dir { path: #root_rel_path, files: &[#( #files diff --git a/include_dir_impl/src/file.rs b/include_dir_impl/src/file.rs index b4839aa341..6495d8a4c3 100644 --- a/include_dir_impl/src/file.rs +++ b/include_dir_impl/src/file.rs @@ -1,6 +1,6 @@ use failure::{Error}; use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::{ToTokens, quote}; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, PartialEq)] @@ -26,7 +26,7 @@ impl ToTokens for File { let abs_path = self.abs_path.display().to_string(); let tok = quote!{ - File { + $crate::File { path: #root_rel_path, contents: include_bytes!(#abs_path), } diff --git a/include_dir_impl/src/lib.rs b/include_dir_impl/src/lib.rs index 250b79321e..8d27997a46 100644 --- a/include_dir_impl/src/lib.rs +++ b/include_dir_impl/src/lib.rs @@ -2,45 +2,41 @@ //! //! [include_dir!()]: https://github.com/Michael-F-Bryan/include_dir -#[macro_use] +extern crate proc_macro; extern crate proc_macro_hack; extern crate failure; extern crate proc_macro2; extern crate syn; -#[macro_use] extern crate quote; -mod dir; -mod file; +use proc_macro::TokenStream; +use proc_macro_hack::proc_macro_hack; +use quote::quote; +use syn::{parse_macro_input, LitStr}; use dir::Dir; use std::env; use std::path::PathBuf; -use syn::LitStr; -proc_macro_expr_impl! { - /// Add one to an expression. - pub fn include_dir_impl(input: &str) -> String { - let input: LitStr = syn::parse_str(input) - .expect("include_dir!() only accepts a single string argument"); - let crate_root = env::var("CARGO_MANIFEST_DIR").unwrap(); +mod dir; +mod file; - let path = PathBuf::from(crate_root) - .join(input.value()); +#[proc_macro_hack] +pub fn include_dir(input: TokenStream) -> TokenStream { + let input: LitStr = parse_macro_input!(input as LitStr); + let crate_root = env::var("CARGO_MANIFEST_DIR").unwrap(); - if !path.exists() { - panic!("\"{}\" doesn't exist", path.display()); - } + let path = PathBuf::from(crate_root).join(input.value()); - let path = path.canonicalize().expect("Can't normalize the path"); + if !path.exists() { + panic!("\"{}\" doesn't exist", path.display()); + } - let dir = Dir::from_disk(&path, &path).expect("Couldn't load the directory"); + let path = path.canonicalize().expect("Can't normalize the path"); - let tokens = quote!({ - __include_dir_use_everything!(); - #dir - }); + let dir = Dir::from_disk(&path, &path).expect("Couldn't load the directory"); - tokens.to_string() - } + TokenStream::from(quote! { + #dir + }) } From cfd52704cd4116248467fc8e52f7fc7e57447a12 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Fri, 1 Mar 2019 23:27:30 -0800 Subject: [PATCH 2/7] These don't need to be public --- include_dir_impl/src/dir.rs | 10 +++++----- include_dir_impl/src/file.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include_dir_impl/src/dir.rs b/include_dir_impl/src/dir.rs index 7f2d604f1f..c96239d74d 100644 --- a/include_dir_impl/src/dir.rs +++ b/include_dir_impl/src/dir.rs @@ -5,11 +5,11 @@ use quote::{ToTokens, quote}; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, PartialEq)] -pub struct Dir { - pub root_rel_path: PathBuf, - pub abs_path: PathBuf, - pub files: Vec, - pub dirs: Vec, +pub(crate) struct Dir { + root_rel_path: PathBuf, + abs_path: PathBuf, + files: Vec, + dirs: Vec, } impl Dir { diff --git a/include_dir_impl/src/file.rs b/include_dir_impl/src/file.rs index 6495d8a4c3..e72f35ef7a 100644 --- a/include_dir_impl/src/file.rs +++ b/include_dir_impl/src/file.rs @@ -4,9 +4,9 @@ use quote::{ToTokens, quote}; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, PartialEq)] -pub struct File { - pub root_rel_path: PathBuf, - pub abs_path: PathBuf, +pub(crate) struct File { + root_rel_path: PathBuf, + abs_path: PathBuf, } impl File { From 9ef34e659f5f83f936badeab25f804d3d46c79c8 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Fri, 1 Mar 2019 23:27:46 -0800 Subject: [PATCH 3/7] this is also an extension to include_bytes!() --- include_dir/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include_dir/src/lib.rs b/include_dir/src/lib.rs index d1b2d54088..007ba0662d 100644 --- a/include_dir/src/lib.rs +++ b/include_dir/src/lib.rs @@ -1,5 +1,5 @@ -//! An extension to the `include_str!()` macro for embedding an entire directory -//! tree into your binary. +//! An extension to the `include_str!()` and `include_bytes!()` macro for embedding an entire +//! directory tree into your binary. //! //! # Examples //! From 1e5c56d11ea457297a00beec09aae9e8202b1226 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Sat, 2 Mar 2019 12:10:31 -0800 Subject: [PATCH 4/7] bump include_dir to 0.2.2, include_dir_impl to 0.3.0 The interface include_dir exposes hasn't changed, so I've only done a minor bump to 0.2.2, but include_dir_impl now exports `include_dir`, so I've given it a major bump. It also raises the minimum tested version to rust 1.31.0, the first version that added support for editions. --- .travis.yml | 2 +- include_dir/Cargo.toml | 2 +- include_dir_impl/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e88e8486ac..a0ae095df7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: rust rust: - stable - - 1.26.0 + - 1.31.0 - nightly cache: cargo diff --git a/include_dir/Cargo.toml b/include_dir/Cargo.toml index 2539ba998f..e2bde78a86 100644 --- a/include_dir/Cargo.toml +++ b/include_dir/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Michael Bryan "] name = "include_dir" -version = "0.2.1" +version = "0.2.2" description = "Embed the contents of a directory in your binary" license = "MIT" readme = "../README.md" diff --git a/include_dir_impl/Cargo.toml b/include_dir_impl/Cargo.toml index 30751eb0ea..053f32fc12 100644 --- a/include_dir_impl/Cargo.toml +++ b/include_dir_impl/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Michael Bryan "] name = "include_dir_impl" -version = "0.2.1" +version = "0.3.0" description = "Implementation crate for include_dir" license = "MIT" readme = "../README.md" From fa2a00734de413b5fe7a1b305a629bf3f533cc7f Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Sun, 3 Mar 2019 22:29:23 -0800 Subject: [PATCH 5/7] Panic if paths cannot be encoded into a `&str`. Not all operating systems guarantee that paths can be converted to unicode. So to be safe, this PR panics instead of silently replacing invalid unicode characters with the unicode replacement character. --- include_dir_impl/src/dir.rs | 3 ++- include_dir_impl/src/file.rs | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include_dir_impl/src/dir.rs b/include_dir_impl/src/dir.rs index c96239d74d..a92776297d 100644 --- a/include_dir_impl/src/dir.rs +++ b/include_dir_impl/src/dir.rs @@ -42,7 +42,8 @@ impl Dir { impl ToTokens for Dir { fn to_tokens(&self, tokens: &mut TokenStream) { - let root_rel_path = self.root_rel_path.display().to_string(); + let root_rel_path = self.root_rel_path.to_str() + .expect("path should contain valid UTF-8 characters"); let files = &self.files; let dirs = &self.dirs; diff --git a/include_dir_impl/src/file.rs b/include_dir_impl/src/file.rs index e72f35ef7a..81c2047402 100644 --- a/include_dir_impl/src/file.rs +++ b/include_dir_impl/src/file.rs @@ -22,8 +22,10 @@ impl File { impl ToTokens for File { fn to_tokens(&self, tokens: &mut TokenStream) { - let root_rel_path = self.root_rel_path.display().to_string(); - let abs_path = self.abs_path.display().to_string(); + let root_rel_path = self.root_rel_path.to_str() + .expect("path should contain valid UTF-8 characters"); + let abs_path = self.abs_path.to_str() + .expect("path should contain valid UTF-8 characters"); let tok = quote!{ $crate::File { From 35fbcf02b7bf74e91dc25a4ebf5853ac16e45fe6 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Tue, 5 Mar 2019 09:12:28 -0800 Subject: [PATCH 6/7] File::from_disk cannot error. --- include_dir_impl/src/dir.rs | 2 +- include_dir_impl/src/file.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/include_dir_impl/src/dir.rs b/include_dir_impl/src/dir.rs index a92776297d..e253164528 100644 --- a/include_dir_impl/src/dir.rs +++ b/include_dir_impl/src/dir.rs @@ -30,7 +30,7 @@ impl Dir { let entry = entry?.path(); if entry.is_file() { - files.push(File::from_disk(&root, entry)?); + files.push(File::from_disk(&root, entry)); } else if entry.is_dir() { dirs.push(Dir::from_disk(&root, entry)?); } diff --git a/include_dir_impl/src/file.rs b/include_dir_impl/src/file.rs index 81c2047402..0bd50d82d5 100644 --- a/include_dir_impl/src/file.rs +++ b/include_dir_impl/src/file.rs @@ -1,4 +1,3 @@ -use failure::{Error}; use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use std::path::{Path, PathBuf}; @@ -10,13 +9,13 @@ pub(crate) struct File { } impl File { - pub fn from_disk, P: Into>(root: Q, path: P) -> Result { + pub fn from_disk, P: Into>(root: Q, path: P) -> File { let abs_path = path.into(); let root = root.as_ref(); let root_rel_path = abs_path.strip_prefix(&root).unwrap().to_path_buf(); - Ok(File { abs_path, root_rel_path }) + File { abs_path, root_rel_path } } } From d64457362641be7a854f4c12a87683f9b39d68bd Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Tue, 5 Mar 2019 09:17:55 -0800 Subject: [PATCH 7/7] Rewrite find files in O(lg(n)), and clean up the api This PR makes some large changes to the library, in order to reduce finding a file from O(n) to O(lg(n)), by maintaining the directory entries in a sorted order, and binary searching for a file. In order to accomplish this, I changed how directories work. Instead of each directory supporting the ability to look up a path relative to the root directory, I instead introduce a `FileSystem` that is the entrypoint for resolving a path into contents. This is more analogous to how std::fs operates. --- include_dir/src/dir.rs | 123 ++++++++++++-------------- include_dir/src/file.rs | 9 ++ include_dir/src/fs.rs | 50 +++++++++++ include_dir/src/globs.rs | 40 ++++----- include_dir/src/lib.rs | 12 +-- include_dir/tests/integration_test.rs | 14 +-- include_dir_impl/src/dir.rs | 78 ++++++++++++---- include_dir_impl/src/file.rs | 10 +++ include_dir_impl/src/lib.rs | 4 +- 9 files changed, 216 insertions(+), 124 deletions(-) create mode 100644 include_dir/src/fs.rs diff --git a/include_dir/src/dir.rs b/include_dir/src/dir.rs index 21d511894a..c27b360630 100644 --- a/include_dir/src/dir.rs +++ b/include_dir/src/dir.rs @@ -1,91 +1,78 @@ -use file::File; -use glob::{Pattern, PatternError}; -use globs::{DirEntry, Globs}; -use std::path::Path; +use std::path::{Iter as PathIter, Path}; +use std::ffi::OsStr; + +use crate::file::File; /// A directory entry. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct Dir<'a> { - #[doc(hidden)] - pub path: &'a str, - #[doc(hidden)] - pub files: &'a [File<'a>], - #[doc(hidden)] - pub dirs: &'a [Dir<'a>], +pub enum DirEntry<'a> { + /// A directory. + Dir(Dir<'a>), + + /// A file. + File(File<'a>), } -impl<'a> Dir<'a> { +impl<'a> DirEntry<'a> { /// Get the directory's path. pub fn path(&self) -> &'a Path { - Path::new(self.path) - } - - /// Get a list of the files in this directory. - pub fn files(&self) -> &'a [File<'a>] { - self.files - } - - /// Get a list of the sub-directories inside this directory. - pub fn dirs(&self) -> &'a [Dir<'a>] { - self.dirs - } - - /// Does this directory contain `path`? - pub fn contains>(&self, path: S) -> bool { - let path = path.as_ref(); - - self.get_file(path).is_some() || self.get_dir(path).is_some() + match self { + DirEntry::Dir(dir) => dir.path(), + DirEntry::File(file) => file.path(), + } } - /// Fetch a sub-directory by *exactly* matching its path relative to the - /// directory included with `include_dir!()`. - pub fn get_dir>(&self, path: S) -> Option { - let path = path.as_ref(); - - for dir in self.dirs { - if Path::new(dir.path) == path { - return Some(*dir); - } - - if let Some(d) = dir.get_dir(path) { - return Some(d); - } + pub(crate) fn file_name(&self) -> &'a OsStr { + match self { + DirEntry::File(file) => file.file_name(), + DirEntry::Dir(dir) => dir.file_name(), } - - None } - /// Fetch a sub-directory by *exactly* matching its path relative to the - /// directory included with `include_dir!()`. - pub fn get_file>(&self, path: S) -> Option { - let path = path.as_ref(); - - for file in self.files { - if Path::new(file.path) == path { - return Some(*file); + pub(crate) fn get(&self, mut iter: PathIter) -> Option<&DirEntry> { + match (self, iter.next()) { + (entry, None) => { + Some(entry) } - } - - for dir in self.dirs { - if let Some(d) = dir.get_file(path) { - return Some(d); + (DirEntry::File(_), Some(_)) => { + // Can't desend into a file. + None + } + (DirEntry::Dir(dir), Some(name)) => { + if let Ok(pos) = dir.entries.binary_search_by_key(&name, |e| e.file_name()) { + dir.entries[pos].get(iter) + } else { + None + } } } - - None } +} - /// Search for a file or directory with a glob pattern. - pub fn find(&self, glob: &str) -> Result>, PatternError> { - let pattern = Pattern::new(glob)?; +/// A directory. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Dir<'a> { + #[doc(hidden)] + pub path: &'a str, + #[doc(hidden)] + pub file_name: &'a str, + #[doc(hidden)] + pub entries: &'a [DirEntry<'a>], +} - Ok(Globs::new(pattern, *self)) +impl<'a> Dir<'a> { + /// Get the directory's path. + pub fn path(&self) -> &'a Path { + Path::new(self.path) } - pub(crate) fn dir_entries(&self) -> impl Iterator> { - let files = self.files().into_iter().map(|f| DirEntry::File(*f)); - let dirs = self.dirs().into_iter().map(|d| DirEntry::Dir(*d)); + /// The directory's name. + pub fn file_name(&self) -> &'a OsStr { + OsStr::new(self.file_name) + } - files.chain(dirs) + /// Get a list of the entries in this directory. + pub fn entries(&self) -> &'a [DirEntry<'a>] { + self.entries } } diff --git a/include_dir/src/file.rs b/include_dir/src/file.rs index 39095cc498..6693533600 100644 --- a/include_dir/src/file.rs +++ b/include_dir/src/file.rs @@ -1,3 +1,4 @@ +use std::ffi::OsStr; use std::fmt::{self, Debug, Formatter}; use std::path::Path; use std::str; @@ -8,6 +9,8 @@ pub struct File<'a> { #[doc(hidden)] pub path: &'a str, #[doc(hidden)] + pub file_name: &'a str, + #[doc(hidden)] pub contents: &'a [u8], } @@ -18,6 +21,11 @@ impl<'a> File<'a> { Path::new(self.path) } + /// The file's name. + pub fn file_name(&self) -> &'a OsStr { + OsStr::new(self.file_name) + } + /// The file's raw contents. pub fn contents(&self) -> &'a [u8] { self.contents @@ -33,6 +41,7 @@ impl<'a> Debug for File<'a> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.debug_struct("File") .field("path", &self.path) + .field("file_name", &self.file_name) .field("contents", &format!("<{} bytes>", self.contents.len())) .finish() } diff --git a/include_dir/src/fs.rs b/include_dir/src/fs.rs new file mode 100644 index 0000000000..42c8ced755 --- /dev/null +++ b/include_dir/src/fs.rs @@ -0,0 +1,50 @@ +use glob::{Pattern, PatternError}; +use std::path::Path; + +use crate::file::File; +use crate::dir::{Dir, DirEntry}; +use crate::globs::Globs; + +/// The file system that includes all the files and directories included with `include_dir!()`. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct FileSystem<'a> { + #[doc(hidden)] + pub root: DirEntry<'a>, +} + +impl<'a> FileSystem<'a> { + /// Does this directory contain `path`? + pub fn contains>(&self, path: S) -> bool { + self.get(path).is_some() + } + + /// Does this file system contain `path`? + pub fn get>(&self, path: S) -> Option<&DirEntry> { + self.root.get(path.as_ref().iter()) + } + + /// Does this file system contain a file `path`? + pub fn get_file>(&self, path: S) -> Option<&File> { + if let Some(DirEntry::File(file)) = self.get(path) { + Some(file) + } else { + None + } + } + + /// Does this file system contain a directory `path`? + pub fn get_dir>(&self, path: S) -> Option<&Dir> { + if let Some(DirEntry::Dir(dir)) = self.get(path) { + Some(dir) + } else { + None + } + } + + /// Search for a file or directory with a glob pattern. + pub fn find(&self, glob: &str) -> Result>, PatternError> { + let pattern = Pattern::new(glob)?; + + Ok(Globs::new(pattern, self.root)) + } +} diff --git a/include_dir/src/globs.rs b/include_dir/src/globs.rs index 9740999f4f..024ed9ceea 100644 --- a/include_dir/src/globs.rs +++ b/include_dir/src/globs.rs @@ -1,7 +1,6 @@ -use dir::Dir; -use file::File; use glob::Pattern; -use std::path::Path; + +use crate::dir::DirEntry; #[derive(Debug, Clone, PartialEq)] pub struct Globs<'a> { @@ -10,14 +9,20 @@ pub struct Globs<'a> { } impl<'a> Globs<'a> { - pub(crate) fn new(pattern: Pattern, root: Dir<'a>) -> Globs<'a> { - let stack = vec![DirEntry::Dir(root)]; - Globs { stack, pattern } + pub(crate) fn new(pattern: Pattern, item: DirEntry<'a>) -> Globs<'a> { + let mut globs = Globs { + stack: vec![], + pattern, + }; + + globs.fill_buffer(item); + + globs } - fn fill_buffer(&mut self, item: &DirEntry<'a>) { - if let DirEntry::Dir(ref dir) = *item { - self.stack.extend(dir.dir_entries()); + fn fill_buffer(&mut self, item: DirEntry<'a>) { + if let DirEntry::Dir(dir) = item { + self.stack.extend(dir.entries()); } } } @@ -27,7 +32,7 @@ impl<'a> Iterator for Globs<'a> { fn next(&mut self) -> Option { while let Some(item) = self.stack.pop() { - self.fill_buffer(&item); + self.fill_buffer(item); if self.pattern.matches_path(item.path()) { return Some(item); @@ -37,18 +42,3 @@ impl<'a> Iterator for Globs<'a> { None } } - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum DirEntry<'a> { - File(File<'a>), - Dir(Dir<'a>), -} - -impl<'a> DirEntry<'a> { - pub fn path(&self) -> &'a Path { - match *self { - DirEntry::File(f) => f.path(), - DirEntry::Dir(d) => d.path(), - } - } -} diff --git a/include_dir/src/lib.rs b/include_dir/src/lib.rs index 007ba0662d..a911669856 100644 --- a/include_dir/src/lib.rs +++ b/include_dir/src/lib.rs @@ -11,10 +11,10 @@ //! #[macro_use] //! extern crate include_dir; //! -//! use include_dir::Dir; +//! use include_dir::FileSystem; //! use std::path::Path; //! -//! const PROJECT_DIR: Dir = include_dir!("."); +//! const PROJECT_DIR: FileSystem = include_dir!("."); //! //! # fn main() { //! // of course, you can retrieve a file by its full path @@ -41,8 +41,6 @@ #![deny(missing_docs, missing_copy_implementations, missing_debug_implementations)] -#[allow(unused_imports)] -#[macro_use] extern crate include_dir_impl; #[macro_use] extern crate proc_macro_hack; @@ -50,10 +48,12 @@ extern crate glob; mod dir; mod file; +mod fs; mod globs; -pub use dir::Dir; -pub use file::File; +pub use crate::dir::{Dir, DirEntry}; +pub use crate::file::File; +pub use crate::fs::FileSystem; #[doc(hidden)] #[proc_macro_hack] diff --git a/include_dir/tests/integration_test.rs b/include_dir/tests/integration_test.rs index e1bdc92e08..faf5b7a4b3 100644 --- a/include_dir/tests/integration_test.rs +++ b/include_dir/tests/integration_test.rs @@ -1,31 +1,31 @@ #[macro_use] extern crate include_dir; -use include_dir::Dir; +use include_dir::FileSystem; use std::path::Path; -const PARENT_DIR: Dir = include_dir!("."); +const FS: FileSystem = include_dir!("."); #[test] fn included_all_files() { let root = Path::new(env!("CARGO_MANIFEST_DIR")); - println!("{:#?}", PARENT_DIR); + println!("{:#?}", FS); - validate_directory(PARENT_DIR, root, root); + validate_directory(&FS, root, root); } -fn validate_directory(dir: Dir, path: &Path, root: &Path) { +fn validate_directory(fs: &FileSystem, path: &Path, root: &Path) { for entry in path.read_dir().unwrap() { let entry = entry.unwrap().path(); let entry = entry.strip_prefix(root).unwrap(); let name = entry.file_name().unwrap(); - assert!(dir.contains(entry), "Can't find {}", entry.display()); + assert!(fs.contains(entry), "Can't find {}", entry.display()); if entry.is_dir() { let child_path = path.join(name); - validate_directory(dir.get_dir(entry).unwrap(), &child_path, root); + validate_directory(fs, &child_path, root); } } } diff --git a/include_dir_impl/src/dir.rs b/include_dir_impl/src/dir.rs index e253164528..f3ac0699c0 100644 --- a/include_dir_impl/src/dir.rs +++ b/include_dir_impl/src/dir.rs @@ -1,15 +1,50 @@ use failure::{self, Error, ResultExt}; -use file::File; use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use std::path::{Path, PathBuf}; +use std::ffi::OsStr; + +use crate::file::File; #[derive(Debug, Clone, PartialEq)] pub(crate) struct Dir { - root_rel_path: PathBuf, abs_path: PathBuf, - files: Vec, - dirs: Vec, + root_rel_path: PathBuf, + entries: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +enum DirEntry { + File(File), + Dir(Dir), +} + +impl DirEntry { + fn file_name(&self) -> Option<&OsStr> { + match self { + DirEntry::File(file) => file.file_name(), + DirEntry::Dir(dir) => dir.file_name(), + } + } +} + +impl ToTokens for DirEntry { + fn to_tokens(&self, tokens: &mut TokenStream) { + let tok = match self { + DirEntry::File(file) => { + quote!{ + $crate::DirEntry::File(#file) + } + } + DirEntry::Dir(dir) => { + quote!{ + $crate::DirEntry::Dir(#dir) + } + } + }; + + tok.to_tokens(tokens); + } } impl Dir { @@ -23,20 +58,25 @@ impl Dir { return Err(failure::err_msg("The directory doesn't exist")); } - let mut files = Vec::new(); - let mut dirs = Vec::new(); + let mut entries = Vec::new(); for entry in abs_path.read_dir().context("Couldn't read the directory")? { let entry = entry?.path(); if entry.is_file() { - files.push(File::from_disk(&root, entry)); + entries.push(DirEntry::File(File::from_disk(&root, entry))); } else if entry.is_dir() { - dirs.push(Dir::from_disk(&root, entry)?); + entries.push(DirEntry::Dir(Dir::from_disk(&root, entry)?)); } } - Ok(Dir { root_rel_path, abs_path, files, dirs }) + entries.sort_unstable_by(|a, b| a.file_name().cmp(&b.file_name())); + + Ok(Dir { abs_path, root_rel_path, entries }) + } + + pub fn file_name(&self) -> Option<&OsStr> { + self.root_rel_path.file_name() } } @@ -44,18 +84,22 @@ impl ToTokens for Dir { fn to_tokens(&self, tokens: &mut TokenStream) { let root_rel_path = self.root_rel_path.to_str() .expect("path should contain valid UTF-8 characters"); - let files = &self.files; - let dirs = &self.dirs; + + let file_name = if let Some(file_name) = self.root_rel_path.file_name() { + file_name.to_str() + .expect("path cannot contain invalid UTF-8 characters") + } else { + "" + }; + let entries = &self.entries; let tok = quote!{ $crate::Dir { path: #root_rel_path, - files: &[#( - #files - ),*], - dirs: &[#( - #dirs - ),*], + file_name: #file_name, + entries: &[#( + #entries + ),*], } }; diff --git a/include_dir_impl/src/file.rs b/include_dir_impl/src/file.rs index 0bd50d82d5..8ad99347bd 100644 --- a/include_dir_impl/src/file.rs +++ b/include_dir_impl/src/file.rs @@ -1,6 +1,7 @@ use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use std::path::{Path, PathBuf}; +use std::ffi::OsStr; #[derive(Debug, Clone, PartialEq)] pub(crate) struct File { @@ -17,6 +18,10 @@ impl File { File { abs_path, root_rel_path } } + + pub fn file_name(&self) -> Option<&OsStr> { + self.root_rel_path.file_name() + } } impl ToTokens for File { @@ -25,10 +30,15 @@ impl ToTokens for File { .expect("path should contain valid UTF-8 characters"); let abs_path = self.abs_path.to_str() .expect("path should contain valid UTF-8 characters"); + let file_name = self.root_rel_path.file_name() + .expect("path should contain a file name") + .to_str() + .expect("path should only contain valid UTF-8 characters"); let tok = quote!{ $crate::File { path: #root_rel_path, + file_name: #file_name, contents: include_bytes!(#abs_path), } }; diff --git a/include_dir_impl/src/lib.rs b/include_dir_impl/src/lib.rs index 8d27997a46..622936381c 100644 --- a/include_dir_impl/src/lib.rs +++ b/include_dir_impl/src/lib.rs @@ -37,6 +37,8 @@ pub fn include_dir(input: TokenStream) -> TokenStream { let dir = Dir::from_disk(&path, &path).expect("Couldn't load the directory"); TokenStream::from(quote! { - #dir + $crate::FileSystem { + root: $crate::DirEntry::Dir(#dir), + } }) }