use std::collections::VecDeque;
use std::fmt::Debug;
use std::fs::{read_dir, DirEntry};
use std::path::PathBuf;
pub struct Walker {
root: PathBuf,
skip_dotted: bool,
skip_directories: Vec<std::path::PathBuf>,
max_depth: usize,
max_entries: usize,
_counter: usize,
}
impl Walker {
pub fn new(root: impl AsRef<std::path::Path>) -> Walker {
Walker {
root: root.as_ref().to_path_buf(),
skip_dotted: Default::default(),
skip_directories: Default::default(),
max_entries: 10_000,
max_depth: 100,
_counter: 0,
}
}
pub fn skip_dotted(mut self) -> Walker {
self.skip_dotted = true;
self
}
pub fn skip_directories(mut self, directories: &[impl AsRef<std::path::Path>]) -> Walker {
self.skip_directories = directories
.iter()
.map(|d| d.as_ref().canonicalize().unwrap())
.collect();
self
}
pub fn max_depth(mut self, depth: usize) -> Walker {
self.max_depth = depth;
self
}
pub fn max_entries(mut self, max: usize) -> Walker {
self.max_entries = max;
self
}
pub fn walk_dir(&mut self) -> Result<Entry, std::io::Error> {
let root = self.root.canonicalize()?;
let root_entry = get_parent_entry(&root)?;
let children = self.walk_dir_inner(&root, 0)?;
let entries = Entry::new(children, Some(root_entry), 0);
Ok(entries)
}
fn walk_dir_inner(
&mut self,
path: impl AsRef<std::path::Path>,
depth: usize,
) -> Result<Vec<Entry>, std::io::Error> {
let mut children: Vec<Entry> = Vec::new();
let entries = self.read_entries(&path)?;
for entry in entries.into_iter() {
self._counter += 1;
if self._counter == self.max_entries {
return Ok(children);
}
if depth <= self.max_depth {
children.push(Entry::new(
self.walk_dir_inner(entry.path().as_path(), depth + 1)?,
Some(entry),
depth,
));
if self._counter >= self.max_entries {
return Ok(children);
}
}
}
Ok(children)
}
fn read_entries(
&self,
path: impl AsRef<std::path::Path>,
) -> Result<Vec<DirEntry>, std::io::Error> {
let mut paths: Vec<DirEntry> = Vec::new();
let mut dirs = self.get_entries(&path, true)?;
let mut files = self.get_entries(&path, false)?;
paths.append(&mut dirs);
paths.append(&mut files);
Ok(paths)
}
fn get_entries(
&self,
entry: impl AsRef<std::path::Path>,
dirs_only: bool,
) -> Result<Vec<DirEntry>, std::io::Error> {
let mut entries: Vec<DirEntry> = Vec::new();
if entry.as_ref().is_dir() {
read_dir(entry)?
.filter_map(|e| e.ok())
.filter(|e| self.should_skip(e.path()))
.filter(|e| !e.path().is_symlink())
.filter(|e| {
if dirs_only {
e.path().is_dir()
} else {
e.path().is_file()
}
})
.for_each(|e| entries.push(e));
entries.sort_by_key(|f| f.path());
}
Ok(entries)
}
fn should_skip(&self, path: impl AsRef<std::path::Path>) -> bool {
let path_str = path.as_ref().display().to_string();
!((self.skip_dotted & (path_str.contains("/.") | path_str.contains("\\.")))
| self.skip_directories.contains(&path.as_ref().to_path_buf()))
}
}
#[derive(Debug)]
pub struct Entry {
pub dirent: Option<DirEntry>,
pub children: Vec<Entry>,
pub depth: usize,
}
impl Entry {
pub(crate) fn new(children: Vec<Entry>, dirent: Option<DirEntry>, depth: usize) -> Entry {
Entry {
children,
dirent,
depth,
}
}
pub fn find(self, name: &str) -> Option<Entry> {
let mut queue: VecDeque<Entry> = VecDeque::new();
queue.push_back(self);
while let Some(mut node) = queue.pop_front() {
if let Some(ref dirent) = node.dirent {
if let Some(label) = dirent.file_name().to_str() {
if label == name {
return Some(node);
}
}
}
node.children.reverse();
let children = VecDeque::from(node.children);
children.into_iter().for_each(|c| queue.push_front(c));
}
None
}
}
#[derive(Debug)]
pub struct EntryItem {
pub dirent: DirEntry,
pub depth: usize,
}
impl EntryItem {
pub fn new(dirent: DirEntry, depth: usize) -> EntryItem {
EntryItem { dirent, depth }
}
}
impl IntoIterator for Entry {
type Item = EntryItem;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut queue: VecDeque<Entry> = VecDeque::new();
let mut flat_vec: Vec<EntryItem> = Vec::new();
queue.push_back(self);
while let Some(mut node) = queue.pop_front() {
if let Some(dirent) = node.dirent {
flat_vec.push(EntryItem::new(dirent, node.depth));
}
node.children.reverse();
let children = VecDeque::from(node.children);
children.into_iter().for_each(|c| queue.push_front(c));
}
flat_vec.into_iter()
}
}
fn get_parent_entry(path: &PathBuf) -> Result<DirEntry, std::io::Error> {
let invalid_input_err = |msg: &str| std::io::Error::new(std::io::ErrorKind::InvalidInput, msg);
let parent_entry = path.parent().unwrap();
let entry = read_dir(parent_entry)
.expect("Error: could not get the parent directory of the root")
.filter_map(|e| e.ok())
.filter(|e| e.path() == path.as_path())
.collect::<Vec<DirEntry>>();
let root_entry = entry.into_iter().next().ok_or(invalid_input_err(
"Error: could not find the root directory",
));
root_entry
}