use std::ffi::OsStr;
use std::fs::FileType;
use std::path::{Path, PathBuf};
use walkdir::{DirEntry, FilterEntry, IntoIter, WalkDir};
use crate::config::Config;
use crate::error::{MyError, MyResult};
use crate::metadata::Metadata;
pub trait Flags {
fn is_file(&self) -> bool;
fn is_dir(&self) -> bool;
fn is_symlink(&self) -> bool;
}
pub trait Entry<F: Flags + Copy> {
fn path(&self) -> &Path;
fn file_name(&self) -> &OsStr;
fn file_type(&self) -> F;
fn depth(&self) -> usize;
fn metadata(&self) -> MyResult<Metadata<F>>;
fn read_link(&self) -> MyResult<Option<PathBuf>>;
}
pub trait System<F: Flags + Copy, E: Entry<F>, I: Iterator<Item = MyResult<E>>> {
fn entries(&self, abs_root: &Path, rel_root: &Path) -> I;
fn metadata(&self, path: &Path) -> MyResult<Metadata<F>>;
}
#[derive(Copy, Clone)]
pub struct FileFlags {
file_type: FileType,
}
pub struct FileEntry {
entry: DirEntry,
}
pub struct FileIterator {
iterator: FilterEntry<IntoIter, fn(&DirEntry) -> bool>,
}
pub struct FileSystem<'a> {
config: &'a Config,
}
impl FileFlags {
pub fn new(file_type: FileType) -> FileFlags {
Self { file_type }
}
}
impl FileEntry {
fn new(entry: DirEntry) -> FileEntry {
Self { entry }
}
}
impl FileIterator {
fn new(iterator: FilterEntry<IntoIter, fn(&DirEntry) -> bool>) -> FileIterator {
Self { iterator }
}
}
impl<'a> FileSystem<'a> {
pub fn new(config: &'a Config) -> FileSystem {
Self { config }
}
}
impl Flags for FileFlags {
fn is_file(&self) -> bool {
self.file_type.is_file()
}
fn is_dir(&self) -> bool {
self.file_type.is_dir()
}
fn is_symlink(&self) -> bool {
self.file_type.is_symlink()
}
}
impl Entry<FileFlags> for FileEntry {
fn path(&self) -> &Path {
self.entry.path()
}
fn file_name(&self) -> &OsStr {
self.entry.file_name()
}
fn file_type(&self) -> FileFlags {
FileFlags::new(self.entry.file_type())
}
fn depth(&self) -> usize {
self.entry.depth()
}
fn metadata(&self) -> MyResult<Metadata<FileFlags>> {
Metadata::from_entry(&self.entry)
}
fn read_link(&self) -> MyResult<Option<PathBuf>> {
return if self.entry.path_is_symlink() {
let link = self.entry.path().read_link()?;
Ok(Some(link))
} else {
Ok(None)
}
}
}
impl Iterator for FileIterator {
type Item = MyResult<FileEntry>;
fn next(&mut self) -> Option<Self::Item> {
match self.iterator.next() {
Some(Ok(entry)) => Some(Ok(FileEntry::new(entry))),
Some(Err(error)) => Some(Err(MyError::from(error))),
None => None,
}
}
}
impl<'a> System<FileFlags, FileEntry, FileIterator> for FileSystem<'a> {
fn entries(&self, abs_root: &Path, _rel_root: &Path) -> FileIterator {
let mut walker = WalkDir::new(abs_root).min_depth(1);
if let Some(depth) = self.config.max_depth {
walker = walker.max_depth(depth);
}
let filter = self.choose_filter();
let iterator = walker.into_iter().filter_entry(filter);
return FileIterator::new(iterator);
}
fn metadata(&self, path: &Path) -> MyResult<Metadata<FileFlags>> {
Metadata::from_path(path)
}
}
impl<'a> FileSystem<'a> {
fn choose_filter(&self) -> fn(&DirEntry) -> bool {
if self.config.all_recurse {
|_| true
} else if self.config.all_files {
Self::filter_hidden_parent
} else {
Self::filter_hidden_entry
}
}
fn filter_hidden_parent(entry: &DirEntry) -> bool {
if let Some(parent) = entry.path().parent() {
if let Some(name) = parent.file_name() {
Self::filter_hidden_name(name.to_str())
} else {
true
}
} else {
true
}
}
fn filter_hidden_entry(entry: &DirEntry) -> bool {
Self::filter_hidden_name(entry.file_name().to_str())
}
fn filter_hidden_name(name: Option<&str>) -> bool {
if let Some(name) = name {
if name.starts_with(".") {
return false;
}
if name.starts_with("__") && name.ends_with("__") {
return false;
}
}
return true;
}
}
#[cfg(test)]
mod tests {
use crate::system::FileSystem;
#[test]
fn test_hides_hidden_files() {
assert_eq!(true, FileSystem::filter_hidden_name(None));
assert_eq!(true, FileSystem::filter_hidden_name(Some("")));
assert_eq!(true, FileSystem::filter_hidden_name(Some("visible")));
assert_eq!(true, FileSystem::filter_hidden_name(Some("visible__")));
assert_eq!(true, FileSystem::filter_hidden_name(Some("_visible_")));
assert_eq!(true, FileSystem::filter_hidden_name(Some("__visible")));
assert_eq!(false, FileSystem::filter_hidden_name(Some(".hidden")));
assert_eq!(false, FileSystem::filter_hidden_name(Some("__hidden__")));
}
}