Просмотр исходного кода

Implement . and .. by inserting them maually

I originally thought that the entries . and .. were in *every* directory entry, and exa was already doing something to filter it out. And then... I could find no such code! Turns out, if we want those entries present, we have to insert them ourselves.

This was harder than expected. Because the file filter doesn’t have access to the parent directory path, it can’t “filter” the files vector by inserting the files at the beginning.

Instead, we do it at the iterator level. A directory can be scanned in three different ways depending on what sort of dotfiles, if any, are wanted. At this point, we already have access to the parent directory’s path, so we can just insert them manually. The enum got moved to the dir module because it’s used most there.
Benjamin Sago 8 лет назад
Родитель
Сommit
20793ce7f4
7 измененных файлов с 74 добавлено и 72 удалено
  1. 2 2
      src/exa.rs
  2. 44 8
      src/fs/dir.rs
  3. 17 24
      src/fs/file.rs
  4. 1 1
      src/fs/mod.rs
  5. 6 33
      src/options/filter.rs
  6. 3 3
      src/options/mod.rs
  7. 1 1
      src/output/details.rs

+ 2 - 2
src/exa.rs

@@ -82,7 +82,7 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
                 },
                 },
                 Ok(f) => {
                 Ok(f) => {
                     if f.is_directory() && !self.options.dir_action.treat_dirs_as_files() {
                     if f.is_directory() && !self.options.dir_action.treat_dirs_as_files() {
-                        match f.to_dir(self.options.should_scan_for_git()) {
+                        match f.to_dir(self.options.filter.dot_filter, self.options.should_scan_for_git()) {
                             Ok(d) => dirs.push(d),
                             Ok(d) => dirs.push(d),
                             Err(e) => writeln!(stderr(), "{}: {}", file_name, e)?,
                             Err(e) => writeln!(stderr(), "{}: {}", file_name, e)?,
                         }
                         }
@@ -142,7 +142,7 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
 
 
                     let mut child_dirs = Vec::new();
                     let mut child_dirs = Vec::new();
                     for child_dir in children.iter().filter(|f| f.is_directory()) {
                     for child_dir in children.iter().filter(|f| f.is_directory()) {
-                        match child_dir.to_dir(false) {
+                        match child_dir.to_dir(self.options.filter.dot_filter, false) {
                             Ok(d)  => child_dirs.push(d),
                             Ok(d)  => child_dirs.push(d),
                             Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
                             Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
                         }
                         }

+ 44 - 8
src/fs/dir.rs

@@ -29,15 +29,28 @@ pub struct Dir {
 impl Dir {
 impl Dir {
 
 
     /// Create a new Dir object filled with all the files in the directory
     /// Create a new Dir object filled with all the files in the directory
-    /// pointed to by the given path. Fails if the directory can't be read, or
-    /// isn't actually a directory, or if there's an IO error that occurs
-    /// while scanning.
-    pub fn read_dir(path: &Path, git: bool) -> IOResult<Dir> {
-        let reader = fs::read_dir(path)?;
-        let contents = try!(reader.map(|e| e.map(|e| e.path())).collect());
+    /// pointed to by the given path. Fails if the directory can’t be read, or
+    /// isn’t actually a directory, or if there’s an IO error that occurs at
+    /// any point.
+    ///
+    /// The `read_dir` iterator doesn’t actually yield the `.` and `..`
+    /// entries, so if the user wants to see them, we’ll have to add them
+    /// ourselves after the files have been read.
+    pub fn read_dir(path: &Path, dots: DotFilter, git: bool) -> IOResult<Dir> {
+        let mut paths: Vec<PathBuf> = try!(fs::read_dir(path)?
+                                              .map(|result| result.map(|entry| entry.path()))
+                                              .collect());
+        match dots {
+            DotFilter::JustFiles => paths.retain(|p| p.file_name().and_then(|name| name.to_str()).map(|s| !s.starts_with('.')).unwrap_or(true)),
+            DotFilter::Dotfiles => {/* Don’t add or remove anything */},
+            DotFilter::DotfilesAndDots => {
+                paths.insert(0, path.join(".."));
+                paths.insert(0, path.join("."));
+            }
+        }
 
 
         Ok(Dir {
         Ok(Dir {
-            contents: contents,
+            contents: paths,
             path: path.to_path_buf(),
             path: path.to_path_buf(),
             git: if git { Git::scan(path).ok() } else { None },
             git: if git { Git::scan(path).ok() } else { None },
         })
         })
@@ -90,4 +103,27 @@ impl<'dir> Iterator for Files<'dir> {
     fn next(&mut self) -> Option<Self::Item> {
     fn next(&mut self) -> Option<Self::Item> {
         self.inner.next().map(|path| File::from_path(path, Some(self.dir)).map_err(|t| (path.clone(), t)))
         self.inner.next().map(|path| File::from_path(path, Some(self.dir)).map_err(|t| (path.clone(), t)))
     }
     }
-}
+}
+
+
+/// Usually files in Unix use a leading dot to be hidden or visible, but two
+/// entries in particular are "extra-hidden": `.` and `..`, which only become
+/// visible after an extra `-a` option.
+#[derive(PartialEq, Debug, Copy, Clone)]
+pub enum DotFilter {
+
+    /// Shows files, dotfiles, and `.` and `..`.
+    DotfilesAndDots,
+
+    /// Show files and dotfiles, but hide `.` and `..`.
+    Dotfiles,
+
+    /// Just show files, hiding anything beginning with a dot.
+    JustFiles,
+}
+
+impl Default for DotFilter {
+    fn default() -> DotFilter {
+        DotFilter::JustFiles
+    }
+}

+ 17 - 24
src/fs/file.rs

@@ -6,7 +6,7 @@ use std::io::Result as IOResult;
 use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt};
 use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt};
 use std::path::{Path, PathBuf};
 use std::path::{Path, PathBuf};
 
 
-use fs::dir::Dir;
+use fs::dir::{Dir, DotFilter};
 use fs::fields as f;
 use fs::fields as f;
 
 
 
 
@@ -94,8 +94,8 @@ impl<'dir> File<'dir> {
     ///
     ///
     /// Returns an IO error upon failure, but this shouldn't be used to check
     /// Returns an IO error upon failure, but this shouldn't be used to check
     /// if a `File` is a directory or not! For that, just use `is_directory()`.
     /// if a `File` is a directory or not! For that, just use `is_directory()`.
-    pub fn to_dir(&self, scan_for_git: bool) -> IOResult<Dir> {
-        Dir::read_dir(&*self.path, scan_for_git)
+    pub fn to_dir(&self, dots: DotFilter, scan_for_git: bool) -> IOResult<Dir> {
+        Dir::read_dir(&*self.path, dots, scan_for_git)
     }
     }
 
 
     /// Whether this file is a regular file on the filesystem - that is, not a
     /// Whether this file is a regular file on the filesystem - that is, not a
@@ -119,32 +119,25 @@ impl<'dir> File<'dir> {
 
 
     /// Whether this file is a named pipe on the filesystem.
     /// Whether this file is a named pipe on the filesystem.
     pub fn is_pipe(&self) -> bool {
     pub fn is_pipe(&self) -> bool {
-       self.metadata.file_type().is_fifo()
-   }
-
-   /// Whether this file is a char device on the filesystem.
-   pub fn is_char_device(&self) -> bool {
-       self.metadata.file_type().is_char_device()
-   }
-
-   /// Whether this file is a block device on the filesystem.
-   pub fn is_block_device(&self) -> bool {
-       self.metadata.file_type().is_block_device()
-   }
+        self.metadata.file_type().is_fifo()
+    }
 
 
-   /// Whether this file is a socket on the filesystem.
-   pub fn is_socket(&self) -> bool {
-       self.metadata.file_type().is_socket()
-   }
+    /// Whether this file is a char device on the filesystem.
+    pub fn is_char_device(&self) -> bool {
+        self.metadata.file_type().is_char_device()
+    }
 
 
+    /// Whether this file is a block device on the filesystem.
+    pub fn is_block_device(&self) -> bool {
+        self.metadata.file_type().is_block_device()
+    }
 
 
-    /// Whether this file is a dotfile, based on its name. In Unix, file names
-    /// beginning with a dot represent system or configuration files, and
-    /// should be hidden by default.
-    pub fn is_dotfile(&self) -> bool {
-        self.name.starts_with('.')
+    /// Whether this file is a socket on the filesystem.
+    pub fn is_socket(&self) -> bool {
+        self.metadata.file_type().is_socket()
     }
     }
 
 
+
     /// Re-prefixes the path pointed to by this file, if it's a symlink, to
     /// Re-prefixes the path pointed to by this file, if it's a symlink, to
     /// make it an absolute path that can be accessed from whichever
     /// make it an absolute path that can be accessed from whichever
     /// directory exa is being run from.
     /// directory exa is being run from.

+ 1 - 1
src/fs/mod.rs

@@ -1,5 +1,5 @@
 mod dir;
 mod dir;
-pub use self::dir::Dir;
+pub use self::dir::{Dir, DotFilter};
 
 
 mod file;
 mod file;
 pub use self::file::{File, FileTarget};
 pub use self::file::{File, FileTarget};

+ 6 - 33
src/options/filter.rs

@@ -6,6 +6,7 @@ use glob;
 use natord;
 use natord;
 
 
 use fs::File;
 use fs::File;
+use fs::DotFilter;
 use options::misfire::Misfire;
 use options::misfire::Misfire;
 
 
 
 
@@ -27,11 +28,12 @@ pub struct FileFilter {
     /// ones, depending on the sort field.
     /// ones, depending on the sort field.
     pub reverse: bool,
     pub reverse: bool,
 
 
-    /// Whether to include invisible “dot” files when listing a directory.
+    /// Which invisible “dot” files to include when listing a directory.
     ///
     ///
     /// Files starting with a single “.” are used to determine “system” or
     /// Files starting with a single “.” are used to determine “system” or
     /// “configuration” files that should not be displayed in a regular
     /// “configuration” files that should not be displayed in a regular
-    /// directory listing.
+    /// directory listing, and the directory entries “.” and “..” are
+    /// considered extra-special.
     ///
     ///
     /// This came about more or less by a complete historical accident,
     /// This came about more or less by a complete historical accident,
     /// when the original `ls` tried to hide `.` and `..`:
     /// when the original `ls` tried to hide `.` and `..`:
@@ -84,12 +86,6 @@ impl FileFilter {
     /// Remove every file in the given vector that does *not* pass the
     /// Remove every file in the given vector that does *not* pass the
     /// filter predicate for files found inside a directory.
     /// filter predicate for files found inside a directory.
     pub fn filter_child_files(&self, files: &mut Vec<File>) {
     pub fn filter_child_files(&self, files: &mut Vec<File>) {
-        match self.dot_filter {
-            DotFilter::JustFiles => files.retain(|f| !f.is_dotfile()),
-            DotFilter::ShowDotfiles => {/* keep all elements */},
-            DotFilter::ShowDotfilesAndDots => unimplemented!(),
-        }
-
         files.retain(|f| !self.ignore_patterns.is_ignored(f));
         files.retain(|f| !self.ignore_patterns.is_ignored(f));
     }
     }
 
 
@@ -254,35 +250,12 @@ impl SortField {
 }
 }
 
 
 
 
-/// Usually files in Unix use a leading dot to be hidden or visible, but two
-/// entries in particular are "extra-hidden": `.` and `..`, which only become
-/// visible after an extra `-a` option.
-#[derive(PartialEq, Debug, Copy, Clone)]
-pub enum DotFilter {
-
-    /// Shows files, dotfiles, and `.` and `..`.
-    ShowDotfilesAndDots,
-
-    /// Show files and dotfiles, but hide `.` and `..`.
-    ShowDotfiles,
-
-    /// Just show files, hiding anything beginning with a dot.
-    JustFiles,
-}
-
-impl Default for DotFilter {
-    fn default() -> DotFilter {
-        DotFilter::JustFiles
-    }
-}
-
-
 impl DotFilter {
 impl DotFilter {
     pub fn deduce(matches: &getopts::Matches) -> DotFilter {
     pub fn deduce(matches: &getopts::Matches) -> DotFilter {
         match matches.opt_count("all") {
         match matches.opt_count("all") {
             0 => DotFilter::JustFiles,
             0 => DotFilter::JustFiles,
-            1 => DotFilter::ShowDotfiles,
-            _ => DotFilter::ShowDotfilesAndDots,
+            1 => DotFilter::Dotfiles,
+            _ => DotFilter::DotfilesAndDots,
         }
         }
     }
     }
 }
 }

+ 3 - 3
src/options/mod.rs

@@ -145,7 +145,7 @@ impl Options {
 #[cfg(test)]
 #[cfg(test)]
 mod test {
 mod test {
     use super::{Options, Misfire, SortField, SortCase};
     use super::{Options, Misfire, SortField, SortCase};
-    use super::filter::DotFilter;
+    use fs::DotFilter;
     use fs::feature::xattr;
     use fs::feature::xattr;
 
 
     fn is_helpful<T>(misfire: Result<T, Misfire>) -> bool {
     fn is_helpful<T>(misfire: Result<T, Misfire>) -> bool {
@@ -290,12 +290,12 @@ mod test {
     #[test]
     #[test]
     fn all() {
     fn all() {
         let dots = Options::getopts(&[ "--all".to_string() ]).unwrap().0.filter.dot_filter;
         let dots = Options::getopts(&[ "--all".to_string() ]).unwrap().0.filter.dot_filter;
-        assert_eq!(dots, DotFilter::ShowDotfiles);
+        assert_eq!(dots, DotFilter::Dotfiles);
     }
     }
 
 
     #[test]
     #[test]
     fn allall() {
     fn allall() {
         let dots = Options::getopts(&[ "-a".to_string(), "-a".to_string() ]).unwrap().0.filter.dot_filter;
         let dots = Options::getopts(&[ "-a".to_string(), "-a".to_string() ]).unwrap().0.filter.dot_filter;
-        assert_eq!(dots, DotFilter::ShowDotfilesAndDots);
+        assert_eq!(dots, DotFilter::DotfilesAndDots);
     }
     }
 }
 }

+ 1 - 1
src/output/details.rs

@@ -315,7 +315,7 @@ impl<'a> Render<'a> {
 
 
                     if let Some(r) = self.recurse {
                     if let Some(r) = self.recurse {
                         if file.is_directory() && r.tree && !r.is_too_deep(depth) {
                         if file.is_directory() && r.tree && !r.is_too_deep(depth) {
-                            if let Ok(d) = file.to_dir(false) {
+                            if let Ok(d) = file.to_dir(self.filter.dot_filter, false) {
                                 dir = Some(d);
                                 dir = Some(d);
                             }
                             }
                         }
                         }