fd_lib/
dir_entry.rs

1use std::cell::OnceCell;
2use std::ffi::OsString;
3use std::fs::{FileType, Metadata};
4use std::path::{Path, PathBuf};
5
6use lscolors::{Colorable, LsColors, Style};
7
8use crate::config::Config;
9use crate::filesystem::strip_current_dir;
10
11#[derive(Debug)]
12enum DirEntryInner {
13    Normal(ignore::DirEntry),
14    BrokenSymlink(PathBuf),
15}
16
17#[derive(Debug)]
18pub struct DirEntry {
19    inner: DirEntryInner,
20    metadata: OnceCell<Option<Metadata>>,
21    style: OnceCell<Option<Style>>,
22}
23
24impl DirEntry {
25    #[inline]
26    pub fn normal(e: ignore::DirEntry) -> Self {
27        Self {
28            inner: DirEntryInner::Normal(e),
29            metadata: OnceCell::new(),
30            style: OnceCell::new(),
31        }
32    }
33
34    pub fn broken_symlink(path: PathBuf) -> Self {
35        Self {
36            inner: DirEntryInner::BrokenSymlink(path),
37            metadata: OnceCell::new(),
38            style: OnceCell::new(),
39        }
40    }
41
42    pub fn path(&self) -> &Path {
43        match &self.inner {
44            DirEntryInner::Normal(e) => e.path(),
45            DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(),
46        }
47    }
48
49    pub fn into_path(self) -> PathBuf {
50        match self.inner {
51            DirEntryInner::Normal(e) => e.into_path(),
52            DirEntryInner::BrokenSymlink(p) => p,
53        }
54    }
55
56    /// Returns the path as it should be presented to the user.
57    pub fn stripped_path(&self, config: &Config) -> &Path {
58        if config.strip_cwd_prefix {
59            strip_current_dir(self.path())
60        } else {
61            self.path()
62        }
63    }
64
65    /// Returns the path as it should be presented to the user.
66    pub fn into_stripped_path(self, config: &Config) -> PathBuf {
67        if config.strip_cwd_prefix {
68            self.stripped_path(config).to_path_buf()
69        } else {
70            self.into_path()
71        }
72    }
73
74    pub fn file_type(&self) -> Option<FileType> {
75        match &self.inner {
76            DirEntryInner::Normal(e) => e.file_type(),
77            DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()),
78        }
79    }
80
81    pub fn metadata(&self) -> Option<&Metadata> {
82        self.metadata
83            .get_or_init(|| match &self.inner {
84                DirEntryInner::Normal(e) => e.metadata().ok(),
85                DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(),
86            })
87            .as_ref()
88    }
89
90    pub fn depth(&self) -> Option<usize> {
91        match &self.inner {
92            DirEntryInner::Normal(e) => Some(e.depth()),
93            DirEntryInner::BrokenSymlink(_) => None,
94        }
95    }
96
97    pub fn style(&self, ls_colors: &LsColors) -> Option<&Style> {
98        self.style
99            .get_or_init(|| ls_colors.style_for(self).cloned())
100            .as_ref()
101    }
102}
103
104impl PartialEq for DirEntry {
105    #[inline]
106    fn eq(&self, other: &Self) -> bool {
107        self.path() == other.path()
108    }
109}
110
111impl Eq for DirEntry {}
112
113impl PartialOrd for DirEntry {
114    #[inline]
115    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
116        Some(self.cmp(other))
117    }
118}
119
120impl Ord for DirEntry {
121    #[inline]
122    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
123        self.path().cmp(other.path())
124    }
125}
126
127impl Colorable for DirEntry {
128    fn path(&self) -> PathBuf {
129        self.path().to_owned()
130    }
131
132    fn file_name(&self) -> OsString {
133        let name = match &self.inner {
134            DirEntryInner::Normal(e) => e.file_name(),
135            DirEntryInner::BrokenSymlink(path) => {
136                // Path::file_name() only works if the last component is Normal,
137                // but we want it for all component types, so we open code it.
138                // Copied from LsColors::style_for_path_with_metadata().
139                path.components()
140                    .last()
141                    .map(|c| c.as_os_str())
142                    .unwrap_or_else(|| path.as_os_str())
143            }
144        };
145        name.to_owned()
146    }
147
148    fn file_type(&self) -> Option<FileType> {
149        self.file_type()
150    }
151
152    fn metadata(&self) -> Option<Metadata> {
153        self.metadata().cloned()
154    }
155}