summaryrefslogtreecommitdiffstats
path: root/src/options/mod.rs
diff options
context:
space:
mode:
authorBenjamin Sago <ogham@bsago.me>2016-04-17 20:38:37 +0100
committerBenjamin Sago <ogham@bsago.me>2016-04-17 20:38:37 +0100
commite9e1161cec18dd04a68a3147897093a5abc062bb (patch)
tree6e0781d2a26ec3e60b83f6e62f5460352ac94292 /src/options/mod.rs
parentb44ae1b56b3e0f83c5ab314b96b221274b3c5d3c (diff)
Split up the options module
The original options was becoming a bit unwieldy, and would have been even more so if I added the same amount of comments. So this commit splits it up. There's no extra hiding going on here, or rearranging things within the module: (almost) everything now has to be marked 'pub' to let other sub-modules in the new options module to see it.
Diffstat (limited to 'src/options/mod.rs')
-rw-r--r--src/options/mod.rs282
1 files changed, 282 insertions, 0 deletions
diff --git a/src/options/mod.rs b/src/options/mod.rs
new file mode 100644
index 0000000..7a417e3
--- /dev/null
+++ b/src/options/mod.rs
@@ -0,0 +1,282 @@
+use getopts;
+
+use fs::feature::xattr;
+use output::{Details, GridDetails};
+
+mod dir_action;
+pub use self::dir_action::{DirAction, RecurseOptions};
+
+mod filter;
+pub use self::filter::{FileFilter, SortField, SortCase};
+
+mod help;
+use self::help::*;
+
+mod misfire;
+pub use self::misfire::Misfire;
+
+mod view;
+pub use self::view::View;
+
+
+/// These **options** represent a parsed, error-checked versions of the
+/// user’s command-line options.
+#[derive(PartialEq, Debug, Copy, Clone)]
+pub struct Options {
+
+ /// The action to perform when encountering a directory rather than a
+ /// regular file.
+ pub dir_action: DirAction,
+
+ /// How to sort and filter files before outputting them.
+ pub filter: FileFilter,
+
+ /// The type of output to use (lines, grid, or details).
+ pub view: View,
+}
+
+impl Options {
+
+ /// Call getopts on the given slice of command-line strings.
+ #[allow(unused_results)]
+ pub fn getopts(args: &[String]) -> Result<(Options, Vec<String>), Misfire> {
+ let mut opts = getopts::Options::new();
+
+ opts.optflag("v", "version", "display version of exa");
+ opts.optflag("?", "help", "show list of command-line options");
+
+ // Display options
+ opts.optflag("1", "oneline", "display one entry per line");
+ opts.optflag("G", "grid", "display entries in a grid view (default)");
+ opts.optflag("l", "long", "display extended details and attributes");
+ opts.optflag("R", "recurse", "recurse into directories");
+ opts.optflag("T", "tree", "recurse into subdirectories in a tree view");
+ opts.optflag("x", "across", "sort multi-column view entries across");
+ opts.optopt ("", "color", "when to show anything in colours", "WHEN");
+ opts.optopt ("", "colour", "when to show anything in colours (alternate spelling)", "WHEN");
+
+ // Filtering and sorting options
+ opts.optflag("", "group-directories-first", "list directories before other files");
+ opts.optflag("a", "all", "show dot-files");
+ opts.optflag("d", "list-dirs", "list directories as regular files");
+ opts.optflag("r", "reverse", "reverse order of files");
+ opts.optopt ("s", "sort", "field to sort by", "WORD");
+
+ // Long view options
+ opts.optflag("b", "binary", "use binary prefixes in file sizes");
+ opts.optflag("B", "bytes", "list file sizes in bytes, without prefixes");
+ opts.optflag("g", "group", "show group as well as user");
+ opts.optflag("h", "header", "show a header row at the top");
+ opts.optflag("H", "links", "show number of hard links");
+ opts.optflag("i", "inode", "show each file's inode number");
+ opts.optopt ("L", "level", "maximum depth of recursion", "DEPTH");
+ opts.optflag("m", "modified", "display timestamp of most recent modification");
+ opts.optflag("S", "blocks", "show number of file system blocks");
+ opts.optopt ("t", "time", "which timestamp to show for a file", "WORD");
+ opts.optflag("u", "accessed", "display timestamp of last access for a file");
+ opts.optflag("U", "created", "display timestamp of creation for a file");
+
+ if cfg!(feature="git") {
+ opts.optflag("", "git", "show git status");
+ }
+
+ if xattr::ENABLED {
+ opts.optflag("@", "extended", "display extended attribute keys and sizes");
+ }
+
+ let matches = match opts.parse(args) {
+ Ok(m) => m,
+ Err(e) => return Err(Misfire::InvalidOptions(e)),
+ };
+
+ if matches.opt_present("help") {
+ let mut help_string = "Usage:\n exa [options] [files...]\n".to_owned();
+
+ 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));
+ }
+ else if matches.opt_present("version") {
+ return Err(Misfire::Version);
+ }
+
+ let options = try!(Options::deduce(&matches));
+ Ok((options, matches.free))
+ }
+
+ /// Whether the View specified in this set of options includes a Git
+ /// status column. It’s only worth trying to discover a repository if the
+ /// results will end up being displayed.
+ pub fn should_scan_for_git(&self) -> bool {
+ match self.view {
+ View::Details(Details { columns: Some(cols), .. }) => cols.should_scan_for_git(),
+ View::GridDetails(GridDetails { details: Details { columns: Some(cols), .. }, .. }) => cols.should_scan_for_git(),
+ _ => false,
+ }
+ }
+
+ /// Determines the complete set of options based on the given command-line
+ /// arguments, after they’ve been parsed.
+ fn deduce(matches: &getopts::Matches) -> Result<Options, Misfire> {
+ let dir_action = try!(DirAction::deduce(&matches));
+ let filter = try!(FileFilter::deduce(&matches));
+ let view = try!(View::deduce(&matches, filter, dir_action));
+
+ Ok(Options {
+ dir_action: dir_action,
+ view: view,
+ filter: filter,
+ })
+ }
+}
+
+
+#[cfg(test)]
+mod test {
+ use super::{Options, Misfire, SortField, SortCase};
+ use fs::feature::xattr;
+
+ fn is_helpful<T>(misfire: Result<T, Misfire>) -> bool {
+ match misfire {
+ Err(Misfire::Help(_)) => true,
+ _ => false,
+ }
+ }
+
+ #[test]
+ fn help() {
+ let opts = Options::getopts(&[ "--help".to_string() ]);
+ assert!(is_helpful(opts))
+ }
+
+ #[test]
+ fn help_with_file() {
+ let opts = Options::getopts(&[ "--help".to_string(), "me".to_string() ]);
+ assert!(is_helpful(opts))
+ }
+
+ #[test]
+ fn files() {
+ let args = Options::getopts(&[ "this file".to_string(), "that file".to_string() ]).unwrap().1;
+ assert_eq!(args, vec![ "this file".to_string(), "that file".to_string() ])
+ }
+
+ #[test]
+ fn no_args() {
+ let args = Options::getopts(&[]).unwrap().1;
+ assert!(args.is_empty()); // Listing the `.` directory is done in main.rs
+ }
+
+ #[test]
+ fn file_sizes() {
+ let opts = Options::getopts(&[ "--long".to_string(), "--binary".to_string(), "--bytes".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Conflict("binary", "bytes"))
+ }
+
+ #[test]
+ fn just_binary() {
+ let opts = Options::getopts(&[ "--binary".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless("binary", false, "long"))
+ }
+
+ #[test]
+ fn just_bytes() {
+ let opts = Options::getopts(&[ "--bytes".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless("bytes", false, "long"))
+ }
+
+ #[test]
+ fn long_across() {
+ let opts = Options::getopts(&[ "--long".to_string(), "--across".to_string() ]);
+ assert_eq!(opts, Err(Misfire::Useless("across", true, "long")))
+ }
+
+ #[test]
+ fn oneline_across() {
+ let opts = Options::getopts(&[ "--oneline".to_string(), "--across".to_string() ]);
+ assert_eq!(opts, Err(Misfire::Useless("across", true, "oneline")))
+ }
+
+ #[test]
+ fn just_header() {
+ let opts = Options::getopts(&[ "--header".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless("header", false, "long"))
+ }
+
+ #[test]
+ fn just_group() {
+ let opts = Options::getopts(&[ "--group".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless("group", false, "long"))
+ }
+
+ #[test]
+ fn just_inode() {
+ let opts = Options::getopts(&[ "--inode".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless("inode", false, "long"))
+ }
+
+ #[test]
+ fn just_links() {
+ let opts = Options::getopts(&[ "--links".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless("links", false, "long"))
+ }
+
+ #[test]
+ fn just_blocks() {
+ let opts = Options::getopts(&[ "--blocks".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless("blocks", false, "long"))
+ }
+
+ #[test]
+ fn test_sort_size() {
+ let opts = Options::getopts(&[ "--sort=size".to_string() ]);
+ assert_eq!(opts.unwrap().0.filter.sort_field, SortField::Size);
+ }
+
+ #[test]
+ fn test_sort_name() {
+ let opts = Options::getopts(&[ "--sort=name".to_string() ]);
+ assert_eq!(opts.unwrap().0.filter.sort_field, SortField::Name(SortCase::Sensitive));
+ }
+
+ #[test]
+ fn test_sort_name_lowercase() {
+ let opts = Options::getopts(&[ "--sort=Name".to_string() ]);
+ assert_eq!(opts.unwrap().0.filter.sort_field, SortField::Name(SortCase::Insensitive));
+ }
+
+ #[test]
+ #[cfg(feature="git")]
+ fn just_git() {
+ let opts = Options::getopts(&[ "--git".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless("git", false, "long"))
+ }
+
+ #[test]
+ fn extended_without_long() {
+ if xattr::ENABLED {
+ let opts = Options::getopts(&[ "--extended".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless("extended", false, "long"))
+ }
+ }
+
+ #[test]
+ fn level_without_recurse_or_tree() {
+ let opts = Options::getopts(&[ "--level".to_string(), "69105".to_string() ]);
+ assert_eq!(opts.unwrap_err(), Misfire::Useless2("level", "recurse", "tree"))
+ }
+}