Be stricter in strict mode

Now the code actually starts to use the Strictness flag that was added in the earlier commit! Well, the *code* doesn’t, but the tests do: the macros that create the test cases now have a parameter for which tests they should run. It’s usually ‘Both’ for both strict mode and default mode, but can be specified to only run in one, for when the results differ (usually when options override one another)

The downside to strict mode is that, now, *any* call to `matches.has` or `matches.get` could fail, because an option could have been specified twice, and this is the place where those are checked for. This makes the code a little less ergonomic in places, but that’s what the ? operator is for. The only place this has really had an effect is in `Classify::deduce`, which used to just return a boolean but can now fail.

In order to more thoroughly test the mode, some of the older parts of the code can now act more strict. For example, `TerminalColours::deduce` will now use the last-given option rather than searching for “colours” before “colors”.

Help and Version continue doing their own thing.
This commit is contained in:
Benjamin Sago 2017-08-09 09:21:29 +01:00
parent d97f603ee3
commit ff497b52e5
8 changed files with 262 additions and 140 deletions

View File

@ -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 wouldnt do anything // Early check for --level when it wouldnt 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)),
@ -56,9 +56,10 @@ impl RecurseOptions {
mod test { mod test {
use super::*; use super::*;
use options::flags; use options::flags;
use options::parser::Flag;
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::Arg; use options::parser::Arg;
@ -66,32 +67,36 @@ mod test {
use options::test::Strictnesses::*; 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 ];
assert_parses($inputs.as_ref(), TEST_ARGS, Both, |mf| $type::deduce(mf), $result) assert_parses($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf), $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
use self::DirAction::Recurse; use self::DirAction::Recurse;
test!(rec_short: DirAction <- ["-R"] => Ok(Recurse(RecurseOptions { tree: false, max_depth: None }))); test!(rec_short: DirAction <- ["-R"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
test!(rec_long: DirAction <- ["--recurse"] => Ok(Recurse(RecurseOptions { tree: false, max_depth: None }))); test!(rec_long: DirAction <- ["--recurse"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
test!(rec_lim_short: DirAction <- ["-RL4"] => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(4) }))); test!(rec_lim_short: DirAction <- ["-RL4"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(4) })));
test!(rec_lim_short_2: DirAction <- ["-RL=5"] => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(5) }))); test!(rec_lim_short_2: DirAction <- ["-RL=5"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(5) })));
test!(rec_lim_long: DirAction <- ["--recurse", "--level", "666"] => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(666) }))); test!(rec_lim_long: DirAction <- ["--recurse", "--level", "666"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(666) })));
test!(rec_lim_long_2: DirAction <- ["--recurse", "--level=0118"] => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(118) }))); test!(rec_lim_long_2: DirAction <- ["--recurse", "--level=0118"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(118) })));
test!(rec_tree: DirAction <- ["--recurse", "--tree"] => Ok(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"] => 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'))));
} }

View File

@ -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,7 +146,7 @@ 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::Arg; use options::parser::Arg;
@ -142,7 +154,7 @@ mod test {
use options::test::Strictnesses::*; 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 ];
assert_parses($inputs.as_ref(), TEST_ARGS, Both, |mf| $type::deduce(mf), $result) assert_parses($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf), $result)
} }
}; };
} }
@ -151,21 +163,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"))));
} }
@ -173,16 +187,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));
} }
@ -196,9 +214,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'))));
} }
} }

View File

@ -72,9 +72,13 @@ impl HelpString {
/// Determines how to show help, if at all, based on the users /// Determines how to show help, if at all, based on the users
/// 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 dont do any strict-mode error checking here: its 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 })

View File

@ -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),
@ -93,6 +96,7 @@ impl fmt::Display for Misfire {
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),

View File

@ -170,6 +170,9 @@ pub mod test {
Both, 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.
pub fn assert_parses<T, F>(inputs: &[&str], args: &'static [&'static Arg], strictnesses: Strictnesses, get: F, result: T) pub fn assert_parses<T, F>(inputs: &[&str], args: &'static [&'static Arg], strictnesses: Strictnesses, get: F, result: T)
where T: PartialEq + Debug, F: Fn(&MatchedFlags) -> T where T: PartialEq + Debug, F: Fn(&MatchedFlags) -> T
{ {

View File

@ -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,
@ -312,21 +314,55 @@ pub struct MatchedFlags<'args> {
strictness: Strictness, strictness: Strictness,
} }
use self::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 { pub fn has(&self, arg: &'static Arg) -> Result<bool, Misfire> {
self.flags.iter().rev() match self.strictness {
.find(|tuple| tuple.1.is_none() && tuple.0.matches(arg)) UseLastArguments => {
.is_some() let any = self.flags.iter().rev()
.find(|tuple| tuple.1.is_none() && tuple.0.matches(arg))
.is_some();
Ok(any)
}
ComplainAboutRedundantArguments => {
let all = self.flags.iter()
.filter(|tuple| tuple.1.is_none() && tuple.0.matches(arg))
.collect::<Vec<_>>();
if all.len() < 2 { Ok(all.len() == 1) }
else { Err(Misfire::Conflict(arg, arg)) }
}
}
}
// This code could probably be better.
pub fn get(&self, arg: &'static Arg) -> Result<Option<&OsStr>, Misfire> {
self.get_where(|flag| flag.matches(arg))
} }
/// If the given argument was specified, return its value. /// If the given argument was specified, return its value.
/// The value is not guaranteed to be valid UTF-8. /// The value is not guaranteed to be valid UTF-8.
pub fn get(&self, arg: &Arg) -> Option<&OsStr> { pub fn get_where<P>(&self, predicate: P) -> Result<Option<&OsStr>, Misfire>
self.flags.iter().rev() where P: Fn(&Flag) -> bool {
.find(|tuple| tuple.1.is_some() && tuple.0.matches(arg)) match self.strictness {
.map(|tuple| tuple.1.unwrap()) UseLastArguments => {
let found = self.flags.iter().rev()
.find(|tuple| tuple.1.is_some() && predicate(&tuple.0))
.map(|tuple| tuple.1.unwrap());
Ok(found)
}
ComplainAboutRedundantArguments => {
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())) }
}
}
} }
// Its annoying that has and get wont work when accidentally given // Its annoying that has and get wont work when accidentally given
@ -338,6 +374,12 @@ impl<'a> MatchedFlags<'a> {
.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 its available in an emergency.
pub fn is_strict(&self) -> bool {
self.strictness == Strictness::ComplainAboutRedundantArguments
}
} }
@ -547,7 +589,7 @@ mod matches_test {
strictness: Strictness::UseLastArguments, strictness: Strictness::UseLastArguments,
}; };
assert_eq!(flags.has(&$param), $result); assert_eq!(flags.has(&$param), Ok($result));
} }
}; };
} }
@ -573,7 +615,7 @@ mod matches_test {
strictness: Strictness::UseLastArguments, strictness: Strictness::UseLastArguments,
}; };
assert_eq!(flags.get(&COUNT), Some(&*everything)); assert_eq!(flags.get(&COUNT), Ok(Some(&*everything)));
} }
#[test] #[test]
@ -587,13 +629,13 @@ mod matches_test {
strictness: Strictness::UseLastArguments, 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(), strictness: Strictness::UseLastArguments }; let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };
assert!(!flags.has(&COUNT)); assert!(!flags.has(&COUNT).unwrap());
} }
} }

View File

@ -17,8 +17,10 @@ impl VersionString {
/// Determines how to show the version, if at all, based on the users /// Determines how to show the version, if at all, based on the users
/// 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 doesnt 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 {

View File

@ -19,7 +19,7 @@ impl View {
pub fn deduce(matches: &MatchedFlags) -> Result<View, Misfire> { pub fn deduce(matches: &MatchedFlags) -> Result<View, Misfire> {
let mode = Mode::deduce(matches)?; let mode = Mode::deduce(matches)?;
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 })
} }
} }
@ -32,17 +32,17 @@ impl Mode {
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 +50,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.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 {
@ -68,26 +68,26 @@ impl Mode {
let other_options_scan = || { let other_options_scan = || {
if let Some(width) = TerminalWidth::deduce()?.width() { if let Some(width) = TerminalWidth::deduce()?.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 +99,11 @@ impl Mode {
// as the programs stdout being connected to a file, then // as the programs 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 +114,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),
@ -185,11 +185,11 @@ impl TableOptions {
time_format: TimeFormat::deduce(matches)?, time_format: TimeFormat::deduce(matches)?,
size_format: SizeFormat::deduce(matches)?, size_format: SizeFormat::deduce(matches)?,
time_types: TimeTypes::deduce(matches)?, time_types: TimeTypes::deduce(matches)?,
inode: matches.has(&flags::INODE), inode: matches.has(&flags::INODE)?,
links: matches.has(&flags::LINKS), links: matches.has(&flags::LINKS)?,
blocks: matches.has(&flags::BLOCKS), blocks: matches.has(&flags::BLOCKS)?,
group: matches.has(&flags::GROUP), group: matches.has(&flags::GROUP)?,
git: cfg!(feature="git") && matches.has(&flags::GIT), git: cfg!(feature="git") && matches.has(&flags::GIT)?,
}) })
} }
} }
@ -206,8 +206,8 @@ 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 binary = matches.has(&flags::BINARY)?;
let bytes = matches.has(&flags::BYTES); let bytes = matches.has(&flags::BYTES)?;
match (binary, bytes) { match (binary, bytes) {
(true, true ) => Err(Misfire::Conflict(&flags::BINARY, &flags::BYTES)), (true, true ) => Err(Misfire::Conflict(&flags::BINARY, &flags::BYTES)),
@ -226,7 +226,7 @@ impl TimeFormat {
pub use output::time::{DefaultFormat, ISOFormat}; pub use output::time::{DefaultFormat, ISOFormat};
const STYLES: &[&str] = &["default", "long-iso", "full-iso", "iso"]; 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())),
}; };
@ -265,10 +265,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 +329,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 +363,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 +375,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 })
} }
} }
@ -407,10 +410,18 @@ lazy_static! {
#[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::Arg; use options::parser::Arg;
@ -418,9 +429,10 @@ mod test {
use options::test::Strictnesses::*; use options::test::Strictnesses::*;
static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES,
&flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED ]; &flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED,
&flags::COLOR, &flags::COLOUR ];
assert_parses($inputs.as_ref(), TEST_ARGS, Both, |mf| $type::deduce(mf), $result) assert_parses($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf), $result)
} }
}; };
} }
@ -429,49 +441,75 @@ mod test {
mod size_formats { mod size_formats {
use super::*; use super::*;
test!(empty: SizeFormat <- [] => Ok(SizeFormat::DecimalBytes)); test!(empty: SizeFormat <- []; Both => Ok(SizeFormat::DecimalBytes));
test!(binary: SizeFormat <- ["--binary"] => Ok(SizeFormat::BinaryBytes)); test!(binary: SizeFormat <- ["--binary"]; Both => Ok(SizeFormat::BinaryBytes));
test!(bytes: SizeFormat <- ["--bytes"] => Ok(SizeFormat::JustBytes)); test!(bytes: SizeFormat <- ["--bytes"]; Both => Ok(SizeFormat::JustBytes));
test!(both: SizeFormat <- ["--binary", "--bytes"] => Err(Misfire::Conflict(&flags::BINARY, &flags::BYTES))); test!(both: SizeFormat <- ["--binary", "--bytes"]; Both => Err(Misfire::Conflict(&flags::BINARY, &flags::BYTES)));
} }
mod time_types { mod time_types {
use super::*; use super::*;
use std::ffi::OsString;
fn os(input: &'static str) -> OsString {
let mut os = OsString::new();
os.push(input);
os
}
// 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!(modified: TimeTypes <- ["--modified"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
test!(m: TimeTypes <- ["-m"] => Ok(TimeTypes { accessed: false, modified: true, created: false })); test!(m: TimeTypes <- ["-m"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
test!(time_mod: TimeTypes <- ["--time=modified"] => Ok(TimeTypes { accessed: false, modified: true, created: false })); test!(time_mod: TimeTypes <- ["--time=modified"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
test!(time_m: TimeTypes <- ["-tmod"] => Ok(TimeTypes { accessed: false, modified: true, created: false })); test!(time_m: TimeTypes <- ["-tmod"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
test!(acc: TimeTypes <- ["--accessed"] => Ok(TimeTypes { accessed: true, modified: false, created: false })); test!(acc: TimeTypes <- ["--accessed"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
test!(a: TimeTypes <- ["-u"] => Ok(TimeTypes { accessed: true, modified: false, created: false })); test!(a: TimeTypes <- ["-u"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
test!(time_acc: TimeTypes <- ["--time", "accessed"] => Ok(TimeTypes { accessed: true, modified: false, created: false })); test!(time_acc: TimeTypes <- ["--time", "accessed"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
test!(time_a: TimeTypes <- ["-t", "acc"] => Ok(TimeTypes { accessed: true, modified: false, created: false })); test!(time_a: TimeTypes <- ["-t", "acc"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
test!(cr: TimeTypes <- ["--created"] => Ok(TimeTypes { accessed: false, modified: false, created: true })); test!(cr: TimeTypes <- ["--created"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
test!(c: TimeTypes <- ["-U"] => 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"] => 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"] => 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"))));
} }
} }