Skip to content

Commit 97d1472

Browse files
committed
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.
2 parents c1e2066 + b286676 commit 97d1472

19 files changed

+791
-314
lines changed

src/exa.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ extern crate term_size;
2222
extern crate lazy_static;
2323

2424

25+
use std::env::var_os;
2526
use std::ffi::{OsStr, OsString};
2627
use std::io::{stderr, Write, Result as IOResult};
2728
use std::path::{Component, PathBuf};
2829

2930
use ansi_term::{ANSIStrings, Style};
3031

3132
use fs::{Dir, File};
32-
use options::Options;
33+
use options::{Options, Vars};
3334
pub use options::Misfire;
3435
use output::{escape, lines, grid, grid_details, details, View, Mode};
3536

@@ -55,10 +56,20 @@ pub struct Exa<'args, 'w, W: Write + 'w> {
5556
pub args: Vec<&'args OsStr>,
5657
}
5758

59+
/// The “real” environment variables type.
60+
/// Instead of just calling `var_os` from within the options module,
61+
/// the method of looking up environment variables has to be passed in.
62+
struct LiveVars;
63+
impl Vars for LiveVars {
64+
fn get(&self, name: &'static str) -> Option<OsString> {
65+
var_os(name)
66+
}
67+
}
68+
5869
impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
5970
pub fn new<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
6071
where I: Iterator<Item=&'args OsString> {
61-
Options::getopts(args).map(move |(options, args)| {
72+
Options::parse(args, LiveVars).map(move |(options, args)| {
6273
Exa { options, writer, args }
6374
})
6475
}

src/options/dir_action.rs

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ impl DirAction {
88

99
/// Determine which action to perform when trying to list a directory.
1010
pub fn deduce(matches: &MatchedFlags) -> Result<DirAction, Misfire> {
11-
let recurse = matches.has(&flags::RECURSE);
12-
let list = matches.has(&flags::LIST_DIRS);
13-
let tree = matches.has(&flags::TREE);
11+
let recurse = matches.has(&flags::RECURSE)?;
12+
let list = matches.has(&flags::LIST_DIRS)?;
13+
let tree = matches.has(&flags::TREE)?;
1414

1515
// Early check for --level when it wouldn’t do anything
16-
if !recurse && !tree && matches.get(&flags::LEVEL).is_some() {
16+
if !recurse && !tree && matches.get(&flags::LEVEL)?.is_some() {
1717
return Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
1818
}
1919

@@ -37,7 +37,7 @@ impl RecurseOptions {
3737

3838
/// Determine which files should be recursed into.
3939
pub fn deduce(matches: &MatchedFlags, tree: bool) -> Result<RecurseOptions, Misfire> {
40-
let max_depth = if let Some(level) = matches.get(&flags::LEVEL) {
40+
let max_depth = if let Some(level) = matches.get(&flags::LEVEL)? {
4141
match level.to_string_lossy().parse() {
4242
Ok(l) => Some(l),
4343
Err(e) => return Err(Misfire::FailedParse(e)),
@@ -55,51 +55,50 @@ impl RecurseOptions {
5555
#[cfg(test)]
5656
mod test {
5757
use super::*;
58-
use std::ffi::OsString;
5958
use options::flags;
60-
61-
pub fn os(input: &'static str) -> OsString {
62-
let mut os = OsString::new();
63-
os.push(input);
64-
os
65-
}
59+
use options::parser::Flag;
6660

6761
macro_rules! test {
68-
($name:ident: $type:ident <- $inputs:expr => $result:expr) => {
62+
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
6963
#[test]
7064
fn $name() {
71-
use options::parser::{Args, Arg};
72-
use std::ffi::OsString;
73-
74-
static TEST_ARGS: &[&Arg] = &[ &flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL ];
75-
76-
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
77-
let results = Args(TEST_ARGS).parse(bits.iter());
78-
assert_eq!($type::deduce(&results.unwrap().flags), $result);
65+
use options::parser::Arg;
66+
use options::test::parse_for_test;
67+
use options::test::Strictnesses::*;
68+
69+
static TEST_ARGS: &[&Arg] = &[&flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL ];
70+
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
71+
assert_eq!(result, $result);
72+
}
7973
}
8074
};
8175
}
8276

8377

8478
// Default behaviour
85-
test!(empty: DirAction <- [] => Ok(DirAction::List));
79+
test!(empty: DirAction <- []; Both => Ok(DirAction::List));
8680

8781
// Listing files as directories
88-
test!(dirs_short: DirAction <- ["-d"] => Ok(DirAction::AsFile));
89-
test!(dirs_long: DirAction <- ["--list-dirs"] => Ok(DirAction::AsFile));
82+
test!(dirs_short: DirAction <- ["-d"]; Both => Ok(DirAction::AsFile));
83+
test!(dirs_long: DirAction <- ["--list-dirs"]; Both => Ok(DirAction::AsFile));
9084

9185
// Recursing
92-
test!(rec_short: DirAction <- ["-R"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: None })));
93-
test!(rec_long: DirAction <- ["--recurse"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: None })));
94-
test!(rec_lim_short: DirAction <- ["-RL4"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(4) })));
95-
test!(rec_lim_short_2: DirAction <- ["-RL=5"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(5) })));
96-
test!(rec_lim_long: DirAction <- ["--recurse", "--level", "666"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(666) })));
97-
test!(rec_lim_long_2: DirAction <- ["--recurse", "--level=0118"] => Ok(DirAction::Recurse(RecurseOptions { tree: false, max_depth: Some(118) })));
98-
test!(rec_tree: DirAction <- ["--recurse", "--tree"] => Ok(DirAction::Recurse(RecurseOptions { tree: true, max_depth: None })));
99-
test!(rec_short_tree: DirAction <- ["--tree", "--recurse"] => Ok(DirAction::Recurse(RecurseOptions { tree: true, max_depth: None })));
86+
use self::DirAction::Recurse;
87+
test!(rec_short: DirAction <- ["-R"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
88+
test!(rec_long: DirAction <- ["--recurse"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: None })));
89+
test!(rec_lim_short: DirAction <- ["-RL4"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(4) })));
90+
test!(rec_lim_short_2: DirAction <- ["-RL=5"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(5) })));
91+
test!(rec_lim_long: DirAction <- ["--recurse", "--level", "666"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(666) })));
92+
test!(rec_lim_long_2: DirAction <- ["--recurse", "--level=0118"]; Both => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(118) })));
93+
test!(rec_tree: DirAction <- ["--recurse", "--tree"]; Both => Ok(Recurse(RecurseOptions { tree: true, max_depth: None })));
94+
test!(rec_short_tree: DirAction <- ["--tree", "--recurse"]; Both => Ok(Recurse(RecurseOptions { tree: true, max_depth: None })));
10095

10196
// Errors
102-
test!(error: DirAction <- ["--list-dirs", "--recurse"] => Err(Misfire::Conflict(&flags::RECURSE, &flags::LIST_DIRS)));
103-
test!(error_2: DirAction <- ["--list-dirs", "--tree"] => Err(Misfire::Conflict(&flags::TREE, &flags::LIST_DIRS)));
104-
test!(underwaterlevel: DirAction <- ["--level=4"] => Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)));
97+
test!(error: DirAction <- ["--list-dirs", "--recurse"]; Both => Err(Misfire::Conflict(&flags::RECURSE, &flags::LIST_DIRS)));
98+
test!(error_2: DirAction <- ["--list-dirs", "--tree"]; Both => Err(Misfire::Conflict(&flags::TREE, &flags::LIST_DIRS)));
99+
test!(underwaterlevel: DirAction <- ["--level=4"]; Both => Err(Misfire::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE)));
100+
101+
// Overriding
102+
test!(overriding_1: DirAction <- ["-RL=6", "-L=7"]; Last => Ok(Recurse(RecurseOptions { tree: false, max_depth: Some(7) })));
103+
test!(overriding_2: DirAction <- ["-RL=6", "-L=7"]; Complain => Err(Misfire::Duplicate(Flag::Short(b'L'), Flag::Short(b'L'))));
105104
}

src/options/filter.rs

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ impl FileFilter {
1111
/// command-line arguments.
1212
pub fn deduce(matches: &MatchedFlags) -> Result<FileFilter, Misfire> {
1313
Ok(FileFilter {
14-
list_dirs_first: matches.has(&flags::DIRS_FIRST),
15-
reverse: matches.has(&flags::REVERSE),
14+
list_dirs_first: matches.has(&flags::DIRS_FIRST)?,
15+
reverse: matches.has(&flags::REVERSE)?,
1616
sort_field: SortField::deduce(matches)?,
1717
dot_filter: DotFilter::deduce(matches)?,
1818
ignore_patterns: IgnorePatterns::deduce(matches)?,
@@ -38,7 +38,7 @@ impl SortField {
3838
/// argument. This will return `Err` if the option is there, but does not
3939
/// correspond to a valid field.
4040
fn deduce(matches: &MatchedFlags) -> Result<SortField, Misfire> {
41-
let word = match matches.get(&flags::SORT) {
41+
let word = match matches.get(&flags::SORT)? {
4242
Some(w) => w,
4343
None => return Ok(SortField::default()),
4444
};
@@ -85,11 +85,22 @@ impl SortField {
8585

8686
impl DotFilter {
8787
pub fn deduce(matches: &MatchedFlags) -> Result<DotFilter, Misfire> {
88-
match matches.count(&flags::ALL) {
89-
0 => Ok(DotFilter::JustFiles),
90-
1 => Ok(DotFilter::Dotfiles),
91-
_ => if matches.has(&flags::TREE) { Err(Misfire::TreeAllAll) }
92-
else { Ok(DotFilter::DotfilesAndDots) }
88+
let count = matches.count(&flags::ALL);
89+
90+
if count == 0 {
91+
Ok(DotFilter::JustFiles)
92+
}
93+
else if count == 1 {
94+
Ok(DotFilter::Dotfiles)
95+
}
96+
else if matches.count(&flags::TREE) > 0 {
97+
Err(Misfire::TreeAllAll)
98+
}
99+
else if count >= 3 && matches.is_strict() {
100+
Err(Misfire::Conflict(&flags::ALL, &flags::ALL))
101+
}
102+
else {
103+
Ok(DotFilter::DotfilesAndDots)
93104
}
94105
}
95106
}
@@ -101,7 +112,7 @@ impl IgnorePatterns {
101112
/// command-line arguments.
102113
pub fn deduce(matches: &MatchedFlags) -> Result<IgnorePatterns, Misfire> {
103114

104-
let inputs = match matches.get(&flags::IGNORE_GLOB) {
115+
let inputs = match matches.get(&flags::IGNORE_GLOB)? {
105116
None => return Ok(IgnorePatterns::empty()),
106117
Some(is) => is,
107118
};
@@ -126,6 +137,7 @@ mod test {
126137
use super::*;
127138
use std::ffi::OsString;
128139
use options::flags;
140+
use options::parser::Flag;
129141

130142
pub fn os(input: &'static str) -> OsString {
131143
let mut os = OsString::new();
@@ -134,17 +146,17 @@ mod test {
134146
}
135147

136148
macro_rules! test {
137-
($name:ident: $type:ident <- $inputs:expr => $result:expr) => {
149+
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
138150
#[test]
139151
fn $name() {
140-
use options::parser::{Args, Arg};
141-
use std::ffi::OsString;
152+
use options::parser::Arg;
153+
use options::test::parse_for_test;
154+
use options::test::Strictnesses::*;
142155

143156
static TEST_ARGS: &[&Arg] = &[ &flags::SORT, &flags::ALL, &flags::TREE, &flags::IGNORE_GLOB ];
144-
145-
let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
146-
let results = Args(TEST_ARGS).parse(bits.iter());
147-
assert_eq!($type::deduce(&results.unwrap().flags), $result);
157+
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
158+
assert_eq!(result, $result);
159+
}
148160
}
149161
};
150162
}
@@ -153,38 +165,44 @@ mod test {
153165
use super::*;
154166

155167
// Default behaviour
156-
test!(empty: SortField <- [] => Ok(SortField::default()));
168+
test!(empty: SortField <- []; Both => Ok(SortField::default()));
157169

158170
// Sort field arguments
159-
test!(one_arg: SortField <- ["--sort=cr"] => Ok(SortField::CreatedDate));
160-
test!(one_long: SortField <- ["--sort=size"] => Ok(SortField::Size));
161-
test!(one_short: SortField <- ["-saccessed"] => Ok(SortField::AccessedDate));
162-
test!(lowercase: SortField <- ["--sort", "name"] => Ok(SortField::Name(SortCase::Sensitive)));
163-
test!(uppercase: SortField <- ["--sort", "Name"] => Ok(SortField::Name(SortCase::Insensitive)));
171+
test!(one_arg: SortField <- ["--sort=cr"]; Both => Ok(SortField::CreatedDate));
172+
test!(one_long: SortField <- ["--sort=size"]; Both => Ok(SortField::Size));
173+
test!(one_short: SortField <- ["-saccessed"]; Both => Ok(SortField::AccessedDate));
174+
test!(lowercase: SortField <- ["--sort", "name"]; Both => Ok(SortField::Name(SortCase::Sensitive)));
175+
test!(uppercase: SortField <- ["--sort", "Name"]; Both => Ok(SortField::Name(SortCase::Insensitive)));
164176

165177
// Errors
166-
test!(error: SortField <- ["--sort=colour"] => Err(Misfire::bad_argument(&flags::SORT, &os("colour"), super::SORTS)));
178+
test!(error: SortField <- ["--sort=colour"]; Both => Err(Misfire::bad_argument(&flags::SORT, &os("colour"), super::SORTS)));
167179

168180
// Overriding
169-
test!(overridden: SortField <- ["--sort=cr", "--sort", "mod"] => Ok(SortField::ModifiedDate));
170-
test!(overridden_2: SortField <- ["--sort", "none", "--sort=Extension"] => Ok(SortField::Extension(SortCase::Insensitive)));
181+
test!(overridden: SortField <- ["--sort=cr", "--sort", "mod"]; Last => Ok(SortField::ModifiedDate));
182+
test!(overridden_2: SortField <- ["--sort", "none", "--sort=Extension"]; Last => Ok(SortField::Extension(SortCase::Insensitive)));
183+
test!(overridden_3: SortField <- ["--sort=cr", "--sort", "mod"]; Complain => Err(Misfire::Duplicate(Flag::Long("sort"), Flag::Long("sort"))));
184+
test!(overridden_4: SortField <- ["--sort", "none", "--sort=Extension"]; Complain => Err(Misfire::Duplicate(Flag::Long("sort"), Flag::Long("sort"))));
171185
}
172186

173187

174188
mod dot_filters {
175189
use super::*;
176190

177191
// Default behaviour
178-
test!(empty: DotFilter <- [] => Ok(DotFilter::JustFiles));
192+
test!(empty: DotFilter <- []; Both => Ok(DotFilter::JustFiles));
179193

180194
// --all
181-
test!(all: DotFilter <- ["--all"] => Ok(DotFilter::Dotfiles));
182-
test!(all_all: DotFilter <- ["--all", "-a"] => Ok(DotFilter::DotfilesAndDots));
183-
test!(all_all_2: DotFilter <- ["-aa"] => Ok(DotFilter::DotfilesAndDots));
195+
test!(all: DotFilter <- ["--all"]; Both => Ok(DotFilter::Dotfiles));
196+
test!(all_all: DotFilter <- ["--all", "-a"]; Both => Ok(DotFilter::DotfilesAndDots));
197+
test!(all_all_2: DotFilter <- ["-aa"]; Both => Ok(DotFilter::DotfilesAndDots));
198+
199+
test!(all_all_3: DotFilter <- ["-aaa"]; Last => Ok(DotFilter::DotfilesAndDots));
200+
test!(all_all_4: DotFilter <- ["-aaa"]; Complain => Err(Misfire::Conflict(&flags::ALL, &flags::ALL)));
184201

185202
// --all and --tree
186-
test!(tree_a: DotFilter <- ["-Ta"] => Ok(DotFilter::Dotfiles));
187-
test!(tree_aa: DotFilter <- ["-Taa"] => Err(Misfire::TreeAllAll));
203+
test!(tree_a: DotFilter <- ["-Ta"]; Both => Ok(DotFilter::Dotfiles));
204+
test!(tree_aa: DotFilter <- ["-Taa"]; Both => Err(Misfire::TreeAllAll));
205+
test!(tree_aaa: DotFilter <- ["-Taaa"]; Both => Err(Misfire::TreeAllAll));
188206
}
189207

190208

@@ -198,9 +216,15 @@ mod test {
198216
}
199217

200218
// Various numbers of globs
201-
test!(none: IgnorePatterns <- [] => Ok(IgnorePatterns::empty()));
202-
test!(one: IgnorePatterns <- ["--ignore-glob", "*.ogg"] => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg") ])));
203-
test!(two: IgnorePatterns <- ["--ignore-glob=*.ogg|*.MP3"] => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg"), pat("*.MP3") ])));
204-
test!(loads: IgnorePatterns <- ["-I*|?|.|*"] => Ok(IgnorePatterns::from_iter(vec![ pat("*"), pat("?"), pat("."), pat("*") ])));
219+
test!(none: IgnorePatterns <- []; Both => Ok(IgnorePatterns::empty()));
220+
test!(one: IgnorePatterns <- ["--ignore-glob", "*.ogg"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg") ])));
221+
test!(two: IgnorePatterns <- ["--ignore-glob=*.ogg|*.MP3"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg"), pat("*.MP3") ])));
222+
test!(loads: IgnorePatterns <- ["-I*|?|.|*"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*"), pat("?"), pat("."), pat("*") ])));
223+
224+
// Overriding
225+
test!(overridden: IgnorePatterns <- ["-I=*.ogg", "-I", "*.mp3"]; Last => Ok(IgnorePatterns::from_iter(vec![ pat("*.mp3") ])));
226+
test!(overridden_2: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Last => Ok(IgnorePatterns::from_iter(vec![ pat("*.MP3") ])));
227+
test!(overridden_3: IgnorePatterns <- ["-I=*.ogg", "-I", "*.mp3"]; Complain => Err(Misfire::Duplicate(Flag::Short(b'I'), Flag::Short(b'I'))));
228+
test!(overridden_4: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Complain => Err(Misfire::Duplicate(Flag::Short(b'I'), Flag::Short(b'I'))));
205229
}
206230
}

src/options/help.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,13 @@ impl HelpString {
7272
/// Determines how to show help, if at all, based on the user’s
7373
/// command-line arguments. This one works backwards from the other
7474
/// ‘deduce’ functions, returning Err if help needs to be shown.
75+
///
76+
/// We don’t do any strict-mode error checking here: it’s OK to give
77+
/// the --help or --long flags more than once. Actually checking for
78+
/// errors when the user wants help is kind of petty!
7579
pub fn deduce(matches: &MatchedFlags) -> Result<(), HelpString> {
76-
if matches.has(&flags::HELP) {
77-
let only_long = matches.has(&flags::LONG);
80+
if matches.count(&flags::HELP) > 0 {
81+
let only_long = matches.count(&flags::LONG) > 0;
7882
let git = cfg!(feature="git");
7983
let xattrs = xattr::ENABLED;
8084
Err(HelpString { only_long, git, xattrs })
@@ -126,21 +130,21 @@ mod test {
126130
#[test]
127131
fn help() {
128132
let args = [ os("--help") ];
129-
let opts = Options::getopts(&args);
133+
let opts = Options::parse(&args, None);
130134
assert!(opts.is_err())
131135
}
132136

133137
#[test]
134138
fn help_with_file() {
135139
let args = [ os("--help"), os("me") ];
136-
let opts = Options::getopts(&args);
140+
let opts = Options::parse(&args, None);
137141
assert!(opts.is_err())
138142
}
139143

140144
#[test]
141145
fn unhelpful() {
142146
let args = [];
143-
let opts = Options::getopts(&args);
147+
let opts = Options::parse(&args, None);
144148
assert!(opts.is_ok()) // no help when --help isn’t passed
145149
}
146150
}

0 commit comments

Comments
 (0)