mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-05-30 22:20:47 +00:00
Switch to the new options parser
This commit removes the dependency on the ‘getopts’ crate entirely, and re-writes all its uses to use the new options parser instead. As expected there are casualties galore: - We now need to collect the options into a vector at the start, so we can use references to them, knowing they’ll be stored *somewhere*. - Because OsString isn’t Display, its Debug impl gets used instead. (This is hopefully temporary) - Options that take values (such as ‘sort’ or ‘time-style’) now parse those values with ‘to_string_lossy’. The ‘lossy’ part means “I’m at a loss for what to do here” - Error messages got a lot worse, but “--tree --all --all” is now a special case of error rather than just another Misfire::Useless. - Some tests had to be re-written to deal with the fact that the parser works with references. - ParseError loses its lifetime and owns its contents, to avoid having to attach <'a> to Misfire. - The parser now takes an iterator instead of a slice. - OsStrings can’t be ‘match’ patterns, so the code devolves to using long Eq chains instead. - Make a change to the xtest that assumed an input argument with invalid UTF-8 in was always an error to stderr, when that now in fact works! - Fix a bug in Vagrant where ‘exa’ and ‘rexa’ didn’t properly escape filenames with spaces in.
This commit is contained in:
parent
5b1966d261
commit
2d1f462bfa
4
Vagrantfile
vendored
4
Vagrantfile
vendored
|
@ -59,8 +59,8 @@ Vagrant.configure(2) do |config|
|
|||
config.vm.provision :shell, privileged: true, inline: <<-EOF
|
||||
set -xe
|
||||
|
||||
echo -e "#!/bin/sh\n/home/#{developer}/target/debug/exa \\$*" > /usr/bin/exa
|
||||
echo -e "#!/bin/sh\n/home/#{developer}/target/release/exa \\$*" > /usr/bin/rexa
|
||||
echo -e "#!/bin/sh\n/home/#{developer}/target/debug/exa \"\\$*\"" > /usr/bin/exa
|
||||
echo -e "#!/bin/sh\n/home/#{developer}/target/release/exa \"\\$*\"" > /usr/bin/rexa
|
||||
chmod +x /usr/bin/{exa,rexa}
|
||||
EOF
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
extern crate exa;
|
||||
use exa::Exa;
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::env::args_os;
|
||||
use std::io::{stdout, stderr, Write, ErrorKind};
|
||||
use std::process::exit;
|
||||
|
||||
|
||||
fn main() {
|
||||
let args = args_os().skip(1);
|
||||
match Exa::new(args, &mut stdout()) {
|
||||
let args: Vec<OsString> = args_os().skip(1).collect();
|
||||
match Exa::new(args.iter(), &mut stdout()) {
|
||||
Ok(mut exa) => {
|
||||
match exa.run() {
|
||||
Ok(exit_status) => exit(exit_status),
|
||||
|
|
23
src/exa.rs
23
src/exa.rs
|
@ -3,7 +3,6 @@
|
|||
|
||||
extern crate ansi_term;
|
||||
extern crate datetime;
|
||||
extern crate getopts;
|
||||
extern crate glob;
|
||||
extern crate libc;
|
||||
extern crate locale;
|
||||
|
@ -22,7 +21,7 @@ extern crate zoneinfo_compiled;
|
|||
extern crate lazy_static;
|
||||
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::{stderr, Write, Result as IOResult};
|
||||
use std::path::{Component, PathBuf};
|
||||
|
||||
|
@ -41,7 +40,7 @@ mod term;
|
|||
|
||||
|
||||
/// The main program wrapper.
|
||||
pub struct Exa<'w, W: Write + 'w> {
|
||||
pub struct Exa<'args, 'w, W: Write + 'w> {
|
||||
|
||||
/// List of command-line options, having been successfully parsed.
|
||||
pub options: Options,
|
||||
|
@ -53,12 +52,12 @@ pub struct Exa<'w, W: Write + 'w> {
|
|||
|
||||
/// List of the free command-line arguments that should correspond to file
|
||||
/// names (anything that isn’t an option).
|
||||
pub args: Vec<String>,
|
||||
pub args: Vec<&'args OsStr>,
|
||||
}
|
||||
|
||||
impl<'w, W: Write + 'w> Exa<'w, W> {
|
||||
pub fn new<C>(args: C, writer: &'w mut W) -> Result<Exa<'w, W>, Misfire>
|
||||
where C: IntoIterator, C::Item: AsRef<OsStr> {
|
||||
impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
|
||||
pub fn new<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
|
||||
where I: Iterator<Item=&'args OsString> {
|
||||
Options::getopts(args).map(move |(options, args)| {
|
||||
Exa { options, writer, args }
|
||||
})
|
||||
|
@ -71,20 +70,20 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
|
|||
|
||||
// List the current directory by default, like ls.
|
||||
if self.args.is_empty() {
|
||||
self.args.push(".".to_owned());
|
||||
self.args = vec![ OsStr::new(".") ];
|
||||
}
|
||||
|
||||
for file_name in &self.args {
|
||||
match File::new(PathBuf::from(file_name), None, None) {
|
||||
for file_path in &self.args {
|
||||
match File::new(PathBuf::from(file_path), None, None) {
|
||||
Err(e) => {
|
||||
exit_status = 2;
|
||||
writeln!(stderr(), "{}: {}", file_name, e)?;
|
||||
writeln!(stderr(), "{:?}: {}", file_path, e)?;
|
||||
},
|
||||
Ok(f) => {
|
||||
if f.is_directory() && !self.options.dir_action.treat_dirs_as_files() {
|
||||
match f.to_dir(self.options.should_scan_for_git()) {
|
||||
Ok(d) => dirs.push(d),
|
||||
Err(e) => writeln!(stderr(), "{}: {}", file_name, e)?,
|
||||
Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
use getopts;
|
||||
use options::parser::Matches;
|
||||
use options::{flags, Misfire};
|
||||
|
||||
use options::misfire::Misfire;
|
||||
use fs::dir_action::{DirAction, RecurseOptions};
|
||||
|
||||
|
||||
|
||||
impl DirAction {
|
||||
|
||||
/// Determine which action to perform when trying to list a directory.
|
||||
pub fn deduce(matches: &getopts::Matches) -> Result<DirAction, Misfire> {
|
||||
let recurse = matches.opt_present("recurse");
|
||||
let list = matches.opt_present("list-dirs");
|
||||
let tree = matches.opt_present("tree");
|
||||
pub fn deduce(matches: &Matches) -> Result<DirAction, Misfire> {
|
||||
let recurse = matches.has(&flags::RECURSE);
|
||||
let list = matches.has(&flags::LIST_DIRS);
|
||||
let tree = matches.has(&flags::TREE);
|
||||
|
||||
match (recurse, list, tree) {
|
||||
|
||||
// You can't --list-dirs along with --recurse or --tree because
|
||||
// they already automatically list directories.
|
||||
(true, true, _ ) => Err(Misfire::Conflict("recurse", "list-dirs")),
|
||||
(_, true, true ) => Err(Misfire::Conflict("tree", "list-dirs")),
|
||||
(true, true, _ ) => Err(Misfire::Conflict(&flags::RECURSE, &flags::LIST_DIRS)),
|
||||
(_, true, true ) => Err(Misfire::Conflict(&flags::TREE, &flags::LIST_DIRS)),
|
||||
|
||||
(_ , _, true ) => Ok(DirAction::Recurse(RecurseOptions::deduce(matches, true)?)),
|
||||
(true, false, false) => Ok(DirAction::Recurse(RecurseOptions::deduce(matches, false)?)),
|
||||
|
@ -32,9 +31,9 @@ impl DirAction {
|
|||
impl RecurseOptions {
|
||||
|
||||
/// Determine which files should be recursed into.
|
||||
pub fn deduce(matches: &getopts::Matches, tree: bool) -> Result<RecurseOptions, Misfire> {
|
||||
let max_depth = if let Some(level) = matches.opt_str("level") {
|
||||
match level.parse() {
|
||||
pub fn deduce(matches: &Matches, tree: bool) -> Result<RecurseOptions, Misfire> {
|
||||
let max_depth = if let Some(level) = matches.get(&flags::LEVEL) {
|
||||
match level.to_string_lossy().parse() {
|
||||
Ok(l) => Some(l),
|
||||
Err(e) => return Err(Misfire::FailedParse(e)),
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
use getopts;
|
||||
use glob;
|
||||
|
||||
use fs::DotFilter;
|
||||
use fs::filter::{FileFilter, SortField, SortCase, IgnorePatterns};
|
||||
use options::misfire::Misfire;
|
||||
|
||||
use options::{flags, Misfire};
|
||||
use options::parser::Matches;
|
||||
|
||||
|
||||
impl FileFilter {
|
||||
|
||||
/// Determines the set of file filter options to use, based on the user’s
|
||||
/// command-line arguments.
|
||||
pub fn deduce(matches: &getopts::Matches) -> Result<FileFilter, Misfire> {
|
||||
pub fn deduce(matches: &Matches) -> Result<FileFilter, Misfire> {
|
||||
Ok(FileFilter {
|
||||
list_dirs_first: matches.opt_present("group-directories-first"),
|
||||
reverse: matches.opt_present("reverse"),
|
||||
list_dirs_first: matches.has(&flags::DIRS_FIRST),
|
||||
reverse: matches.has(&flags::REVERSE),
|
||||
sort_field: SortField::deduce(matches)?,
|
||||
dot_filter: DotFilter::deduce(matches)?,
|
||||
ignore_patterns: IgnorePatterns::deduce(matches)?,
|
||||
|
@ -34,45 +35,67 @@ impl SortField {
|
|||
/// Determine the sort field to use, based on the presence of a “sort”
|
||||
/// 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> {
|
||||
fn deduce(matches: &Matches) -> Result<SortField, Misfire> {
|
||||
|
||||
const SORTS: &[&str] = &[ "name", "Name", "size", "extension",
|
||||
"Extension", "modified", "accessed",
|
||||
"created", "inode", "type", "none" ];
|
||||
|
||||
if let Some(word) = matches.opt_str("sort") {
|
||||
match &*word {
|
||||
"name" | "filename" => Ok(SortField::Name(SortCase::Sensitive)),
|
||||
"Name" | "Filename" => Ok(SortField::Name(SortCase::Insensitive)),
|
||||
"size" | "filesize" => Ok(SortField::Size),
|
||||
"ext" | "extension" => Ok(SortField::Extension(SortCase::Sensitive)),
|
||||
"Ext" | "Extension" => Ok(SortField::Extension(SortCase::Insensitive)),
|
||||
"mod" | "modified" => Ok(SortField::ModifiedDate),
|
||||
"acc" | "accessed" => Ok(SortField::AccessedDate),
|
||||
"cr" | "created" => Ok(SortField::CreatedDate),
|
||||
"inode" => Ok(SortField::FileInode),
|
||||
"type" => Ok(SortField::FileType),
|
||||
"none" => Ok(SortField::Unsorted),
|
||||
field => Err(Misfire::bad_argument("sort", field, SORTS))
|
||||
}
|
||||
let word = match matches.get(&flags::SORT) {
|
||||
Some(w) => w,
|
||||
None => return Ok(SortField::default()),
|
||||
};
|
||||
|
||||
if word == "name" || word == "filename" {
|
||||
Ok(SortField::Name(SortCase::Sensitive))
|
||||
}
|
||||
else if word == "Name" || word == "Filename" {
|
||||
Ok(SortField::Name(SortCase::Insensitive))
|
||||
}
|
||||
else if word == "size" || word == "filesize" {
|
||||
Ok(SortField::Size)
|
||||
}
|
||||
else if word == "ext" || word == "extension" {
|
||||
Ok(SortField::Extension(SortCase::Sensitive))
|
||||
}
|
||||
else if word == "Ext" || word == "Extension" {
|
||||
Ok(SortField::Extension(SortCase::Insensitive))
|
||||
}
|
||||
else if word == "mod" || word == "modified" {
|
||||
Ok(SortField::ModifiedDate)
|
||||
}
|
||||
else if word == "acc" || word == "accessed" {
|
||||
Ok(SortField::AccessedDate)
|
||||
}
|
||||
else if word == "cr" || word == "created" {
|
||||
Ok(SortField::CreatedDate)
|
||||
}
|
||||
else if word == "inode" {
|
||||
Ok(SortField::FileInode)
|
||||
}
|
||||
else if word == "type" {
|
||||
Ok(SortField::FileType)
|
||||
}
|
||||
else if word == "none" {
|
||||
Ok(SortField::Unsorted)
|
||||
}
|
||||
else {
|
||||
Ok(SortField::default())
|
||||
Err(Misfire::bad_argument(&flags::SORT, word, SORTS))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl DotFilter {
|
||||
pub fn deduce(matches: &getopts::Matches) -> Result<DotFilter, Misfire> {
|
||||
let dots = match matches.opt_count("all") {
|
||||
pub fn deduce(matches: &Matches) -> Result<DotFilter, Misfire> {
|
||||
let dots = match matches.count(&flags::ALL) {
|
||||
0 => return Ok(DotFilter::JustFiles),
|
||||
1 => DotFilter::Dotfiles,
|
||||
_ => DotFilter::DotfilesAndDots,
|
||||
};
|
||||
|
||||
if matches.opt_present("tree") {
|
||||
Err(Misfire::Useless("all --all", true, "tree"))
|
||||
if matches.has(&flags::TREE) {
|
||||
Err(Misfire::TreeAllAll)
|
||||
}
|
||||
else {
|
||||
Ok(dots)
|
||||
|
@ -85,14 +108,15 @@ impl IgnorePatterns {
|
|||
|
||||
/// Determines the set of file filter options to use, based on the user’s
|
||||
/// command-line arguments.
|
||||
pub fn deduce(matches: &getopts::Matches) -> Result<IgnorePatterns, Misfire> {
|
||||
let patterns = match matches.opt_str("ignore-glob") {
|
||||
pub fn deduce(matches: &Matches) -> Result<IgnorePatterns, Misfire> {
|
||||
let patterns = match matches.get(&flags::IGNORE_GLOB) {
|
||||
None => Ok(Vec::new()),
|
||||
Some(is) => is.split('|').map(|a| glob::Pattern::new(a)).collect(),
|
||||
};
|
||||
Some(is) => is.to_string_lossy().split('|').map(|a| glob::Pattern::new(a)).collect(),
|
||||
}?;
|
||||
|
||||
Ok(IgnorePatterns {
|
||||
patterns: patterns?,
|
||||
})
|
||||
// TODO: is to_string_lossy really the best way to handle
|
||||
// invalid UTF-8 there?
|
||||
|
||||
Ok(IgnorePatterns { patterns })
|
||||
}
|
||||
}
|
||||
|
|
64
src/options/flags.rs
Normal file
64
src/options/flags.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use options::parser::{Arg, Args, TakesValue};
|
||||
|
||||
|
||||
// exa options
|
||||
pub static VERSION: Arg = Arg { short: Some(b'v'), long: "version", takes_value: TakesValue::Forbidden };
|
||||
pub static HELP: Arg = Arg { short: Some(b'?'), long: "help", takes_value: TakesValue::Forbidden };
|
||||
|
||||
// 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 COLOR: Arg = Arg { short: None, long: "color", takes_value: TakesValue::Necessary };
|
||||
pub static COLOUR: Arg = Arg { short: None, long: "colour", takes_value: TakesValue::Necessary };
|
||||
|
||||
pub static COLOR_SCALE: Arg = Arg { short: None, long: "color-scale", takes_value: TakesValue::Forbidden };
|
||||
pub static COLOUR_SCALE: Arg = Arg { short: None, long: "colour-scale", takes_value: TakesValue::Forbidden };
|
||||
|
||||
// filtering and sorting options
|
||||
pub static ALL: Arg = Arg { short: Some(b'a'), long: "all", takes_value: TakesValue::Forbidden };
|
||||
pub static LIST_DIRS: Arg = Arg { short: Some(b'd'), long: "list-dirs", takes_value: TakesValue::Forbidden };
|
||||
pub static LEVEL: Arg = Arg { short: Some(b'L'), long: "level", takes_value: TakesValue::Forbidden };
|
||||
pub static REVERSE: Arg = Arg { short: Some(b'r'), long: "reverse", takes_value: TakesValue::Forbidden };
|
||||
pub static SORT: Arg = Arg { short: Some(b's'), long: "sort", takes_value: TakesValue::Necessary };
|
||||
pub static IGNORE_GLOB: Arg = Arg { short: Some(b'I'), long: "ignore-glob", takes_value: TakesValue::Necessary };
|
||||
pub static DIRS_FIRST: Arg = Arg { short: None, long: "group-directories-first", takes_value: TakesValue::Forbidden };
|
||||
|
||||
// display options
|
||||
pub static BINARY: Arg = Arg { short: Some(b'b'), long: "binary", takes_value: TakesValue::Forbidden };
|
||||
pub static BYTES: Arg = Arg { short: Some(b'B'), long: "bytes", takes_value: TakesValue::Forbidden };
|
||||
pub static GROUP: Arg = Arg { short: Some(b'g'), long: "group", takes_value: TakesValue::Forbidden };
|
||||
pub static HEADER: Arg = Arg { short: Some(b'h'), long: "header", takes_value: TakesValue::Forbidden };
|
||||
pub static INODE: Arg = Arg { short: Some(b'i'), long: "inode", takes_value: TakesValue::Forbidden };
|
||||
pub static LINKS: Arg = Arg { short: Some(b'H'), long: "links", takes_value: TakesValue::Forbidden };
|
||||
pub static MODIFIED: Arg = Arg { short: Some(b'm'), long: "modified", takes_value: TakesValue::Forbidden };
|
||||
pub static BLOCKS: Arg = Arg { short: Some(b'S'), long: "blocks", takes_value: TakesValue::Forbidden };
|
||||
pub static TIME: Arg = Arg { short: Some(b't'), long: "time", takes_value: TakesValue::Forbidden };
|
||||
pub static ACCESSED: Arg = Arg { short: Some(b'u'), long: "accessed", takes_value: TakesValue::Forbidden };
|
||||
pub static CREATED: Arg = Arg { short: Some(b'U'), long: "created", takes_value: TakesValue::Forbidden };
|
||||
pub static TIME_STYLE: Arg = Arg { short: None, long: "time-style", takes_value: TakesValue::Necessary };
|
||||
|
||||
// optional feature options
|
||||
pub static GIT: Arg = Arg { short: None, long: "git", takes_value: TakesValue::Forbidden };
|
||||
pub static EXTENDED: Arg = Arg { short: Some(b'@'), long: "extended", takes_value: TakesValue::Forbidden };
|
||||
|
||||
|
||||
pub static ALL_ARGS: Args = Args(&[
|
||||
&VERSION, &HELP,
|
||||
|
||||
&ONE_LINE, &LONG, &GRID, &ACROSS, &RECURSE, &TREE, &CLASSIFY,
|
||||
&COLOR, &COLOUR, &COLOR_SCALE, &COLOUR_SCALE,
|
||||
|
||||
&ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &IGNORE_GLOB, &DIRS_FIRST,
|
||||
|
||||
&BINARY, &BYTES, &GROUP, &HEADER, &INODE, &LINKS, &MODIFIED, &BLOCKS,
|
||||
&TIME, &ACCESSED, &CREATED, &TIME_STYLE,
|
||||
|
||||
&GIT, &EXTENDED,
|
||||
]);
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use getopts;
|
||||
use glob;
|
||||
|
||||
use options::help::HelpString;
|
||||
use options::parser::{Arg, ParseError};
|
||||
|
||||
|
||||
/// A list of legal choices for an argument-taking option
|
||||
|
@ -13,7 +14,7 @@ pub struct Choices(&'static [&'static str]);
|
|||
|
||||
impl fmt::Display for Choices {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "(choices: {})", self.0.join(" "))
|
||||
write!(f, "(choices: {})", self.0.join(", "))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,11 +23,11 @@ impl fmt::Display for Choices {
|
|||
#[derive(PartialEq, Debug)]
|
||||
pub enum Misfire {
|
||||
|
||||
/// The getopts crate didn’t like these arguments.
|
||||
InvalidOptions(getopts::Fail),
|
||||
/// The getopts crate didn’t like these Arguments.
|
||||
InvalidOptions(ParseError),
|
||||
|
||||
/// The user supplied an illegal choice to an argument
|
||||
BadArgument(getopts::Fail, Choices),
|
||||
/// The user supplied an illegal choice to an Argument.
|
||||
BadArgument(&'static Arg, OsString, Choices),
|
||||
|
||||
/// The user asked for help. This isn’t strictly an error, which is why
|
||||
/// this enum isn’t named Error!
|
||||
|
@ -36,15 +37,18 @@ pub enum Misfire {
|
|||
Version,
|
||||
|
||||
/// Two options were given that conflict with one another.
|
||||
Conflict(&'static str, &'static str),
|
||||
Conflict(&'static Arg, &'static Arg),
|
||||
|
||||
/// An option was given that does nothing when another one either is or
|
||||
/// isn't present.
|
||||
Useless(&'static str, bool, &'static str),
|
||||
Useless(&'static Arg, bool, &'static Arg),
|
||||
|
||||
/// An option was given that does nothing when either of two other options
|
||||
/// are not present.
|
||||
Useless2(&'static str, &'static str, &'static str),
|
||||
Useless2(&'static Arg, &'static Arg, &'static Arg),
|
||||
|
||||
/// A very specific edge case where --tree can’t be used with --all twice.
|
||||
TreeAllAll,
|
||||
|
||||
/// A numeric option was given that failed to be parsed as a number.
|
||||
FailedParse(ParseIntError),
|
||||
|
@ -68,10 +72,8 @@ 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 [&'static str]) -> Misfire {
|
||||
Misfire::BadArgument(getopts::Fail::UnrecognizedOption(format!(
|
||||
"--{} {}",
|
||||
option, otherwise)), Choices(legal))
|
||||
pub fn bad_argument(option: &'static Arg, otherwise: &OsStr, legal: &'static [&'static str]) -> Misfire {
|
||||
Misfire::BadArgument(option, otherwise.to_os_string(), Choices(legal))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,16 +88,17 @@ impl fmt::Display for Misfire {
|
|||
use self::Misfire::*;
|
||||
|
||||
match *self {
|
||||
InvalidOptions(ref e) => write!(f, "{}", e),
|
||||
BadArgument(ref e, ref c) => write!(f, "{} {}", e, c),
|
||||
Help(ref text) => write!(f, "{}", text),
|
||||
Version => write!(f, "exa {}", env!("CARGO_PKG_VERSION")),
|
||||
Conflict(a, b) => write!(f, "Option --{} conflicts with option {}.", a, b),
|
||||
Useless(a, false, b) => write!(f, "Option --{} is useless without option --{}.", a, b),
|
||||
Useless(a, true, b) => write!(f, "Option --{} is useless given option --{}.", a, b),
|
||||
Useless2(a, b1, b2) => write!(f, "Option --{} is useless without options --{} or --{}.", a, b1, b2),
|
||||
FailedParse(ref e) => write!(f, "Failed to parse number: {}", e),
|
||||
FailedGlobPattern(ref e) => write!(f, "Failed to parse glob pattern: {}", e),
|
||||
BadArgument(ref a, ref b, ref c) => write!(f, "Option {} has no value {:?} (Choices: {})", a, b, c),
|
||||
InvalidOptions(ref e) => write!(f, "{:?}", e),
|
||||
Help(ref text) => write!(f, "{}", text),
|
||||
Version => write!(f, "exa {}", env!("CARGO_PKG_VERSION")),
|
||||
Conflict(ref a, ref b) => write!(f, "Option {} conflicts with option {}.", a, b),
|
||||
Useless(ref a, false, ref b) => write!(f, "Option {} is useless without option {}.", a, b),
|
||||
Useless(ref a, true, ref b) => write!(f, "Option {} is useless given option {}.", a, b),
|
||||
Useless2(ref a, ref b1, ref b2) => write!(f, "Option {} is useless without options {} or {}.", a, b1, b2),
|
||||
TreeAllAll => write!(f, "Option --tree is useless given --all --all."),
|
||||
FailedParse(ref e) => write!(f, "Failed to parse number: {}", e),
|
||||
FailedGlobPattern(ref e) => write!(f, "Failed to parse glob pattern: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,9 +69,7 @@
|
|||
//! it’s clear what the user wants.
|
||||
|
||||
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use getopts;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
use fs::feature::xattr;
|
||||
use fs::dir_action::DirAction;
|
||||
|
@ -90,6 +88,8 @@ mod misfire;
|
|||
pub use self::misfire::Misfire;
|
||||
|
||||
mod parser;
|
||||
mod flags;
|
||||
use self::parser::Matches;
|
||||
|
||||
|
||||
/// These **options** represent a parsed, error-checked versions of the
|
||||
|
@ -118,77 +118,29 @@ impl Options {
|
|||
|
||||
/// Call getopts on the given slice of command-line strings.
|
||||
#[allow(unused_results)]
|
||||
pub fn getopts<C>(args: C) -> Result<(Options, Vec<String>), Misfire>
|
||||
where C: IntoIterator, C::Item: AsRef<OsStr> {
|
||||
let mut opts = getopts::Options::new();
|
||||
pub fn getopts<'args, I>(args: I) -> Result<(Options, Vec<&'args OsStr>), Misfire>
|
||||
where I: IntoIterator<Item=&'args OsString> {
|
||||
|
||||
opts.optflag("v", "version", "show 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("l", "long", "display extended file metadata in a table");
|
||||
opts.optflag("G", "grid", "display entries as a grid (default)");
|
||||
opts.optflag("x", "across", "sort the grid across, rather than downwards");
|
||||
opts.optflag("R", "recurse", "recurse into directories");
|
||||
opts.optflag("T", "tree", "recurse into directories as a tree");
|
||||
opts.optflag("F", "classify", "display type indicator by file names (one of */=@|)");
|
||||
opts.optopt ("", "color", "when to use terminal colours", "WHEN");
|
||||
opts.optopt ("", "colour", "when to use terminal colours", "WHEN");
|
||||
opts.optflag("", "color-scale", "highlight levels of file sizes distinctly");
|
||||
opts.optflag("", "colour-scale", "highlight levels of file sizes distinctly");
|
||||
|
||||
// Filtering and sorting options
|
||||
opts.optflag("", "group-directories-first", "sort directories before other files");
|
||||
opts.optflagmulti("a", "all", "show hidden and 'dot' files");
|
||||
opts.optflag("d", "list-dirs", "list directories like regular files");
|
||||
opts.optopt ("L", "level", "limit the depth of recursion", "DEPTH");
|
||||
opts.optflag("r", "reverse", "reverse the sert order");
|
||||
opts.optopt ("s", "sort", "which field to sort by", "WORD");
|
||||
opts.optopt ("I", "ignore-glob", "ignore files that match these glob patterns", "GLOB1|GLOB2...");
|
||||
|
||||
// Long view options
|
||||
opts.optflag("b", "binary", "list file sizes with binary prefixes");
|
||||
opts.optflag("B", "bytes", "list file sizes in bytes, without prefixes");
|
||||
opts.optflag("g", "group", "list each file's group");
|
||||
opts.optflag("h", "header", "add a header row to each column");
|
||||
opts.optflag("H", "links", "list each file's number of hard links");
|
||||
opts.optflag("i", "inode", "list each file's inode number");
|
||||
opts.optflag("m", "modified", "use the modified timestamp field");
|
||||
opts.optflag("S", "blocks", "list each file's number of file system blocks");
|
||||
opts.optopt ("t", "time", "which timestamp field to show", "WORD");
|
||||
opts.optflag("u", "accessed", "use the accessed timestamp field");
|
||||
opts.optflag("U", "created", "use the created timestamp field");
|
||||
opts.optopt ("", "time-style", "how to format timestamp fields", "STYLE");
|
||||
|
||||
if cfg!(feature="git") {
|
||||
opts.optflag("", "git", "list each file's git status");
|
||||
}
|
||||
|
||||
if xattr::ENABLED {
|
||||
opts.optflag("@", "extended", "list each file's extended attribute keys and sizes");
|
||||
}
|
||||
|
||||
let matches = match opts.parse(args) {
|
||||
let matches = match parser::parse(&flags::ALL_ARGS, args) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return Err(Misfire::InvalidOptions(e)),
|
||||
};
|
||||
|
||||
if matches.opt_present("help") {
|
||||
if matches.has(&flags::HELP) {
|
||||
let help = HelpString {
|
||||
only_long: matches.opt_present("long"),
|
||||
only_long: matches.has(&flags::LONG),
|
||||
git: cfg!(feature="git"),
|
||||
xattrs: xattr::ENABLED,
|
||||
};
|
||||
|
||||
return Err(Misfire::Help(help));
|
||||
}
|
||||
else if matches.opt_present("version") {
|
||||
else if matches.has(&flags::VERSION) {
|
||||
return Err(Misfire::Version);
|
||||
}
|
||||
|
||||
let options = Options::deduce(&matches)?;
|
||||
Ok((options, matches.free))
|
||||
Ok((options, matches.frees))
|
||||
}
|
||||
|
||||
/// Whether the View specified in this set of options includes a Git
|
||||
|
@ -204,7 +156,7 @@ impl Options {
|
|||
|
||||
/// 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> {
|
||||
fn deduce(matches: &Matches) -> Result<Options, Misfire> {
|
||||
let dir_action = DirAction::deduce(matches)?;
|
||||
let filter = FileFilter::deduce(matches)?;
|
||||
let view = View::deduce(matches)?;
|
||||
|
@ -214,9 +166,11 @@ impl Options {
|
|||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Options, Misfire};
|
||||
use super::{Options, Misfire, flags};
|
||||
use std::ffi::OsString;
|
||||
use fs::DotFilter;
|
||||
use fs::filter::{SortField, SortCase};
|
||||
use fs::feature::xattr;
|
||||
|
@ -228,152 +182,183 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates an `OSStr` (used in tests)
|
||||
#[cfg(test)]
|
||||
fn os(input: &'static str) -> OsString {
|
||||
let mut os = OsString::new();
|
||||
os.push(input);
|
||||
os
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let opts = Options::getopts(&[ "--help".to_string() ]);
|
||||
let args = [ os("--help") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert!(is_helpful(opts))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_with_file() {
|
||||
let opts = Options::getopts(&[ "--help".to_string(), "me".to_string() ]);
|
||||
let args = [ os("--help"), os("me") ];
|
||||
let opts = Options::getopts(&args);
|
||||
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() ])
|
||||
let args = [ os("this file"), os("that file") ];
|
||||
let outs = Options::getopts(&args).unwrap().1;
|
||||
assert_eq!(outs, vec![ &os("this file"), &os("that file") ])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_args() {
|
||||
let nothing: Vec<String> = Vec::new();
|
||||
let args = Options::getopts(¬hing).unwrap().1;
|
||||
assert!(args.is_empty()); // Listing the `.` directory is done in main.rs
|
||||
let nothing: Vec<OsString> = Vec::new();
|
||||
let outs = Options::getopts(¬hing).unwrap().1;
|
||||
assert!(outs.is_empty()); // Listing the `.` directory is done in main.rs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_sizes() {
|
||||
let opts = Options::getopts(&[ "--long", "--binary", "--bytes" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Conflict("binary", "bytes"))
|
||||
let args = [ os("--long"), os("--binary"), os("--bytes") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Conflict(&flags::BINARY, &flags::BYTES))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_binary() {
|
||||
let opts = Options::getopts(&[ "--binary" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("binary", false, "long"))
|
||||
let args = [ os("--binary") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::BINARY, false, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_bytes() {
|
||||
let opts = Options::getopts(&[ "--bytes" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("bytes", false, "long"))
|
||||
let args = [ os("--bytes") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::BYTES, false, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_across() {
|
||||
let opts = Options::getopts(&[ "--long", "--across" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("across", true, "long"))
|
||||
let args = [ os("--long"), os("--across") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::ACROSS, true, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oneline_across() {
|
||||
let opts = Options::getopts(&[ "--oneline", "--across" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("across", true, "oneline"))
|
||||
let args = [ os("--oneline"), os("--across") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::ACROSS, true, &flags::ONE_LINE))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_header() {
|
||||
let opts = Options::getopts(&[ "--header" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("header", false, "long"))
|
||||
let args = [ os("--header") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::HEADER, false, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_group() {
|
||||
let opts = Options::getopts(&[ "--group" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("group", false, "long"))
|
||||
let args = [ os("--group") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::GROUP, false, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_inode() {
|
||||
let opts = Options::getopts(&[ "--inode" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("inode", false, "long"))
|
||||
let args = [ os("--inode") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::INODE, false, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_links() {
|
||||
let opts = Options::getopts(&[ "--links" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("links", false, "long"))
|
||||
let args = [ os("--links") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::LINKS, false, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_blocks() {
|
||||
let opts = Options::getopts(&[ "--blocks" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("blocks", false, "long"))
|
||||
let args = [ os("--blocks") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::BLOCKS, false, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_size() {
|
||||
let opts = Options::getopts(&[ "--sort=size" ]);
|
||||
let args = [ os("--sort=size") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap().0.filter.sort_field, SortField::Size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_name() {
|
||||
let opts = Options::getopts(&[ "--sort=name" ]);
|
||||
let args = [ os("--sort=name") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap().0.filter.sort_field, SortField::Name(SortCase::Sensitive));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_name_lowercase() {
|
||||
let opts = Options::getopts(&[ "--sort=Name" ]);
|
||||
let args = [ os("--sort=Name") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap().0.filter.sort_field, SortField::Name(SortCase::Insensitive));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature="git")]
|
||||
fn just_git() {
|
||||
let opts = Options::getopts(&[ "--git" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("git", false, "long"))
|
||||
let args = [ os("--git") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::GIT, false, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extended_without_long() {
|
||||
if xattr::ENABLED {
|
||||
let opts = Options::getopts(&[ "--extended" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("extended", false, "long"))
|
||||
let args = [ os("--extended") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::EXTENDED, false, &flags::LONG))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn level_without_recurse_or_tree() {
|
||||
let opts = Options::getopts(&[ "--level", "69105" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless2("level", "recurse", "tree"))
|
||||
let args = [ os("--level"), os("69105") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_all_with_tree() {
|
||||
let opts = Options::getopts(&[ "--all", "--all", "--tree" ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("all --all", true, "tree"))
|
||||
let args = [ os("--all"), os("--all"), os("--tree") ];
|
||||
let opts = Options::getopts(&args);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::TreeAllAll)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nowt() {
|
||||
let nothing: Vec<String> = Vec::new();
|
||||
let nothing: Vec<OsString> = Vec::new();
|
||||
let dots = Options::getopts(¬hing).unwrap().0.filter.dot_filter;
|
||||
assert_eq!(dots, DotFilter::JustFiles);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all() {
|
||||
let dots = Options::getopts(&[ "--all".to_string() ]).unwrap().0.filter.dot_filter;
|
||||
let args = [ os("--all") ];
|
||||
let dots = Options::getopts(&args).unwrap().0.filter.dot_filter;
|
||||
assert_eq!(dots, DotFilter::Dotfiles);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allall() {
|
||||
let dots = Options::getopts(&[ "-a".to_string(), "-a".to_string() ]).unwrap().0.filter.dot_filter;
|
||||
let args = [ os("-a"), os("-a") ];
|
||||
let dots = Options::getopts(&args).unwrap().0.filter.dot_filter;
|
||||
assert_eq!(dots, DotFilter::DotfilesAndDots);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#![allow(unused_variables, dead_code)]
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt;
|
||||
|
||||
|
||||
pub type ShortArg = u8;
|
||||
|
@ -66,63 +67,87 @@ pub enum TakesValue {
|
|||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Arg {
|
||||
short: Option<ShortArg>,
|
||||
long: LongArg,
|
||||
takes_value: TakesValue,
|
||||
pub short: Option<ShortArg>,
|
||||
pub long: LongArg,
|
||||
pub takes_value: TakesValue,
|
||||
}
|
||||
|
||||
impl fmt::Display for Arg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "--{}", self.long)?;
|
||||
|
||||
if let Some(short) = self.short {
|
||||
write!(f, " (-{})", short as char)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Args(&'static [&'static Arg]);
|
||||
pub struct Args(pub &'static [&'static Arg]);
|
||||
|
||||
impl Args {
|
||||
fn lookup_short<'a>(&self, short: ShortArg) -> Result<&Arg, ParseError<'a>> {
|
||||
fn lookup_short<'a>(&self, short: ShortArg) -> Result<&Arg, ParseError> {
|
||||
match self.0.into_iter().find(|arg| arg.short == Some(short)) {
|
||||
Some(arg) => Ok(arg),
|
||||
None => Err(ParseError::UnknownShortArgument { attempt: short })
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_long<'a>(&self, long: &'a OsStr) -> Result<&Arg, ParseError<'a>> {
|
||||
fn lookup_long<'a>(&self, long: &'a OsStr) -> Result<&Arg, ParseError> {
|
||||
match self.0.into_iter().find(|arg| arg.long == long) {
|
||||
Some(arg) => Ok(arg),
|
||||
None => Err(ParseError::UnknownArgument { attempt: long })
|
||||
None => Err(ParseError::UnknownArgument { attempt: long.to_os_string() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Matches<'a> {
|
||||
pub struct Matches<'args> {
|
||||
/// Long and short arguments need to be kept in the same vector, because
|
||||
/// we usually want the one nearest the end to count.
|
||||
flags: Vec<(Flag, Option<&'a OsStr>)>,
|
||||
frees: Vec<&'a OsStr>,
|
||||
pub flags: Vec<(Flag, Option<&'args OsStr>)>,
|
||||
pub frees: Vec<&'args OsStr>,
|
||||
}
|
||||
|
||||
impl<'a> Matches<'a> {
|
||||
fn has(&self, arg: &Arg) -> bool {
|
||||
pub fn has(&self, arg: &Arg) -> bool {
|
||||
self.flags.iter().rev()
|
||||
.find(|tuple| tuple.1.is_none() && tuple.0.matches(arg))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn get(&self, arg: &Arg) -> Option<&OsStr> {
|
||||
pub fn get(&self, arg: &Arg) -> Option<&OsStr> {
|
||||
self.flags.iter().rev()
|
||||
.find(|tuple| tuple.1.is_some() && tuple.0.matches(arg))
|
||||
.map(|tuple| tuple.1.unwrap())
|
||||
}
|
||||
|
||||
pub fn count(&self, arg: &Arg) -> usize {
|
||||
self.flags.iter()
|
||||
.filter(|tuple| tuple.0.matches(arg))
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ParseError<'a> {
|
||||
pub enum ParseError {
|
||||
NeedsValue { flag: Flag },
|
||||
ForbiddenValue { flag: Flag },
|
||||
UnknownShortArgument { attempt: ShortArg },
|
||||
UnknownArgument { attempt: &'a OsStr },
|
||||
UnknownArgument { attempt: OsString },
|
||||
}
|
||||
|
||||
// It’s technically possible for ParseError::UnknownArgument to borrow its
|
||||
// OsStr rather than owning it, but that would give ParseError a lifetime,
|
||||
// which would give Misfire a lifetime, which gets used everywhere. And this
|
||||
// only happens when an error occurs, so it’s not really worth it.
|
||||
|
||||
fn parse<'a>(args: Args, inputs: &'a [OsString]) -> Result<Matches<'a>, ParseError<'a>> {
|
||||
|
||||
pub fn parse<'args, I>(args: &Args, inputs: I) -> Result<Matches<'args>, ParseError>
|
||||
where I: IntoIterator<Item=&'args OsString> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use self::TakesValue::*;
|
||||
|
||||
|
@ -133,8 +158,8 @@ fn parse<'a>(args: Args, inputs: &'a [OsString]) -> Result<Matches<'a>, ParseErr
|
|||
frees: Vec::new(),
|
||||
};
|
||||
|
||||
let mut iter = inputs.iter();
|
||||
while let Some(arg) = iter.next() {
|
||||
let mut inputs = inputs.into_iter();
|
||||
while let Some(arg) = inputs.next() {
|
||||
let bytes = arg.as_bytes();
|
||||
|
||||
if !parsing {
|
||||
|
@ -160,7 +185,7 @@ fn parse<'a>(args: Args, inputs: &'a [OsString]) -> Result<Matches<'a>, ParseErr
|
|||
match arg.takes_value {
|
||||
Forbidden => results.flags.push((flag, None)),
|
||||
Necessary => {
|
||||
if let Some(next_arg) = iter.next() {
|
||||
if let Some(next_arg) = inputs.next() {
|
||||
results.flags.push((flag, Some(next_arg)));
|
||||
}
|
||||
else {
|
||||
|
@ -203,7 +228,7 @@ fn parse<'a>(args: Args, inputs: &'a [OsString]) -> Result<Matches<'a>, ParseErr
|
|||
results.flags.push((flag, Some(OsStr::from_bytes(remnants))));
|
||||
break;
|
||||
}
|
||||
else if let Some(next_arg) = iter.next() {
|
||||
else if let Some(next_arg) = inputs.next() {
|
||||
results.flags.push((flag, Some(next_arg)));
|
||||
}
|
||||
else {
|
||||
|
@ -250,6 +275,7 @@ fn os(input: &'static str) -> OsString {
|
|||
os
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod split_test {
|
||||
use super::{split_on_equals, os};
|
||||
|
@ -294,7 +320,7 @@ mod parse_test {
|
|||
#[test]
|
||||
fn $name() {
|
||||
let bits = $input;
|
||||
let results = parse(Args(TEST_ARGS), &bits);
|
||||
let results = parse(&Args(TEST_ARGS), bits.into_iter());
|
||||
assert_eq!(results, $result);
|
||||
}
|
||||
};
|
||||
|
@ -309,47 +335,47 @@ mod parse_test {
|
|||
|
||||
// Just filenames
|
||||
test!(empty: [] => Ok(Matches { frees: vec![], flags: vec![] }));
|
||||
test!(one_arg: [os("exa")] => Ok(Matches { frees: vec![ os("exa").as_os_str() ], flags: vec![] }));
|
||||
test!(one_arg: [os("exa")] => Ok(Matches { frees: vec![ &os("exa") ], flags: vec![] }));
|
||||
|
||||
// Dashes and double dashes
|
||||
test!(one_dash: [os("-")] => Ok(Matches { frees: vec![ os("-").as_os_str() ], flags: vec![] }));
|
||||
test!(two_dashes: [os("--")] => Ok(Matches { frees: vec![], flags: vec![] }));
|
||||
test!(two_file: [os("--"), os("file")] => Ok(Matches { frees: vec![ os("file").as_os_str() ], flags: vec![] }));
|
||||
test!(two_arg_l: [os("--"), os("--long")] => Ok(Matches { frees: vec![ os("--long").as_os_str() ], flags: vec![] }));
|
||||
test!(two_arg_s: [os("--"), os("-l")] => Ok(Matches { frees: vec![ os("-l").as_os_str() ], flags: vec![] }));
|
||||
test!(one_dash: [os("-")] => Ok(Matches { frees: vec![ &os("-") ], flags: vec![] }));
|
||||
test!(two_dashes: [os("--")] => Ok(Matches { frees: vec![], flags: vec![] }));
|
||||
test!(two_file: [os("--"), os("file")] => Ok(Matches { frees: vec![ &os("file") ], flags: vec![] }));
|
||||
test!(two_arg_l: [os("--"), os("--long")] => Ok(Matches { frees: vec![ &os("--long") ], flags: vec![] }));
|
||||
test!(two_arg_s: [os("--"), os("-l")] => Ok(Matches { frees: vec![ &os("-l") ], flags: vec![] }));
|
||||
|
||||
|
||||
// Long args
|
||||
test!(long: [os("--long")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("long"), None) ] }));
|
||||
test!(long_then: [os("--long"), os("4")] => Ok(Matches { frees: vec![ os("4").as_os_str() ], flags: vec![ (Flag::Long("long"), None) ] }));
|
||||
test!(long_two: [os("--long"), os("--verbose")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("long"), None), (Flag::Long("verbose"), None) ] }));
|
||||
test!(long: [os("--long")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("long"), None) ] }));
|
||||
test!(long_then: [os("--long"), os("4")] => Ok(Matches { frees: vec![ &os("4") ], flags: vec![ (Flag::Long("long"), None) ] }));
|
||||
test!(long_two: [os("--long"), os("--verbose")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("long"), None), (Flag::Long("verbose"), None) ] }));
|
||||
|
||||
// Long args with values
|
||||
test!(bad_equals: [os("--long=equals")] => Err(ParseError::ForbiddenValue { flag: Flag::Long("long") }));
|
||||
test!(no_arg: [os("--count")] => Err(ParseError::NeedsValue { flag: Flag::Long("count") }));
|
||||
test!(arg_equals: [os("--count=4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("count"), Some(os("4").as_os_str())) ] }));
|
||||
test!(arg_then: [os("--count"), os("4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("count"), Some(os("4").as_os_str())) ] }));
|
||||
test!(arg_equals: [os("--count=4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("count"), Some(&*os("4"))) ] }));
|
||||
test!(arg_then: [os("--count"), os("4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("count"), Some(&*os("4"))) ] }));
|
||||
|
||||
|
||||
// Short args
|
||||
test!(short: [os("-l")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None) ] }));
|
||||
test!(short_then: [os("-l"), os("4")] => Ok(Matches { frees: vec![ os("4").as_os_str() ], flags: vec![ (Flag::Short(b'l'), None) ] }));
|
||||
test!(short_two: [os("-lv")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'v'), None) ] }));
|
||||
test!(mixed: [os("-v"), os("--long")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'v'), None), (Flag::Long("long"), None) ] }));
|
||||
test!(short: [os("-l")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None) ] }));
|
||||
test!(short_then: [os("-l"), os("4")] => Ok(Matches { frees: vec![ &*os("4") ], flags: vec![ (Flag::Short(b'l'), None) ] }));
|
||||
test!(short_two: [os("-lv")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'v'), None) ] }));
|
||||
test!(mixed: [os("-v"), os("--long")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'v'), None), (Flag::Long("long"), None) ] }));
|
||||
|
||||
// Short args with values
|
||||
test!(bad_short: [os("-l=equals")] => Err(ParseError::ForbiddenValue { flag: Flag::Short(b'l') }));
|
||||
test!(short_none: [os("-c")] => Err(ParseError::NeedsValue { flag: Flag::Short(b'c') }));
|
||||
test!(short_arg_eq: [os("-c=4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'c'), Some(os("4").as_os_str())) ] }));
|
||||
test!(short_arg_then: [os("-c"), os("4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'c'), Some(os("4").as_os_str())) ] }));
|
||||
test!(short_two_together: [os("-lctwo")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(os("two").as_os_str())) ] }));
|
||||
test!(short_two_equals: [os("-lc=two")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(os("two").as_os_str())) ] }));
|
||||
test!(short_two_next: [os("-lc"), os("two")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(os("two").as_os_str())) ] }));
|
||||
test!(short_arg_eq: [os("-c=4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'c'), Some(&*os("4"))) ] }));
|
||||
test!(short_arg_then: [os("-c"), os("4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'c'), Some(&*os("4"))) ] }));
|
||||
test!(short_two_together: [os("-lctwo")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(&*os("two"))) ] }));
|
||||
test!(short_two_equals: [os("-lc=two")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(&*os("two"))) ] }));
|
||||
test!(short_two_next: [os("-lc"), os("two")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(&*os("two"))) ] }));
|
||||
|
||||
|
||||
// Unknown args
|
||||
test!(unknown_long: [os("--quiet")] => Err(ParseError::UnknownArgument { attempt: os("quiet").as_os_str() }));
|
||||
test!(unknown_long_eq: [os("--quiet=shhh")] => Err(ParseError::UnknownArgument { attempt: os("quiet").as_os_str() }));
|
||||
test!(unknown_long: [os("--quiet")] => Err(ParseError::UnknownArgument { attempt: os("quiet") }));
|
||||
test!(unknown_long_eq: [os("--quiet=shhh")] => Err(ParseError::UnknownArgument { attempt: os("quiet") }));
|
||||
test!(unknown_short: [os("-q")] => Err(ParseError::UnknownShortArgument { attempt: b'q' }));
|
||||
test!(unknown_short_2nd: [os("-lq")] => Err(ParseError::UnknownShortArgument { attempt: b'q' }));
|
||||
test!(unknown_short_eq: [os("-q=shhh")] => Err(ParseError::UnknownShortArgument { attempt: b'q' }));
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
use std::env::var_os;
|
||||
|
||||
use getopts;
|
||||
|
||||
use info::filetype::FileExtensions;
|
||||
use output::Colours;
|
||||
use output::{View, Mode, grid, details};
|
||||
use output::table::{TimeTypes, Environment, SizeFormat, Options as TableOptions};
|
||||
use output::file_name::{Classify, FileStyle};
|
||||
use output::time::TimeFormat;
|
||||
use options::Misfire;
|
||||
|
||||
use options::{flags, Misfire};
|
||||
use options::parser::Matches;
|
||||
|
||||
use fs::feature::xattr;
|
||||
use info::filetype::FileExtensions;
|
||||
|
||||
|
||||
impl View {
|
||||
|
||||
/// Determine which view to use and all of that view’s arguments.
|
||||
pub fn deduce(matches: &getopts::Matches) -> Result<View, Misfire> {
|
||||
pub fn deduce(matches: &Matches) -> Result<View, Misfire> {
|
||||
let mode = Mode::deduce(matches)?;
|
||||
let colours = Colours::deduce(matches)?;
|
||||
let style = FileStyle::deduce(matches);
|
||||
|
@ -27,40 +28,41 @@ impl View {
|
|||
impl Mode {
|
||||
|
||||
/// Determine the mode from the command-line arguments.
|
||||
pub fn deduce(matches: &getopts::Matches) -> Result<Mode, Misfire> {
|
||||
pub fn deduce(matches: &Matches) -> Result<Mode, Misfire> {
|
||||
use options::misfire::Misfire::*;
|
||||
|
||||
let long = || {
|
||||
if matches.opt_present("across") && !matches.opt_present("grid") {
|
||||
Err(Useless("across", true, "long"))
|
||||
if matches.has(&flags::ACROSS) && !matches.has(&flags::GRID) {
|
||||
Err(Useless(&flags::ACROSS, true, &flags::LONG))
|
||||
}
|
||||
else if matches.opt_present("oneline") {
|
||||
Err(Useless("oneline", true, "long"))
|
||||
else if matches.has(&flags::ONE_LINE) {
|
||||
Err(Useless(&flags::ONE_LINE, true, &flags::LONG))
|
||||
}
|
||||
else {
|
||||
Ok(details::Options {
|
||||
table: Some(TableOptions::deduce(matches)?),
|
||||
header: matches.opt_present("header"),
|
||||
xattr: xattr::ENABLED && matches.opt_present("extended"),
|
||||
header: matches.has(&flags::HEADER),
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let long_options_scan = || {
|
||||
for option in &[ "binary", "bytes", "inode", "links", "header", "blocks", "time", "group" ] {
|
||||
if matches.opt_present(option) {
|
||||
return Err(Useless(option, false, "long"));
|
||||
for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
|
||||
&flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP ] {
|
||||
if matches.has(option) {
|
||||
return Err(Useless(*option, false, &flags::LONG));
|
||||
}
|
||||
}
|
||||
|
||||
if cfg!(feature="git") && matches.opt_present("git") {
|
||||
Err(Useless("git", false, "long"))
|
||||
if cfg!(feature="git") && matches.has(&flags::GIT) {
|
||||
Err(Useless(&flags::GIT, false, &flags::LONG))
|
||||
}
|
||||
else if matches.opt_present("level") && !matches.opt_present("recurse") && !matches.opt_present("tree") {
|
||||
Err(Useless2("level", "recurse", "tree"))
|
||||
else if matches.has(&flags::LEVEL) && !matches.has(&flags::RECURSE) && !matches.has(&flags::TREE) {
|
||||
Err(Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE))
|
||||
}
|
||||
else if xattr::ENABLED && matches.opt_present("extended") {
|
||||
Err(Useless("extended", false, "long"))
|
||||
else if xattr::ENABLED && matches.has(&flags::EXTENDED) {
|
||||
Err(Useless(&flags::EXTENDED, false, &flags::LONG))
|
||||
}
|
||||
else {
|
||||
Ok(())
|
||||
|
@ -69,15 +71,15 @@ impl Mode {
|
|||
|
||||
let other_options_scan = || {
|
||||
if let Some(width) = TerminalWidth::deduce()?.width() {
|
||||
if matches.opt_present("oneline") {
|
||||
if matches.opt_present("across") {
|
||||
Err(Useless("across", true, "oneline"))
|
||||
if matches.has(&flags::ONE_LINE) {
|
||||
if matches.has(&flags::ACROSS) {
|
||||
Err(Useless(&flags::ACROSS, true, &flags::ONE_LINE))
|
||||
}
|
||||
else {
|
||||
Ok(Mode::Lines)
|
||||
}
|
||||
}
|
||||
else if matches.opt_present("tree") {
|
||||
else if matches.has(&flags::TREE) {
|
||||
let details = details::Options {
|
||||
table: None,
|
||||
header: false,
|
||||
|
@ -88,7 +90,7 @@ impl Mode {
|
|||
}
|
||||
else {
|
||||
let grid = grid::Options {
|
||||
across: matches.opt_present("across"),
|
||||
across: matches.has(&flags::ACROSS),
|
||||
console_width: width,
|
||||
};
|
||||
|
||||
|
@ -100,7 +102,7 @@ impl Mode {
|
|||
// as the program’s stdout being connected to a file, then
|
||||
// fallback to the lines view.
|
||||
|
||||
if matches.opt_present("tree") {
|
||||
if matches.has(&flags::TREE) {
|
||||
let details = details::Options {
|
||||
table: None,
|
||||
header: false,
|
||||
|
@ -115,9 +117,9 @@ impl Mode {
|
|||
}
|
||||
};
|
||||
|
||||
if matches.opt_present("long") {
|
||||
if matches.has(&flags::LONG) {
|
||||
let details = long()?;
|
||||
if matches.opt_present("grid") {
|
||||
if matches.has(&flags::GRID) {
|
||||
match other_options_scan()? {
|
||||
Mode::Grid(grid) => return Ok(Mode::GridDetails(grid, details)),
|
||||
others => return Ok(others),
|
||||
|
@ -180,17 +182,17 @@ impl TerminalWidth {
|
|||
|
||||
|
||||
impl TableOptions {
|
||||
fn deduce(matches: &getopts::Matches) -> Result<Self, Misfire> {
|
||||
fn deduce(matches: &Matches) -> Result<Self, Misfire> {
|
||||
Ok(TableOptions {
|
||||
env: Environment::load_all(),
|
||||
time_format: TimeFormat::deduce(matches)?,
|
||||
size_format: SizeFormat::deduce(matches)?,
|
||||
time_types: TimeTypes::deduce(matches)?,
|
||||
inode: matches.opt_present("inode"),
|
||||
links: matches.opt_present("links"),
|
||||
blocks: matches.opt_present("blocks"),
|
||||
group: matches.opt_present("group"),
|
||||
git: cfg!(feature="git") && matches.opt_present("git"),
|
||||
inode: matches.has(&flags::INODE),
|
||||
links: matches.has(&flags::LINKS),
|
||||
blocks: matches.has(&flags::BLOCKS),
|
||||
group: matches.has(&flags::GROUP),
|
||||
git: cfg!(feature="git") && matches.has(&flags::GIT),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -206,12 +208,12 @@ impl SizeFormat {
|
|||
/// strings of digits in your head. Changing the format to anything else
|
||||
/// involves the `--binary` or `--bytes` flags, and these conflict with
|
||||
/// each other.
|
||||
fn deduce(matches: &getopts::Matches) -> Result<SizeFormat, Misfire> {
|
||||
let binary = matches.opt_present("binary");
|
||||
let bytes = matches.opt_present("bytes");
|
||||
fn deduce(matches: &Matches) -> Result<SizeFormat, Misfire> {
|
||||
let binary = matches.has(&flags::BINARY);
|
||||
let bytes = matches.has(&flags::BYTES);
|
||||
|
||||
match (binary, bytes) {
|
||||
(true, true ) => Err(Misfire::Conflict("binary", "bytes")),
|
||||
(true, true ) => Err(Misfire::Conflict(&flags::BINARY, &flags::BYTES)),
|
||||
(true, false) => Ok(SizeFormat::BinaryBytes),
|
||||
(false, true ) => Ok(SizeFormat::JustBytes),
|
||||
(false, false) => Ok(SizeFormat::DecimalBytes),
|
||||
|
@ -223,21 +225,29 @@ impl SizeFormat {
|
|||
impl TimeFormat {
|
||||
|
||||
/// Determine how time should be formatted in timestamp columns.
|
||||
fn deduce(matches: &getopts::Matches) -> Result<TimeFormat, Misfire> {
|
||||
fn deduce(matches: &Matches) -> Result<TimeFormat, Misfire> {
|
||||
pub use output::time::{DefaultFormat, ISOFormat};
|
||||
const STYLES: &[&str] = &["default", "long-iso", "full-iso", "iso"];
|
||||
|
||||
if let Some(word) = matches.opt_str("time-style") {
|
||||
match &*word {
|
||||
"default" => Ok(TimeFormat::DefaultFormat(DefaultFormat::new())),
|
||||
"iso" => Ok(TimeFormat::ISOFormat(ISOFormat::new())),
|
||||
"long-iso" => Ok(TimeFormat::LongISO),
|
||||
"full-iso" => Ok(TimeFormat::FullISO),
|
||||
otherwise => Err(Misfire::bad_argument("time-style", otherwise, STYLES)),
|
||||
}
|
||||
let word = match matches.get(&flags::TIME_STYLE) {
|
||||
Some(w) => w,
|
||||
None => return Ok(TimeFormat::DefaultFormat(DefaultFormat::new())),
|
||||
};
|
||||
|
||||
if word == "default" {
|
||||
Ok(TimeFormat::DefaultFormat(DefaultFormat::new()))
|
||||
}
|
||||
else if word == "iso" {
|
||||
Ok(TimeFormat::ISOFormat(ISOFormat::new()))
|
||||
}
|
||||
else if word == "long-iso" {
|
||||
Ok(TimeFormat::LongISO)
|
||||
}
|
||||
else if word == "full-iso" {
|
||||
Ok(TimeFormat::FullISO)
|
||||
}
|
||||
else {
|
||||
Ok(TimeFormat::DefaultFormat(DefaultFormat::new()))
|
||||
Err(Misfire::bad_argument(&flags::TIME_STYLE, word, STYLES))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -255,29 +265,35 @@ impl TimeTypes {
|
|||
/// It’s valid to show more than one column by passing in more than one
|
||||
/// option, but passing *no* options means that the user just wants to
|
||||
/// see the default set.
|
||||
fn deduce(matches: &getopts::Matches) -> Result<TimeTypes, Misfire> {
|
||||
let possible_word = matches.opt_str("time");
|
||||
let modified = matches.opt_present("modified");
|
||||
let created = matches.opt_present("created");
|
||||
let accessed = matches.opt_present("accessed");
|
||||
fn deduce(matches: &Matches) -> Result<TimeTypes, Misfire> {
|
||||
let possible_word = matches.get(&flags::TIME);
|
||||
let modified = matches.has(&flags::MODIFIED);
|
||||
let created = matches.has(&flags::CREATED);
|
||||
let accessed = matches.has(&flags::ACCESSED);
|
||||
|
||||
if let Some(word) = possible_word {
|
||||
if modified {
|
||||
return Err(Misfire::Useless("modified", true, "time"));
|
||||
return Err(Misfire::Useless(&flags::MODIFIED, true, &flags::TIME));
|
||||
}
|
||||
else if created {
|
||||
return Err(Misfire::Useless("created", true, "time"));
|
||||
return Err(Misfire::Useless(&flags::CREATED, true, &flags::TIME));
|
||||
}
|
||||
else if accessed {
|
||||
return Err(Misfire::Useless("accessed", true, "time"));
|
||||
return Err(Misfire::Useless(&flags::ACCESSED, true, &flags::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, TIMES))
|
||||
static TIMES: &[&str] = &["modified", "accessed", "created"];
|
||||
if word == "mod" || word == "modified" {
|
||||
Ok(TimeTypes { accessed: false, modified: true, created: false })
|
||||
}
|
||||
else if word == "acc" || word == "accessed" {
|
||||
Ok(TimeTypes { accessed: true, modified: false, created: false })
|
||||
}
|
||||
else if word == "cr" || word == "created" {
|
||||
Ok(TimeTypes { accessed: false, modified: false, created: true })
|
||||
}
|
||||
else {
|
||||
Err(Misfire::bad_argument(&flags::TIME, word, TIMES))
|
||||
}
|
||||
}
|
||||
else if modified || created || accessed {
|
||||
|
@ -319,31 +335,37 @@ impl Default for TerminalColours {
|
|||
impl TerminalColours {
|
||||
|
||||
/// Determine which terminal colour conditions to use.
|
||||
fn deduce(matches: &getopts::Matches) -> Result<TerminalColours, Misfire> {
|
||||
fn deduce(matches: &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, COLOURS))
|
||||
}
|
||||
let word = match matches.get(&flags::COLOR).or_else(|| matches.get(&flags::COLOUR)) {
|
||||
Some(w) => w,
|
||||
None => return Ok(TerminalColours::default()),
|
||||
};
|
||||
|
||||
if word == "always" {
|
||||
Ok(TerminalColours::Always)
|
||||
}
|
||||
else if word == "auto" || word == "automatic" {
|
||||
Ok(TerminalColours::Automatic)
|
||||
}
|
||||
else if word == "never" {
|
||||
Ok(TerminalColours::Never)
|
||||
}
|
||||
else {
|
||||
Ok(TerminalColours::default())
|
||||
Err(Misfire::bad_argument(&flags::COLOR, word, COLOURS))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Colours {
|
||||
fn deduce(matches: &getopts::Matches) -> Result<Colours, Misfire> {
|
||||
fn deduce(matches: &Matches) -> Result<Colours, Misfire> {
|
||||
use self::TerminalColours::*;
|
||||
|
||||
let tc = TerminalColours::deduce(matches)?;
|
||||
if tc == Always || (tc == Automatic && TERM_WIDTH.is_some()) {
|
||||
let scale = matches.opt_present("color-scale") || matches.opt_present("colour-scale");
|
||||
let scale = matches.has(&flags::COLOR_SCALE) || matches.has(&flags::COLOUR_SCALE);
|
||||
Ok(Colours::colourful(scale))
|
||||
}
|
||||
else {
|
||||
|
@ -355,18 +377,17 @@ impl Colours {
|
|||
|
||||
|
||||
impl FileStyle {
|
||||
fn deduce(matches: &getopts::Matches) -> FileStyle {
|
||||
fn deduce(matches: &Matches) -> FileStyle {
|
||||
let classify = Classify::deduce(matches);
|
||||
let exts = FileExtensions;
|
||||
FileStyle { classify, exts }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Classify {
|
||||
fn deduce(matches: &getopts::Matches) -> Classify {
|
||||
if matches.opt_present("classify") { Classify::AddFileIndicators }
|
||||
else { Classify::JustFilenames }
|
||||
fn deduce(matches: &Matches) -> Classify {
|
||||
if matches.has(&flags::CLASSIFY) { Classify::AddFileIndicators }
|
||||
else { Classify::JustFilenames }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ COLUMNS=80 $exa $testcases/file-names -R 2>&1 | diff -q - $results/file_names_R
|
|||
$exa $testcases/file-names -T 2>&1 | diff -q - $results/file_names_T || exit 1
|
||||
|
||||
# At least make sure it handles invalid UTF-8 arguments without crashing
|
||||
$exa $testcases/file-names/* 2>/dev/null
|
||||
$exa $testcases/file-names/* >/dev/null || exit 1
|
||||
|
||||
|
||||
# Sorting and extension file types
|
||||
|
|
Loading…
Reference in New Issue
Block a user