use std::cell::OnceCell; use std::ffi::OsString; use std::fs::{FileType, Metadata}; use std::path::{Path, PathBuf}; use lscolors::{Colorable, LsColors, Style}; use crate::config::Config; use crate::filesystem::strip_current_dir; #[derive(Debug)] enum DirEntryInner { Normal(ignore::DirEntry), BrokenSymlink(PathBuf), } #[derive(Debug)] pub struct DirEntry { inner: DirEntryInner, metadata: OnceCell>, style: OnceCell>, } impl DirEntry { #[inline] pub fn normal(e: ignore::DirEntry) -> Self { Self { inner: DirEntryInner::Normal(e), metadata: OnceCell::new(), style: OnceCell::new(), } } pub fn broken_symlink(path: PathBuf) -> Self { Self { inner: DirEntryInner::BrokenSymlink(path), metadata: OnceCell::new(), style: OnceCell::new(), } } pub fn path(&self) -> &Path { match &self.inner { DirEntryInner::Normal(e) => e.path(), DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(), } } pub fn into_path(self) -> PathBuf { match self.inner { DirEntryInner::Normal(e) => e.into_path(), DirEntryInner::BrokenSymlink(p) => p, } } /// Returns the path as it should be presented to the user. pub fn stripped_path(&self, config: &Config) -> &Path { if config.strip_cwd_prefix { strip_current_dir(self.path()) } else { self.path() } } /// Returns the path as it should be presented to the user. pub fn into_stripped_path(self, config: &Config) -> PathBuf { if config.strip_cwd_prefix { self.stripped_path(config).to_path_buf() } else { self.into_path() } } pub fn file_type(&self) -> Option { match &self.inner { DirEntryInner::Normal(e) => e.file_type(), DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()), } } pub fn metadata(&self) -> Option<&Metadata> { self.metadata .get_or_init(|| match &self.inner { DirEntryInner::Normal(e) => e.metadata().ok(), DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(), }) .as_ref() } pub fn depth(&self) -> Option { match &self.inner { DirEntryInner::Normal(e) => Some(e.depth()), DirEntryInner::BrokenSymlink(_) => None, } } pub fn style(&self, ls_colors: &LsColors) -> Option<&Style> { self.style .get_or_init(|| ls_colors.style_for(self).cloned()) .as_ref() } } impl PartialEq for DirEntry { #[inline] fn eq(&self, other: &Self) -> bool { self.path() == other.path() } } impl Eq for DirEntry {} impl PartialOrd for DirEntry { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for DirEntry { #[inline] fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.path().cmp(other.path()) } } impl Colorable for DirEntry { fn path(&self) -> PathBuf { self.path().to_owned() } fn file_name(&self) -> OsString { let name = match &self.inner { DirEntryInner::Normal(e) => e.file_name(), DirEntryInner::BrokenSymlink(path) => { // Path::file_name() only works if the last component is Normal, // but we want it for all component types, so we open code it. // Copied from LsColors::style_for_path_with_metadata(). path.components() .last() .map(|c| c.as_os_str()) .unwrap_or_else(|| path.as_os_str()) } }; name.to_owned() } fn file_type(&self) -> Option { self.file_type() } fn metadata(&self) -> Option { self.metadata().cloned() } }