Skip to content

Introduces O(lg(n)) file retrieval and several API changes #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,8 @@ To-Do list:

- File metadata
- Compression?

## Limitations

- Currently `include_dir!()` does not support files/directories that cannot be represented as UTF-8.
This is also a limitation of `include_bytes!()` and `include_str!()`
90 changes: 33 additions & 57 deletions include_dir/src/dir.rs
Original file line number Diff line number Diff line change
@@ -1,75 +1,51 @@
use crate::file::File;
use std::path::Path;

use crate::DirEntry;
use std::convert::TryInto;

/// 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>],
path: &'a str,
entries: &'a [DirEntry<'a>]
}

impl<'a> Dir<'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
/// Create a new [`Dir`]
pub const fn new(path: &'a str, entries: &'a [DirEntry<'_>]) -> Self {
Self {
path,
entries
}
}

/// Get a list of the sub-directories inside this directory.
pub fn dirs(&self) -> &'a [Dir<'a>] {
self.dirs
/// The directory's path relative to the directory included with [include_dir!()]
pub fn path(&self) -> &Path {
Path::new(self.path)
}

/// Does this directory contain `path`?
pub fn contains<S: AsRef<Path>>(&self, path: S) -> bool {
let path = path.as_ref();

self.get_file(path).is_some() || self.get_dir(path).is_some()
/// Retrieve the entries within the directory
pub fn entries(&self) -> &[DirEntry<'_>] {
self.entries
}

/// Fetch a sub-directory by *exactly* matching its path relative to the
/// directory included with `include_dir!()`.
pub fn get_dir<S: AsRef<Path>>(&self, path: S) -> Option<Dir<'_>> {
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);
}
}

None
}

/// Fetch a sub-directory by *exactly* matching its path relative to the
/// directory included with `include_dir!()`.
pub fn get_file<S: AsRef<Path>>(&self, path: S) -> Option<File<'_>> {
let path = path.as_ref();

for file in self.files {
if Path::new(file.path) == path {
return Some(*file);
}
}

for dir in self.dirs {
if let Some(d) = dir.get_file(path) {
return Some(d);
}
}

None
/// Return an iterator over all files contained within the directory
pub fn files(&self) -> impl Iterator<Item=&File<'_>> {
self
.entries
.iter()
.map(TryInto::try_into)
.filter_map(Result::ok)
}

/// Return an iterator over all sub-directories within the directory
pub fn dirs(&self) -> impl Iterator<Item=&Dir<'_>> {
self
.entries
.iter()
.map(TryInto::try_into)
.filter_map(Result::ok)
}
}
132 changes: 132 additions & 0 deletions include_dir/src/direntry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use std::path::Path;
use std::path;

use crate::{File, Dir};
use std::convert::TryFrom;

/// An entry within the embedded filesystem representation
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DirEntry<'a> {
/// A directory
Dir(Dir<'a>),
/// A regular file
File(File<'a>)
}


impl DirEntry<'_> {
/// The [`Path`] that corresponds to the entry
pub fn path(&self) -> &'_ Path {
match self {
DirEntry::Dir(dir) => dir.path(),
DirEntry::File(file) => file.path(),
}
}

/// Traverses the directory sub-tree from this entry
fn traverse(&self, path_iter: &mut path::Iter<'_>) -> Option<&'_ DirEntry<'_>> {
match (path_iter.next(), self) {
// If there are no more components, this is the chosen path
(None, _) => {
Some(self)
},
// If there are more components and we are in a directory, keep searching if able
(Some(child), DirEntry::Dir(current_dir)) => {
current_dir.entries()
.binary_search_by_key(&child.into(), |entry| entry.path().file_name())
.ok()
.map(|index| &current_dir.entries()[index])
.and_then(|child_entry| child_entry.traverse(path_iter))
}
// otherwise we are a file then there is nowhere else to search, so we give up
(Some(_), DirEntry::File(_)) => None,
}
}

/// Attempts to retrieve the path from the sub-tree
pub fn get(&self, path: impl AsRef<Path>) -> Option<&DirEntry<'_>> {
self.traverse(&mut path.as_ref().iter())
}

/// Attempts to retrieve the path from the sub-tree as a [`Dir`]
pub fn get_dir(&self, path: impl AsRef<Path>) -> Option<&Dir<'_>> {
match self.traverse(&mut path.as_ref().iter()) {
Some(DirEntry::Dir(dir)) => Some(dir),
_ => None
}
}

/// Attempts to retrieve a path from the sub-tree as a [`File`]
pub fn get_file(&self, path: impl AsRef<Path>) -> Option<&File<'_>> {
match self.traverse(&mut path.as_ref().iter()) {
Some(DirEntry::File(file)) => Some(file),
_=> None
}
}

/// Returns true if the entry corresponds to a [`DirEntry::Dir`]
pub fn is_dir(&self) -> bool {
if let DirEntry::Dir(_) = *self {
true
} else {
false
}
}

/// Returns true if the entry corresponds to a regular [`DirEntry::File`]
pub fn is_file(&self) -> bool {
if let DirEntry::File(_) = *self {
true
} else {
false
}
}
}

impl<'a> TryFrom<DirEntry<'a>> for Dir<'a> {
type Error = ();

fn try_from(entry: DirEntry<'a>) -> Result<Self, Self::Error> {
if let DirEntry::Dir(dir) = entry {
Ok(dir)
} else {
Err(())
}
}
}

impl<'a> TryFrom<&'a DirEntry<'a>> for &Dir<'a> {
type Error = ();

fn try_from(entry: &'a DirEntry<'a>) -> Result<Self, Self::Error> {
if let DirEntry::Dir(dir) = entry {
Ok(dir)
} else {
Err(())
}
}
}

impl<'a> TryFrom<DirEntry<'a>> for File<'a> {
type Error = ();

fn try_from(entry: DirEntry<'a>) -> Result<Self, Self::Error> {
if let DirEntry::File(file) = entry {
Ok(file)
} else {
Err(())
}
}
}

impl<'a> TryFrom<&'a DirEntry<'a>> for &File<'a> {
type Error = ();

fn try_from(entry: &'a DirEntry<'a>) -> Result<Self, Self::Error> {
if let DirEntry::File(file) = entry {
Ok(file)
} else {
Err(())
}
}
}
26 changes: 15 additions & 11 deletions include_dir/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,32 @@ use std::str;
/// A file with its contents stored in a `&'static [u8]`.
#[derive(Copy, Clone, PartialEq)]
pub struct File<'a> {
#[doc(hidden)]
pub path: &'a str,
#[doc(hidden)]
pub contents: &'a [u8],
path: &'a str,
contents: &'a [u8],
}

impl<'a> File<'a> {
/// The file's path, relative to the directory included with
/// `include_dir!()`.
pub fn path(&self) -> &'a Path {
Path::new(self.path)
/// Create a new [`File`]
pub const fn new(path: &'a str, contents: &'a [u8]) -> Self {
Self {
path,
contents,
}
}

/// The file's raw contents.
pub fn contents(&self) -> &'a [u8] {
pub fn contents(&self) -> &[u8] {
self.contents
}

/// The file's contents interpreted as a string.
pub fn contents_utf8(&self) -> Option<&'a str> {
pub fn contents_utf8(&self) -> Option<&str> {
str::from_utf8(self.contents()).ok()
}

/// Returns the File's path relative to the directory included with `include_dir!()`.
pub fn path(&self) -> &Path {
Path::new(self.path)
}
}

impl<'a> Debug for File<'a> {
Expand Down
51 changes: 11 additions & 40 deletions include_dir/src/globs.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,36 @@
use crate::dir::Dir;
use crate::file::File;
use crate::direntry::DirEntry;
use glob::{Pattern, PatternError};
use std::path::Path;

#[derive(Debug, Clone, PartialEq)]
pub struct Globs<'a> {
stack: Vec<DirEntry<'a>>,
stack: Vec<&'a DirEntry<'a>>,
pattern: Pattern,
}

impl<'a> Dir<'a> {
impl DirEntry<'_> {
/// Search for a file or directory with a glob pattern.
pub fn find(&self, glob: &str) -> Result<impl Iterator<Item = DirEntry<'a>>, PatternError> {
pub fn find(&self, glob: &str) -> Result<impl Iterator<Item = &'_ DirEntry<'_>>, PatternError> {
let pattern = Pattern::new(glob)?;

Ok(Globs::new(pattern, *self))
}

pub(crate) fn dir_entries(&self) -> impl Iterator<Item = DirEntry<'a>> {
let files = self.files().iter().map(|f| DirEntry::File(*f));
let dirs = self.dirs().iter().map(|d| DirEntry::Dir(*d));

files.chain(dirs)
Ok(Globs::new(pattern, self))
}
}

impl<'a> Globs<'a> {
pub(crate) fn new(pattern: Pattern, root: Dir<'a>) -> Globs<'a> {
let stack = vec![DirEntry::Dir(root)];
pub(crate) fn new(pattern: Pattern, root: &'a DirEntry<'a>) -> Globs<'a> {
let stack = vec![root];
Globs { stack, pattern }
}

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: &'a DirEntry<'a>) {
if let DirEntry::Dir(dir) = item {
self.stack.extend(dir.entries());
}
}
}

impl<'a> Iterator for Globs<'a> {
type Item = DirEntry<'a>;
type Item = &'a DirEntry<'a>;

fn next(&mut self) -> Option<Self::Item> {
while let Some(item) = self.stack.pop() {
Expand All @@ -49,26 +40,6 @@ impl<'a> Iterator for Globs<'a> {
return Some(item);
}
}

None
}
}

/// Entries returned by the Globs iterator
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum DirEntry<'a> {
/// A file with its contents stored in a &'static [u8].
File(File<'a>),
/// A directory entry.
Dir(Dir<'a>),
}

impl<'a> DirEntry<'a> {
/// Get the entries's path
pub fn path(&self) -> &'a Path {
match *self {
DirEntry::File(f) => f.path(),
DirEntry::Dir(d) => d.path(),
}
}
}
Loading