Bladeren bron

Add a flag to print directories as files

Also, re-use the stat result from directory-checking.
Ben S 11 jaren geleden
bovenliggende
commit
bcaf54d7dd
4 gewijzigde bestanden met toevoegingen van 52 en 31 verwijderingen
  1. 2 1
      README.md
  2. 27 13
      src/exa.rs
  3. 8 5
      src/file.rs
  4. 15 12
      src/options.rs

+ 2 - 1
README.md

@@ -16,6 +16,7 @@ Options
 - **-1**, **--oneline**: display one entry per line
 - **-1**, **--oneline**: display one entry per line
 - **-a**, **--all**: show dot files
 - **-a**, **--all**: show dot files
 - **-b**, **--binary**: use binary (power of two) file sizes
 - **-b**, **--binary**: use binary (power of two) file sizes
+- **-d**, **--list-dirs**: list directories as regular files
 - **-g**, **--group**: show group as well as user
 - **-g**, **--group**: show group as well as user
 - **-h**, **--header**: show a header row
 - **-h**, **--header**: show a header row
 - **-H**, **--links**: show number of hard links column
 - **-H**, **--links**: show number of hard links column
@@ -31,4 +32,4 @@ You can sort by **name**, **size**, **ext**, **inode**, or **none**.
 Installation
 Installation
 ------------
 ------------
 
 
-exa is written in [Rust](http://www.rust-lang.org). You'll have to use the nightly -- I try to keep it up to date with the latest version when possible. You will also need [Cargo](http://crates.io), the Rust package manager. Once you have them both set up, a simple `cargo build` will pull in all the dependencies and compile exa.
+exa is written in [Rust](http://www.rust-lang.org). You'll have to use the nightly -- I try to keep it up to date with the latest version when possible. Once you have it set up, a simple `cargo build` will pull in all the dependencies and compile exa.

+ 27 - 13
src/exa.rs

@@ -36,24 +36,38 @@ fn main() {
     };
     };
 }
 }
 
 
-fn exa(opts: &Options) {    
+fn exa(opts: &Options) {
+	let mut dirs: Vec<String> = vec![];
+	let mut files: Vec<File> = vec![];
+	
+	// Separate the user-supplied paths into directories and files.
+	// Files are shown first, and then each directory is expanded
+	// and listed second.
+	for file in opts.path_strs.iter() {
+		let path = Path::new(file);
+		match fs::stat(&path) {
+			Ok(stat) => {
+				if !opts.list_dirs && stat.kind == TypeDirectory {
+					dirs.push(file.clone());
+				}
+				else {
+					// May as well reuse the stat result from earlier
+					// instead of just using File::from_path().
+					files.push(File::with_stat(stat, path, None));
+				}
+			}
+			Err(e) => println!("{}: {}", file, e),
+		}
+	}
+
     // It's only worth printing out directory names if the user supplied
     // It's only worth printing out directory names if the user supplied
     // more than one of them.
     // more than one of them.
     let print_dir_names = opts.path_strs.len() > 1;
     let print_dir_names = opts.path_strs.len() > 1;
-    let (dir_strs, file_strs) = opts.path_strs.clone().partition(|n| fs::stat(&Path::new(n)).unwrap().kind == TypeDirectory);
-    
-	let mut first = file_strs.is_empty();
-	let mut files = vec![];
-	for f in file_strs.iter() {
-		match File::from_path(Path::new(f), None) {
-			Ok(file) => files.push(file),
-			Err(e)   => println!("{}: {}", f, e),
-		}
-	}
-	
+	let mut first = files.is_empty();
+
 	view(opts, files);
 	view(opts, files);
     
     
-    for dir_name in dir_strs.into_iter() {
+    for dir_name in dirs.into_iter() {
         if first {
         if first {
             first = false;
             first = false;
         }
         }

+ 8 - 5
src/file.rs

@@ -32,21 +32,24 @@ pub struct File<'a> {
 
 
 impl<'a> File<'a> {
 impl<'a> File<'a> {
     pub fn from_path(path: Path, parent: Option<&'a Dir<'a>>) -> IoResult<File<'a>> {
     pub fn from_path(path: Path, parent: Option<&'a Dir<'a>>) -> IoResult<File<'a>> {
-        let v = path.filename().unwrap();  // fails if / or . or ..
-        let filename = String::from_utf8(v.to_vec()).unwrap_or_else(|_| panic!("Name was not valid UTF-8"));
-        
         // Use lstat here instead of file.stat(), as it doesn't follow
         // Use lstat here instead of file.stat(), as it doesn't follow
         // symbolic links. Otherwise, the stat() call will fail if it
         // symbolic links. Otherwise, the stat() call will fail if it
         // encounters a link that's target is non-existent.
         // encounters a link that's target is non-existent.
+        fs::lstat(&path).map(|stat| File::with_stat(stat, path.clone(), parent))
+    }
+    
+    pub fn with_stat(stat: io::FileStat, path: Path, parent: Option<&'a Dir<'a>>) -> File<'a> {
+		let v = path.filename().unwrap();  // fails if / or . or ..
+        let filename = String::from_utf8(v.to_vec()).unwrap_or_else(|_| panic!("Name was not valid UTF-8"));
 
 
-        fs::lstat(&path).map(|stat| File {
+    	File {
             path:  path.clone(),
             path:  path.clone(),
             dir:   parent,
             dir:   parent,
             stat:  stat,
             stat:  stat,
             name:  filename.clone(),
             name:  filename.clone(),
             ext:   File::ext(filename.clone()),
             ext:   File::ext(filename.clone()),
             parts: SortPart::split_into_parts(filename.clone()),
             parts: SortPart::split_into_parts(filename.clone()),
-        })
+    	}
     }
     }
 
 
     fn ext(name: String) -> Option<String> {
     fn ext(name: String) -> Option<String> {

+ 15 - 12
src/options.rs

@@ -32,6 +32,7 @@ pub enum View {
 
 
 pub struct Options {
 pub struct Options {
     pub header: bool,
     pub header: bool,
+    pub list_dirs: bool,
     pub path_strs: Vec<String>,
     pub path_strs: Vec<String>,
     pub reverse: bool,
     pub reverse: bool,
     pub show_invisibles: bool,
     pub show_invisibles: bool,
@@ -43,24 +44,26 @@ pub struct Options {
 impl Options {
 impl Options {
     pub fn getopts(args: Vec<String>) -> Result<Options, getopts::Fail_> {
     pub fn getopts(args: Vec<String>) -> Result<Options, getopts::Fail_> {
         let opts = &[
         let opts = &[
-            getopts::optflag("1", "oneline", "display one entry per line"),
-            getopts::optflag("a", "all", "show dot-files"),
-            getopts::optflag("b", "binary", "use binary prefixes in file sizes"),
-            getopts::optflag("g", "group", "show group as well as user"),
-            getopts::optflag("h", "header", "show a header row at the top"),
-            getopts::optflag("H", "links", "show number of hard links"),
-            getopts::optflag("l", "long", "display extended details and attributes"),
-            getopts::optflag("i", "inode", "show each file's inode number"),
-            getopts::optflag("r", "reverse", "reverse order of files"),
-            getopts::optopt ("s", "sort", "field to sort by", "WORD"),
-            getopts::optflag("S", "blocks", "show number of file system blocks"),
-            getopts::optflag("x", "across", "sort multi-column view entries across"),
+            getopts::optflag("1", "oneline",   "display one entry per line"),
+            getopts::optflag("a", "all",       "show dot-files"),
+            getopts::optflag("b", "binary",    "use binary prefixes in file sizes"),
+			getopts::optflag("d", "list-dirs", "list directories as regular files"),
+            getopts::optflag("g", "group",     "show group as well as user"),
+            getopts::optflag("h", "header",    "show a header row at the top"),
+            getopts::optflag("H", "links",     "show number of hard links"),
+            getopts::optflag("l", "long",      "display extended details and attributes"),
+            getopts::optflag("i", "inode",     "show each file's inode number"),
+            getopts::optflag("r", "reverse",   "reverse order of files"),
+            getopts::optopt ("s", "sort",      "field to sort by", "WORD"),
+            getopts::optflag("S", "blocks",    "show number of file system blocks"),
+            getopts::optflag("x", "across",    "sort multi-column view entries across"),
         ];
         ];
 
 
         match getopts::getopts(args.tail(), opts) {
         match getopts::getopts(args.tail(), opts) {
             Err(f) => Err(f),
             Err(f) => Err(f),
             Ok(ref matches) => Ok(Options {
             Ok(ref matches) => Ok(Options {
                 header:          matches.opt_present("header"),
                 header:          matches.opt_present("header"),
+                list_dirs:       matches.opt_present("list-dirs"),
                 path_strs:       if matches.free.is_empty() { vec![ ".".to_string() ] } else { matches.free.clone() },
                 path_strs:       if matches.free.is_empty() { vec![ ".".to_string() ] } else { matches.free.clone() },
                 reverse:         matches.opt_present("reverse"),
                 reverse:         matches.opt_present("reverse"),
                 show_invisibles: matches.opt_present("all"),
                 show_invisibles: matches.opt_present("all"),