Browse Source

Match up fields with parameter names

The arguments passed to File’s constructor were different from the field names used — these might as well both be the same.

Also, move ext and filename to be File methods to save an import, and add tests.

Also also, by passing a PathBuf in to the constructor directly, we can save one (possibly two) instance/s where we pass in a reference to something we were going to lose ownership of anyway, only to have it basically cloned.
Benjamin Sago 8 years ago
parent
commit
31148eda7b
5 changed files with 96 additions and 71 deletions
  1. 2 2
      src/exa.rs
  2. 4 6
      src/fs/dir.rs
  3. 88 61
      src/fs/file.rs
  4. 1 1
      src/info/filetype.rs
  5. 1 1
      src/output/file_name.rs

+ 2 - 2
src/exa.rs

@@ -24,7 +24,7 @@ extern crate lazy_static;
 
 use std::ffi::OsStr;
 use std::io::{stderr, Write, Result as IOResult};
-use std::path::{Component, Path};
+use std::path::{Component, PathBuf};
 
 use ansi_term::{ANSIStrings, Style};
 
@@ -75,7 +75,7 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
         }
 
         for file_name in &self.args {
-            match File::new(Path::new(&file_name), None, None) {
+            match File::new(PathBuf::from(file_name), None, None) {
                 Err(e) => {
                     exit_status = 2;
                     writeln!(stderr(), "{}: {}", file_name, e)?;

+ 4 - 6
src/fs/dir.rs

@@ -112,14 +112,12 @@ impl<'dir> Files<'dir> {
     /// Go through the directory until we encounter a file we can list (which
     /// varies depending on the dotfile visibility flag)
     fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {
-        use fs::file::path_filename;
-
         loop {
             if let Some(path) = self.inner.next() {
-                let filen = path_filename(path);
+                let filen = File::filename(path);
                 if !self.dotfiles && filen.starts_with(".") { continue }
 
-                return Some(File::new(path, Some(self.dir), Some(filen))
+                return Some(File::new(path.clone(), Some(self.dir), Some(filen))
                                  .map_err(|e| (path.clone(), e)))
             }
             else {
@@ -150,12 +148,12 @@ impl<'dir> Iterator for Files<'dir> {
     fn next(&mut self) -> Option<Self::Item> {
         if let Dots::DotNext = self.dots {
             self.dots = Dots::DotDotNext;
-            Some(File::new(&self.dir.path, Some(self.dir), Some(String::from(".")))
+            Some(File::new(self.dir.path.to_path_buf(), Some(self.dir), Some(String::from(".")))
                       .map_err(|e| (Path::new(".").to_path_buf(), e)))
         }
         else if let Dots::DotDotNext = self.dots {
             self.dots = Dots::FilesNext;
-            Some(File::new(&self.parent(), Some(self.dir), Some(String::from("..")))
+            Some(File::new(self.parent(), Some(self.dir), Some(String::from("..")))
                       .map_err(|e| (self.parent(), e)))
         }
         else {

+ 88 - 61
src/fs/file.rs

@@ -53,34 +53,48 @@ pub struct File<'dir> {
     /// However, *directories* that get passed in will produce files that
     /// contain a reference to it, which is used in certain operations (such
     /// as looking up a file's Git status).
-    pub dir: Option<&'dir Dir>,
-}
-
-/// A file’s name is derived from its string. This needs to handle directories
-/// such as `/` or `..`, which have no `file_name` component. So instead, just
-/// use the last component as the name.
-pub fn path_filename(path: &Path) -> String {
-    match path.components().next_back() {
-        Some(back) => back.as_os_str().to_string_lossy().to_string(),
-        None       => path.display().to_string(),  // use the path as fallback
-    }
+    pub parent_dir: Option<&'dir Dir>,
 }
 
 impl<'dir> File<'dir> {
-    pub fn new(path: &Path, parent: Option<&'dir Dir>, mut filename: Option<String>) -> IOResult<File<'dir>> {
+    pub fn new(path: PathBuf, parent_dir: Option<&'dir Dir>, mut filename: Option<String>) -> IOResult<File<'dir>> {
         if filename.is_none() {
-            filename = Some(path_filename(path));
+            filename = Some(File::filename(&path));
         }
 
-        let metadata = fs::symlink_metadata(path)?;
+        let metadata = fs::symlink_metadata(&path)?;
+        let ext      = File::ext(&path);
 
-        Ok(File {
-            path:      path.to_path_buf(),
-            dir:       parent,
-            metadata:  metadata,
-            ext:       ext(path),
-            name:      filename.unwrap(),
-        })
+        Ok(File { path, parent_dir, metadata, ext, name: filename.unwrap() })
+    }
+
+    /// A file’s name is derived from its string. This needs to handle directories
+    /// such as `/` or `..`, which have no `file_name` component. So instead, just
+    /// use the last component as the name.
+    pub fn filename(path: &Path) -> String {
+        match path.components().next_back() {
+            Some(back) => back.as_os_str().to_string_lossy().to_string(),
+            None       => path.display().to_string(),  // use the path as fallback
+        }
+    }
+
+    /// Extract an extension from a file path, if one is present, in lowercase.
+    ///
+    /// The extension is the series of characters after the last dot. This
+    /// deliberately counts dotfiles, so the ".git" folder has the extension "git".
+    ///
+    /// ASCII lowercasing is used because these extensions are only compared
+    /// against a pre-compiled list of extensions which are known to only exist
+    /// within ASCII, so it's alright.
+    fn ext(path: &Path) -> Option<String> {
+        use std::ascii::AsciiExt;
+
+        let name = match path.file_name() {
+            Some(f) => f.to_string_lossy().to_string(),
+            None => return None,
+        };
+
+        name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase())
     }
 
     /// Whether this file is a directory on the filesystem.
@@ -145,7 +159,7 @@ impl<'dir> File<'dir> {
         if path.is_absolute() {
             path.to_path_buf()
         }
-        else if let Some(dir) = self.dir {
+        else if let Some(dir) = self.parent_dir {
             dir.join(&*path)
         }
         else if let Some(parent) = self.path.parent() {
@@ -172,26 +186,22 @@ impl<'dir> File<'dir> {
         // this file -- which could be absolute or relative -- to the path
         // we actually look up and turn into a `File` -- which needs to be
         // absolute to be accessible from any directory.
-        let display_path = match fs::read_link(&self.path) {
-            Ok(path)  => path,
-            Err(e)    => return FileTarget::Err(e),
+        let path = match fs::read_link(&self.path) {
+            Ok(p)   => p,
+            Err(e)  => return FileTarget::Err(e),
         };
 
-        let target_path = self.reorient_target_path(&*display_path);
+        let absolute_path = self.reorient_target_path(&path);
 
         // Use plain `metadata` instead of `symlink_metadata` - we *want* to
         // follow links.
-        if let Ok(metadata) = fs::metadata(&target_path) {
-            FileTarget::Ok(File {
-                dir:       None,
-                ext:       ext(&display_path),
-                metadata:  metadata,
-                name:      path_filename(&display_path),
-                path:      display_path,
-            })
+        if let Ok(metadata) = fs::metadata(&absolute_path) {
+            let ext  = File::ext(&path);
+            let name = File::filename(&path);
+            FileTarget::Ok(File { parent_dir: None, path, ext, metadata, name })
         }
         else {
-            FileTarget::Broken(display_path)
+            FileTarget::Broken(path)
         }
     }
 
@@ -355,7 +365,7 @@ impl<'dir> File<'dir> {
     pub fn git_status(&self) -> f::Git {
         use std::env::current_dir;
 
-        match self.dir {
+        match self.parent_dir {
             None    => f::Git { staged: f::GitStatus::NotModified, unstaged: f::GitStatus::NotModified },
             Some(d) => {
                 let cwd = match current_dir() {
@@ -377,26 +387,6 @@ impl<'a> AsRef<File<'a>> for File<'a> {
 }
 
 
-/// Extract an extension from a file path, if one is present, in lowercase.
-///
-/// The extension is the series of characters after the last dot. This
-/// deliberately counts dotfiles, so the ".git" folder has the extension "git".
-///
-/// ASCII lowercasing is used because these extensions are only compared
-/// against a pre-compiled list of extensions which are known to only exist
-/// within ASCII, so it's alright.
-fn ext(path: &Path) -> Option<String> {
-    use std::ascii::AsciiExt;
-
-    let name = match path.file_name() {
-        Some(f) => f.to_string_lossy().to_string(),
-        None => return None,
-    };
-
-    name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase())
-}
-
-
 /// The result of following a symlink.
 pub enum FileTarget<'dir> {
 
@@ -458,22 +448,59 @@ mod modes {
 
 
 #[cfg(test)]
-mod test {
-    use super::ext;
+mod ext_test {
+    use super::File;
     use std::path::Path;
 
     #[test]
     fn extension() {
-        assert_eq!(Some("dat".to_string()), ext(Path::new("fester.dat")))
+        assert_eq!(Some("dat".to_string()), File::ext(Path::new("fester.dat")))
     }
 
     #[test]
     fn dotfile() {
-        assert_eq!(Some("vimrc".to_string()), ext(Path::new(".vimrc")))
+        assert_eq!(Some("vimrc".to_string()), File::ext(Path::new(".vimrc")))
     }
 
     #[test]
     fn no_extension() {
-        assert_eq!(None, ext(Path::new("jarlsberg")))
+        assert_eq!(None, File::ext(Path::new("jarlsberg")))
+    }
+}
+
+
+#[cfg(test)]
+mod filename_test {
+    use super::File;
+    use std::path::Path;
+
+    #[test]
+    fn file() {
+        assert_eq!("fester.dat", File::filename(Path::new("fester.dat")))
+    }
+
+    #[test]
+    fn no_path() {
+        assert_eq!("foo.wha", File::filename(Path::new("/var/cache/foo.wha")))
+    }
+
+    #[test]
+    fn here() {
+        assert_eq!(".", File::filename(Path::new(".")))
+    }
+
+    #[test]
+    fn there() {
+        assert_eq!("..", File::filename(Path::new("..")))
+    }
+
+    #[test]
+    fn everywhere() {
+        assert_eq!("..", File::filename(Path::new("./..")))
+    }
+
+    #[test]
+    fn topmost() {
+        assert_eq!("/", File::filename(Path::new("/")))
     }
 }

+ 1 - 1
src/info/filetype.rs

@@ -80,7 +80,7 @@ impl<'a> File<'a> {
         if self.extension_is_one_of( &[ "class", "elc", "hi", "o", "pyc" ]) {
             true
         }
-        else if let Some(dir) = self.dir {
+        else if let Some(dir) = self.parent_dir {
             self.get_source_files().iter().any(|path| dir.contains(path))
         }
         else {

+ 1 - 1
src/output/file_name.rs

@@ -49,7 +49,7 @@ impl<'a, 'dir> FileName<'a, 'dir> {
     pub fn paint(&self) -> TextCellContents {
         let mut bits = Vec::new();
 
-        if self.file.dir.is_none() {
+        if self.file.parent_dir.is_none() {
             if let Some(parent) = self.file.path.parent() {
                 self.add_parent_bits(&mut bits, parent);
             }