Sfoglia il codice sorgente

feat: add symlink dereferencing flag

...and propagate it.

Add the `-X`/`--dereference` flags which are used to enable
dereferencing through symbolic links, similar to ls's `-L`. Added the
`link_target_recurse` method in order to recursively call the existing
`link_target` method to get the final target at the end of the chain of
links. In order to appropriately display the final output and play
nicely with existing functionality, a `deref_links` variable has been
added to a few places such that it can be propagated until the time it
is needed during rendering. Following commits will use this information
to display dereferenced information.

Signed-off-by: Christina Sørensen <christina@cafkafk.com>
Andrzej Grzeslak 3 anni fa
parent
commit
c7f1163aca
7 ha cambiato i file con 55 aggiunte e 19 eliminazioni
  1. 6 2
      src/fs/dir.rs
  2. 34 5
      src/fs/file.rs
  3. 2 2
      src/main.rs
  4. 9 8
      src/options/flags.rs
  5. 2 1
      src/options/view.rs
  6. 1 1
      src/output/details.rs
  7. 1 0
      src/output/mod.rs

+ 6 - 2
src/fs/dir.rs

@@ -47,7 +47,7 @@ impl Dir {
 
 
     /// Produce an iterator of IO results of trying to read all the files in
     /// Produce an iterator of IO results of trying to read all the files in
     /// this directory.
     /// this directory.
-    pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool) -> Files<'dir, 'ig> {
+    pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool, deref_links: bool) -> Files<'dir, 'ig> {
         Files {
         Files {
             inner:     self.contents.iter(),
             inner:     self.contents.iter(),
             dir:       self,
             dir:       self,
@@ -55,6 +55,7 @@ impl Dir {
             dots:      dots.dots(),
             dots:      dots.dots(),
             git,
             git,
             git_ignoring,
             git_ignoring,
+            deref_links,
         }
         }
     }
     }
 
 
@@ -89,6 +90,9 @@ pub struct Files<'dir, 'ig> {
     git: Option<&'ig GitCache>,
     git: Option<&'ig GitCache>,
 
 
     git_ignoring: bool,
     git_ignoring: bool,
+
+    /// Whether symbolic links should be dereferenced when querying information.
+    deref_links: bool,
 }
 }
 
 
 impl<'dir, 'ig> Files<'dir, 'ig> {
 impl<'dir, 'ig> Files<'dir, 'ig> {
@@ -118,7 +122,7 @@ impl<'dir, 'ig> Files<'dir, 'ig> {
                     }
                     }
                 }
                 }
 
 
-                return Some(File::from_args(path.clone(), self.dir, filename)
+                return Some(File::from_args(path.clone(), self.dir, filename, self.deref_links)
                                  .map_err(|e| (path.clone(), e)))
                                  .map_err(|e| (path.clone(), e)))
             }
             }
 
 

+ 34 - 5
src/fs/file.rs

@@ -63,10 +63,17 @@ pub struct File<'dir> {
     /// directory’s children, and are in fact added specifically by exa; this
     /// directory’s children, and are in fact added specifically by exa; this
     /// means that they should be skipped when recursing.
     /// means that they should be skipped when recursing.
     pub is_all_all: bool,
     pub is_all_all: bool,
+
+    /// Whether to dereference symbolic links when querying for information.
+    ///
+    /// For instance, when querying the size of a symbolic link, if
+    /// dereferencing is enabled, the size of the target will be displayed
+    /// instead.
+    pub deref_links: bool,
 }
 }
 
 
 impl<'dir> File<'dir> {
 impl<'dir> File<'dir> {
-    pub fn from_args<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN) -> io::Result<File<'dir>>
+    pub fn from_args<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN, deref_links: bool) -> io::Result<File<'dir>>
     where PD: Into<Option<&'dir Dir>>,
     where PD: Into<Option<&'dir Dir>>,
           FN: Into<Option<String>>
           FN: Into<Option<String>>
     {
     {
@@ -78,7 +85,7 @@ impl<'dir> File<'dir> {
         let metadata   = std::fs::symlink_metadata(&path)?;
         let metadata   = std::fs::symlink_metadata(&path)?;
         let is_all_all = false;
         let is_all_all = false;
 
 
-        Ok(File { name, ext, path, metadata, parent_dir, is_all_all })
+        Ok(File { name, ext, path, metadata, parent_dir, is_all_all, deref_links })
     }
     }
 
 
     pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
     pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
@@ -90,7 +97,7 @@ impl<'dir> File<'dir> {
         let is_all_all = true;
         let is_all_all = true;
         let parent_dir = Some(parent_dir);
         let parent_dir = Some(parent_dir);
 
 
-        Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all })
+        Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, deref_links: false })
     }
     }
 
 
     pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
     pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
@@ -101,7 +108,7 @@ impl<'dir> File<'dir> {
         let is_all_all = true;
         let is_all_all = true;
         let parent_dir = Some(parent_dir);
         let parent_dir = Some(parent_dir);
 
 
-        Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all })
+        Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, deref_links: false })
     }
     }
 
 
     /// A file’s name is derived from its string. This needs to handle directories
     /// A file’s name is derived from its string. This needs to handle directories
@@ -253,7 +260,7 @@ impl<'dir> File<'dir> {
             Ok(metadata) => {
             Ok(metadata) => {
                 let ext  = File::ext(&path);
                 let ext  = File::ext(&path);
                 let name = File::filename(&path);
                 let name = File::filename(&path);
-                let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false };
+                let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false, deref_links: self.deref_links};
                 FileTarget::Ok(Box::new(file))
                 FileTarget::Ok(Box::new(file))
             }
             }
             Err(e) => {
             Err(e) => {
@@ -263,6 +270,28 @@ impl<'dir> File<'dir> {
         }
         }
     }
     }
 
 
+    /// Assuming this file is a symlink, follows that link and any further
+    /// links recursively, returning the result from following the trail.
+    ///
+    /// For a working symlink that the user is allowed to follow,
+    /// this will be the `File` object at the other end, which can then have
+    /// its name, colour, and other details read.
+    ///
+    /// For a broken symlink, returns where the file *would* be, if it
+    /// existed. If this file cannot be read at all, returns the error that
+    /// we got when we tried to read it.
+    pub fn link_target_recurse(&self) -> FileTarget<'dir> {
+        let target = self.link_target();
+        if let FileTarget::Ok(f) = target {
+            if f.is_link() {
+                return f.link_target_recurse();
+            } else {
+                return FileTarget::Ok(f);
+            }
+        } 
+        target
+    }
+
     /// This file’s number of hard links.
     /// This file’s number of hard links.
     ///
     ///
     /// It also reports whether this is both a regular file, and a file with
     /// It also reports whether this is both a regular file, and a file with

+ 2 - 2
src/main.rs

@@ -171,7 +171,7 @@ impl<'args> Exa<'args> {
         let mut exit_status = 0;
         let mut exit_status = 0;
 
 
         for file_path in &self.input_paths {
         for file_path in &self.input_paths {
-            match File::from_args(PathBuf::from(file_path), None, None) {
+            match File::from_args(PathBuf::from(file_path), None, None, self.options.view.deref_links) {
                 Err(e) => {
                 Err(e) => {
                     exit_status = 2;
                     exit_status = 2;
                     writeln!(io::stderr(), "{:?}: {}", file_path, e)?;
                     writeln!(io::stderr(), "{:?}: {}", file_path, e)?;
@@ -224,7 +224,7 @@ impl<'args> Exa<'args> {
 
 
             let mut children = Vec::new();
             let mut children = Vec::new();
             let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
             let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
-            for file in dir.files(self.options.filter.dot_filter, self.git.as_ref(), git_ignore) {
+            for file in dir.files(self.options.filter.dot_filter, self.git.as_ref(), git_ignore, self.options.view.deref_links) {
                 match file {
                 match file {
                     Ok(file)        => children.push(file),
                     Ok(file)        => children.push(file),
                     Err((path, e))  => writeln!(io::stderr(), "[{}: {}]", path.display(), e)?,
                     Err((path, e))  => writeln!(io::stderr(), "[{}: {}]", path.display(), e)?,

+ 9 - 8
src/options/flags.rs

@@ -6,13 +6,14 @@ pub static VERSION: Arg = Arg { short: Some(b'v'), long: "version",  takes_value
 pub static HELP:    Arg = Arg { short: Some(b'?'), long: "help",     takes_value: TakesValue::Forbidden };
 pub static HELP:    Arg = Arg { short: Some(b'?'), long: "help",     takes_value: TakesValue::Forbidden };
 
 
 // display options
 // display options
-pub static ONE_LINE: Arg = Arg { short: Some(b'1'), long: "oneline",  takes_value: TakesValue::Forbidden };
-pub static LONG:     Arg = Arg { short: Some(b'l'), long: "long",     takes_value: TakesValue::Forbidden };
-pub static GRID:     Arg = Arg { short: Some(b'G'), long: "grid",     takes_value: TakesValue::Forbidden };
-pub static ACROSS:   Arg = Arg { short: Some(b'x'), long: "across",   takes_value: TakesValue::Forbidden };
-pub static RECURSE:  Arg = Arg { short: Some(b'R'), long: "recurse",  takes_value: TakesValue::Forbidden };
-pub static TREE:     Arg = Arg { short: Some(b'T'), long: "tree",     takes_value: TakesValue::Forbidden };
-pub static CLASSIFY: Arg = Arg { short: Some(b'F'), long: "classify", takes_value: TakesValue::Forbidden };
+pub static ONE_LINE:    Arg = Arg { short: Some(b'1'), long: "oneline",     takes_value: TakesValue::Forbidden };
+pub static LONG:        Arg = Arg { short: Some(b'l'), long: "long",        takes_value: TakesValue::Forbidden };
+pub static GRID:        Arg = Arg { short: Some(b'G'), long: "grid",        takes_value: TakesValue::Forbidden };
+pub static ACROSS:      Arg = Arg { short: Some(b'x'), long: "across",      takes_value: TakesValue::Forbidden };
+pub static RECURSE:     Arg = Arg { short: Some(b'R'), long: "recurse",     takes_value: TakesValue::Forbidden };
+pub static TREE:        Arg = Arg { short: Some(b'T'), long: "tree",        takes_value: TakesValue::Forbidden };
+pub static CLASSIFY:    Arg = Arg { short: Some(b'F'), long: "classify",    takes_value: TakesValue::Forbidden };
+pub static DEREF_LINKS: Arg = Arg { short: Some(b'X'), long: "dereference", takes_value: TakesValue::Forbidden };
 
 
 pub static COLOR:  Arg = Arg { short: None, long: "color",  takes_value: TakesValue::Necessary(Some(COLOURS)) };
 pub static COLOR:  Arg = Arg { short: None, long: "color",  takes_value: TakesValue::Necessary(Some(COLOURS)) };
 pub static COLOUR: Arg = Arg { short: None, long: "colour", takes_value: TakesValue::Necessary(Some(COLOURS)) };
 pub static COLOUR: Arg = Arg { short: None, long: "colour", takes_value: TakesValue::Necessary(Some(COLOURS)) };
@@ -70,7 +71,7 @@ pub static OCTAL:     Arg = Arg { short: None,       long: "octal-permissions",
 pub static ALL_ARGS: Args = Args(&[
 pub static ALL_ARGS: Args = Args(&[
     &VERSION, &HELP,
     &VERSION, &HELP,
 
 
-    &ONE_LINE, &LONG, &GRID, &ACROSS, &RECURSE, &TREE, &CLASSIFY,
+    &ONE_LINE, &LONG, &GRID, &ACROSS, &RECURSE, &TREE, &CLASSIFY, &DEREF_LINKS,
     &COLOR, &COLOUR, &COLOR_SCALE, &COLOUR_SCALE,
     &COLOR, &COLOUR, &COLOR_SCALE, &COLOUR_SCALE,
 
 
     &ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,
     &ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,

+ 2 - 1
src/options/view.rs

@@ -13,7 +13,8 @@ impl View {
         let mode = Mode::deduce(matches, vars)?;
         let mode = Mode::deduce(matches, vars)?;
         let width = TerminalWidth::deduce(vars)?;
         let width = TerminalWidth::deduce(vars)?;
         let file_style = FileStyle::deduce(matches, vars)?;
         let file_style = FileStyle::deduce(matches, vars)?;
-        Ok(Self { mode, width, file_style })
+        let deref_links = matches.has(&flags::DEREF_LINKS)?;
+        Ok(Self { mode, width, file_style, deref_links })
     }
     }
 }
 }
 
 

+ 1 - 1
src/output/details.rs

@@ -300,7 +300,7 @@ impl<'a> Render<'a> {
             rows.push(row);
             rows.push(row);
 
 
             if let Some(ref dir) = egg.dir {
             if let Some(ref dir) = egg.dir {
-                for file_to_add in dir.files(self.filter.dot_filter, self.git, self.git_ignoring) {
+                for file_to_add in dir.files(self.filter.dot_filter, self.git, self.git_ignoring, egg.file.deref_links) {
                     match file_to_add {
                     match file_to_add {
                         Ok(f) => {
                         Ok(f) => {
                             files.push(f);
                             files.push(f);

+ 1 - 0
src/output/mod.rs

@@ -22,6 +22,7 @@ pub struct View {
     pub mode: Mode,
     pub mode: Mode,
     pub width: TerminalWidth,
     pub width: TerminalWidth,
     pub file_style: file_name::Options,
     pub file_style: file_name::Options,
+    pub deref_links: bool,
 }
 }