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 170 171 172 173 174 175 176 177 178 179 180 181
/*!
Crate `gitall` provides an simple API for recursively finding all Git
directories below a given directory.
# Example
The following code prints the directory paths of each Git directory located
beneath the current directory:
```
use gitall::Giterator;
use std::default::Default;
let giterator = Giterator::default();
for direntry in giterator {
println!("{}", direntry.path().display());
}
```
This snippet only finds Git directories whose name matches a regular expression:
```
use gitall::Giterator;
use regex::Regex;
use std::default::Default;
let giterator = Giterator::default().regex(Regex::new(r"some-pattern-.+").unwrap());
for direntry in giterator {
println!("{}", direntry.path().display());
}
```
Check the [`Giterator`][] documentation for more ways to filter results.
*/
use regex::Regex;
use walkdir::{DirEntry, WalkDir};
use std::{
default::Default,
iter,
path::{Path, PathBuf},
};
#[derive(Debug)]
struct GiteratorOptions {
root: PathBuf,
follow_links: bool,
max_depth: usize,
regex: Regex,
match_full_path: bool,
}
/// A builder to define how to search for Git directories.
#[derive(Debug)]
pub struct Giterator {
options: GiteratorOptions,
}
impl Giterator {
/// Sets the apex root directory to begin searching. The directory itself
/// will not be considered for inclusion in the final results; only
/// directories within `root`.
pub fn root<P: AsRef<Path>>(mut self, root: P) -> Self {
self.options.root = root.as_ref().to_path_buf();
self
}
/// Sets wether or not to follow symbolic links in the directory tree.
pub fn follow_links(mut self, follow: bool) -> Self {
self.options.follow_links = follow;
self
}
/// Sets the maximum number of directories from `root` to search. Directories
/// more than `depth` directories deeper than `root` will not be found.
pub fn max_depth(mut self, depth: usize) -> Self {
self.options.max_depth = depth;
self
}
/// Sets the regular expression that must match directory paths.
pub fn regex(mut self, regex: Regex) -> Self {
self.options.regex = regex;
self
}
/// Sets wether or not to match `regex` against the full
/// [canonicalized][canonical] directory path.
///
/// [canonical]: std::path::Path::canonicalize
pub fn match_full_path(mut self, match_full_path: bool) -> Self {
self.options.match_full_path = match_full_path;
self
}
}
impl Default for Giterator {
/// Creates a default Giterator with the following settings:
/// - search the current working directory
/// - do not follow symbolic links
/// - search [`usize::MAX`][] directories deep
/// - match any directory path
fn default() -> Self {
Giterator {
options: GiteratorOptions {
root: Path::new(".").to_path_buf(),
follow_links: false,
max_depth: usize::MAX,
regex: Regex::new(".*").unwrap(),
match_full_path: false,
},
}
}
}
impl iter::IntoIterator for Giterator {
type Item = DirEntry;
type IntoIter = IntoIter;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
walker: WalkDir::new(self.options.root).into_iter(),
regex: self.options.regex,
full_path: self.options.match_full_path,
}
}
}
#[derive(Debug)]
/// An iterator for recursively finding Git directories.
pub struct IntoIter {
walker: ::walkdir::IntoIter,
regex: Regex,
full_path: bool,
}
impl iter::Iterator for IntoIter {
type Item = DirEntry;
fn next(&mut self) -> Option<Self::Item> {
loop {
let entry = match self.walker.next() {
None => return None, // no more directories to walk
Some(Err(_err)) => {
// TODO provide a debug log message
self.walker.skip_current_dir();
continue;
}
Some(Ok(entry)) => entry,
};
if is_git_dir(&entry) {
self.walker.skip_current_dir();
if is_matching_dir(&entry, &self.regex, self.full_path) {
return Some(entry);
}
}
}
}
}
/// Returns true if the directory represented by `entry` is a Git directory root.
pub fn is_git_dir(entry: &DirEntry) -> bool {
entry.file_type().is_dir() && entry.path().join(".git").is_dir()
}
/// Returns true if the directory represented by `entry` matches the given
/// regular express. If `full_path` is true, the regex matches against the
/// [canonicalized][canonical] path of `entry`.
///
/// [canonical]: std::path::Path::canonicalize
fn is_matching_dir(entry: &DirEntry, regex: &Regex, full_path: bool) -> bool {
let canonical_path = entry
.path()
.canonicalize()
.expect("failed to canonicalize the directory path");
let pathname: &str = if full_path {
canonical_path.to_str().unwrap()
} else {
entry.file_name().to_str().unwrap()
};
regex.is_match(pathname)
}