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 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 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.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}