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 e4f30c4579..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" @@ -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/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 19e15ce34c..a911669856 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 //! @@ -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,26 +48,16 @@ extern crate glob; mod dir; mod file; +mod fs; mod globs; -pub use dir::Dir; -pub use file::File; - -#[doc(hidden)] -pub use include_dir_impl::*; +pub use crate::dir::{Dir, DirEntry}; +pub use crate::file::File; +pub use crate::fs::FileSystem; -#[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/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/Cargo.toml b/include_dir_impl/Cargo.toml index 5a5a3f7124..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" @@ -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..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; +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 { + abs_path: PathBuf, + root_rel_path: PathBuf, + entries: Vec, +} #[derive(Debug, Clone, PartialEq)] -pub struct Dir { - pub root_rel_path: PathBuf, - pub abs_path: PathBuf, - pub files: Vec, - pub dirs: Vec, +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,38 +58,48 @@ 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() } } impl ToTokens for Dir { fn to_tokens(&self, tokens: &mut TokenStream) { - let root_rel_path = self.root_rel_path.display().to_string(); - let files = &self.files; - let dirs = &self.dirs; + let root_rel_path = self.root_rel_path.to_str() + .expect("path should contain valid UTF-8 characters"); + + 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!{ - Dir { + $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 b4839aa341..8ad99347bd 100644 --- a/include_dir_impl/src/file.rs +++ b/include_dir_impl/src/file.rs @@ -1,33 +1,44 @@ -use failure::{Error}; use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::{ToTokens, quote}; use std::path::{Path, PathBuf}; +use std::ffi::OsStr; #[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 { - 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 } + } + + pub fn file_name(&self) -> Option<&OsStr> { + self.root_rel_path.file_name() } } 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 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!{ - File { + $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 250b79321e..622936381c 100644 --- a/include_dir_impl/src/lib.rs +++ b/include_dir_impl/src/lib.rs @@ -2,45 +2,43 @@ //! //! [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! { + $crate::FileSystem { + root: $crate::DirEntry::Dir(#dir), + } + }) }