Benjamin Sago 8 лет назад
Родитель
Сommit
763e833b6f
9 измененных файлов с 150 добавлено и 49 удалено
  1. 23 7
      src/bin/main.rs
  2. 6 4
      src/options/filter.rs
  3. 36 7
      src/options/help.rs
  4. 10 8
      src/options/misfire.rs
  5. 7 19
      src/options/mod.rs
  6. 5 4
      src/options/view.rs
  7. 42 0
      xtests/help
  8. 18 0
      xtests/help_long
  9. 3 0
      xtests/run.sh

+ 23 - 7
src/bin/main.rs

@@ -5,28 +5,44 @@ use std::env::args_os;
 use std::io::{stdout, stderr, Write, ErrorKind};
 use std::process::exit;
 
+
 fn main() {
     let args = args_os().skip(1);
-    let mut stdout = stdout();
-
-    match Exa::new(args, &mut stdout) {
+    match Exa::new(args, &mut stdout()) {
         Ok(mut exa) => {
             match exa.run() {
                 Ok(exit_status) => exit(exit_status),
                 Err(e) => {
                     match e.kind() {
-                        ErrorKind::BrokenPipe => exit(0),
+                        ErrorKind::BrokenPipe => exit(exits::SUCCESS),
                         _ => {
                             writeln!(stderr(), "{}", e).unwrap();
-                            exit(1);
+                            exit(exits::RUNTIME_ERROR);
                         },
                     };
                 }
             };
         },
-        Err(e) => {
+
+        Err(ref e) if e.is_error() => {
             writeln!(stderr(), "{}", e).unwrap();
-            exit(e.error_code());
+            exit(exits::OPTIONS_ERROR);
+        },
+
+        Err(ref e) => {
+            writeln!(stdout(), "{}", e).unwrap();
+            exit(exits::SUCCESS);
         },
     };
 }
+
+
+extern crate libc;
+#[allow(trivial_numeric_casts)]
+mod exits {
+    use libc::{self, c_int};
+
+    pub const SUCCESS:       c_int = libc::EXIT_SUCCESS;
+    pub const RUNTIME_ERROR: c_int = libc::EXIT_FAILURE;
+    pub const OPTIONS_ERROR: c_int = 3 as c_int;
+}

+ 6 - 4
src/options/filter.rs

@@ -225,6 +225,11 @@ impl SortField {
     /// argument. This will return `Err` if the option is there, but does not
     /// correspond to a valid field.
     fn deduce(matches: &getopts::Matches) -> Result<SortField, Misfire> {
+
+        const SORTS: &[&str] = &[ "name", "Name", "size", "extension",
+                                  "Extension", "modified", "accessed",
+                                  "created", "inode", "none" ];
+
         if let Some(word) = matches.opt_str("sort") {
             match &*word {
                 "name" | "filename"   => Ok(SortField::Name(SortCase::Sensitive)),
@@ -237,10 +242,7 @@ impl SortField {
                 "cr"   | "created"    => Ok(SortField::CreatedDate),
                 "none"                => Ok(SortField::Unsorted),
                 "inode"               => Ok(SortField::FileInode),
-                field                 => Err(Misfire::bad_argument("sort", field, &[
-                                            "name", "Name", "size", "extension", "Extension",
-                                            "modified", "accessed", "created", "inode", "none"]
-                ))
+                field                 => Err(Misfire::bad_argument("sort", field, SORTS))
             }
         }
         else {

+ 36 - 7
src/options/help.rs

@@ -1,5 +1,7 @@
+use std::fmt;
 
-pub static OPTIONS: &str = r##"
+
+static OPTIONS: &str = r##"
   -?, --help         show list of command-line options
   -v, --version      show version of exa
 
@@ -23,10 +25,9 @@ FILTERING AND SORTING OPTIONS
   -I, --ignore-glob GLOBS    glob patterns (pipe-separated) of files to ignore
   Valid sort fields:         name, Name, extension, Extension, size,
                              modified, accessed, created, inode, none
-
 "##;
 
-pub static LONG_OPTIONS: &str = r##"
+static LONG_OPTIONS: &str = r##"
 LONG VIEW OPTIONS
   -b, --binary       list file sizes with binary prefixes
   -B, --bytes        list file sizes in bytes, without any prefixes
@@ -39,8 +40,36 @@ LONG VIEW OPTIONS
   -S, --blocks       show number of file system blocks
   -t, --time FIELD   which timestamp field to list (modified, accessed, created)
   -u, --accessed     use the accessed timestamp field
-  -U, --created      use the created timestamp field
-"##;
+  -U, --created      use the created timestamp field"##;
+
+static GIT_HELP:      &str = r##"  --git              list each file's Git status, if tracked"##;
+static EXTENDED_HELP: &str = r##"  -@, --extended     list each file's extended attributes and sizes"##;
+
+#[derive(PartialEq, Debug)]
+pub struct HelpString {
+    pub only_long: bool,
+    pub git: bool,
+    pub xattrs: bool,
+}
+
+impl fmt::Display for HelpString {
+    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+        try!(write!(f, "Usage:\n  exa [options] [files...]\n"));
+
+        if !self.only_long {
+            try!(write!(f, "{}", OPTIONS));
+        }
+
+        try!(write!(f, "{}", LONG_OPTIONS));
+
+        if self.git {
+            try!(write!(f, "\n{}", GIT_HELP));
+        }
+
+        if self.xattrs {
+            try!(write!(f, "\n{}", EXTENDED_HELP));
+        }
 
-pub static GIT_HELP:      &str = r##"  --git              list each file's Git status, if tracked"##;
-pub static EXTENDED_HELP: &str = r##"  -@, --extended     list each file's extended attributes and sizes"##;
+        Ok(())
+    }
+}

+ 10 - 8
src/options/misfire.rs

@@ -4,10 +4,12 @@ use std::num::ParseIntError;
 use getopts;
 use glob;
 
+use options::help::HelpString;
+
 
 /// A list of legal choices for an argument-taking option
 #[derive(PartialEq, Debug)]
-pub struct Choices(Vec<&'static str>);
+pub struct Choices(&'static [&'static str]);
 
 impl fmt::Display for Choices {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -28,7 +30,7 @@ pub enum Misfire {
 
     /// The user asked for help. This isn’t strictly an error, which is why
     /// this enum isn’t named Error!
-    Help(String),
+    Help(HelpString),
 
     /// The user wanted the version number.
     Version,
@@ -54,11 +56,11 @@ pub enum Misfire {
 impl Misfire {
 
     /// The OS return code this misfire should signify.
-    pub fn error_code(&self) -> i32 {
+    pub fn is_error(&self) -> bool {
         match *self {
-            Misfire::Help(_) => 0,
-            Misfire::Version => 0,
-            _                => 3,
+            Misfire::Help(_) => false,
+            Misfire::Version => false,
+            _                => true,
         }
     }
 
@@ -66,10 +68,10 @@ impl Misfire {
     /// argument. This has to use one of the `getopts` failure
     /// variants--it’s meant to take just an option name, rather than an
     /// option *and* an argument, but it works just as well.
-    pub fn bad_argument(option: &str, otherwise: &str, legal: &[&'static str]) -> Misfire {
+    pub fn bad_argument(option: &str, otherwise: &str, legal: &'static [&'static str]) -> Misfire {
         Misfire::BadArgument(getopts::Fail::UnrecognizedOption(format!(
             "--{} {}",
-            option, otherwise)), Choices(legal.into()))
+            option, otherwise)), Choices(legal))
     }
 }
 

+ 7 - 19
src/options/mod.rs

@@ -12,7 +12,7 @@ mod filter;
 pub use self::filter::{FileFilter, SortField, SortCase};
 
 mod help;
-use self::help::*;
+use self::help::HelpString;
 
 mod misfire;
 pub use self::misfire::Misfire;
@@ -103,25 +103,13 @@ impl Options {
         };
 
         if matches.opt_present("help") {
-            let mut help_string = "Usage:\n  exa [options] [files...]\n".to_owned();
+            let help = HelpString {
+                only_long: matches.opt_present("long"),
+                git: cfg!(feature="git"),
+                xattrs: xattr::ENABLED,
+            };
 
-            if !matches.opt_present("long") {
-                help_string.push_str(OPTIONS);
-            }
-
-            help_string.push_str(LONG_OPTIONS);
-
-            if cfg!(feature="git") {
-                help_string.push_str(GIT_HELP);
-                help_string.push('\n');
-            }
-
-            if xattr::ENABLED {
-                help_string.push_str(EXTENDED_HELP);
-                help_string.push('\n');
-            }
-
-            return Err(Misfire::Help(help_string));
+            return Err(Misfire::Help(help));
         }
         else if matches.opt_present("version") {
             return Err(Misfire::Version);

+ 5 - 4
src/options/view.rs

@@ -294,12 +294,12 @@ impl TimeTypes {
                 return Err(Misfire::Useless("accessed", true, "time"));
             }
 
+            static TIMES: &[& str] = &["modified", "accessed", "created"];
             match &*word {
                 "mod" | "modified"  => Ok(TimeTypes { accessed: false, modified: true,  created: false }),
                 "acc" | "accessed"  => Ok(TimeTypes { accessed: true,  modified: false, created: false }),
                 "cr"  | "created"   => Ok(TimeTypes { accessed: false, modified: false, created: true  }),
-                otherwise           => Err(Misfire::bad_argument("time", otherwise,
-                                                                 &["modified", "accessed", "created"])),
+                otherwise           => Err(Misfire::bad_argument("time", otherwise, TIMES))
             }
         }
         else if modified || created || accessed {
@@ -342,13 +342,14 @@ impl TerminalColours {
 
     /// Determine which terminal colour conditions to use.
     fn deduce(matches: &getopts::Matches) -> Result<TerminalColours, Misfire> {
+        const COLOURS: &[&str] = &["always", "auto", "never"];
+
         if let Some(word) = matches.opt_str("color").or_else(|| matches.opt_str("colour")) {
             match &*word {
                 "always"              => Ok(TerminalColours::Always),
                 "auto" | "automatic"  => Ok(TerminalColours::Automatic),
                 "never"               => Ok(TerminalColours::Never),
-                otherwise             => Err(Misfire::bad_argument("color", otherwise,
-                                                                   &["always", "auto", "never"]))
+                otherwise             => Err(Misfire::bad_argument("color", otherwise, COLOURS))
             }
         }
         else {

+ 42 - 0
xtests/help

@@ -0,0 +1,42 @@
+Usage:
+  exa [options] [files...]
+
+  -?, --help         show list of command-line options
+  -v, --version      show version of exa
+
+DISPLAY OPTIONS
+  -1, --oneline      display one entry per line
+  -l, --long         display extended file metadata as a table
+  -G, --grid         display entries as a grid (default)
+  -x, --across       sort the grid across, rather than downwards
+  -R, --recurse      recurse into directories
+  -T, --tree         recurse into directories as a tree
+  -F, --classify     display type indicator by file names
+  --colo[u]r=WHEN    when to use terminal colours (always, auto, never)
+  --colo[u]r-scale   highlight levels of file sizes distinctly
+
+FILTERING AND SORTING OPTIONS
+  -a, --all                  don't hide hidden and 'dot' files
+  -d, --list-dirs            list directories like regular files
+  -r, --reverse              reverse the sort order
+  -s, --sort SORT_FIELD      which field to sort by:
+  --group-directories-first  list directories before other files
+  -I, --ignore-glob GLOBS    glob patterns (pipe-separated) of files to ignore
+  Valid sort fields:         name, Name, extension, Extension, size,
+                             modified, accessed, created, inode, none
+
+LONG VIEW OPTIONS
+  -b, --binary       list file sizes with binary prefixes
+  -B, --bytes        list file sizes in bytes, without any prefixes
+  -g, --group        list each file's group
+  -h, --header       add a header row to each column
+  -H, --links        list each file's number of hard links
+  -i, --inode        list each file's inode number
+  -L, --level DEPTH  limit the depth of recursion
+  -m, --modified     use the modified timestamp field
+  -S, --blocks       show number of file system blocks
+  -t, --time FIELD   which timestamp field to list (modified, accessed, created)
+  -u, --accessed     use the accessed timestamp field
+  -U, --created      use the created timestamp field
+  --git              list each file's Git status, if tracked
+  -@, --extended     list each file's extended attributes and sizes

+ 18 - 0
xtests/help_long

@@ -0,0 +1,18 @@
+Usage:
+  exa [options] [files...]
+
+LONG VIEW OPTIONS
+  -b, --binary       list file sizes with binary prefixes
+  -B, --bytes        list file sizes in bytes, without any prefixes
+  -g, --group        list each file's group
+  -h, --header       add a header row to each column
+  -H, --links        list each file's number of hard links
+  -i, --inode        list each file's inode number
+  -L, --level DEPTH  limit the depth of recursion
+  -m, --modified     use the modified timestamp field
+  -S, --blocks       show number of file system blocks
+  -t, --time FIELD   which timestamp field to list (modified, accessed, created)
+  -u, --accessed     use the accessed timestamp field
+  -U, --created      use the created timestamp field
+  --git              list each file's Git status, if tracked
+  -@, --extended     list each file's extended attributes and sizes

+ 3 - 0
xtests/run.sh

@@ -109,5 +109,8 @@ $exa $testcases/links/* -1 | diff -q - $results/links_1_files || exit 1
 $exa $testcases/git/additions -l --git 2>&1 | diff -q - $results/git_additions  || exit 1
 $exa $testcases/git/edits     -l --git 2>&1 | diff -q - $results/git_edits      || exit 1
 
+# And finally...
+$exa --help        | diff -q - $results/help      || exit 1
+$exa --help --long | diff -q - $results/help_long || exit 1
 
 echo "All the tests passed!"