exa/src/fs/dir.rs

212 lines
6.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::fs::feature::git::GitCache;
use crate::fs::fields::GitStatus;
use std::io::{self, Result as IOResult};
use std::fs;
use std::path::{Path, PathBuf};
use std::slice::Iter as SliceIter;
use log::*;
use crate::fs::File;
/// A **Dir** provides a cached list of the file paths in a directory thats
/// being listed.
///
/// This object gets passed to the Files themselves, in order for them to
/// check the existence of surrounding files, then highlight themselves
/// accordingly. (See `File#get_source_files`)
pub struct Dir {
/// A vector of the files that have been read from this directory.
contents: Vec<PathBuf>,
/// The path that was read.
pub path: PathBuf,
}
impl Dir {
/// Create a new Dir object filled with all the files in the directory
/// pointed to by the given path. Fails if the directory cant be read, or
/// isnt actually a directory, or if theres an IO error that occurs at
/// any point.
///
/// The `read_dir` iterator doesnt actually yield the `.` and `..`
/// entries, so if the user wants to see them, well have to add them
/// ourselves after the files have been read.
pub fn read_dir(path: PathBuf) -> IOResult<Self> {
info!("Reading directory {:?}", &path);
let contents = fs::read_dir(&path)?
.map(|result| result.map(|entry| entry.path()))
.collect::<Result<_, _>>()?;
Ok(Self { contents, path })
}
/// Produce an iterator of IO results of trying to read all the files in
/// this directory.
pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool) -> Files<'dir, 'ig> {
Files {
inner: self.contents.iter(),
dir: self,
dotfiles: dots.shows_dotfiles(),
dots: dots.dots(),
git,
git_ignoring,
}
}
/// Whether this directory contains a file with the given path.
pub fn contains(&self, path: &Path) -> bool {
self.contents.iter().any(|p| p.as_path() == path)
}
/// Append a path onto the path specified by this directory.
pub fn join(&self, child: &Path) -> PathBuf {
self.path.join(child)
}
}
/// Iterator over reading the contents of a directory as `File` objects.
pub struct Files<'dir, 'ig> {
/// The internal iterator over the paths that have been read already.
inner: SliceIter<'dir, PathBuf>,
/// The directory that begat those paths.
dir: &'dir Dir,
/// Whether to include dotfiles in the list.
dotfiles: bool,
/// Whether the `.` or `..` directories should be produced first, before
/// any files have been listed.
dots: DotsNext,
git: Option<&'ig GitCache>,
git_ignoring: bool,
}
impl<'dir, 'ig> Files<'dir, 'ig> {
fn parent(&self) -> PathBuf {
// We cant use `Path#parent` here because all it does is remove the
// last path component, which is no good for us if the path is
// relative. For example, while the parent of `/testcases/files` is
// `/testcases`, the parent of `.` is an empty path. Adding `..` on
// the end is the only way to get to the *actual* parent directory.
self.dir.path.join("..")
}
/// Go through the directory until we encounter a file we can list (which
/// varies depending on the dotfile visibility flag)
fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {
loop {
if let Some(path) = self.inner.next() {
let filename = File::filename(path);
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;
}
}
return Some(File::from_args(path.clone(), self.dir, filename)
.map_err(|e| (path.clone(), e)))
}
else {
return None
}
}
}
}
/// The dot directories that need to be listed before actual files, if any.
/// If these arent being printed, then `FilesNext` is used to skip them.
enum DotsNext {
/// List the `.` directory next.
Dot,
/// List the `..` directory next.
DotDot,
/// Forget about the dot directories and just list files.
Files,
}
impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
fn next(&mut self) -> Option<Self::Item> {
match self.dots {
DotsNext::Dot => {
self.dots = DotsNext::DotDot;
Some(File::new_aa_current(self.dir)
.map_err(|e| (Path::new(".").to_path_buf(), e)))
}
DotsNext::DotDot => {
self.dots = DotsNext::Files;
Some(File::new_aa_parent(self.parent(), self.dir)
.map_err(|e| (self.parent(), e)))
}
DotsNext::Files => {
self.next_visible_file()
}
}
}
}
/// Usually files in Unix use a leading dot to be hidden or visible, but two
/// entries in particular are “extra-hidden”: `.` and `..`, which only become
/// visible after an extra `-a` option.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum DotFilter {
/// Shows files, dotfiles, and `.` and `..`.
DotfilesAndDots,
/// Show files and dotfiles, but hide `.` and `..`.
Dotfiles,
/// Just show files, hiding anything beginning with a dot.
JustFiles,
}
impl Default for DotFilter {
fn default() -> Self {
Self::JustFiles
}
}
impl DotFilter {
/// Whether this filter should show dotfiles in a listing.
fn shows_dotfiles(self) -> bool {
match self {
Self::JustFiles => false,
Self::Dotfiles => true,
Self::DotfilesAndDots => true,
}
}
/// Whether this filter should add dot directories to a listing.
fn dots(self) -> DotsNext {
match self {
Self::JustFiles => DotsNext::Files,
Self::Dotfiles => DotsNext::Files,
Self::DotfilesAndDots => DotsNext::Dot,
}
}
}