1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
/// Depth of recursion through kids.
pub type KidsDepth = u8;
/// Depth of recursion through parents.
pub type ParentsDepth = u8;
/// The direction in which `find_folder` should search for the folder.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Search {
/// Search recursively through parent directories with the given depth.
Parents(ParentsDepth),
/// Search recursively through children directories with the given depth.
Kids(KidsDepth),
/// Search parents and then kids (same as `Both`).
ParentsThenKids(ParentsDepth, KidsDepth),
/// Search kids and then parents.
KidsThenParents(KidsDepth, ParentsDepth),
}
/// A search defined as a starting path and a route to take.
///
/// Don't instantiate this type directly. Instead, use `Search::of`.
#[derive(Clone, Debug)]
pub struct SearchFolder {
/// The starting path of the search.
pub start: PathBuf,
/// The route to take while searching.
pub direction: Search,
}
/// If the search was unsuccessful.
#[derive(Debug)]
pub enum Error {
/// Some std io Error occurred.
IO(::std::io::Error),
/// The directory requested was not found.
NotFound,
}
impl ::std::convert::From<io::Error> for Error {
fn from(io_err: io::Error) -> Error {
Error::IO(io_err)
}
}
impl ::std::fmt::Display for Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
writeln!(f, "{:?}", *self)
}
}
impl ::std::error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::IO(ref io_err) => ::std::error::Error::description(io_err),
Error::NotFound => "The folder could not be found",
}
}
}
impl Search {
/// An easy API method for finding a folder with a given name.
/// i.e. `Search::Kids(u8).for_folder("assets")`
pub fn for_folder(&self, target: &str) -> Result<PathBuf, Error> {
let cwd = try!(::std::env::current_dir());
self.of(cwd).for_folder(target)
}
/// Use this to search in a specific folder.
///
/// This method transforms a `Search` into a `SearchFolder`, but that detail is mostly
/// irrelevant. See the example for recommended usage.
///
/// # Example
///
/// ```
/// use find_folder::Search;
///
/// let mut exe_folder = std::env::current_exe().unwrap();
/// exe_folder.pop(); // Remove the executable's name, leaving the path to the containing folder
/// let resource_path = Search::KidsThenParents(1, 2).of(exe_folder).for_folder("resources");
/// ```
pub fn of(self, start: PathBuf) -> SearchFolder {
SearchFolder {
start: start,
direction: self,
}
}
}
impl SearchFolder {
/// Search for a folder with the given name.
pub fn for_folder(&self, target: &str) -> Result<PathBuf, Error> {
match self.direction {
Search::Parents(depth) => check_parents(depth, target, &self.start),
Search::Kids(depth) => check_kids(depth, target, &self.start),
Search::ParentsThenKids(parents_depth, kids_depth) => {
match check_parents(parents_depth, target, &self.start) {
Err(Error::NotFound) => check_kids(kids_depth, target, &self.start),
other_result => other_result,
}
},
Search::KidsThenParents(kids_depth, parents_depth) => {
match check_kids(kids_depth, target, &self.start) {
Err(Error::NotFound) => check_parents(parents_depth, target, &self.start),
other_result => other_result,
}
},
}
}
}
/// Check the contents of this folder and children folders.
pub fn check_kids(depth: u8, name: &str, path: &Path) -> Result<PathBuf, Error> {
match check_dir(name, path) {
err @ Err(Error::NotFound) => match depth > 0 {
true => {
for entry in try!(fs::read_dir(path)) {
let entry = try!(entry);
let entry_path = entry.path();
if try!(fs::metadata(&entry_path)).is_dir() {
if let Ok(folder) = check_kids(depth-1, name, &entry_path) {
return Ok(folder);
}
}
}
err
},
false => err,
},
other_result => other_result,
}
}
/// Check the given path and `depth` number of parent directories for a folder with the given name.
pub fn check_parents(depth: u8, name: &str, path: &Path) -> Result<PathBuf, Error> {
match check_dir(name, path) {
err @ Err(Error::NotFound) => match depth > 0 {
true => match path.parent() {
None => err,
Some(parent) => check_parents(depth-1, name, parent),
},
false => err,
},
other_result => other_result,
}
}
/// Check the given directory for a folder with the matching name.
pub fn check_dir(name: &str, path: &Path) -> Result<PathBuf, Error> {
for entry in try!(fs::read_dir(path)) {
let entry = try!(entry);
let entry_path = entry.path();
if entry_path.ends_with(name) {
return Ok(entry_path)
}
}
Err(Error::NotFound)
}