use crate::fs::feature::git::GitCache;
use crate::fs::fields::GitStatus;
use std::fs;
use std::fs::DirEntry;
use std::io;
use std::path::{Path, PathBuf};
use std::slice::Iter as SliceIter;
use log::info;
use crate::fs::File;
pub struct Dir {
contents: Vec<DirEntry>,
pub path: PathBuf,
}
impl Dir {
pub fn new(path: PathBuf) -> Self {
Self {
contents: vec![],
path,
}
}
pub fn read(&mut self) -> io::Result<&Self> {
info!("Reading directory {:?}", &self.path);
self.contents = fs::read_dir(&self.path)?.collect::<Result<Vec<_>, _>>()?;
info!("Read directory success {:?}", &self.path);
Ok(self)
}
pub fn read_dir(path: PathBuf) -> io::Result<Self> {
info!("Reading directory {:?}", &path);
let contents = fs::read_dir(&path)?.collect::<Result<Vec<_>, _>>()?;
info!("Read directory success {:?}", &path);
Ok(Self { contents, path })
}
#[must_use]
pub fn files<'dir, 'ig>(
&'dir self,
dots: DotFilter,
git: Option<&'ig GitCache>,
git_ignoring: bool,
deref_links: bool,
total_size: bool,
) -> Files<'dir, 'ig> {
Files {
inner: self.contents.iter(),
dir: self,
dotfiles: dots.shows_dotfiles(),
dots: dots.dots(),
git,
git_ignoring,
deref_links,
total_size,
}
}
#[must_use]
pub fn contains(&self, path: &Path) -> bool {
self.contents.iter().any(|p| p.path().as_path() == path)
}
#[must_use]
pub fn join(&self, child: &Path) -> PathBuf {
self.path.join(child)
}
}
#[allow(clippy::struct_excessive_bools)]
pub struct Files<'dir, 'ig> {
inner: SliceIter<'dir, DirEntry>,
dir: &'dir Dir,
dotfiles: bool,
dots: DotsNext,
git: Option<&'ig GitCache>,
git_ignoring: bool,
deref_links: bool,
total_size: bool,
}
impl<'dir> Files<'dir, '_> {
fn parent(&self) -> PathBuf {
self.dir.path.join("..")
}
fn next_visible_file(&mut self) -> Option<File<'dir>> {
loop {
if let Some(entry) = self.inner.next() {
let path = entry.path();
let filename = File::filename(&path);
if !self.dotfiles && filename.starts_with('.') {
continue;
}
#[cfg(windows)]
if !self.dotfiles && filename.starts_with('_') {
continue;
}
if self.git_ignoring {
let git_status = self.git.map(|g| g.get(&path, false)).unwrap_or_default();
if git_status.unstaged == GitStatus::Ignored {
continue;
}
}
let file = File::from_args(
path,
self.dir,
filename,
self.deref_links,
self.total_size,
entry.file_type().ok(),
);
#[cfg(windows)]
if !self.dotfiles && file.attributes().map_or(false, |a| a.hidden) {
continue;
}
return Some(file);
}
return None;
}
}
}
enum DotsNext {
Dot,
DotDot,
Files,
}
impl<'dir> Iterator for Files<'dir, '_> {
type Item = File<'dir>;
fn next(&mut self) -> Option<Self::Item> {
match self.dots {
DotsNext::Dot => {
self.dots = DotsNext::DotDot;
Some(File::new_aa_current(self.dir, self.total_size))
}
DotsNext::DotDot => {
self.dots = DotsNext::Files;
Some(File::new_aa_parent(
self.parent(),
self.dir,
self.total_size,
))
}
DotsNext::Files => self.next_visible_file(),
}
}
}
#[derive(PartialEq, Eq, Debug, Default, Copy, Clone)]
pub enum DotFilter {
DotfilesAndDots,
Dotfiles,
#[default]
JustFiles,
}
impl DotFilter {
fn shows_dotfiles(self) -> bool {
match self {
Self::JustFiles => false,
Self::Dotfiles => true,
Self::DotfilesAndDots => true,
}
}
fn dots(self) -> DotsNext {
match self {
Self::JustFiles => DotsNext::Files,
Self::Dotfiles => DotsNext::Files,
Self::DotfilesAndDots => DotsNext::Dot,
}
}
}