dir.rs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. use crate::fs::feature::git::GitCache;
  2. use crate::fs::fields::GitStatus;
  3. use std::io::{self, Result as IOResult};
  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) -> IOResult<Dir> {
  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(Dir { 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) -> Files<'dir, 'ig> {
  40. Files {
  41. inner: self.contents.iter(),
  42. dir: self,
  43. dotfiles: dots.shows_dotfiles(),
  44. dots: dots.dots(),
  45. git, git_ignoring,
  46. }
  47. }
  48. /// Whether this directory contains a file with the given path.
  49. pub fn contains(&self, path: &Path) -> bool {
  50. self.contents.iter().any(|p| p.as_path() == path)
  51. }
  52. /// Append a path onto the path specified by this directory.
  53. pub fn join(&self, child: &Path) -> PathBuf {
  54. self.path.join(child)
  55. }
  56. }
  57. /// Iterator over reading the contents of a directory as `File` objects.
  58. pub struct Files<'dir, 'ig> {
  59. /// The internal iterator over the paths that have been read already.
  60. inner: SliceIter<'dir, PathBuf>,
  61. /// The directory that begat those paths.
  62. dir: &'dir Dir,
  63. /// Whether to include dotfiles in the list.
  64. dotfiles: bool,
  65. /// Whether the `.` or `..` directories should be produced first, before
  66. /// any files have been listed.
  67. dots: DotsNext,
  68. git: Option<&'ig GitCache>,
  69. git_ignoring: bool,
  70. }
  71. impl<'dir, 'ig> Files<'dir, 'ig> {
  72. fn parent(&self) -> PathBuf {
  73. // We can’t use `Path#parent` here because all it does is remove the
  74. // last path component, which is no good for us if the path is
  75. // relative. For example, while the parent of `/testcases/files` is
  76. // `/testcases`, the parent of `.` is an empty path. Adding `..` on
  77. // the end is the only way to get to the *actual* parent directory.
  78. self.dir.path.join("..")
  79. }
  80. /// Go through the directory until we encounter a file we can list (which
  81. /// varies depending on the dotfile visibility flag)
  82. fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {
  83. loop {
  84. if let Some(path) = self.inner.next() {
  85. let filename = File::filename(path);
  86. if !self.dotfiles && filename.starts_with('.') { continue }
  87. if self.git_ignoring {
  88. let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
  89. if git_status.unstaged == GitStatus::Ignored {
  90. continue;
  91. }
  92. }
  93. return Some(File::from_args(path.clone(), self.dir, filename)
  94. .map_err(|e| (path.clone(), e)))
  95. }
  96. else {
  97. return None
  98. }
  99. }
  100. }
  101. }
  102. /// The dot directories that need to be listed before actual files, if any.
  103. /// If these aren’t being printed, then `FilesNext` is used to skip them.
  104. enum DotsNext {
  105. /// List the `.` directory next.
  106. Dot,
  107. /// List the `..` directory next.
  108. DotDot,
  109. /// Forget about the dot directories and just list files.
  110. Files,
  111. }
  112. impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
  113. type Item = Result<File<'dir>, (PathBuf, io::Error)>;
  114. fn next(&mut self) -> Option<Self::Item> {
  115. match self.dots {
  116. DotsNext::Dot => {
  117. self.dots = DotsNext::DotDot;
  118. Some(File::new_aa_current(self.dir)
  119. .map_err(|e| (Path::new(".").to_path_buf(), e)))
  120. },
  121. DotsNext::DotDot => {
  122. self.dots = DotsNext::Files;
  123. Some(File::new_aa_parent(self.parent(), self.dir)
  124. .map_err(|e| (self.parent(), e)))
  125. },
  126. DotsNext::Files => {
  127. self.next_visible_file()
  128. },
  129. }
  130. }
  131. }
  132. /// Usually files in Unix use a leading dot to be hidden or visible, but two
  133. /// entries in particular are "extra-hidden": `.` and `..`, which only become
  134. /// visible after an extra `-a` option.
  135. #[derive(PartialEq, Debug, Copy, Clone)]
  136. pub enum DotFilter {
  137. /// Shows files, dotfiles, and `.` and `..`.
  138. DotfilesAndDots,
  139. /// Show files and dotfiles, but hide `.` and `..`.
  140. Dotfiles,
  141. /// Just show files, hiding anything beginning with a dot.
  142. JustFiles,
  143. }
  144. impl Default for DotFilter {
  145. fn default() -> DotFilter {
  146. DotFilter::JustFiles
  147. }
  148. }
  149. impl DotFilter {
  150. /// Whether this filter should show dotfiles in a listing.
  151. fn shows_dotfiles(self) -> bool {
  152. match self {
  153. DotFilter::JustFiles => false,
  154. DotFilter::Dotfiles => true,
  155. DotFilter::DotfilesAndDots => true,
  156. }
  157. }
  158. /// Whether this filter should add dot directories to a listing.
  159. fn dots(self) -> DotsNext {
  160. match self {
  161. DotFilter::JustFiles => DotsNext::Files,
  162. DotFilter::Dotfiles => DotsNext::Files,
  163. DotFilter::DotfilesAndDots => DotsNext::Dot,
  164. }
  165. }
  166. }