1
0

dir.rs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. use crate::fs::feature::git::GitCache;
  2. use crate::fs::fields::GitStatus;
  3. use std::io;
  4. use std::fs;
  5. use std::path::{Path, PathBuf};
  6. use std::slice::Iter as SliceIter;
  7. use log::*;
  8. use crate::fs::File;
  9. /// A **Dir** provides a cached list of the file paths in a directory that’s
  10. /// being listed.
  11. ///
  12. /// This object gets passed to the Files themselves, in order for them to
  13. /// check the existence of surrounding files, then highlight themselves
  14. /// accordingly. (See `File#get_source_files`)
  15. pub struct Dir {
  16. /// A vector of the files that have been read from this directory.
  17. contents: Vec<PathBuf>,
  18. /// The path that was read.
  19. pub path: PathBuf,
  20. }
  21. impl Dir {
  22. /// Create a new Dir object filled with all the files in the directory
  23. /// pointed to by the given path. Fails if the directory can’t be read, or
  24. /// isn’t actually a directory, or if there’s an IO error that occurs at
  25. /// any point.
  26. ///
  27. /// The `read_dir` iterator doesn’t actually yield the `.` and `..`
  28. /// entries, so if the user wants to see them, we’ll have to add them
  29. /// ourselves after the files have been read.
  30. pub fn read_dir(path: PathBuf) -> io::Result<Self> {
  31. info!("Reading directory {:?}", &path);
  32. let contents = fs::read_dir(&path)?
  33. .map(|result| result.map(|entry| entry.path()))
  34. .collect::<Result<_, _>>()?;
  35. Ok(Self { contents, path })
  36. }
  37. /// Produce an iterator of IO results of trying to read all the files in
  38. /// this directory.
  39. pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool, deref_links: bool) -> Files<'dir, 'ig> {
  40. Files {
  41. inner: self.contents.iter(),
  42. dir: self,
  43. dotfiles: dots.shows_dotfiles(),
  44. dots: dots.dots(),
  45. git,
  46. git_ignoring,
  47. deref_links,
  48. }
  49. }
  50. /// Whether this directory contains a file with the given path.
  51. pub fn contains(&self, path: &Path) -> bool {
  52. self.contents.iter().any(|p| p.as_path() == path)
  53. }
  54. /// Append a path onto the path specified by this directory.
  55. pub fn join(&self, child: &Path) -> PathBuf {
  56. self.path.join(child)
  57. }
  58. }
  59. /// Iterator over reading the contents of a directory as `File` objects.
  60. pub struct Files<'dir, 'ig> {
  61. /// The internal iterator over the paths that have been read already.
  62. inner: SliceIter<'dir, PathBuf>,
  63. /// The directory that begat those paths.
  64. dir: &'dir Dir,
  65. /// Whether to include dotfiles in the list.
  66. dotfiles: bool,
  67. /// Whether the `.` or `..` directories should be produced first, before
  68. /// any files have been listed.
  69. dots: DotsNext,
  70. git: Option<&'ig GitCache>,
  71. git_ignoring: bool,
  72. /// Whether symbolic links should be dereferenced when querying information.
  73. deref_links: bool,
  74. }
  75. impl<'dir, 'ig> Files<'dir, 'ig> {
  76. fn parent(&self) -> PathBuf {
  77. // We can’t use `Path#parent` here because all it does is remove the
  78. // last path component, which is no good for us if the path is
  79. // relative. For example, while the parent of `/testcases/files` is
  80. // `/testcases`, the parent of `.` is an empty path. Adding `..` on
  81. // the end is the only way to get to the *actual* parent directory.
  82. self.dir.path.join("..")
  83. }
  84. /// Go through the directory until we encounter a file we can list (which
  85. /// varies depending on the dotfile visibility flag)
  86. fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {
  87. loop {
  88. if let Some(path) = self.inner.next() {
  89. let filename = File::filename(path);
  90. if ! self.dotfiles && filename.starts_with('.') {
  91. continue;
  92. }
  93. // Also hide _prefix files on Windows because it's used by old applications
  94. // as an alternative to dot-prefix files.
  95. #[cfg(windows)]
  96. if ! self.dotfiles && filename.starts_with('_') {
  97. continue;
  98. }
  99. if self.git_ignoring {
  100. let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
  101. if git_status.unstaged == GitStatus::Ignored {
  102. continue;
  103. }
  104. }
  105. return Some(File::from_args(path.clone(), self.dir, filename, self.deref_links)
  106. .map_err(|e| (path.clone(), e)))
  107. }
  108. return None
  109. }
  110. }
  111. }
  112. /// The dot directories that need to be listed before actual files, if any.
  113. /// If these aren’t being printed, then `FilesNext` is used to skip them.
  114. enum DotsNext {
  115. /// List the `.` directory next.
  116. Dot,
  117. /// List the `..` directory next.
  118. DotDot,
  119. /// Forget about the dot directories and just list files.
  120. Files,
  121. }
  122. impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
  123. type Item = Result<File<'dir>, (PathBuf, io::Error)>;
  124. fn next(&mut self) -> Option<Self::Item> {
  125. match self.dots {
  126. DotsNext::Dot => {
  127. self.dots = DotsNext::DotDot;
  128. Some(File::new_aa_current(self.dir)
  129. .map_err(|e| (Path::new(".").to_path_buf(), e)))
  130. }
  131. DotsNext::DotDot => {
  132. self.dots = DotsNext::Files;
  133. Some(File::new_aa_parent(self.parent(), self.dir)
  134. .map_err(|e| (self.parent(), e)))
  135. }
  136. DotsNext::Files => {
  137. self.next_visible_file()
  138. }
  139. }
  140. }
  141. }
  142. /// Usually files in Unix use a leading dot to be hidden or visible, but two
  143. /// entries in particular are “extra-hidden”: `.` and `..`, which only become
  144. /// visible after an extra `-a` option.
  145. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  146. pub enum DotFilter {
  147. /// Shows files, dotfiles, and `.` and `..`.
  148. DotfilesAndDots,
  149. /// Show files and dotfiles, but hide `.` and `..`.
  150. Dotfiles,
  151. /// Just show files, hiding anything beginning with a dot.
  152. JustFiles,
  153. }
  154. impl Default for DotFilter {
  155. fn default() -> Self {
  156. Self::JustFiles
  157. }
  158. }
  159. impl DotFilter {
  160. /// Whether this filter should show dotfiles in a listing.
  161. fn shows_dotfiles(self) -> bool {
  162. match self {
  163. Self::JustFiles => false,
  164. Self::Dotfiles => true,
  165. Self::DotfilesAndDots => true,
  166. }
  167. }
  168. /// Whether this filter should add dot directories to a listing.
  169. fn dots(self) -> DotsNext {
  170. match self {
  171. Self::JustFiles => DotsNext::Files,
  172. Self::Dotfiles => DotsNext::Files,
  173. Self::DotfilesAndDots => DotsNext::Dot,
  174. }
  175. }
  176. }