mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-12-26 01:57:32 +00:00
Merge branch 'strict-mode-properly'
This adds a check for the EXA_STRICT environment variable, and uses it to put exa in a strict mode, which enables more checks for useless/redundant arguments. Its other goal was to move all the option parsing tests out of options/mod.rs and into the files where they’re actually relevant. THIS IS NOT YET DONE and I’ll have to do some more work on this prior to release to clear up the last few lingering issues. There are almost certainly going to be cases where redundant arguments are still complained about (or ignored) by mistake. But I want to get this branch merged so I can take care of some other stuff for the next release.
This commit is contained in:
commit
97d1472331
15
src/exa.rs
15
src/exa.rs
@ -22,6 +22,7 @@ extern crate term_size;
|
||||
extern crate lazy_static;
|
||||
|
||||
|
||||
use std::env::var_os;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::{stderr, Write, Result as IOResult};
|
||||
use std::path::{Component, PathBuf};
|
||||
@ -29,7 +30,7 @@ use std::path::{Component, PathBuf};
|
||||
use ansi_term::{ANSIStrings, Style};
|
||||
|
||||
use fs::{Dir, File};
|
||||
use options::Options;
|
||||
use options::{Options, Vars};
|
||||
pub use options::Misfire;
|
||||
use output::{escape, lines, grid, grid_details, details, View, Mode};
|
||||
|
||||
@ -55,10 +56,20 @@ pub struct Exa<'args, 'w, W: Write + 'w> {
|
||||
pub args: Vec<&'args OsStr>,
|
||||
}
|
||||
|
||||
/// The “real” environment variables type.
|
||||
/// Instead of just calling `var_os` from within the options module,
|
||||
/// the method of looking up environment variables has to be passed in.
|
||||
struct LiveVars;
|
||||
impl Vars for LiveVars {
|
||||
fn get(&self, name: &'static str) -> Option<OsString> {
|
||||
var_os(name)
|
||||
}
|
||||
}
|
||||
|
||||
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)| {
|
||||
Options::parse(args, LiveVars).map(move |(options, args)| {
|
||||
Exa { options, writer, args }
|
||||
})
|
||||
}
|
||||
|
@ -8,12 +8,12 @@ impl DirAction {
|
||||
|
||||
/// Determine which action to perform when trying to list a directory.
|
||||
pub fn deduce(matches: &MatchedFlags) -> Result<DirAction, Misfire> {
|
||||
let recurse = matches.has(&flags::RECURSE);
|
||||
let list = matches.has(&flags::LIST_DIRS);
|
||||
let tree = matches.has(&flags::TREE);
|
||||
let recurse = matches.has(&flags::RECURSE)?;
|
||||
let list = matches.has(&flags::LIST_DIRS)?;
|
||||
let tree = matches.has(&flags::TREE)?;
|
||||
|
||||
// Early check for --level when it wouldn’t do anything
|
||||
if !recurse && !tree && matches.get(&flags::LEVEL).is_some() {
|
||||
if !recurse && !tree && matches.get(&flags::LEVEL)?.is_some() {
|
||||
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ impl RecurseOptions {
|
||||
|
||||
/// Determine which files should be recursed into.
|
||||
pub fn deduce(matches: &MatchedFlags, tree: bool) -> Result<RecurseOptions, Misfire> {
|
||||
let max_depth = if let Some(level) = matches.get(&flags::LEVEL) {
|
||||
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)),
|
||||
@ -55,51 +55,50 @@ impl RecurseOptions {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::ffi::OsString;
|
||||
use options::flags;
|
||||
|
||||
pub fn os(input: &'static str) -> OsString {
|
||||
let mut os = OsString::new();
|
||||
os.push(input);
|
||||
os
|
||||
}
|
||||
use options::parser::Flag;
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $type:ident <- $inputs:expr => $result:expr) => {
|
||||
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
use options::parser::{Args, Arg};
|
||||
use std::ffi::OsString;
|
||||
use options::parser::Arg;
|
||||
use options::test::parse_for_test;
|
||||
use options::test::Strictnesses::*;
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[ &flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL ];
|
||||
|
||||
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
||||
let results = Args(TEST_ARGS).parse(bits.iter());
|
||||
assert_eq!($type::deduce(&results.unwrap().flags), $result);
|
||||
static TEST_ARGS: &[&Arg] = &[&flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL ];
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||
assert_eq!(result, $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Default behaviour
|
||||
test!(empty: DirAction <- [] => Ok(DirAction::List));
|
||||
test!(empty: DirAction <- []; Both => Ok(DirAction::List));
|
||||
|
||||
// Listing files as directories
|
||||
test!(dirs_short: DirAction <- ["-d"] => Ok(DirAction::AsFile));
|
||||
test!(dirs_long: DirAction <- ["--list-dirs"] => Ok(DirAction::AsFile));
|
||||
test!(dirs_short: DirAction <- ["-d"]; Both => Ok(DirAction::AsFile));
|
||||
test!(dirs_long: DirAction <- ["--list-dirs"]; Both => Ok(DirAction::AsFile));
|
||||
|
||||
// Recursing
|
||||
test!(rec_short: DirAction <- ["-R"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: None })));
|
||||
test!(rec_long: DirAction <- ["--recurse"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: None })));
|
||||
test!(rec_lim_short: DirAction <- ["-RL4"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(4) })));
|
||||
test!(rec_lim_short_2: DirAction <- ["-RL=5"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(5) })));
|
||||
test!(rec_lim_long: DirAction <- ["--recurse", "--level", "666"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(666) })));
|
||||
test!(rec_lim_long_2: DirAction <- ["--recurse", "--level=0118"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(118) })));
|
||||
test!(rec_tree: DirAction <- ["--recurse", "--tree"] => Ok(DirAction::Recurse(RecurseOptions { tree: true, max_depth: None })));
|
||||
test!(rec_short_tree: DirAction <- ["--tree", "--recurse"] => Ok(DirAction::Recurse(RecurseOptions { tree: true, max_depth: None })));
|
||||
use self::DirAction::Recurse;
|
||||
test!(rec_short: DirAction <- ["-R"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
|
||||
test!(rec_long: DirAction <- ["--recurse"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
|
||||
test!(rec_lim_short: DirAction <- ["-RL4"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(4) })));
|
||||
test!(rec_lim_short_2: DirAction <- ["-RL=5"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(5) })));
|
||||
test!(rec_lim_long: DirAction <- ["--recurse", "--level", "666"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(666) })));
|
||||
test!(rec_lim_long_2: DirAction <- ["--recurse", "--level=0118"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(118) })));
|
||||
test!(rec_tree: DirAction <- ["--recurse", "--tree"]; Both => Ok(Recurse(RecurseOptions { tree: true, max_depth: None })));
|
||||
test!(rec_short_tree: DirAction <- ["--tree", "--recurse"]; Both => Ok(Recurse(RecurseOptions { tree: true, max_depth: None })));
|
||||
|
||||
// Errors
|
||||
test!(error: DirAction <- ["--list-dirs", "--recurse"] => Err(Misfire::Conflict(&flags::RECURSE, &flags::LIST_DIRS)));
|
||||
test!(error_2: DirAction <- ["--list-dirs", "--tree"] => Err(Misfire::Conflict(&flags::TREE, &flags::LIST_DIRS)));
|
||||
test!(underwaterlevel: DirAction <- ["--level=4"] => Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)));
|
||||
test!(error: DirAction <- ["--list-dirs", "--recurse"]; Both => Err(Misfire::Conflict(&flags::RECURSE, &flags::LIST_DIRS)));
|
||||
test!(error_2: DirAction <- ["--list-dirs", "--tree"]; Both => Err(Misfire::Conflict(&flags::TREE, &flags::LIST_DIRS)));
|
||||
test!(underwaterlevel: DirAction <- ["--level=4"]; Both => Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)));
|
||||
|
||||
// Overriding
|
||||
test!(overriding_1: DirAction <- ["-RL=6", "-L=7"]; Last => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(7) })));
|
||||
test!(overriding_2: DirAction <- ["-RL=6", "-L=7"]; Complain => Err(Misfire::Duplicate(Flag::Short(b'L'), Flag::Short(b'L'))));
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ impl FileFilter {
|
||||
/// command-line arguments.
|
||||
pub fn deduce(matches: &MatchedFlags) -> Result<FileFilter, Misfire> {
|
||||
Ok(FileFilter {
|
||||
list_dirs_first: matches.has(&flags::DIRS_FIRST),
|
||||
reverse: matches.has(&flags::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)?,
|
||||
@ -38,7 +38,7 @@ impl SortField {
|
||||
/// argument. This will return `Err` if the option is there, but does not
|
||||
/// correspond to a valid field.
|
||||
fn deduce(matches: &MatchedFlags) -> Result<SortField, Misfire> {
|
||||
let word = match matches.get(&flags::SORT) {
|
||||
let word = match matches.get(&flags::SORT)? {
|
||||
Some(w) => w,
|
||||
None => return Ok(SortField::default()),
|
||||
};
|
||||
@ -85,11 +85,22 @@ impl SortField {
|
||||
|
||||
impl DotFilter {
|
||||
pub fn deduce(matches: &MatchedFlags) -> Result<DotFilter, Misfire> {
|
||||
match matches.count(&flags::ALL) {
|
||||
0 => Ok(DotFilter::JustFiles),
|
||||
1 => Ok(DotFilter::Dotfiles),
|
||||
_ => if matches.has(&flags::TREE) { Err(Misfire::TreeAllAll) }
|
||||
else { Ok(DotFilter::DotfilesAndDots) }
|
||||
let count = matches.count(&flags::ALL);
|
||||
|
||||
if count == 0 {
|
||||
Ok(DotFilter::JustFiles)
|
||||
}
|
||||
else if count == 1 {
|
||||
Ok(DotFilter::Dotfiles)
|
||||
}
|
||||
else if matches.count(&flags::TREE) > 0 {
|
||||
Err(Misfire::TreeAllAll)
|
||||
}
|
||||
else if count >= 3 && matches.is_strict() {
|
||||
Err(Misfire::Conflict(&flags::ALL, &flags::ALL))
|
||||
}
|
||||
else {
|
||||
Ok(DotFilter::DotfilesAndDots)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -101,7 +112,7 @@ impl IgnorePatterns {
|
||||
/// command-line arguments.
|
||||
pub fn deduce(matches: &MatchedFlags) -> Result<IgnorePatterns, Misfire> {
|
||||
|
||||
let inputs = match matches.get(&flags::IGNORE_GLOB) {
|
||||
let inputs = match matches.get(&flags::IGNORE_GLOB)? {
|
||||
None => return Ok(IgnorePatterns::empty()),
|
||||
Some(is) => is,
|
||||
};
|
||||
@ -126,6 +137,7 @@ mod test {
|
||||
use super::*;
|
||||
use std::ffi::OsString;
|
||||
use options::flags;
|
||||
use options::parser::Flag;
|
||||
|
||||
pub fn os(input: &'static str) -> OsString {
|
||||
let mut os = OsString::new();
|
||||
@ -134,17 +146,17 @@ mod test {
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $type:ident <- $inputs:expr => $result:expr) => {
|
||||
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
use options::parser::{Args, Arg};
|
||||
use std::ffi::OsString;
|
||||
use options::parser::Arg;
|
||||
use options::test::parse_for_test;
|
||||
use options::test::Strictnesses::*;
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[ &flags::SORT, &flags::ALL, &flags::TREE, &flags::IGNORE_GLOB ];
|
||||
|
||||
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
||||
let results = Args(TEST_ARGS).parse(bits.iter());
|
||||
assert_eq!($type::deduce(&results.unwrap().flags), $result);
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||
assert_eq!(result, $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -153,21 +165,23 @@ mod test {
|
||||
use super::*;
|
||||
|
||||
// Default behaviour
|
||||
test!(empty: SortField <- [] => Ok(SortField::default()));
|
||||
test!(empty: SortField <- []; Both => Ok(SortField::default()));
|
||||
|
||||
// Sort field arguments
|
||||
test!(one_arg: SortField <- ["--sort=cr"] => Ok(SortField::CreatedDate));
|
||||
test!(one_long: SortField <- ["--sort=size"] => Ok(SortField::Size));
|
||||
test!(one_short: SortField <- ["-saccessed"] => Ok(SortField::AccessedDate));
|
||||
test!(lowercase: SortField <- ["--sort", "name"] => Ok(SortField::Name(SortCase::Sensitive)));
|
||||
test!(uppercase: SortField <- ["--sort", "Name"] => Ok(SortField::Name(SortCase::Insensitive)));
|
||||
test!(one_arg: SortField <- ["--sort=cr"]; Both => Ok(SortField::CreatedDate));
|
||||
test!(one_long: SortField <- ["--sort=size"]; Both => Ok(SortField::Size));
|
||||
test!(one_short: SortField <- ["-saccessed"]; Both => Ok(SortField::AccessedDate));
|
||||
test!(lowercase: SortField <- ["--sort", "name"]; Both => Ok(SortField::Name(SortCase::Sensitive)));
|
||||
test!(uppercase: SortField <- ["--sort", "Name"]; Both => Ok(SortField::Name(SortCase::Insensitive)));
|
||||
|
||||
// Errors
|
||||
test!(error: SortField <- ["--sort=colour"] => Err(Misfire::bad_argument(&flags::SORT, &os("colour"), super::SORTS)));
|
||||
test!(error: SortField <- ["--sort=colour"]; Both => Err(Misfire::bad_argument(&flags::SORT, &os("colour"), super::SORTS)));
|
||||
|
||||
// Overriding
|
||||
test!(overridden: SortField <- ["--sort=cr", "--sort", "mod"] => Ok(SortField::ModifiedDate));
|
||||
test!(overridden_2: SortField <- ["--sort", "none", "--sort=Extension"] => Ok(SortField::Extension(SortCase::Insensitive)));
|
||||
test!(overridden: SortField <- ["--sort=cr", "--sort", "mod"]; Last => Ok(SortField::ModifiedDate));
|
||||
test!(overridden_2: SortField <- ["--sort", "none", "--sort=Extension"]; Last => Ok(SortField::Extension(SortCase::Insensitive)));
|
||||
test!(overridden_3: SortField <- ["--sort=cr", "--sort", "mod"]; Complain => Err(Misfire::Duplicate(Flag::Long("sort"), Flag::Long("sort"))));
|
||||
test!(overridden_4: SortField <- ["--sort", "none", "--sort=Extension"]; Complain => Err(Misfire::Duplicate(Flag::Long("sort"), Flag::Long("sort"))));
|
||||
}
|
||||
|
||||
|
||||
@ -175,16 +189,20 @@ mod test {
|
||||
use super::*;
|
||||
|
||||
// Default behaviour
|
||||
test!(empty: DotFilter <- [] => Ok(DotFilter::JustFiles));
|
||||
test!(empty: DotFilter <- []; Both => Ok(DotFilter::JustFiles));
|
||||
|
||||
// --all
|
||||
test!(all: DotFilter <- ["--all"] => Ok(DotFilter::Dotfiles));
|
||||
test!(all_all: DotFilter <- ["--all", "-a"] => Ok(DotFilter::DotfilesAndDots));
|
||||
test!(all_all_2: DotFilter <- ["-aa"] => Ok(DotFilter::DotfilesAndDots));
|
||||
test!(all: DotFilter <- ["--all"]; Both => Ok(DotFilter::Dotfiles));
|
||||
test!(all_all: DotFilter <- ["--all", "-a"]; Both => Ok(DotFilter::DotfilesAndDots));
|
||||
test!(all_all_2: DotFilter <- ["-aa"]; Both => Ok(DotFilter::DotfilesAndDots));
|
||||
|
||||
test!(all_all_3: DotFilter <- ["-aaa"]; Last => Ok(DotFilter::DotfilesAndDots));
|
||||
test!(all_all_4: DotFilter <- ["-aaa"]; Complain => Err(Misfire::Conflict(&flags::ALL, &flags::ALL)));
|
||||
|
||||
// --all and --tree
|
||||
test!(tree_a: DotFilter <- ["-Ta"] => Ok(DotFilter::Dotfiles));
|
||||
test!(tree_aa: DotFilter <- ["-Taa"] => Err(Misfire::TreeAllAll));
|
||||
test!(tree_a: DotFilter <- ["-Ta"]; Both => Ok(DotFilter::Dotfiles));
|
||||
test!(tree_aa: DotFilter <- ["-Taa"]; Both => Err(Misfire::TreeAllAll));
|
||||
test!(tree_aaa: DotFilter <- ["-Taaa"]; Both => Err(Misfire::TreeAllAll));
|
||||
}
|
||||
|
||||
|
||||
@ -198,9 +216,15 @@ mod test {
|
||||
}
|
||||
|
||||
// Various numbers of globs
|
||||
test!(none: IgnorePatterns <- [] => Ok(IgnorePatterns::empty()));
|
||||
test!(one: IgnorePatterns <- ["--ignore-glob", "*.ogg"] => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg") ])));
|
||||
test!(two: IgnorePatterns <- ["--ignore-glob=*.ogg|*.MP3"] => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg"), pat("*.MP3") ])));
|
||||
test!(loads: IgnorePatterns <- ["-I*|?|.|*"] => Ok(IgnorePatterns::from_iter(vec![ pat("*"), pat("?"), pat("."), pat("*") ])));
|
||||
test!(none: IgnorePatterns <- []; Both => Ok(IgnorePatterns::empty()));
|
||||
test!(one: IgnorePatterns <- ["--ignore-glob", "*.ogg"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg") ])));
|
||||
test!(two: IgnorePatterns <- ["--ignore-glob=*.ogg|*.MP3"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg"), pat("*.MP3") ])));
|
||||
test!(loads: IgnorePatterns <- ["-I*|?|.|*"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*"), pat("?"), pat("."), pat("*") ])));
|
||||
|
||||
// Overriding
|
||||
test!(overridden: IgnorePatterns <- ["-I=*.ogg", "-I", "*.mp3"]; Last => Ok(IgnorePatterns::from_iter(vec![ pat("*.mp3") ])));
|
||||
test!(overridden_2: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Last => Ok(IgnorePatterns::from_iter(vec![ pat("*.MP3") ])));
|
||||
test!(overridden_3: IgnorePatterns <- ["-I=*.ogg", "-I", "*.mp3"]; Complain => Err(Misfire::Duplicate(Flag::Short(b'I'), Flag::Short(b'I'))));
|
||||
test!(overridden_4: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Complain => Err(Misfire::Duplicate(Flag::Short(b'I'), Flag::Short(b'I'))));
|
||||
}
|
||||
}
|
||||
|
@ -72,9 +72,13 @@ impl HelpString {
|
||||
/// Determines how to show help, if at all, based on the user’s
|
||||
/// command-line arguments. This one works backwards from the other
|
||||
/// ‘deduce’ functions, returning Err if help needs to be shown.
|
||||
///
|
||||
/// We don’t do any strict-mode error checking here: it’s OK to give
|
||||
/// the --help or --long flags more than once. Actually checking for
|
||||
/// errors when the user wants help is kind of petty!
|
||||
pub fn deduce(matches: &MatchedFlags) -> Result<(), HelpString> {
|
||||
if matches.has(&flags::HELP) {
|
||||
let only_long = matches.has(&flags::LONG);
|
||||
if matches.count(&flags::HELP) > 0 {
|
||||
let only_long = matches.count(&flags::LONG) > 0;
|
||||
let git = cfg!(feature="git");
|
||||
let xattrs = xattr::ENABLED;
|
||||
Err(HelpString { only_long, git, xattrs })
|
||||
@ -126,21 +130,21 @@ mod test {
|
||||
#[test]
|
||||
fn help() {
|
||||
let args = [ os("--help") ];
|
||||
let opts = Options::getopts(&args);
|
||||
let opts = Options::parse(&args, None);
|
||||
assert!(opts.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_with_file() {
|
||||
let args = [ os("--help"), os("me") ];
|
||||
let opts = Options::getopts(&args);
|
||||
let opts = Options::parse(&args, None);
|
||||
assert!(opts.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unhelpful() {
|
||||
let args = [];
|
||||
let opts = Options::getopts(&args);
|
||||
let opts = Options::parse(&args, None);
|
||||
assert!(opts.is_ok()) // no help when --help isn’t passed
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use std::num::ParseIntError;
|
||||
use glob;
|
||||
|
||||
use options::{HelpString, VersionString};
|
||||
use options::parser::{Arg, ParseError};
|
||||
use options::parser::{Arg, Flag, ParseError};
|
||||
|
||||
|
||||
/// A list of legal choices for an argument-taking option
|
||||
@ -36,6 +36,9 @@ pub enum Misfire {
|
||||
/// The user wanted the version number.
|
||||
Version(VersionString),
|
||||
|
||||
/// An option was given twice or more in strict mode.
|
||||
Duplicate(Flag, Flag),
|
||||
|
||||
/// Two options were given that conflict with one another.
|
||||
Conflict(&'static Arg, &'static Arg),
|
||||
|
||||
@ -89,10 +92,11 @@ impl fmt::Display for Misfire {
|
||||
|
||||
match *self {
|
||||
BadArgument(ref a, ref b, ref c) => write!(f, "Option {} has no value {:?} (Choices: {})", a, b, c),
|
||||
InvalidOptions(ref e) => write!(f, "{:?}", e),
|
||||
InvalidOptions(ref e) => write!(f, "{}", e),
|
||||
Help(ref text) => write!(f, "{}", text),
|
||||
Version(ref version) => write!(f, "{}", version),
|
||||
Conflict(ref a, ref b) => write!(f, "Option {} conflicts with option {}.", a, b),
|
||||
Duplicate(ref a, ref b) => write!(f, "Flag {:?} conflicts with flag {:?}.", 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),
|
||||
@ -102,3 +106,16 @@ impl fmt::Display for Misfire {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::ParseError::*;
|
||||
|
||||
match *self {
|
||||
NeedsValue { ref flag } => write!(f, "Flag {} needs a value", flag),
|
||||
ForbiddenValue { ref flag } => write!(f, "Flag {} cannot take a value", flag),
|
||||
UnknownShortArgument { ref attempt } => write!(f, "Unknown argument -{}", *attempt as char),
|
||||
UnknownArgument { ref attempt } => write!(f, "Unknown argument --{}", attempt.to_string_lossy()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,13 +112,22 @@ pub struct Options {
|
||||
|
||||
impl Options {
|
||||
|
||||
/// Call getopts on the given slice of command-line strings.
|
||||
/// Parse the given iterator of command-line strings into an Options
|
||||
/// struct and a list of free filenames, using the environment variables
|
||||
/// for extra options.
|
||||
#[allow(unused_results)]
|
||||
pub fn getopts<'args, I>(args: I) -> Result<(Options, Vec<&'args OsStr>), Misfire>
|
||||
where I: IntoIterator<Item=&'args OsString> {
|
||||
use options::parser::Matches;
|
||||
pub fn parse<'args, I, V>(args: I, vars: V) -> Result<(Options, Vec<&'args OsStr>), Misfire>
|
||||
where I: IntoIterator<Item=&'args OsString>,
|
||||
V: Vars {
|
||||
use options::parser::{Matches, Strictness};
|
||||
|
||||
let Matches { flags, frees } = match flags::ALL_ARGS.parse(args) {
|
||||
let strictness = match vars.get("EXA_STRICT") {
|
||||
None => Strictness::UseLastArguments,
|
||||
Some(ref t) if t.is_empty() => Strictness::UseLastArguments,
|
||||
_ => Strictness::ComplainAboutRedundantArguments,
|
||||
};
|
||||
|
||||
let Matches { flags, frees } = match flags::ALL_ARGS.parse(args, strictness) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return Err(Misfire::InvalidOptions(e)),
|
||||
};
|
||||
@ -126,7 +135,7 @@ impl Options {
|
||||
HelpString::deduce(&flags).map_err(Misfire::Help)?;
|
||||
VersionString::deduce(&flags).map_err(Misfire::Version)?;
|
||||
|
||||
let options = Options::deduce(&flags)?;
|
||||
let options = Options::deduce(&flags, vars)?;
|
||||
Ok((options, frees))
|
||||
}
|
||||
|
||||
@ -136,33 +145,84 @@ impl Options {
|
||||
pub fn should_scan_for_git(&self) -> bool {
|
||||
match self.view.mode {
|
||||
Mode::Details(details::Options { table: Some(ref table), .. }) |
|
||||
Mode::GridDetails(_, details::Options { table: Some(ref table), .. }) => table.should_scan_for_git(),
|
||||
Mode::GridDetails(_, details::Options { table: Some(ref table), .. }) => table.extra_columns.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: &MatchedFlags) -> Result<Options, Misfire> {
|
||||
fn deduce<V: Vars>(matches: &MatchedFlags, vars: V) -> Result<Options, Misfire> {
|
||||
let dir_action = DirAction::deduce(matches)?;
|
||||
let filter = FileFilter::deduce(matches)?;
|
||||
let view = View::deduce(matches)?;
|
||||
let view = View::deduce(matches, vars)?;
|
||||
|
||||
Ok(Options { dir_action, view, filter })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Mockable wrapper for `std::env::var_os`.
|
||||
pub trait Vars {
|
||||
fn get(&self, name: &'static str) -> Option<OsString>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Options, Misfire, flags};
|
||||
pub mod test {
|
||||
use super::{Options, Misfire, Vars, flags};
|
||||
use options::parser::{Arg, MatchedFlags};
|
||||
use std::ffi::OsString;
|
||||
use fs::filter::{SortField, SortCase};
|
||||
|
||||
// Test impl that just returns the value it has.
|
||||
impl Vars for Option<OsString> {
|
||||
fn get(&self, _name: &'static str) -> Option<OsString> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Strictnesses {
|
||||
Last,
|
||||
Complain,
|
||||
Both,
|
||||
}
|
||||
|
||||
/// This function gets used by the other testing modules.
|
||||
/// It can run with one or both strictness values: if told to run with
|
||||
/// both, then both should resolve to the same result.
|
||||
///
|
||||
/// It returns a vector with one or two elements in.
|
||||
/// These elements can then be tested with assert_eq or what have you.
|
||||
pub fn parse_for_test<T, F>(inputs: &[&str], args: &'static [&'static Arg], strictnesses: Strictnesses, get: F) -> Vec<T>
|
||||
where F: Fn(&MatchedFlags) -> T
|
||||
{
|
||||
use self::Strictnesses::*;
|
||||
use options::parser::{Args, Strictness};
|
||||
use std::ffi::OsString;
|
||||
|
||||
let bits = inputs.into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
||||
let mut rezzies = Vec::new();
|
||||
|
||||
if strictnesses == Last || strictnesses == Both {
|
||||
let results = Args(args).parse(bits.iter(), Strictness::UseLastArguments);
|
||||
rezzies.push(get(&results.unwrap().flags));
|
||||
}
|
||||
|
||||
if strictnesses == Complain || strictnesses == Both {
|
||||
let results = Args(args).parse(bits.iter(), Strictness::ComplainAboutRedundantArguments);
|
||||
rezzies.push(get(&results.unwrap().flags));
|
||||
}
|
||||
|
||||
rezzies
|
||||
}
|
||||
|
||||
/// Creates an `OSStr` (used in tests)
|
||||
#[cfg(test)]
|
||||
fn os(input: &'static str) -> OsString {
|
||||
fn os(input: &str) -> OsString {
|
||||
let mut os = OsString::new();
|
||||
os.push(input);
|
||||
os
|
||||
@ -171,106 +231,36 @@ mod test {
|
||||
#[test]
|
||||
fn files() {
|
||||
let args = [ os("this file"), os("that file") ];
|
||||
let outs = Options::getopts(&args).unwrap().1;
|
||||
let outs = Options::parse(&args, None).unwrap().1;
|
||||
assert_eq!(outs, vec![ &os("this file"), &os("that file") ])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_args() {
|
||||
let nothing: Vec<OsString> = Vec::new();
|
||||
let outs = Options::getopts(¬hing).unwrap().1;
|
||||
let outs = Options::parse(¬hing, None).unwrap().1;
|
||||
assert!(outs.is_empty()); // Listing the `.` directory is done in main.rs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_binary() {
|
||||
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 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 args = [ os("--long"), os("--across") ];
|
||||
let opts = Options::getopts(&args);
|
||||
let opts = Options::parse(&args, None);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::ACROSS, true, &flags::LONG))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oneline_across() {
|
||||
let args = [ os("--oneline"), os("--across") ];
|
||||
let opts = Options::getopts(&args);
|
||||
let opts = Options::parse(&args, None);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::ACROSS, true, &flags::ONE_LINE))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_header() {
|
||||
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 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 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 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 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 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 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 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 args = [ os("--git") ];
|
||||
let opts = Options::getopts(&args);
|
||||
let opts = Options::parse(&args, None);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::GIT, false, &flags::LONG))
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,8 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt;
|
||||
|
||||
use options::Misfire;
|
||||
|
||||
|
||||
/// A **short argument** is a single ASCII character.
|
||||
pub type ShortArg = u8;
|
||||
@ -50,7 +52,7 @@ pub enum Flag {
|
||||
}
|
||||
|
||||
impl Flag {
|
||||
fn matches(&self, arg: &Arg) -> bool {
|
||||
pub fn matches(&self, arg: &Arg) -> bool {
|
||||
match *self {
|
||||
Flag::Short(short) => arg.short == Some(short),
|
||||
Flag::Long(long) => arg.long == long,
|
||||
@ -58,10 +60,17 @@ impl Flag {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Flag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Flag::Short(short) => write!(f, "-{}", short as char),
|
||||
Flag::Long(long) => write!(f, "--{}", long),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether redundant arguments should be considered a problem.
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[allow(dead_code)] // until strict mode is actually implemented
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub enum Strictness {
|
||||
|
||||
/// Throw an error when an argument doesn’t do anything, either because
|
||||
@ -122,7 +131,7 @@ impl Args {
|
||||
|
||||
/// Iterates over the given list of command-line arguments and parses
|
||||
/// them into a list of matched flags and free strings.
|
||||
pub fn parse<'args, I>(&self, inputs: I) -> Result<Matches<'args>, ParseError>
|
||||
pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
|
||||
where I: IntoIterator<Item=&'args OsString> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use self::TakesValue::*;
|
||||
@ -267,17 +276,17 @@ impl Args {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Matches { frees, flags: MatchedFlags { flags: result_flags } })
|
||||
Ok(Matches { frees, flags: MatchedFlags { flags: result_flags, strictness } })
|
||||
}
|
||||
|
||||
fn lookup_short<'a>(&self, short: ShortArg) -> Result<&Arg, ParseError> {
|
||||
fn lookup_short(&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> {
|
||||
fn lookup_long<'b>(&self, long: &'b OsStr) -> Result<&Arg, ParseError> {
|
||||
match self.0.into_iter().find(|arg| arg.long == long) {
|
||||
Some(arg) => Ok(arg),
|
||||
None => Err(ParseError::UnknownArgument { attempt: long.to_os_string() })
|
||||
@ -308,34 +317,93 @@ pub struct MatchedFlags<'args> {
|
||||
/// we usually want the one nearest the end to count, and to know this,
|
||||
/// we need to know where they are in relation to one another.
|
||||
flags: Vec<(Flag, Option<&'args OsStr>)>,
|
||||
|
||||
/// Whether to check for duplicate or redundant arguments.
|
||||
strictness: Strictness,
|
||||
}
|
||||
|
||||
impl<'a> MatchedFlags<'a> {
|
||||
|
||||
/// Whether the given argument was specified.
|
||||
pub fn has(&self, arg: &Arg) -> bool {
|
||||
self.flags.iter().rev()
|
||||
.find(|tuple| tuple.1.is_none() && tuple.0.matches(arg))
|
||||
.is_some()
|
||||
/// Returns `true` if it was, `false` if it wasn’t, and an error in
|
||||
/// strict mode if it was specified more than once.
|
||||
pub fn has(&self, arg: &'static Arg) -> Result<bool, Misfire> {
|
||||
self.has_where(|flag| flag.matches(arg)).map(|flag| flag.is_some())
|
||||
}
|
||||
|
||||
/// If the given argument was specified, return its value.
|
||||
/// The value is not guaranteed to be valid UTF-8.
|
||||
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())
|
||||
/// Returns the first found argument that satisfies the predicate, or
|
||||
/// nothing if none is found, or an error in strict mode if multiple
|
||||
/// argument satisfy the predicate.
|
||||
///
|
||||
/// You’ll have to test the resulting flag to see which argument it was.
|
||||
pub fn has_where<P>(&self, predicate: P) -> Result<Option<&Flag>, Misfire>
|
||||
where P: Fn(&Flag) -> bool {
|
||||
if self.is_strict() {
|
||||
let all = self.flags.iter()
|
||||
.filter(|tuple| tuple.1.is_none() && predicate(&tuple.0))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if all.len() < 2 { Ok(all.first().map(|t| &t.0)) }
|
||||
else { Err(Misfire::Duplicate(all[0].0.clone(), all[1].0.clone())) }
|
||||
}
|
||||
else {
|
||||
let any = self.flags.iter().rev()
|
||||
.find(|tuple| tuple.1.is_none() && predicate(&tuple.0))
|
||||
.map(|tuple| &tuple.0);
|
||||
Ok(any)
|
||||
}
|
||||
}
|
||||
|
||||
// This code could probably be better.
|
||||
// Both ‘has’ and ‘get’ immediately begin with a conditional, which makes
|
||||
// me think the functionality could be moved to inside Strictness.
|
||||
|
||||
/// Returns the value of the given argument if it was specified, nothing
|
||||
/// if it wasn’t, and an error in strict mode if it was specified more
|
||||
/// than once.
|
||||
pub fn get(&self, arg: &'static Arg) -> Result<Option<&OsStr>, Misfire> {
|
||||
self.get_where(|flag| flag.matches(arg))
|
||||
}
|
||||
|
||||
/// Returns the value of the argument that matches the predicate if it
|
||||
/// was specified, nothing if it wasn't, and an error in strict mode if
|
||||
/// multiple arguments matched the predicate.
|
||||
///
|
||||
/// It’s not possible to tell which flag the value belonged to from this.
|
||||
pub fn get_where<P>(&self, predicate: P) -> Result<Option<&OsStr>, Misfire>
|
||||
where P: Fn(&Flag) -> bool {
|
||||
if self.is_strict() {
|
||||
let those = self.flags.iter()
|
||||
.filter(|tuple| tuple.1.is_some() && predicate(&tuple.0))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if those.len() < 2 { Ok(those.first().cloned().map(|t| t.1.unwrap())) }
|
||||
else { Err(Misfire::Duplicate(those[0].0.clone(), those[1].0.clone())) }
|
||||
}
|
||||
else {
|
||||
let found = self.flags.iter().rev()
|
||||
.find(|tuple| tuple.1.is_some() && predicate(&tuple.0))
|
||||
.map(|tuple| tuple.1.unwrap());
|
||||
Ok(found)
|
||||
}
|
||||
}
|
||||
|
||||
// It’s annoying that ‘has’ and ‘get’ won’t work when accidentally given
|
||||
// flags that do/don’t take values, but this should be caught by tests.
|
||||
|
||||
/// Counts the number of occurrences of the given argument.
|
||||
/// Counts the number of occurrences of the given argument, even in
|
||||
/// strict mode.
|
||||
pub fn count(&self, arg: &Arg) -> usize {
|
||||
self.flags.iter()
|
||||
.filter(|tuple| tuple.0.matches(arg))
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Checks whether strict mode is on. This is usually done from within
|
||||
/// ‘has’ and ‘get’, but it’s available in an emergency.
|
||||
pub fn is_strict(&self) -> bool {
|
||||
self.strictness == Strictness::ComplainAboutRedundantArguments
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -433,6 +501,13 @@ mod split_test {
|
||||
mod parse_test {
|
||||
use super::*;
|
||||
|
||||
pub fn os(input: &'static str) -> OsString {
|
||||
let mut os = OsString::new();
|
||||
os.push(input);
|
||||
os
|
||||
}
|
||||
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $inputs:expr => frees: $frees:expr, flags: $flags:expr) => {
|
||||
#[test]
|
||||
@ -445,15 +520,11 @@ mod parse_test {
|
||||
let frees: Vec<OsString> = $frees.as_ref().into_iter().map(|&o| os(o)).collect();
|
||||
let frees: Vec<&OsStr> = frees.iter().map(|os| os.as_os_str()).collect();
|
||||
|
||||
// And again for the flags
|
||||
let flags: Vec<(Flag, Option<&OsStr>)> = $flags
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.map(|&(ref f, ref os): &(Flag, Option<&'static str>)| (f.clone(), os.map(OsStr::new)))
|
||||
.collect();
|
||||
let flags = <[_]>::into_vec(Box::new($flags));
|
||||
|
||||
let got = Args(TEST_ARGS).parse(inputs.iter());
|
||||
let expected = Ok(Matches { frees, flags: MatchedFlags { flags } });
|
||||
let strictness = Strictness::UseLastArguments; // this isn’t even used
|
||||
let got = Args(TEST_ARGS).parse(inputs.iter(), strictness);
|
||||
let expected = Ok(Matches { frees, flags: MatchedFlags { flags, strictness } });
|
||||
assert_eq!(got, expected);
|
||||
}
|
||||
};
|
||||
@ -463,8 +534,9 @@ mod parse_test {
|
||||
fn $name() {
|
||||
use self::ParseError::*;
|
||||
|
||||
let strictness = Strictness::UseLastArguments; // this isn’t even used
|
||||
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
||||
let got = Args(TEST_ARGS).parse(bits.iter());
|
||||
let got = Args(TEST_ARGS).parse(bits.iter(), strictness);
|
||||
|
||||
assert_eq!(got, Err($error));
|
||||
}
|
||||
@ -498,8 +570,8 @@ mod parse_test {
|
||||
// Long args with values
|
||||
test!(bad_equals: ["--long=equals"] => error ForbiddenValue { flag: Flag::Long("long") });
|
||||
test!(no_arg: ["--count"] => error NeedsValue { flag: Flag::Long("count") });
|
||||
test!(arg_equals: ["--count=4"] => frees: [], flags: [ (Flag::Long("count"), Some("4")) ]);
|
||||
test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some("4")) ]);
|
||||
test!(arg_equals: ["--count=4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
|
||||
test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
|
||||
|
||||
|
||||
// Short args
|
||||
@ -511,11 +583,11 @@ mod parse_test {
|
||||
// Short args with values
|
||||
test!(bad_short: ["-l=equals"] => error ForbiddenValue { flag: Flag::Short(b'l') });
|
||||
test!(short_none: ["-c"] => error NeedsValue { flag: Flag::Short(b'c') });
|
||||
test!(short_arg_eq: ["-c=4"] => frees: [], flags: [(Flag::Short(b'c'), Some("4")) ]);
|
||||
test!(short_arg_then: ["-c", "4"] => frees: [], flags: [(Flag::Short(b'c'), Some("4")) ]);
|
||||
test!(short_two_together: ["-lctwo"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some("two")) ]);
|
||||
test!(short_two_equals: ["-lc=two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some("two")) ]);
|
||||
test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some("two")) ]);
|
||||
test!(short_arg_eq: ["-c=4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
|
||||
test!(short_arg_then: ["-c", "4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
|
||||
test!(short_two_together: ["-lctwo"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
|
||||
test!(short_two_equals: ["-lc=two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
|
||||
test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
|
||||
|
||||
|
||||
// Unknown args
|
||||
@ -536,8 +608,12 @@ mod matches_test {
|
||||
($name:ident: $input:expr, has $param:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let flags = MatchedFlags { flags: $input.to_vec() };
|
||||
assert_eq!(flags.has(&$param), $result);
|
||||
let flags = MatchedFlags {
|
||||
flags: $input.to_vec(),
|
||||
strictness: Strictness::UseLastArguments,
|
||||
};
|
||||
|
||||
assert_eq!(flags.has(&$param), Ok($result));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -557,8 +633,13 @@ mod matches_test {
|
||||
#[test]
|
||||
fn only_count() {
|
||||
let everything = os("everything");
|
||||
let flags = MatchedFlags { flags: vec![ (Flag::Short(b'c'), Some(&*everything)) ] };
|
||||
assert_eq!(flags.get(&COUNT), Some(&*everything));
|
||||
|
||||
let flags = MatchedFlags {
|
||||
flags: vec![ (Flag::Short(b'c'), Some(&*everything)) ],
|
||||
strictness: Strictness::UseLastArguments,
|
||||
};
|
||||
|
||||
assert_eq!(flags.get(&COUNT), Ok(Some(&*everything)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -568,16 +649,17 @@ mod matches_test {
|
||||
|
||||
let flags = MatchedFlags {
|
||||
flags: vec![ (Flag::Short(b'c'), Some(&*everything)),
|
||||
(Flag::Short(b'c'), Some(&*nothing)) ]
|
||||
(Flag::Short(b'c'), Some(&*nothing)) ],
|
||||
strictness: Strictness::UseLastArguments,
|
||||
};
|
||||
|
||||
assert_eq!(flags.get(&COUNT), Some(&*nothing));
|
||||
assert_eq!(flags.get(&COUNT), Ok(Some(&*nothing)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_count() {
|
||||
let flags = MatchedFlags { flags: Vec::new() };
|
||||
let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };
|
||||
|
||||
assert!(!flags.has(&COUNT));
|
||||
assert!(!flags.has(&COUNT).unwrap());
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,10 @@ impl VersionString {
|
||||
/// Determines how to show the version, if at all, based on the user’s
|
||||
/// command-line arguments. This one works backwards from the other
|
||||
/// ‘deduce’ functions, returning Err if help needs to be shown.
|
||||
///
|
||||
/// Like --help, this doesn’t bother checking for errors.
|
||||
pub fn deduce(matches: &MatchedFlags) -> Result<(), VersionString> {
|
||||
if matches.has(&flags::VERSION) {
|
||||
if matches.count(&flags::VERSION) > 0 {
|
||||
Err(VersionString { cargo: env!("CARGO_PKG_VERSION") })
|
||||
}
|
||||
else {
|
||||
@ -52,7 +54,7 @@ mod test {
|
||||
#[test]
|
||||
fn help() {
|
||||
let args = [ os("--version") ];
|
||||
let opts = Options::getopts(&args);
|
||||
let opts = Options::parse(&args, None);
|
||||
assert!(opts.is_err())
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,22 @@
|
||||
use std::env::var_os;
|
||||
|
||||
use output::Colours;
|
||||
use output::{View, Mode, grid, details};
|
||||
use output::table::{TimeTypes, Environment, SizeFormat, Options as TableOptions};
|
||||
use output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
|
||||
use output::file_name::{Classify, FileStyle};
|
||||
use output::time::TimeFormat;
|
||||
|
||||
use options::{flags, Misfire};
|
||||
use options::{flags, Misfire, Vars};
|
||||
use options::parser::MatchedFlags;
|
||||
|
||||
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: &MatchedFlags) -> Result<View, Misfire> {
|
||||
let mode = Mode::deduce(matches)?;
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: V) -> Result<View, Misfire> {
|
||||
let mode = Mode::deduce(matches, vars)?;
|
||||
let colours = Colours::deduce(matches)?;
|
||||
let style = FileStyle::deduce(matches);
|
||||
let style = FileStyle::deduce(matches)?;
|
||||
Ok(View { mode, colours, style })
|
||||
}
|
||||
}
|
||||
@ -28,21 +25,21 @@ impl View {
|
||||
impl Mode {
|
||||
|
||||
/// Determine the mode from the command-line arguments.
|
||||
pub fn deduce(matches: &MatchedFlags) -> Result<Mode, Misfire> {
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: V) -> Result<Mode, Misfire> {
|
||||
use options::misfire::Misfire::*;
|
||||
|
||||
let long = || {
|
||||
if matches.has(&flags::ACROSS) && !matches.has(&flags::GRID) {
|
||||
if matches.has(&flags::ACROSS)? && !matches.has(&flags::GRID)? {
|
||||
Err(Useless(&flags::ACROSS, true, &flags::LONG))
|
||||
}
|
||||
else if matches.has(&flags::ONE_LINE) {
|
||||
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.has(&flags::HEADER),
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED),
|
||||
header: matches.has(&flags::HEADER)?,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
})
|
||||
}
|
||||
};
|
||||
@ -50,15 +47,15 @@ impl Mode {
|
||||
let long_options_scan = || {
|
||||
for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
|
||||
&flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP ] {
|
||||
if matches.has(option) {
|
||||
if matches.is_strict() && matches.has(option)? {
|
||||
return Err(Useless(*option, false, &flags::LONG));
|
||||
}
|
||||
}
|
||||
|
||||
if cfg!(feature="git") && matches.has(&flags::GIT) {
|
||||
if cfg!(feature="git") && matches.has(&flags::GIT)? {
|
||||
Err(Useless(&flags::GIT, false, &flags::LONG))
|
||||
}
|
||||
else if matches.has(&flags::LEVEL) && !matches.has(&flags::RECURSE) && !matches.has(&flags::TREE) {
|
||||
else if matches.has(&flags::LEVEL)? && !matches.has(&flags::RECURSE)? && !matches.has(&flags::TREE)? {
|
||||
Err(Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE))
|
||||
}
|
||||
else {
|
||||
@ -67,27 +64,27 @@ impl Mode {
|
||||
};
|
||||
|
||||
let other_options_scan = || {
|
||||
if let Some(width) = TerminalWidth::deduce()?.width() {
|
||||
if matches.has(&flags::ONE_LINE) {
|
||||
if matches.has(&flags::ACROSS) {
|
||||
if let Some(width) = TerminalWidth::deduce(vars)?.width() {
|
||||
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.has(&flags::TREE) {
|
||||
else if matches.has(&flags::TREE)? {
|
||||
let details = details::Options {
|
||||
table: None,
|
||||
header: false,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED),
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
};
|
||||
|
||||
Ok(Mode::Details(details))
|
||||
}
|
||||
else {
|
||||
let grid = grid::Options {
|
||||
across: matches.has(&flags::ACROSS),
|
||||
across: matches.has(&flags::ACROSS)?,
|
||||
console_width: width,
|
||||
};
|
||||
|
||||
@ -99,11 +96,11 @@ impl Mode {
|
||||
// as the program’s stdout being connected to a file, then
|
||||
// fallback to the lines view.
|
||||
|
||||
if matches.has(&flags::TREE) {
|
||||
if matches.has(&flags::TREE)? {
|
||||
let details = details::Options {
|
||||
table: None,
|
||||
header: false,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED),
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
};
|
||||
|
||||
Ok(Mode::Details(details))
|
||||
@ -114,9 +111,9 @@ impl Mode {
|
||||
}
|
||||
};
|
||||
|
||||
if matches.has(&flags::LONG) {
|
||||
if matches.has(&flags::LONG)? {
|
||||
let details = long()?;
|
||||
if matches.has(&flags::GRID) {
|
||||
if matches.has(&flags::GRID)? {
|
||||
match other_options_scan()? {
|
||||
Mode::Grid(grid) => return Ok(Mode::GridDetails(grid, details)),
|
||||
others => return Ok(others),
|
||||
@ -153,8 +150,8 @@ impl TerminalWidth {
|
||||
/// Determine a requested terminal width from the command-line arguments.
|
||||
///
|
||||
/// Returns an error if a requested width doesn’t parse to an integer.
|
||||
fn deduce() -> Result<TerminalWidth, Misfire> {
|
||||
if let Some(columns) = var_os("COLUMNS").and_then(|s| s.into_string().ok()) {
|
||||
fn deduce<V: Vars>(vars: V) -> Result<TerminalWidth, Misfire> {
|
||||
if let Some(columns) = vars.get("COLUMNS").and_then(|s| s.into_string().ok()) {
|
||||
match columns.parse() {
|
||||
Ok(width) => Ok(TerminalWidth::Set(width)),
|
||||
Err(e) => Err(Misfire::FailedParse(e)),
|
||||
@ -180,17 +177,26 @@ impl TerminalWidth {
|
||||
|
||||
impl TableOptions {
|
||||
fn deduce(matches: &MatchedFlags) -> 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.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),
|
||||
})
|
||||
let env = Environment::load_all();
|
||||
let time_format = TimeFormat::deduce(matches)?;
|
||||
let size_format = SizeFormat::deduce(matches)?;
|
||||
let extra_columns = Columns::deduce(matches)?;
|
||||
Ok(TableOptions { env, time_format, size_format, extra_columns })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Columns {
|
||||
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||
let time_types = TimeTypes::deduce(matches)?;
|
||||
let git = cfg!(feature="git") && matches.has(&flags::GIT)?;
|
||||
|
||||
let blocks = matches.has(&flags::BLOCKS)?;
|
||||
let group = matches.has(&flags::GROUP)?;
|
||||
let inode = matches.has(&flags::INODE)?;
|
||||
let links = matches.has(&flags::LINKS)?;
|
||||
|
||||
Ok(Columns { time_types, git, blocks, group, inode, links })
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,27 +212,26 @@ impl SizeFormat {
|
||||
/// involves the `--binary` or `--bytes` flags, and these conflict with
|
||||
/// each other.
|
||||
fn deduce(matches: &MatchedFlags) -> Result<SizeFormat, Misfire> {
|
||||
let binary = matches.has(&flags::BINARY);
|
||||
let bytes = matches.has(&flags::BYTES);
|
||||
let flag = matches.has_where(|f| f.matches(&flags::BINARY) || f.matches(&flags::BYTES))?;
|
||||
|
||||
match (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),
|
||||
}
|
||||
Ok(match flag {
|
||||
Some(f) if f.matches(&flags::BINARY) => SizeFormat::BinaryBytes,
|
||||
Some(f) if f.matches(&flags::BYTES) => SizeFormat::JustBytes,
|
||||
_ => SizeFormat::DecimalBytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const TIME_STYLES: &[&str] = &["default", "long-iso", "full-iso", "iso"];
|
||||
|
||||
impl TimeFormat {
|
||||
|
||||
/// Determine how time should be formatted in timestamp columns.
|
||||
fn deduce(matches: &MatchedFlags) -> Result<TimeFormat, Misfire> {
|
||||
pub use output::time::{DefaultFormat, ISOFormat};
|
||||
const STYLES: &[&str] = &["default", "long-iso", "full-iso", "iso"];
|
||||
|
||||
let word = match matches.get(&flags::TIME_STYLE) {
|
||||
let word = match matches.get(&flags::TIME_STYLE)? {
|
||||
Some(w) => w,
|
||||
None => return Ok(TimeFormat::DefaultFormat(DefaultFormat::new())),
|
||||
};
|
||||
@ -244,7 +249,7 @@ impl TimeFormat {
|
||||
Ok(TimeFormat::FullISO)
|
||||
}
|
||||
else {
|
||||
Err(Misfire::bad_argument(&flags::TIME_STYLE, word, STYLES))
|
||||
Err(Misfire::bad_argument(&flags::TIME_STYLE, word, TIME_STYLES))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -265,10 +270,10 @@ impl TimeTypes {
|
||||
/// option, but passing *no* options means that the user just wants to
|
||||
/// see the default set.
|
||||
fn deduce(matches: &MatchedFlags) -> 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);
|
||||
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 {
|
||||
@ -329,13 +334,14 @@ impl Default for TerminalColours {
|
||||
}
|
||||
}
|
||||
|
||||
const COLOURS: &[&str] = &["always", "auto", "never"];
|
||||
|
||||
impl TerminalColours {
|
||||
|
||||
/// Determine which terminal colour conditions to use.
|
||||
fn deduce(matches: &MatchedFlags) -> Result<TerminalColours, Misfire> {
|
||||
const COLOURS: &[&str] = &["always", "auto", "never"];
|
||||
|
||||
let word = match matches.get(&flags::COLOR).or_else(|| matches.get(&flags::COLOUR)) {
|
||||
let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
|
||||
Some(w) => w,
|
||||
None => return Ok(TerminalColours::default()),
|
||||
};
|
||||
@ -362,7 +368,7 @@ impl Colours {
|
||||
|
||||
let tc = TerminalColours::deduce(matches)?;
|
||||
if tc == Always || (tc == Automatic && TERM_WIDTH.is_some()) {
|
||||
let scale = matches.has(&flags::COLOR_SCALE) || matches.has(&flags::COLOUR_SCALE);
|
||||
let scale = matches.has(&flags::COLOR_SCALE)? || matches.has(&flags::COLOUR_SCALE)?;
|
||||
Ok(Colours::colourful(scale))
|
||||
}
|
||||
else {
|
||||
@ -374,17 +380,19 @@ impl Colours {
|
||||
|
||||
|
||||
impl FileStyle {
|
||||
fn deduce(matches: &MatchedFlags) -> FileStyle {
|
||||
let classify = Classify::deduce(matches);
|
||||
fn deduce(matches: &MatchedFlags) -> Result<FileStyle, Misfire> {
|
||||
let classify = Classify::deduce(matches)?;
|
||||
let exts = FileExtensions;
|
||||
FileStyle { classify, exts }
|
||||
Ok(FileStyle { classify, exts })
|
||||
}
|
||||
}
|
||||
|
||||
impl Classify {
|
||||
fn deduce(matches: &MatchedFlags) -> Classify {
|
||||
if matches.has(&flags::CLASSIFY) { Classify::AddFileIndicators }
|
||||
else { Classify::JustFilenames }
|
||||
fn deduce(matches: &MatchedFlags) -> Result<Classify, Misfire> {
|
||||
let flagged = matches.has(&flags::CLASSIFY)?;
|
||||
|
||||
Ok(if flagged { Classify::AddFileIndicators }
|
||||
else { Classify::JustFilenames })
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,6 +417,10 @@ mod test {
|
||||
use super::*;
|
||||
use std::ffi::OsString;
|
||||
use options::flags;
|
||||
use options::parser::{Flag, Arg};
|
||||
|
||||
use options::test::parse_for_test;
|
||||
use options::test::Strictnesses::*;
|
||||
|
||||
pub fn os(input: &'static str) -> OsString {
|
||||
let mut os = OsString::new();
|
||||
@ -416,19 +428,74 @@ mod test {
|
||||
os
|
||||
}
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE,
|
||||
&flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED,
|
||||
&flags::COLOR, &flags::COLOUR,
|
||||
&flags::HEADER, &flags::GROUP, &flags::INODE,
|
||||
&flags::LINKS, &flags::BLOCKS, &flags::LONG,
|
||||
&flags::GRID, &flags::ACROSS, &flags::ONE_LINE ];
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $type:ident <- $inputs:expr => $result:expr) => {
|
||||
|
||||
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
|
||||
/// Macro that writes a test.
|
||||
/// If testing both strictnesses, they’ll both be done in the same function.
|
||||
#[test]
|
||||
fn $name() {
|
||||
use options::parser::{Args, Arg};
|
||||
use std::ffi::OsString;
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||
assert_eq!(result, $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES,
|
||||
&flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED ];
|
||||
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => {
|
||||
/// Special macro for testing Err results.
|
||||
/// This is needed because sometimes the Ok type doesn’t implement PartialEq.
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||
assert_eq!(result.unwrap_err(), $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
||||
let results = Args(TEST_ARGS).parse(bits.iter());
|
||||
assert_eq!($type::deduce(&results.unwrap().flags), $result);
|
||||
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => like $pat:pat) => {
|
||||
/// More general macro for testing against a pattern.
|
||||
/// Instead of using PartialEq, this just tests if it matches a pat.
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||
println!("Testing {:?}", result);
|
||||
match result {
|
||||
$pat => assert!(true),
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => err $result:expr) => {
|
||||
/// Like above, but with $vars.
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, $vars)) {
|
||||
assert_eq!(result.unwrap_err(), $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => like $pat:pat) => {
|
||||
/// Like further above, but with $vars.
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, $vars)) {
|
||||
println!("Testing {:?}", result);
|
||||
match result {
|
||||
$pat => assert!(true),
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -437,10 +504,51 @@ mod test {
|
||||
mod size_formats {
|
||||
use super::*;
|
||||
|
||||
test!(empty: SizeFormat <- [] => Ok(SizeFormat::DecimalBytes));
|
||||
test!(binary: SizeFormat <- ["--binary"] => Ok(SizeFormat::BinaryBytes));
|
||||
test!(bytes: SizeFormat <- ["--bytes"] => Ok(SizeFormat::JustBytes));
|
||||
test!(both: SizeFormat <- ["--binary", "--bytes"] => Err(Misfire::Conflict(&flags::BINARY, &flags::BYTES)));
|
||||
// Default behaviour
|
||||
test!(empty: SizeFormat <- []; Both => Ok(SizeFormat::DecimalBytes));
|
||||
|
||||
// Individual flags
|
||||
test!(binary: SizeFormat <- ["--binary"]; Both => Ok(SizeFormat::BinaryBytes));
|
||||
test!(bytes: SizeFormat <- ["--bytes"]; Both => Ok(SizeFormat::JustBytes));
|
||||
|
||||
// Overriding
|
||||
test!(both_1: SizeFormat <- ["--binary", "--binary"]; Last => Ok(SizeFormat::BinaryBytes));
|
||||
test!(both_2: SizeFormat <- ["--bytes", "--binary"]; Last => Ok(SizeFormat::BinaryBytes));
|
||||
test!(both_3: SizeFormat <- ["--binary", "--bytes"]; Last => Ok(SizeFormat::JustBytes));
|
||||
test!(both_4: SizeFormat <- ["--bytes", "--bytes"]; Last => Ok(SizeFormat::JustBytes));
|
||||
|
||||
test!(both_5: SizeFormat <- ["--binary", "--binary"]; Complain => err Misfire::Duplicate(Flag::Long("binary"), Flag::Long("binary")));
|
||||
test!(both_6: SizeFormat <- ["--bytes", "--binary"]; Complain => err Misfire::Duplicate(Flag::Long("bytes"), Flag::Long("binary")));
|
||||
test!(both_7: SizeFormat <- ["--binary", "--bytes"]; Complain => err Misfire::Duplicate(Flag::Long("binary"), Flag::Long("bytes")));
|
||||
test!(both_8: SizeFormat <- ["--bytes", "--bytes"]; Complain => err Misfire::Duplicate(Flag::Long("bytes"), Flag::Long("bytes")));
|
||||
}
|
||||
|
||||
|
||||
mod time_formats {
|
||||
use super::*;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
// These tests use pattern matching because TimeFormat doesn’t
|
||||
// implement PartialEq.
|
||||
|
||||
// Default behaviour
|
||||
test!(empty: TimeFormat <- []; Both => like Ok(TimeFormat::DefaultFormat(_)));
|
||||
|
||||
// Individual settings
|
||||
test!(default: TimeFormat <- ["--time-style=default"]; Both => like Ok(TimeFormat::DefaultFormat(_)));
|
||||
test!(iso: TimeFormat <- ["--time-style", "iso"]; Both => like Ok(TimeFormat::ISOFormat(_)));
|
||||
test!(long_iso: TimeFormat <- ["--time-style=long-iso"]; Both => like Ok(TimeFormat::LongISO));
|
||||
test!(full_iso: TimeFormat <- ["--time-style", "full-iso"]; Both => like Ok(TimeFormat::FullISO));
|
||||
|
||||
// Overriding
|
||||
test!(actually: TimeFormat <- ["--time-style=default", "--time-style", "iso"]; Last => like Ok(TimeFormat::ISOFormat(_)));
|
||||
test!(actual_2: TimeFormat <- ["--time-style=default", "--time-style", "iso"]; Complain => err Misfire::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")));
|
||||
|
||||
test!(nevermind: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"]; Last => like Ok(TimeFormat::FullISO));
|
||||
test!(nevermore: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"]; Complain => err Misfire::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")));
|
||||
|
||||
// Errors
|
||||
test!(daily: TimeFormat <- ["--time-style=24-hour"]; Both => err Misfire::bad_argument(&flags::TIME_STYLE, OsStr::new("24-hour"), TIME_STYLES));
|
||||
}
|
||||
|
||||
|
||||
@ -448,30 +556,113 @@ mod test {
|
||||
use super::*;
|
||||
|
||||
// Default behaviour
|
||||
test!(empty: TimeTypes <- [] => Ok(TimeTypes::default()));
|
||||
test!(modified: TimeTypes <- ["--modified"] => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(m: TimeTypes <- ["-m"] => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(time_mod: TimeTypes <- ["--time=modified"] => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(time_m: TimeTypes <- ["-tmod"] => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(empty: TimeTypes <- []; Both => Ok(TimeTypes::default()));
|
||||
|
||||
test!(acc: TimeTypes <- ["--accessed"] => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(a: TimeTypes <- ["-u"] => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(time_acc: TimeTypes <- ["--time", "accessed"] => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(time_a: TimeTypes <- ["-t", "acc"] => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
// Modified
|
||||
test!(modified: TimeTypes <- ["--modified"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(m: TimeTypes <- ["-m"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(time_mod: TimeTypes <- ["--time=modified"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(time_m: TimeTypes <- ["-tmod"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
|
||||
test!(cr: TimeTypes <- ["--created"] => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
test!(c: TimeTypes <- ["-U"] => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
test!(time_cr: TimeTypes <- ["--time=created"] => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
test!(time_c: TimeTypes <- ["-tcr"] => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
// Accessed
|
||||
test!(acc: TimeTypes <- ["--accessed"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(a: TimeTypes <- ["-u"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(time_acc: TimeTypes <- ["--time", "accessed"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(time_a: TimeTypes <- ["-t", "acc"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
|
||||
// Created
|
||||
test!(cr: TimeTypes <- ["--created"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
test!(c: TimeTypes <- ["-U"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
test!(time_cr: TimeTypes <- ["--time=created"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
test!(time_c: TimeTypes <- ["-tcr"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
|
||||
// Multiples
|
||||
test!(time_uu: TimeTypes <- ["-uU"] => Ok(TimeTypes { accessed: true, modified: false, created: true }));
|
||||
|
||||
// Overriding
|
||||
test!(time_mc: TimeTypes <- ["-tcr", "-tmod"] => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(time_uu: TimeTypes <- ["-uU"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: true }));
|
||||
|
||||
// Errors
|
||||
test!(time_tea: TimeTypes <- ["--time=tea"] => Err(Misfire::bad_argument(&flags::TIME, &os("tea"), super::TIMES)));
|
||||
test!(time_ea: TimeTypes <- ["-tea"] => Err(Misfire::bad_argument(&flags::TIME, &os("ea"), super::TIMES)));
|
||||
test!(time_tea: TimeTypes <- ["--time=tea"]; Both => err Misfire::bad_argument(&flags::TIME, &os("tea"), super::TIMES));
|
||||
test!(time_ea: TimeTypes <- ["-tea"]; Both => err Misfire::bad_argument(&flags::TIME, &os("ea"), super::TIMES));
|
||||
|
||||
// Overriding
|
||||
test!(overridden: TimeTypes <- ["-tcr", "-tmod"]; Last => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(overridden_2: TimeTypes <- ["-tcr", "-tmod"]; Complain => err Misfire::Duplicate(Flag::Short(b't'), Flag::Short(b't')));
|
||||
}
|
||||
|
||||
|
||||
mod colourses {
|
||||
use super::*;
|
||||
|
||||
// Default
|
||||
test!(empty: TerminalColours <- []; Both => Ok(TerminalColours::default()));
|
||||
|
||||
// --colour
|
||||
test!(u_always: TerminalColours <- ["--colour=always"]; Both => Ok(TerminalColours::Always));
|
||||
test!(u_auto: TerminalColours <- ["--colour", "auto"]; Both => Ok(TerminalColours::Automatic));
|
||||
test!(u_never: TerminalColours <- ["--colour=never"]; Both => Ok(TerminalColours::Never));
|
||||
|
||||
// --color
|
||||
test!(no_u_always: TerminalColours <- ["--color", "always"]; Both => Ok(TerminalColours::Always));
|
||||
test!(no_u_auto: TerminalColours <- ["--color=auto"]; Both => Ok(TerminalColours::Automatic));
|
||||
test!(no_u_never: TerminalColours <- ["--color", "never"]; Both => Ok(TerminalColours::Never));
|
||||
|
||||
// Errors
|
||||
test!(no_u_error: TerminalColours <- ["--color=upstream"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("upstream"), super::COLOURS)); // the error is for --color
|
||||
test!(u_error: TerminalColours <- ["--colour=lovers"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("lovers"), super::COLOURS)); // and so is this one!
|
||||
|
||||
// Overriding
|
||||
test!(overridden_1: TerminalColours <- ["--colour=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
|
||||
test!(overridden_2: TerminalColours <- ["--color=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
|
||||
test!(overridden_3: TerminalColours <- ["--colour=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
|
||||
test!(overridden_4: TerminalColours <- ["--color=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
|
||||
|
||||
test!(overridden_5: TerminalColours <- ["--colour=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("colour")));
|
||||
test!(overridden_6: TerminalColours <- ["--color=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("colour")));
|
||||
test!(overridden_7: TerminalColours <- ["--colour=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("color")));
|
||||
test!(overridden_8: TerminalColours <- ["--color=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("color")));
|
||||
}
|
||||
|
||||
|
||||
mod views {
|
||||
use super::*;
|
||||
use output::grid::Options as GridOptions;
|
||||
|
||||
// Default
|
||||
test!(empty: Mode <- [], None; Both => like Ok(Mode::Grid(_)));
|
||||
|
||||
// Grid views
|
||||
test!(original_g: Mode <- ["-G"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, console_width: _ })));
|
||||
test!(grid: Mode <- ["--grid"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, console_width: _ })));
|
||||
test!(across: Mode <- ["--across"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, console_width: _ })));
|
||||
test!(gracross: Mode <- ["-xG"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, console_width: _ })));
|
||||
|
||||
// Lines views
|
||||
test!(lines: Mode <- ["--oneline"], None; Both => like Ok(Mode::Lines));
|
||||
test!(prima: Mode <- ["-1"], None; Both => like Ok(Mode::Lines));
|
||||
|
||||
// Details views
|
||||
test!(long: Mode <- ["--long"], None; Both => like Ok(Mode::Details(_)));
|
||||
test!(ell: Mode <- ["-l"], None; Both => like Ok(Mode::Details(_)));
|
||||
|
||||
// Grid-details views
|
||||
test!(lid: Mode <- ["--long", "--grid"], None; Both => like Ok(Mode::GridDetails(_, _)));
|
||||
test!(leg: Mode <- ["-lG"], None; Both => like Ok(Mode::GridDetails(_, _)));
|
||||
|
||||
|
||||
// Options that do nothing without --long
|
||||
test!(just_header: Mode <- ["--header"], None; Last => like Ok(Mode::Grid(_)));
|
||||
test!(just_group: Mode <- ["--group"], None; Last => like Ok(Mode::Grid(_)));
|
||||
test!(just_inode: Mode <- ["--inode"], None; Last => like Ok(Mode::Grid(_)));
|
||||
test!(just_links: Mode <- ["--links"], None; Last => like Ok(Mode::Grid(_)));
|
||||
test!(just_blocks: Mode <- ["--blocks"], None; Last => like Ok(Mode::Grid(_)));
|
||||
test!(just_binary: Mode <- ["--binary"], None; Last => like Ok(Mode::Grid(_)));
|
||||
test!(just_bytes: Mode <- ["--bytes"], None; Last => like Ok(Mode::Grid(_)));
|
||||
|
||||
test!(just_header_2: Mode <- ["--header"], None; Complain => err Misfire::Useless(&flags::HEADER, false, &flags::LONG));
|
||||
test!(just_group_2: Mode <- ["--group"], None; Complain => err Misfire::Useless(&flags::GROUP, false, &flags::LONG));
|
||||
test!(just_inode_2: Mode <- ["--inode"], None; Complain => err Misfire::Useless(&flags::INODE, false, &flags::LONG));
|
||||
test!(just_links_2: Mode <- ["--links"], None; Complain => err Misfire::Useless(&flags::LINKS, false, &flags::LONG));
|
||||
test!(just_blocks_2: Mode <- ["--blocks"], None; Complain => err Misfire::Useless(&flags::BLOCKS, false, &flags::LONG));
|
||||
test!(just_binary_2: Mode <- ["--binary"], None; Complain => err Misfire::Useless(&flags::BINARY, false, &flags::LONG));
|
||||
test!(just_bytes_2: Mode <- ["--bytes"], None; Complain => err Misfire::Useless(&flags::BYTES, false, &flags::LONG));
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,17 @@ pub struct Options {
|
||||
pub env: Environment,
|
||||
pub size_format: SizeFormat,
|
||||
pub time_format: TimeFormat,
|
||||
pub extra_columns: Columns,
|
||||
}
|
||||
|
||||
/// Extra columns to display in the table.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Columns {
|
||||
|
||||
/// At least one of these timestamps will be shown.
|
||||
pub time_types: TimeTypes,
|
||||
|
||||
// The rest are just on/off
|
||||
pub inode: bool,
|
||||
pub links: bool,
|
||||
pub blocks: bool,
|
||||
@ -39,7 +49,7 @@ impl fmt::Debug for Options {
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
impl Columns {
|
||||
pub fn should_scan_for_git(&self) -> bool {
|
||||
self.git
|
||||
}
|
||||
@ -57,7 +67,7 @@ impl Options {
|
||||
columns.push(Column::HardLinks);
|
||||
}
|
||||
|
||||
columns.push(Column::FileSize(self.size_format));
|
||||
columns.push(Column::FileSize);
|
||||
|
||||
if self.blocks {
|
||||
columns.push(Column::Blocks);
|
||||
@ -98,7 +108,7 @@ impl Options {
|
||||
#[derive(Debug)]
|
||||
pub enum Column {
|
||||
Permissions,
|
||||
FileSize(SizeFormat),
|
||||
FileSize,
|
||||
Timestamp(TimeType),
|
||||
Blocks,
|
||||
User,
|
||||
@ -120,7 +130,7 @@ impl Column {
|
||||
/// Get the alignment this column should use.
|
||||
pub fn alignment(&self) -> Alignment {
|
||||
match *self {
|
||||
Column::FileSize(_)
|
||||
Column::FileSize
|
||||
| Column::HardLinks
|
||||
| Column::Inode
|
||||
| Column::Blocks
|
||||
@ -134,7 +144,7 @@ impl Column {
|
||||
pub fn header(&self) -> &'static str {
|
||||
match *self {
|
||||
Column::Permissions => "Permissions",
|
||||
Column::FileSize(_) => "Size",
|
||||
Column::FileSize => "Size",
|
||||
Column::Timestamp(t) => t.header(),
|
||||
Column::Blocks => "Blocks",
|
||||
Column::User => "User",
|
||||
@ -276,6 +286,7 @@ pub struct Table<'a> {
|
||||
env: &'a Environment,
|
||||
widths: TableWidths,
|
||||
time_format: &'a TimeFormat,
|
||||
size_format: SizeFormat,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -285,9 +296,14 @@ pub struct Row {
|
||||
|
||||
impl<'a, 'f> Table<'a> {
|
||||
pub fn new(options: &'a Options, dir: Option<&'a Dir>, colours: &'a Colours) -> Table<'a> {
|
||||
let colz = options.for_dir(dir);
|
||||
let colz = options.extra_columns.for_dir(dir);
|
||||
let widths = TableWidths::zero(colz.len());
|
||||
Table { columns: colz, colours, env: &options.env, widths, time_format: &options.time_format }
|
||||
Table { colours, widths,
|
||||
columns: colz,
|
||||
env: &options.env,
|
||||
time_format: &options.time_format,
|
||||
size_format: options.size_format,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn widths(&self) -> &TableWidths {
|
||||
@ -327,7 +343,7 @@ impl<'a, 'f> Table<'a> {
|
||||
|
||||
match *column {
|
||||
Column::Permissions => self.permissions_plus(file, xattrs).render(&self.colours),
|
||||
Column::FileSize(fmt) => file.size().render(&self.colours, fmt, &self.env.numeric),
|
||||
Column::FileSize => file.size().render(&self.colours, self.size_format, &self.env.numeric),
|
||||
Column::HardLinks => file.links().render(&self.colours, &self.env.numeric),
|
||||
Column::Inode => file.inode().render(&self.colours),
|
||||
Column::Blocks => file.blocks().render(&self.colours),
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Timestamp formatting.
|
||||
|
||||
use datetime::{LocalDateTime, TimeZone, DatePiece, TimePiece};
|
||||
use datetime::fmt::DateFormat;
|
||||
use locale;
|
||||
@ -6,13 +8,48 @@ use std::cmp;
|
||||
use fs::fields::Time;
|
||||
|
||||
|
||||
/// Every timestamp in exa needs to be rendered by a **time format**.
|
||||
/// Formatting times is tricky, because how a timestamp is rendered can
|
||||
/// depend on one or more of the following:
|
||||
///
|
||||
/// - The user’s locale, for printing the month name as “Feb”, or as “fév”,
|
||||
/// or as “2月”;
|
||||
/// - The current year, because certain formats will be less precise when
|
||||
/// dealing with dates far in the past;
|
||||
/// - The formatting style that the user asked for on the command-line.
|
||||
///
|
||||
/// Because not all formatting styles need the same data, they all have their
|
||||
/// own enum variants. It’s not worth looking the locale up if the formatter
|
||||
/// prints month names as numbers.
|
||||
///
|
||||
/// Currently exa does not support *custom* styles, where the user enters a
|
||||
/// format string in an environment variable or something. Just these four.
|
||||
#[derive(Debug)]
|
||||
pub enum TimeFormat {
|
||||
|
||||
/// The **default format** uses the user’s locale to print month names,
|
||||
/// and specifies the timestamp down to the minute for recent times, and
|
||||
/// day for older times.
|
||||
DefaultFormat(DefaultFormat),
|
||||
|
||||
/// Use the **ISO format**, which specifies the timestamp down to the
|
||||
/// minute for recent times, and day for older times. It uses a number
|
||||
/// for the month so it doesn’t need a locale.
|
||||
ISOFormat(ISOFormat),
|
||||
|
||||
/// Use the **long ISO format**, which specifies the timestamp down to the
|
||||
/// minute using only numbers, without needing the locale or year.
|
||||
LongISO,
|
||||
|
||||
/// Use the **full ISO format**, which specifies the timestamp down to the
|
||||
/// millisecond and includes its offset down to the minute. This too uses
|
||||
/// only numbers so doesn’t require any special consideration.
|
||||
FullISO,
|
||||
}
|
||||
|
||||
// There are two different formatting functions because local and zoned
|
||||
// timestamps are separate types.
|
||||
|
||||
impl TimeFormat {
|
||||
pub fn format_local(&self, time: Time) -> String {
|
||||
match *self {
|
||||
|
1
xtests/error_long
Normal file
1
xtests/error_long
Normal file
@ -0,0 +1 @@
|
||||
Unknown argument --ternary
|
1
xtests/error_overvalued
Normal file
1
xtests/error_overvalued
Normal file
@ -0,0 +1 @@
|
||||
Flag --long cannot take a value
|
1
xtests/error_short
Normal file
1
xtests/error_short
Normal file
@ -0,0 +1 @@
|
||||
Unknown argument -4
|
1
xtests/error_useless
Normal file
1
xtests/error_useless
Normal file
@ -0,0 +1 @@
|
||||
Option --binary (-b) is useless without option --long (-l).
|
1
xtests/error_value
Normal file
1
xtests/error_value
Normal file
@ -0,0 +1 @@
|
||||
Flag --time needs a value
|
39
xtests/files_l_binary
Normal file
39
xtests/files_l_binary
Normal file
@ -0,0 +1,39 @@
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1.0[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 1_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1.0[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 1_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1[0m cassowary [34m 1 Jan 12:34[0m 1_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2.0[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 2_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2.0[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 2_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2[0m cassowary [34m 1 Jan 12:34[0m 2_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3.0[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 3_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3.0[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 3_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3[0m cassowary [34m 1 Jan 12:34[0m 3_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4.0[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 4_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4.0[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 4_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4[0m cassowary [34m 1 Jan 12:34[0m 4_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5.0[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 5_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5.0[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 5_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5[0m cassowary [34m 1 Jan 12:34[0m 5_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6.0[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 6_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6.0[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 6_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6[0m cassowary [34m 1 Jan 12:34[0m 6_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7.0[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 7_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7.0[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 7_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7[0m cassowary [34m 1 Jan 12:34[0m 7_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8.0[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 8_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8.0[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 8_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8[0m cassowary [34m 1 Jan 12:34[0m 8_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9.0[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 9_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9.0[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 9_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9[0m cassowary [34m 1 Jan 12:34[0m 9_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 10_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 10_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m cassowary [34m 1 Jan 12:34[0m 10_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 11_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 11_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m cassowary [34m 1 Jan 12:34[0m 11_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 12_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 12_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m cassowary [34m 1 Jan 12:34[0m 12_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m[32mKi[0m cassowary [34m 1 Jan 12:34[0m 13_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m[32mMi[0m cassowary [34m 1 Jan 12:34[0m 13_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m cassowary [34m 1 Jan 12:34[0m 13_bytes
|
39
xtests/files_l_bytes
Normal file
39
xtests/files_l_bytes
Normal file
@ -0,0 +1,39 @@
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1,024[0m cassowary [34m 1 Jan 12:34[0m 1_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1,048,576[0m cassowary [34m 1 Jan 12:34[0m 1_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1[0m cassowary [34m 1 Jan 12:34[0m 1_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2,048[0m cassowary [34m 1 Jan 12:34[0m 2_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2,097,152[0m cassowary [34m 1 Jan 12:34[0m 2_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2[0m cassowary [34m 1 Jan 12:34[0m 2_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3,072[0m cassowary [34m 1 Jan 12:34[0m 3_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3,145,728[0m cassowary [34m 1 Jan 12:34[0m 3_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3[0m cassowary [34m 1 Jan 12:34[0m 3_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4,096[0m cassowary [34m 1 Jan 12:34[0m 4_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4,194,304[0m cassowary [34m 1 Jan 12:34[0m 4_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4[0m cassowary [34m 1 Jan 12:34[0m 4_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5,120[0m cassowary [34m 1 Jan 12:34[0m 5_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5,242,880[0m cassowary [34m 1 Jan 12:34[0m 5_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5[0m cassowary [34m 1 Jan 12:34[0m 5_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6,144[0m cassowary [34m 1 Jan 12:34[0m 6_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6,291,456[0m cassowary [34m 1 Jan 12:34[0m 6_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6[0m cassowary [34m 1 Jan 12:34[0m 6_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7,168[0m cassowary [34m 1 Jan 12:34[0m 7_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7,340,032[0m cassowary [34m 1 Jan 12:34[0m 7_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7[0m cassowary [34m 1 Jan 12:34[0m 7_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8,192[0m cassowary [34m 1 Jan 12:34[0m 8_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8,388,608[0m cassowary [34m 1 Jan 12:34[0m 8_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8[0m cassowary [34m 1 Jan 12:34[0m 8_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9,216[0m cassowary [34m 1 Jan 12:34[0m 9_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9,437,184[0m cassowary [34m 1 Jan 12:34[0m 9_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9[0m cassowary [34m 1 Jan 12:34[0m 9_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10,240[0m cassowary [34m 1 Jan 12:34[0m 10_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10,485,760[0m cassowary [34m 1 Jan 12:34[0m 10_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m cassowary [34m 1 Jan 12:34[0m 10_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11,264[0m cassowary [34m 1 Jan 12:34[0m 11_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11,534,336[0m cassowary [34m 1 Jan 12:34[0m 11_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m cassowary [34m 1 Jan 12:34[0m 11_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12,288[0m cassowary [34m 1 Jan 12:34[0m 12_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12,582,912[0m cassowary [34m 1 Jan 12:34[0m 12_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m cassowary [34m 1 Jan 12:34[0m 12_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13,312[0m cassowary [34m 1 Jan 12:34[0m 13_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13,631,488[0m cassowary [34m 1 Jan 12:34[0m 13_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m cassowary [34m 1 Jan 12:34[0m 13_bytes
|
@ -15,6 +15,11 @@ testcases="/testcases"
|
||||
results="/vagrant/xtests"
|
||||
|
||||
|
||||
# We want to use strict mode here. It’s important that no combination of
|
||||
# testing flags happens to work by accident!
|
||||
export EXA_STRICT=1
|
||||
|
||||
|
||||
# Check that no files were created more than a year ago.
|
||||
# Files not from the current year use a different date format, meaning
|
||||
# that tests will fail until the VM gets re-provisioned.
|
||||
@ -56,6 +61,14 @@ COLUMNS=150 $exa $testcases/files/* -lG | diff -q - $results/files_star_lG_150
|
||||
COLUMNS=200 $exa $testcases/files/* -lG | diff -q - $results/files_star_lG_200 || exit 1
|
||||
|
||||
|
||||
# File size tests
|
||||
$exa $testcases/files -l --binary | diff -q - $results/files_l_binary || exit 1
|
||||
$exa $testcases/files -l --bytes | diff -q - $results/files_l_bytes || exit 1
|
||||
|
||||
EXA_STRICT= $exa $testcases/files -l --bytes --binary | diff -q - $results/files_l_binary || exit 1
|
||||
EXA_STRICT= $exa $testcases/files -l --binary --bytes | diff -q - $results/files_l_bytes || exit 1
|
||||
|
||||
|
||||
# Attributes
|
||||
# (there are many tests, but they’re all done in one go)
|
||||
$exa $testcases/attributes -l@T | diff -q - $results/attributes || exit 1
|
||||
@ -164,6 +177,14 @@ $exa $testcases/hiddens -l -a 2>&1 | diff -q - $results/hiddens_la || exit 1
|
||||
$exa $testcases/hiddens -l -aa 2>&1 | diff -q - $results/hiddens_laa || exit 1
|
||||
|
||||
|
||||
# Errors
|
||||
$exa --binary 2>&1 | diff -q - $results/error_useless || exit 1
|
||||
$exa --ternary 2>&1 | diff -q - $results/error_long || exit 1
|
||||
$exa -4 2>&1 | diff -q - $results/error_short || exit 1
|
||||
$exa --time 2>&1 | diff -q - $results/error_value || exit 1
|
||||
$exa --long=time 2>&1 | diff -q - $results/error_overvalued || exit 1
|
||||
|
||||
|
||||
# And finally...
|
||||
$exa --help | diff -q - $results/help || exit 1
|
||||
$exa --help --long | diff -q - $results/help_long || exit 1
|
||||
|
Loading…
Reference in New Issue
Block a user