mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-23 04:22:06 +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;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
|
||||||
|
use std::env::var_os;
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::io::{stderr, Write, Result as IOResult};
|
use std::io::{stderr, Write, Result as IOResult};
|
||||||
use std::path::{Component, PathBuf};
|
use std::path::{Component, PathBuf};
|
||||||
@ -29,7 +30,7 @@ use std::path::{Component, PathBuf};
|
|||||||
use ansi_term::{ANSIStrings, Style};
|
use ansi_term::{ANSIStrings, Style};
|
||||||
|
|
||||||
use fs::{Dir, File};
|
use fs::{Dir, File};
|
||||||
use options::Options;
|
use options::{Options, Vars};
|
||||||
pub use options::Misfire;
|
pub use options::Misfire;
|
||||||
use output::{escape, lines, grid, grid_details, details, View, Mode};
|
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>,
|
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> {
|
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>
|
pub fn new<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
|
||||||
where I: Iterator<Item=&'args OsString> {
|
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 }
|
Exa { options, writer, args }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,12 @@ impl DirAction {
|
|||||||
|
|
||||||
/// Determine which action to perform when trying to list a directory.
|
/// Determine which action to perform when trying to list a directory.
|
||||||
pub fn deduce(matches: &MatchedFlags) -> Result<DirAction, Misfire> {
|
pub fn deduce(matches: &MatchedFlags) -> Result<DirAction, Misfire> {
|
||||||
let recurse = matches.has(&flags::RECURSE);
|
let recurse = matches.has(&flags::RECURSE)?;
|
||||||
let list = matches.has(&flags::LIST_DIRS);
|
let list = matches.has(&flags::LIST_DIRS)?;
|
||||||
let tree = matches.has(&flags::TREE);
|
let tree = matches.has(&flags::TREE)?;
|
||||||
|
|
||||||
// Early check for --level when it wouldn’t do anything
|
// 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));
|
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ impl RecurseOptions {
|
|||||||
|
|
||||||
/// Determine which files should be recursed into.
|
/// Determine which files should be recursed into.
|
||||||
pub fn deduce(matches: &MatchedFlags, tree: bool) -> Result<RecurseOptions, Misfire> {
|
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() {
|
match level.to_string_lossy().parse() {
|
||||||
Ok(l) => Some(l),
|
Ok(l) => Some(l),
|
||||||
Err(e) => return Err(Misfire::FailedParse(e)),
|
Err(e) => return Err(Misfire::FailedParse(e)),
|
||||||
@ -55,51 +55,50 @@ impl RecurseOptions {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::ffi::OsString;
|
|
||||||
use options::flags;
|
use options::flags;
|
||||||
|
use options::parser::Flag;
|
||||||
pub fn os(input: &'static str) -> OsString {
|
|
||||||
let mut os = OsString::new();
|
|
||||||
os.push(input);
|
|
||||||
os
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! test {
|
macro_rules! test {
|
||||||
($name:ident: $type:ident <- $inputs:expr => $result:expr) => {
|
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
use options::parser::{Args, Arg};
|
use options::parser::Arg;
|
||||||
use std::ffi::OsString;
|
use options::test::parse_for_test;
|
||||||
|
use options::test::Strictnesses::*;
|
||||||
|
|
||||||
static TEST_ARGS: &[&Arg] = &[ &flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL ];
|
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)) {
|
||||||
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
assert_eq!(result, $result);
|
||||||
let results = Args(TEST_ARGS).parse(bits.iter());
|
}
|
||||||
assert_eq!($type::deduce(&results.unwrap().flags), $result);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Default behaviour
|
// Default behaviour
|
||||||
test!(empty: DirAction <- [] => Ok(DirAction::List));
|
test!(empty: DirAction <- []; Both => Ok(DirAction::List));
|
||||||
|
|
||||||
// Listing files as directories
|
// Listing files as directories
|
||||||
test!(dirs_short: DirAction <- ["-d"] => Ok(DirAction::AsFile));
|
test!(dirs_short: DirAction <- ["-d"]; Both => Ok(DirAction::AsFile));
|
||||||
test!(dirs_long: DirAction <- ["--list-dirs"] => Ok(DirAction::AsFile));
|
test!(dirs_long: DirAction <- ["--list-dirs"]; Both => Ok(DirAction::AsFile));
|
||||||
|
|
||||||
// Recursing
|
// Recursing
|
||||||
test!(rec_short: DirAction <- ["-R"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: None })));
|
use self::DirAction::Recurse;
|
||||||
test!(rec_long: DirAction <- ["--recurse"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: None })));
|
test!(rec_short: DirAction <- ["-R"]; Both => Ok(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_long: DirAction <- ["--recurse"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
|
||||||
test!(rec_lim_short_2: DirAction <- ["-RL=5"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(5) })));
|
test!(rec_lim_short: DirAction <- ["-RL4"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(4) })));
|
||||||
test!(rec_lim_long: DirAction <- ["--recurse", "--level", "666"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(666) })));
|
test!(rec_lim_short_2: DirAction <- ["-RL=5"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(5) })));
|
||||||
test!(rec_lim_long_2: DirAction <- ["--recurse", "--level=0118"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(118) })));
|
test!(rec_lim_long: DirAction <- ["--recurse", "--level", "666"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(666) })));
|
||||||
test!(rec_tree: DirAction <- ["--recurse", "--tree"] => Ok(DirAction::Recurse(RecurseOptions { tree: true, max_depth: None })));
|
test!(rec_lim_long_2: DirAction <- ["--recurse", "--level=0118"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(118) })));
|
||||||
test!(rec_short_tree: DirAction <- ["--tree", "--recurse"] => Ok(DirAction::Recurse(RecurseOptions { tree: true, max_depth: None })));
|
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
|
// Errors
|
||||||
test!(error: DirAction <- ["--list-dirs", "--recurse"] => Err(Misfire::Conflict(&flags::RECURSE, &flags::LIST_DIRS)));
|
test!(error: DirAction <- ["--list-dirs", "--recurse"]; Both => Err(Misfire::Conflict(&flags::RECURSE, &flags::LIST_DIRS)));
|
||||||
test!(error_2: DirAction <- ["--list-dirs", "--tree"] => Err(Misfire::Conflict(&flags::TREE, &flags::LIST_DIRS)));
|
test!(error_2: DirAction <- ["--list-dirs", "--tree"]; Both => Err(Misfire::Conflict(&flags::TREE, &flags::LIST_DIRS)));
|
||||||
test!(underwaterlevel: DirAction <- ["--level=4"] => Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)));
|
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.
|
/// command-line arguments.
|
||||||
pub fn deduce(matches: &MatchedFlags) -> Result<FileFilter, Misfire> {
|
pub fn deduce(matches: &MatchedFlags) -> Result<FileFilter, Misfire> {
|
||||||
Ok(FileFilter {
|
Ok(FileFilter {
|
||||||
list_dirs_first: matches.has(&flags::DIRS_FIRST),
|
list_dirs_first: matches.has(&flags::DIRS_FIRST)?,
|
||||||
reverse: matches.has(&flags::REVERSE),
|
reverse: matches.has(&flags::REVERSE)?,
|
||||||
sort_field: SortField::deduce(matches)?,
|
sort_field: SortField::deduce(matches)?,
|
||||||
dot_filter: DotFilter::deduce(matches)?,
|
dot_filter: DotFilter::deduce(matches)?,
|
||||||
ignore_patterns: IgnorePatterns::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
|
/// argument. This will return `Err` if the option is there, but does not
|
||||||
/// correspond to a valid field.
|
/// correspond to a valid field.
|
||||||
fn deduce(matches: &MatchedFlags) -> Result<SortField, Misfire> {
|
fn deduce(matches: &MatchedFlags) -> Result<SortField, Misfire> {
|
||||||
let word = match matches.get(&flags::SORT) {
|
let word = match matches.get(&flags::SORT)? {
|
||||||
Some(w) => w,
|
Some(w) => w,
|
||||||
None => return Ok(SortField::default()),
|
None => return Ok(SortField::default()),
|
||||||
};
|
};
|
||||||
@ -85,11 +85,22 @@ impl SortField {
|
|||||||
|
|
||||||
impl DotFilter {
|
impl DotFilter {
|
||||||
pub fn deduce(matches: &MatchedFlags) -> Result<DotFilter, Misfire> {
|
pub fn deduce(matches: &MatchedFlags) -> Result<DotFilter, Misfire> {
|
||||||
match matches.count(&flags::ALL) {
|
let count = matches.count(&flags::ALL);
|
||||||
0 => Ok(DotFilter::JustFiles),
|
|
||||||
1 => Ok(DotFilter::Dotfiles),
|
if count == 0 {
|
||||||
_ => if matches.has(&flags::TREE) { Err(Misfire::TreeAllAll) }
|
Ok(DotFilter::JustFiles)
|
||||||
else { Ok(DotFilter::DotfilesAndDots) }
|
}
|
||||||
|
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.
|
/// command-line arguments.
|
||||||
pub fn deduce(matches: &MatchedFlags) -> Result<IgnorePatterns, Misfire> {
|
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()),
|
None => return Ok(IgnorePatterns::empty()),
|
||||||
Some(is) => is,
|
Some(is) => is,
|
||||||
};
|
};
|
||||||
@ -126,6 +137,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use options::flags;
|
use options::flags;
|
||||||
|
use options::parser::Flag;
|
||||||
|
|
||||||
pub fn os(input: &'static str) -> OsString {
|
pub fn os(input: &'static str) -> OsString {
|
||||||
let mut os = OsString::new();
|
let mut os = OsString::new();
|
||||||
@ -134,17 +146,17 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! test {
|
macro_rules! test {
|
||||||
($name:ident: $type:ident <- $inputs:expr => $result:expr) => {
|
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
use options::parser::{Args, Arg};
|
use options::parser::Arg;
|
||||||
use std::ffi::OsString;
|
use options::test::parse_for_test;
|
||||||
|
use options::test::Strictnesses::*;
|
||||||
|
|
||||||
static TEST_ARGS: &[&Arg] = &[ &flags::SORT, &flags::ALL, &flags::TREE, &flags::IGNORE_GLOB ];
|
static TEST_ARGS: &[&Arg] = &[ &flags::SORT, &flags::ALL, &flags::TREE, &flags::IGNORE_GLOB ];
|
||||||
|
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||||
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
|
assert_eq!(result, $result);
|
||||||
let results = Args(TEST_ARGS).parse(bits.iter());
|
}
|
||||||
assert_eq!($type::deduce(&results.unwrap().flags), $result);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -153,21 +165,23 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// Default behaviour
|
// Default behaviour
|
||||||
test!(empty: SortField <- [] => Ok(SortField::default()));
|
test!(empty: SortField <- []; Both => Ok(SortField::default()));
|
||||||
|
|
||||||
// Sort field arguments
|
// Sort field arguments
|
||||||
test!(one_arg: SortField <- ["--sort=cr"] => Ok(SortField::CreatedDate));
|
test!(one_arg: SortField <- ["--sort=cr"]; Both => Ok(SortField::CreatedDate));
|
||||||
test!(one_long: SortField <- ["--sort=size"] => Ok(SortField::Size));
|
test!(one_long: SortField <- ["--sort=size"]; Both => Ok(SortField::Size));
|
||||||
test!(one_short: SortField <- ["-saccessed"] => Ok(SortField::AccessedDate));
|
test!(one_short: SortField <- ["-saccessed"]; Both => Ok(SortField::AccessedDate));
|
||||||
test!(lowercase: SortField <- ["--sort", "name"] => Ok(SortField::Name(SortCase::Sensitive)));
|
test!(lowercase: SortField <- ["--sort", "name"]; Both => Ok(SortField::Name(SortCase::Sensitive)));
|
||||||
test!(uppercase: SortField <- ["--sort", "Name"] => Ok(SortField::Name(SortCase::Insensitive)));
|
test!(uppercase: SortField <- ["--sort", "Name"]; Both => Ok(SortField::Name(SortCase::Insensitive)));
|
||||||
|
|
||||||
// Errors
|
// 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
|
// Overriding
|
||||||
test!(overridden: SortField <- ["--sort=cr", "--sort", "mod"] => Ok(SortField::ModifiedDate));
|
test!(overridden: SortField <- ["--sort=cr", "--sort", "mod"]; Last => Ok(SortField::ModifiedDate));
|
||||||
test!(overridden_2: SortField <- ["--sort", "none", "--sort=Extension"] => Ok(SortField::Extension(SortCase::Insensitive)));
|
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::*;
|
use super::*;
|
||||||
|
|
||||||
// Default behaviour
|
// Default behaviour
|
||||||
test!(empty: DotFilter <- [] => Ok(DotFilter::JustFiles));
|
test!(empty: DotFilter <- []; Both => Ok(DotFilter::JustFiles));
|
||||||
|
|
||||||
// --all
|
// --all
|
||||||
test!(all: DotFilter <- ["--all"] => Ok(DotFilter::Dotfiles));
|
test!(all: DotFilter <- ["--all"]; Both => Ok(DotFilter::Dotfiles));
|
||||||
test!(all_all: DotFilter <- ["--all", "-a"] => Ok(DotFilter::DotfilesAndDots));
|
test!(all_all: DotFilter <- ["--all", "-a"]; Both => Ok(DotFilter::DotfilesAndDots));
|
||||||
test!(all_all_2: DotFilter <- ["-aa"] => 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
|
// --all and --tree
|
||||||
test!(tree_a: DotFilter <- ["-Ta"] => Ok(DotFilter::Dotfiles));
|
test!(tree_a: DotFilter <- ["-Ta"]; Both => Ok(DotFilter::Dotfiles));
|
||||||
test!(tree_aa: DotFilter <- ["-Taa"] => Err(Misfire::TreeAllAll));
|
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
|
// Various numbers of globs
|
||||||
test!(none: IgnorePatterns <- [] => Ok(IgnorePatterns::empty()));
|
test!(none: IgnorePatterns <- []; Both => Ok(IgnorePatterns::empty()));
|
||||||
test!(one: IgnorePatterns <- ["--ignore-glob", "*.ogg"] => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg") ])));
|
test!(one: IgnorePatterns <- ["--ignore-glob", "*.ogg"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg") ])));
|
||||||
test!(two: IgnorePatterns <- ["--ignore-glob=*.ogg|*.MP3"] => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg"), pat("*.MP3") ])));
|
test!(two: IgnorePatterns <- ["--ignore-glob=*.ogg|*.MP3"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg"), pat("*.MP3") ])));
|
||||||
test!(loads: IgnorePatterns <- ["-I*|?|.|*"] => Ok(IgnorePatterns::from_iter(vec![ pat("*"), pat("?"), pat("."), pat("*") ])));
|
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
|
/// Determines how to show help, if at all, based on the user’s
|
||||||
/// command-line arguments. This one works backwards from the other
|
/// command-line arguments. This one works backwards from the other
|
||||||
/// ‘deduce’ functions, returning Err if help needs to be shown.
|
/// ‘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> {
|
pub fn deduce(matches: &MatchedFlags) -> Result<(), HelpString> {
|
||||||
if matches.has(&flags::HELP) {
|
if matches.count(&flags::HELP) > 0 {
|
||||||
let only_long = matches.has(&flags::LONG);
|
let only_long = matches.count(&flags::LONG) > 0;
|
||||||
let git = cfg!(feature="git");
|
let git = cfg!(feature="git");
|
||||||
let xattrs = xattr::ENABLED;
|
let xattrs = xattr::ENABLED;
|
||||||
Err(HelpString { only_long, git, xattrs })
|
Err(HelpString { only_long, git, xattrs })
|
||||||
@ -126,21 +130,21 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn help() {
|
fn help() {
|
||||||
let args = [ os("--help") ];
|
let args = [ os("--help") ];
|
||||||
let opts = Options::getopts(&args);
|
let opts = Options::parse(&args, None);
|
||||||
assert!(opts.is_err())
|
assert!(opts.is_err())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn help_with_file() {
|
fn help_with_file() {
|
||||||
let args = [ os("--help"), os("me") ];
|
let args = [ os("--help"), os("me") ];
|
||||||
let opts = Options::getopts(&args);
|
let opts = Options::parse(&args, None);
|
||||||
assert!(opts.is_err())
|
assert!(opts.is_err())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unhelpful() {
|
fn unhelpful() {
|
||||||
let args = [];
|
let args = [];
|
||||||
let opts = Options::getopts(&args);
|
let opts = Options::parse(&args, None);
|
||||||
assert!(opts.is_ok()) // no help when --help isn’t passed
|
assert!(opts.is_ok()) // no help when --help isn’t passed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use std::num::ParseIntError;
|
|||||||
use glob;
|
use glob;
|
||||||
|
|
||||||
use options::{HelpString, VersionString};
|
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
|
/// A list of legal choices for an argument-taking option
|
||||||
@ -36,6 +36,9 @@ pub enum Misfire {
|
|||||||
/// The user wanted the version number.
|
/// The user wanted the version number.
|
||||||
Version(VersionString),
|
Version(VersionString),
|
||||||
|
|
||||||
|
/// An option was given twice or more in strict mode.
|
||||||
|
Duplicate(Flag, Flag),
|
||||||
|
|
||||||
/// Two options were given that conflict with one another.
|
/// Two options were given that conflict with one another.
|
||||||
Conflict(&'static Arg, &'static Arg),
|
Conflict(&'static Arg, &'static Arg),
|
||||||
|
|
||||||
@ -89,10 +92,11 @@ impl fmt::Display for Misfire {
|
|||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
BadArgument(ref a, ref b, ref c) => write!(f, "Option {} has no value {:?} (Choices: {})", a, b, c),
|
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),
|
Help(ref text) => write!(f, "{}", text),
|
||||||
Version(ref version) => write!(f, "{}", version),
|
Version(ref version) => write!(f, "{}", version),
|
||||||
Conflict(ref a, ref b) => write!(f, "Option {} conflicts with option {}.", a, b),
|
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, 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),
|
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),
|
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 {
|
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)]
|
#[allow(unused_results)]
|
||||||
pub fn getopts<'args, I>(args: I) -> Result<(Options, Vec<&'args OsStr>), Misfire>
|
pub fn parse<'args, I, V>(args: I, vars: V) -> Result<(Options, Vec<&'args OsStr>), Misfire>
|
||||||
where I: IntoIterator<Item=&'args OsString> {
|
where I: IntoIterator<Item=&'args OsString>,
|
||||||
use options::parser::Matches;
|
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,
|
Ok(m) => m,
|
||||||
Err(e) => return Err(Misfire::InvalidOptions(e)),
|
Err(e) => return Err(Misfire::InvalidOptions(e)),
|
||||||
};
|
};
|
||||||
@ -126,7 +135,7 @@ impl Options {
|
|||||||
HelpString::deduce(&flags).map_err(Misfire::Help)?;
|
HelpString::deduce(&flags).map_err(Misfire::Help)?;
|
||||||
VersionString::deduce(&flags).map_err(Misfire::Version)?;
|
VersionString::deduce(&flags).map_err(Misfire::Version)?;
|
||||||
|
|
||||||
let options = Options::deduce(&flags)?;
|
let options = Options::deduce(&flags, vars)?;
|
||||||
Ok((options, frees))
|
Ok((options, frees))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,33 +145,84 @@ impl Options {
|
|||||||
pub fn should_scan_for_git(&self) -> bool {
|
pub fn should_scan_for_git(&self) -> bool {
|
||||||
match self.view.mode {
|
match self.view.mode {
|
||||||
Mode::Details(details::Options { table: Some(ref table), .. }) |
|
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,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the complete set of options based on the given command-line
|
/// Determines the complete set of options based on the given command-line
|
||||||
/// arguments, after they’ve been parsed.
|
/// 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 dir_action = DirAction::deduce(matches)?;
|
||||||
let filter = FileFilter::deduce(matches)?;
|
let filter = FileFilter::deduce(matches)?;
|
||||||
let view = View::deduce(matches)?;
|
let view = View::deduce(matches, vars)?;
|
||||||
|
|
||||||
Ok(Options { dir_action, view, filter })
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
pub mod test {
|
||||||
use super::{Options, Misfire, flags};
|
use super::{Options, Misfire, Vars, flags};
|
||||||
|
use options::parser::{Arg, MatchedFlags};
|
||||||
use std::ffi::OsString;
|
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)
|
/// Creates an `OSStr` (used in tests)
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn os(input: &'static str) -> OsString {
|
fn os(input: &str) -> OsString {
|
||||||
let mut os = OsString::new();
|
let mut os = OsString::new();
|
||||||
os.push(input);
|
os.push(input);
|
||||||
os
|
os
|
||||||
@ -171,106 +231,36 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn files() {
|
fn files() {
|
||||||
let args = [ os("this file"), os("that file") ];
|
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") ])
|
assert_eq!(outs, vec![ &os("this file"), &os("that file") ])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_args() {
|
fn no_args() {
|
||||||
let nothing: Vec<OsString> = Vec::new();
|
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
|
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]
|
#[test]
|
||||||
fn long_across() {
|
fn long_across() {
|
||||||
let args = [ os("--long"), os("--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))
|
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::ACROSS, true, &flags::LONG))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn oneline_across() {
|
fn oneline_across() {
|
||||||
let args = [ os("--oneline"), os("--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))
|
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]
|
#[test]
|
||||||
#[cfg(feature="git")]
|
#[cfg(feature="git")]
|
||||||
fn just_git() {
|
fn just_git() {
|
||||||
let args = [ os("--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))
|
assert_eq!(opts.unwrap_err(), Misfire::Useless(&flags::GIT, false, &flags::LONG))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,8 @@
|
|||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use options::Misfire;
|
||||||
|
|
||||||
|
|
||||||
/// A **short argument** is a single ASCII character.
|
/// A **short argument** is a single ASCII character.
|
||||||
pub type ShortArg = u8;
|
pub type ShortArg = u8;
|
||||||
@ -50,7 +52,7 @@ pub enum Flag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Flag {
|
impl Flag {
|
||||||
fn matches(&self, arg: &Arg) -> bool {
|
pub fn matches(&self, arg: &Arg) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Flag::Short(short) => arg.short == Some(short),
|
Flag::Short(short) => arg.short == Some(short),
|
||||||
Flag::Long(long) => arg.long == long,
|
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.
|
/// Whether redundant arguments should be considered a problem.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
#[allow(dead_code)] // until strict mode is actually implemented
|
|
||||||
pub enum Strictness {
|
pub enum Strictness {
|
||||||
|
|
||||||
/// Throw an error when an argument doesn’t do anything, either because
|
/// 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
|
/// Iterates over the given list of command-line arguments and parses
|
||||||
/// them into a list of matched flags and free strings.
|
/// 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> {
|
where I: IntoIterator<Item=&'args OsString> {
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use self::TakesValue::*;
|
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)) {
|
match self.0.into_iter().find(|arg| arg.short == Some(short)) {
|
||||||
Some(arg) => Ok(arg),
|
Some(arg) => Ok(arg),
|
||||||
None => Err(ParseError::UnknownShortArgument { attempt: short })
|
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) {
|
match self.0.into_iter().find(|arg| arg.long == long) {
|
||||||
Some(arg) => Ok(arg),
|
Some(arg) => Ok(arg),
|
||||||
None => Err(ParseError::UnknownArgument { attempt: long.to_os_string() })
|
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 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.
|
/// we need to know where they are in relation to one another.
|
||||||
flags: Vec<(Flag, Option<&'args OsStr>)>,
|
flags: Vec<(Flag, Option<&'args OsStr>)>,
|
||||||
|
|
||||||
|
/// Whether to check for duplicate or redundant arguments.
|
||||||
|
strictness: Strictness,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MatchedFlags<'a> {
|
impl<'a> MatchedFlags<'a> {
|
||||||
|
|
||||||
/// Whether the given argument was specified.
|
/// Whether the given argument was specified.
|
||||||
pub fn has(&self, arg: &Arg) -> bool {
|
/// Returns `true` if it was, `false` if it wasn’t, and an error in
|
||||||
self.flags.iter().rev()
|
/// strict mode if it was specified more than once.
|
||||||
.find(|tuple| tuple.1.is_none() && tuple.0.matches(arg))
|
pub fn has(&self, arg: &'static Arg) -> Result<bool, Misfire> {
|
||||||
.is_some()
|
self.has_where(|flag| flag.matches(arg)).map(|flag| flag.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the given argument was specified, return its value.
|
/// Returns the first found argument that satisfies the predicate, or
|
||||||
/// The value is not guaranteed to be valid UTF-8.
|
/// nothing if none is found, or an error in strict mode if multiple
|
||||||
pub fn get(&self, arg: &Arg) -> Option<&OsStr> {
|
/// argument satisfy the predicate.
|
||||||
self.flags.iter().rev()
|
///
|
||||||
.find(|tuple| tuple.1.is_some() && tuple.0.matches(arg))
|
/// You’ll have to test the resulting flag to see which argument it was.
|
||||||
.map(|tuple| tuple.1.unwrap())
|
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
|
// 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.
|
// 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 {
|
pub fn count(&self, arg: &Arg) -> usize {
|
||||||
self.flags.iter()
|
self.flags.iter()
|
||||||
.filter(|tuple| tuple.0.matches(arg))
|
.filter(|tuple| tuple.0.matches(arg))
|
||||||
.count()
|
.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 {
|
mod parse_test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
pub fn os(input: &'static str) -> OsString {
|
||||||
|
let mut os = OsString::new();
|
||||||
|
os.push(input);
|
||||||
|
os
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
macro_rules! test {
|
macro_rules! test {
|
||||||
($name:ident: $inputs:expr => frees: $frees:expr, flags: $flags:expr) => {
|
($name:ident: $inputs:expr => frees: $frees:expr, flags: $flags:expr) => {
|
||||||
#[test]
|
#[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<OsString> = $frees.as_ref().into_iter().map(|&o| os(o)).collect();
|
||||||
let frees: Vec<&OsStr> = frees.iter().map(|os| os.as_os_str()).collect();
|
let frees: Vec<&OsStr> = frees.iter().map(|os| os.as_os_str()).collect();
|
||||||
|
|
||||||
// And again for the flags
|
let flags = <[_]>::into_vec(Box::new($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 got = Args(TEST_ARGS).parse(inputs.iter());
|
let strictness = Strictness::UseLastArguments; // this isn’t even used
|
||||||
let expected = Ok(Matches { frees, flags: MatchedFlags { flags } });
|
let got = Args(TEST_ARGS).parse(inputs.iter(), strictness);
|
||||||
|
let expected = Ok(Matches { frees, flags: MatchedFlags { flags, strictness } });
|
||||||
assert_eq!(got, expected);
|
assert_eq!(got, expected);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -463,8 +534,9 @@ mod parse_test {
|
|||||||
fn $name() {
|
fn $name() {
|
||||||
use self::ParseError::*;
|
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 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));
|
assert_eq!(got, Err($error));
|
||||||
}
|
}
|
||||||
@ -498,8 +570,8 @@ mod parse_test {
|
|||||||
// Long args with values
|
// Long args with values
|
||||||
test!(bad_equals: ["--long=equals"] => error ForbiddenValue { flag: Flag::Long("long") });
|
test!(bad_equals: ["--long=equals"] => error ForbiddenValue { flag: Flag::Long("long") });
|
||||||
test!(no_arg: ["--count"] => error NeedsValue { flag: Flag::Long("count") });
|
test!(no_arg: ["--count"] => error NeedsValue { flag: Flag::Long("count") });
|
||||||
test!(arg_equals: ["--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("4")) ]);
|
test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
|
||||||
|
|
||||||
|
|
||||||
// Short args
|
// Short args
|
||||||
@ -511,11 +583,11 @@ mod parse_test {
|
|||||||
// Short args with values
|
// Short args with values
|
||||||
test!(bad_short: ["-l=equals"] => error ForbiddenValue { flag: Flag::Short(b'l') });
|
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_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_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("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("two")) ]);
|
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("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("two")) ]);
|
test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
|
||||||
|
|
||||||
|
|
||||||
// Unknown args
|
// Unknown args
|
||||||
@ -536,8 +608,12 @@ mod matches_test {
|
|||||||
($name:ident: $input:expr, has $param:expr => $result:expr) => {
|
($name:ident: $input:expr, has $param:expr => $result:expr) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let flags = MatchedFlags { flags: $input.to_vec() };
|
let flags = MatchedFlags {
|
||||||
assert_eq!(flags.has(&$param), $result);
|
flags: $input.to_vec(),
|
||||||
|
strictness: Strictness::UseLastArguments,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(flags.has(&$param), Ok($result));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -557,8 +633,13 @@ mod matches_test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn only_count() {
|
fn only_count() {
|
||||||
let everything = os("everything");
|
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]
|
#[test]
|
||||||
@ -568,16 +649,17 @@ mod matches_test {
|
|||||||
|
|
||||||
let flags = MatchedFlags {
|
let flags = MatchedFlags {
|
||||||
flags: vec![ (Flag::Short(b'c'), Some(&*everything)),
|
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]
|
#[test]
|
||||||
fn no_count() {
|
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
|
/// Determines how to show the version, if at all, based on the user’s
|
||||||
/// command-line arguments. This one works backwards from the other
|
/// command-line arguments. This one works backwards from the other
|
||||||
/// ‘deduce’ functions, returning Err if help needs to be shown.
|
/// ‘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> {
|
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") })
|
Err(VersionString { cargo: env!("CARGO_PKG_VERSION") })
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -52,7 +54,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn help() {
|
fn help() {
|
||||||
let args = [ os("--version") ];
|
let args = [ os("--version") ];
|
||||||
let opts = Options::getopts(&args);
|
let opts = Options::parse(&args, None);
|
||||||
assert!(opts.is_err())
|
assert!(opts.is_err())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,22 @@
|
|||||||
use std::env::var_os;
|
|
||||||
|
|
||||||
use output::Colours;
|
use output::Colours;
|
||||||
use output::{View, Mode, grid, details};
|
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::file_name::{Classify, FileStyle};
|
||||||
use output::time::TimeFormat;
|
use output::time::TimeFormat;
|
||||||
|
|
||||||
use options::{flags, Misfire};
|
use options::{flags, Misfire, Vars};
|
||||||
use options::parser::MatchedFlags;
|
use options::parser::MatchedFlags;
|
||||||
|
|
||||||
use fs::feature::xattr;
|
use fs::feature::xattr;
|
||||||
use info::filetype::FileExtensions;
|
use info::filetype::FileExtensions;
|
||||||
|
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
|
|
||||||
/// Determine which view to use and all of that view’s arguments.
|
/// Determine which view to use and all of that view’s arguments.
|
||||||
pub fn deduce(matches: &MatchedFlags) -> Result<View, Misfire> {
|
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: V) -> Result<View, Misfire> {
|
||||||
let mode = Mode::deduce(matches)?;
|
let mode = Mode::deduce(matches, vars)?;
|
||||||
let colours = Colours::deduce(matches)?;
|
let colours = Colours::deduce(matches)?;
|
||||||
let style = FileStyle::deduce(matches);
|
let style = FileStyle::deduce(matches)?;
|
||||||
Ok(View { mode, colours, style })
|
Ok(View { mode, colours, style })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,21 +25,21 @@ impl View {
|
|||||||
impl Mode {
|
impl Mode {
|
||||||
|
|
||||||
/// Determine the mode from the command-line arguments.
|
/// 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::*;
|
use options::misfire::Misfire::*;
|
||||||
|
|
||||||
let long = || {
|
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))
|
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))
|
Err(Useless(&flags::ONE_LINE, true, &flags::LONG))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Ok(details::Options {
|
Ok(details::Options {
|
||||||
table: Some(TableOptions::deduce(matches)?),
|
table: Some(TableOptions::deduce(matches)?),
|
||||||
header: matches.has(&flags::HEADER),
|
header: matches.has(&flags::HEADER)?,
|
||||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED),
|
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -50,15 +47,15 @@ impl Mode {
|
|||||||
let long_options_scan = || {
|
let long_options_scan = || {
|
||||||
for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
|
for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
|
||||||
&flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP ] {
|
&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));
|
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))
|
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))
|
Err(Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -67,27 +64,27 @@ impl Mode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let other_options_scan = || {
|
let other_options_scan = || {
|
||||||
if let Some(width) = TerminalWidth::deduce()?.width() {
|
if let Some(width) = TerminalWidth::deduce(vars)?.width() {
|
||||||
if matches.has(&flags::ONE_LINE) {
|
if matches.has(&flags::ONE_LINE)? {
|
||||||
if matches.has(&flags::ACROSS) {
|
if matches.has(&flags::ACROSS)? {
|
||||||
Err(Useless(&flags::ACROSS, true, &flags::ONE_LINE))
|
Err(Useless(&flags::ACROSS, true, &flags::ONE_LINE))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Ok(Mode::Lines)
|
Ok(Mode::Lines)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if matches.has(&flags::TREE) {
|
else if matches.has(&flags::TREE)? {
|
||||||
let details = details::Options {
|
let details = details::Options {
|
||||||
table: None,
|
table: None,
|
||||||
header: false,
|
header: false,
|
||||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED),
|
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Mode::Details(details))
|
Ok(Mode::Details(details))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let grid = grid::Options {
|
let grid = grid::Options {
|
||||||
across: matches.has(&flags::ACROSS),
|
across: matches.has(&flags::ACROSS)?,
|
||||||
console_width: width,
|
console_width: width,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -99,11 +96,11 @@ impl Mode {
|
|||||||
// as the program’s stdout being connected to a file, then
|
// as the program’s stdout being connected to a file, then
|
||||||
// fallback to the lines view.
|
// fallback to the lines view.
|
||||||
|
|
||||||
if matches.has(&flags::TREE) {
|
if matches.has(&flags::TREE)? {
|
||||||
let details = details::Options {
|
let details = details::Options {
|
||||||
table: None,
|
table: None,
|
||||||
header: false,
|
header: false,
|
||||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED),
|
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Mode::Details(details))
|
Ok(Mode::Details(details))
|
||||||
@ -114,9 +111,9 @@ impl Mode {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches.has(&flags::LONG) {
|
if matches.has(&flags::LONG)? {
|
||||||
let details = long()?;
|
let details = long()?;
|
||||||
if matches.has(&flags::GRID) {
|
if matches.has(&flags::GRID)? {
|
||||||
match other_options_scan()? {
|
match other_options_scan()? {
|
||||||
Mode::Grid(grid) => return Ok(Mode::GridDetails(grid, details)),
|
Mode::Grid(grid) => return Ok(Mode::GridDetails(grid, details)),
|
||||||
others => return Ok(others),
|
others => return Ok(others),
|
||||||
@ -153,8 +150,8 @@ impl TerminalWidth {
|
|||||||
/// Determine a requested terminal width from the command-line arguments.
|
/// Determine a requested terminal width from the command-line arguments.
|
||||||
///
|
///
|
||||||
/// Returns an error if a requested width doesn’t parse to an integer.
|
/// Returns an error if a requested width doesn’t parse to an integer.
|
||||||
fn deduce() -> Result<TerminalWidth, Misfire> {
|
fn deduce<V: Vars>(vars: V) -> Result<TerminalWidth, Misfire> {
|
||||||
if let Some(columns) = var_os("COLUMNS").and_then(|s| s.into_string().ok()) {
|
if let Some(columns) = vars.get("COLUMNS").and_then(|s| s.into_string().ok()) {
|
||||||
match columns.parse() {
|
match columns.parse() {
|
||||||
Ok(width) => Ok(TerminalWidth::Set(width)),
|
Ok(width) => Ok(TerminalWidth::Set(width)),
|
||||||
Err(e) => Err(Misfire::FailedParse(e)),
|
Err(e) => Err(Misfire::FailedParse(e)),
|
||||||
@ -180,17 +177,26 @@ impl TerminalWidth {
|
|||||||
|
|
||||||
impl TableOptions {
|
impl TableOptions {
|
||||||
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
|
||||||
Ok(TableOptions {
|
let env = Environment::load_all();
|
||||||
env: Environment::load_all(),
|
let time_format = TimeFormat::deduce(matches)?;
|
||||||
time_format: TimeFormat::deduce(matches)?,
|
let size_format = SizeFormat::deduce(matches)?;
|
||||||
size_format: SizeFormat::deduce(matches)?,
|
let extra_columns = Columns::deduce(matches)?;
|
||||||
time_types: TimeTypes::deduce(matches)?,
|
Ok(TableOptions { env, time_format, size_format, extra_columns })
|
||||||
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),
|
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
|
/// involves the `--binary` or `--bytes` flags, and these conflict with
|
||||||
/// each other.
|
/// each other.
|
||||||
fn deduce(matches: &MatchedFlags) -> Result<SizeFormat, Misfire> {
|
fn deduce(matches: &MatchedFlags) -> Result<SizeFormat, Misfire> {
|
||||||
let binary = matches.has(&flags::BINARY);
|
let flag = matches.has_where(|f| f.matches(&flags::BINARY) || f.matches(&flags::BYTES))?;
|
||||||
let bytes = matches.has(&flags::BYTES);
|
|
||||||
|
|
||||||
match (binary, bytes) {
|
Ok(match flag {
|
||||||
(true, true ) => Err(Misfire::Conflict(&flags::BINARY, &flags::BYTES)),
|
Some(f) if f.matches(&flags::BINARY) => SizeFormat::BinaryBytes,
|
||||||
(true, false) => Ok(SizeFormat::BinaryBytes),
|
Some(f) if f.matches(&flags::BYTES) => SizeFormat::JustBytes,
|
||||||
(false, true ) => Ok(SizeFormat::JustBytes),
|
_ => SizeFormat::DecimalBytes,
|
||||||
(false, false) => Ok(SizeFormat::DecimalBytes),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const TIME_STYLES: &[&str] = &["default", "long-iso", "full-iso", "iso"];
|
||||||
|
|
||||||
impl TimeFormat {
|
impl TimeFormat {
|
||||||
|
|
||||||
/// Determine how time should be formatted in timestamp columns.
|
/// Determine how time should be formatted in timestamp columns.
|
||||||
fn deduce(matches: &MatchedFlags) -> Result<TimeFormat, Misfire> {
|
fn deduce(matches: &MatchedFlags) -> Result<TimeFormat, Misfire> {
|
||||||
pub use output::time::{DefaultFormat, ISOFormat};
|
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,
|
Some(w) => w,
|
||||||
None => return Ok(TimeFormat::DefaultFormat(DefaultFormat::new())),
|
None => return Ok(TimeFormat::DefaultFormat(DefaultFormat::new())),
|
||||||
};
|
};
|
||||||
@ -244,7 +249,7 @@ impl TimeFormat {
|
|||||||
Ok(TimeFormat::FullISO)
|
Ok(TimeFormat::FullISO)
|
||||||
}
|
}
|
||||||
else {
|
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
|
/// option, but passing *no* options means that the user just wants to
|
||||||
/// see the default set.
|
/// see the default set.
|
||||||
fn deduce(matches: &MatchedFlags) -> Result<TimeTypes, Misfire> {
|
fn deduce(matches: &MatchedFlags) -> Result<TimeTypes, Misfire> {
|
||||||
let possible_word = matches.get(&flags::TIME);
|
let possible_word = matches.get(&flags::TIME)?;
|
||||||
let modified = matches.has(&flags::MODIFIED);
|
let modified = matches.has(&flags::MODIFIED)?;
|
||||||
let created = matches.has(&flags::CREATED);
|
let created = matches.has(&flags::CREATED)?;
|
||||||
let accessed = matches.has(&flags::ACCESSED);
|
let accessed = matches.has(&flags::ACCESSED)?;
|
||||||
|
|
||||||
if let Some(word) = possible_word {
|
if let Some(word) = possible_word {
|
||||||
if modified {
|
if modified {
|
||||||
@ -329,13 +334,14 @@ impl Default for TerminalColours {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const COLOURS: &[&str] = &["always", "auto", "never"];
|
||||||
|
|
||||||
impl TerminalColours {
|
impl TerminalColours {
|
||||||
|
|
||||||
/// Determine which terminal colour conditions to use.
|
/// Determine which terminal colour conditions to use.
|
||||||
fn deduce(matches: &MatchedFlags) -> Result<TerminalColours, Misfire> {
|
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,
|
Some(w) => w,
|
||||||
None => return Ok(TerminalColours::default()),
|
None => return Ok(TerminalColours::default()),
|
||||||
};
|
};
|
||||||
@ -362,7 +368,7 @@ impl Colours {
|
|||||||
|
|
||||||
let tc = TerminalColours::deduce(matches)?;
|
let tc = TerminalColours::deduce(matches)?;
|
||||||
if tc == Always || (tc == Automatic && TERM_WIDTH.is_some()) {
|
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))
|
Ok(Colours::colourful(scale))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -374,17 +380,19 @@ impl Colours {
|
|||||||
|
|
||||||
|
|
||||||
impl FileStyle {
|
impl FileStyle {
|
||||||
fn deduce(matches: &MatchedFlags) -> FileStyle {
|
fn deduce(matches: &MatchedFlags) -> Result<FileStyle, Misfire> {
|
||||||
let classify = Classify::deduce(matches);
|
let classify = Classify::deduce(matches)?;
|
||||||
let exts = FileExtensions;
|
let exts = FileExtensions;
|
||||||
FileStyle { classify, exts }
|
Ok(FileStyle { classify, exts })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Classify {
|
impl Classify {
|
||||||
fn deduce(matches: &MatchedFlags) -> Classify {
|
fn deduce(matches: &MatchedFlags) -> Result<Classify, Misfire> {
|
||||||
if matches.has(&flags::CLASSIFY) { Classify::AddFileIndicators }
|
let flagged = matches.has(&flags::CLASSIFY)?;
|
||||||
else { Classify::JustFilenames }
|
|
||||||
|
Ok(if flagged { Classify::AddFileIndicators }
|
||||||
|
else { Classify::JustFilenames })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,6 +417,10 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use options::flags;
|
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 {
|
pub fn os(input: &'static str) -> OsString {
|
||||||
let mut os = OsString::new();
|
let mut os = OsString::new();
|
||||||
@ -416,19 +428,74 @@ mod test {
|
|||||||
os
|
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 {
|
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]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
use options::parser::{Args, Arg};
|
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||||
use std::ffi::OsString;
|
assert_eq!(result, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES,
|
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => {
|
||||||
&flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED ];
|
/// 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>>();
|
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => like $pat:pat) => {
|
||||||
let results = Args(TEST_ARGS).parse(bits.iter());
|
/// More general macro for testing against a pattern.
|
||||||
assert_eq!($type::deduce(&results.unwrap().flags), $result);
|
/// 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 {
|
mod size_formats {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
test!(empty: SizeFormat <- [] => Ok(SizeFormat::DecimalBytes));
|
// Default behaviour
|
||||||
test!(binary: SizeFormat <- ["--binary"] => Ok(SizeFormat::BinaryBytes));
|
test!(empty: SizeFormat <- []; Both => Ok(SizeFormat::DecimalBytes));
|
||||||
test!(bytes: SizeFormat <- ["--bytes"] => Ok(SizeFormat::JustBytes));
|
|
||||||
test!(both: SizeFormat <- ["--binary", "--bytes"] => Err(Misfire::Conflict(&flags::BINARY, &flags::BYTES)));
|
// 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::*;
|
use super::*;
|
||||||
|
|
||||||
// Default behaviour
|
// Default behaviour
|
||||||
test!(empty: TimeTypes <- [] => Ok(TimeTypes::default()));
|
test!(empty: TimeTypes <- []; Both => 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!(acc: TimeTypes <- ["--accessed"] => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
// Modified
|
||||||
test!(a: TimeTypes <- ["-u"] => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
test!(modified: TimeTypes <- ["--modified"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||||
test!(time_acc: TimeTypes <- ["--time", "accessed"] => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
test!(m: TimeTypes <- ["-m"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||||
test!(time_a: TimeTypes <- ["-t", "acc"] => Ok(TimeTypes { accessed: true, modified: false, 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 }));
|
// Accessed
|
||||||
test!(c: TimeTypes <- ["-U"] => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
test!(acc: TimeTypes <- ["--accessed"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||||
test!(time_cr: TimeTypes <- ["--time=created"] => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
test!(a: TimeTypes <- ["-u"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||||
test!(time_c: TimeTypes <- ["-tcr"] => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
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
|
// Multiples
|
||||||
test!(time_uu: TimeTypes <- ["-uU"] => Ok(TimeTypes { accessed: true, modified: false, created: true }));
|
test!(time_uu: TimeTypes <- ["-uU"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: true }));
|
||||||
|
|
||||||
// Overriding
|
|
||||||
test!(time_mc: TimeTypes <- ["-tcr", "-tmod"] => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
test!(time_tea: TimeTypes <- ["--time=tea"] => Err(Misfire::bad_argument(&flags::TIME, &os("tea"), super::TIMES)));
|
test!(time_tea: TimeTypes <- ["--time=tea"]; Both => 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_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 env: Environment,
|
||||||
pub size_format: SizeFormat,
|
pub size_format: SizeFormat,
|
||||||
pub time_format: TimeFormat,
|
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,
|
pub time_types: TimeTypes,
|
||||||
|
|
||||||
|
// The rest are just on/off
|
||||||
pub inode: bool,
|
pub inode: bool,
|
||||||
pub links: bool,
|
pub links: bool,
|
||||||
pub blocks: 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 {
|
pub fn should_scan_for_git(&self) -> bool {
|
||||||
self.git
|
self.git
|
||||||
}
|
}
|
||||||
@ -57,7 +67,7 @@ impl Options {
|
|||||||
columns.push(Column::HardLinks);
|
columns.push(Column::HardLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
columns.push(Column::FileSize(self.size_format));
|
columns.push(Column::FileSize);
|
||||||
|
|
||||||
if self.blocks {
|
if self.blocks {
|
||||||
columns.push(Column::Blocks);
|
columns.push(Column::Blocks);
|
||||||
@ -98,7 +108,7 @@ impl Options {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Column {
|
pub enum Column {
|
||||||
Permissions,
|
Permissions,
|
||||||
FileSize(SizeFormat),
|
FileSize,
|
||||||
Timestamp(TimeType),
|
Timestamp(TimeType),
|
||||||
Blocks,
|
Blocks,
|
||||||
User,
|
User,
|
||||||
@ -120,7 +130,7 @@ impl Column {
|
|||||||
/// Get the alignment this column should use.
|
/// Get the alignment this column should use.
|
||||||
pub fn alignment(&self) -> Alignment {
|
pub fn alignment(&self) -> Alignment {
|
||||||
match *self {
|
match *self {
|
||||||
Column::FileSize(_)
|
Column::FileSize
|
||||||
| Column::HardLinks
|
| Column::HardLinks
|
||||||
| Column::Inode
|
| Column::Inode
|
||||||
| Column::Blocks
|
| Column::Blocks
|
||||||
@ -134,7 +144,7 @@ impl Column {
|
|||||||
pub fn header(&self) -> &'static str {
|
pub fn header(&self) -> &'static str {
|
||||||
match *self {
|
match *self {
|
||||||
Column::Permissions => "Permissions",
|
Column::Permissions => "Permissions",
|
||||||
Column::FileSize(_) => "Size",
|
Column::FileSize => "Size",
|
||||||
Column::Timestamp(t) => t.header(),
|
Column::Timestamp(t) => t.header(),
|
||||||
Column::Blocks => "Blocks",
|
Column::Blocks => "Blocks",
|
||||||
Column::User => "User",
|
Column::User => "User",
|
||||||
@ -276,6 +286,7 @@ pub struct Table<'a> {
|
|||||||
env: &'a Environment,
|
env: &'a Environment,
|
||||||
widths: TableWidths,
|
widths: TableWidths,
|
||||||
time_format: &'a TimeFormat,
|
time_format: &'a TimeFormat,
|
||||||
|
size_format: SizeFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -285,9 +296,14 @@ pub struct Row {
|
|||||||
|
|
||||||
impl<'a, 'f> Table<'a> {
|
impl<'a, 'f> Table<'a> {
|
||||||
pub fn new(options: &'a Options, dir: Option<&'a Dir>, colours: &'a Colours) -> 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());
|
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 {
|
pub fn widths(&self) -> &TableWidths {
|
||||||
@ -327,7 +343,7 @@ impl<'a, 'f> Table<'a> {
|
|||||||
|
|
||||||
match *column {
|
match *column {
|
||||||
Column::Permissions => self.permissions_plus(file, xattrs).render(&self.colours),
|
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::HardLinks => file.links().render(&self.colours, &self.env.numeric),
|
||||||
Column::Inode => file.inode().render(&self.colours),
|
Column::Inode => file.inode().render(&self.colours),
|
||||||
Column::Blocks => file.blocks().render(&self.colours),
|
Column::Blocks => file.blocks().render(&self.colours),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Timestamp formatting.
|
||||||
|
|
||||||
use datetime::{LocalDateTime, TimeZone, DatePiece, TimePiece};
|
use datetime::{LocalDateTime, TimeZone, DatePiece, TimePiece};
|
||||||
use datetime::fmt::DateFormat;
|
use datetime::fmt::DateFormat;
|
||||||
use locale;
|
use locale;
|
||||||
@ -6,13 +8,48 @@ use std::cmp;
|
|||||||
use fs::fields::Time;
|
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 {
|
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),
|
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),
|
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,
|
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,
|
FullISO,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There are two different formatting functions because local and zoned
|
||||||
|
// timestamps are separate types.
|
||||||
|
|
||||||
impl TimeFormat {
|
impl TimeFormat {
|
||||||
pub fn format_local(&self, time: Time) -> String {
|
pub fn format_local(&self, time: Time) -> String {
|
||||||
match *self {
|
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"
|
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.
|
# Check that no files were created more than a year ago.
|
||||||
# Files not from the current year use a different date format, meaning
|
# Files not from the current year use a different date format, meaning
|
||||||
# that tests will fail until the VM gets re-provisioned.
|
# 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
|
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
|
# Attributes
|
||||||
# (there are many tests, but they’re all done in one go)
|
# (there are many tests, but they’re all done in one go)
|
||||||
$exa $testcases/attributes -l@T | diff -q - $results/attributes || exit 1
|
$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
|
$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...
|
# And finally...
|
||||||
$exa --help | diff -q - $results/help || exit 1
|
$exa --help | diff -q - $results/help || exit 1
|
||||||
$exa --help --long | diff -q - $results/help_long || exit 1
|
$exa --help --long | diff -q - $results/help_long || exit 1
|
||||||
|
Loading…
Reference in New Issue
Block a user