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)
}