1 use crate::fs::feature::xattr;
2 use crate::options::{flags, OptionsError, NumberSource, Vars};
3 use crate::options::parser::MatchedFlags;
4 use crate::output::{View, Mode, TerminalWidth, grid, details};
5 use crate::output::grid_details::{self, RowThreshold};
6 use crate::output::file_name::Options as FileStyle;
7 use crate::output::table::{TimeTypes, SizeFormat, UserFormat, Columns, Options as TableOptions};
8 use crate::output::time::TimeFormat;
9 
10 
11 impl View {
deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError>12     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
13         let mode = Mode::deduce(matches, vars)?;
14         let width = TerminalWidth::deduce(vars)?;
15         let file_style = FileStyle::deduce(matches, vars)?;
16         Ok(Self { mode, width, file_style })
17     }
18 }
19 
20 
21 impl Mode {
22 
23     /// Determine which viewing mode to use based on the user’s options.
24     ///
25     /// As with the other options, arguments are scanned right-to-left and the
26     /// first flag found is matched, so `exa --oneline --long` will pick a
27     /// details view, and `exa --long --oneline` will pick the lines view.
28     ///
29     /// This is complicated a little by the fact that `--grid` and `--tree`
30     /// can also combine with `--long`, so care has to be taken to use the
deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError>31     pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
32         let flag = matches.has_where_any(|f| f.matches(&flags::LONG) || f.matches(&flags::ONE_LINE)
33                                           || f.matches(&flags::GRID) || f.matches(&flags::TREE));
34 
35         let flag = match flag {
36             Some(f) => f,
37             None => {
38                 Self::strict_check_long_flags(matches)?;
39                 let grid = grid::Options::deduce(matches)?;
40                 return Ok(Self::Grid(grid));
41             }
42         };
43 
44         if flag.matches(&flags::LONG)
45         || (flag.matches(&flags::TREE) && matches.has(&flags::LONG)?)
46         || (flag.matches(&flags::GRID) && matches.has(&flags::LONG)?)
47         {
48             let _ = matches.has(&flags::LONG)?;
49             let details = details::Options::deduce_long(matches, vars)?;
50 
51             let flag = matches.has_where_any(|f| f.matches(&flags::GRID) || f.matches(&flags::TREE));
52 
53             if flag.is_some() && flag.unwrap().matches(&flags::GRID) {
54                 let _ = matches.has(&flags::GRID)?;
55                 let grid = grid::Options::deduce(matches)?;
56                 let row_threshold = RowThreshold::deduce(vars)?;
57                 let grid_details = grid_details::Options { grid, details, row_threshold };
58                 return Ok(Self::GridDetails(grid_details));
59             }
60             else {
61                 // the --tree case is handled by the DirAction parser later
62                 return Ok(Self::Details(details));
63             }
64         }
65 
66         Self::strict_check_long_flags(matches)?;
67 
68         if flag.matches(&flags::TREE) {
69             let _ = matches.has(&flags::TREE)?;
70             let details = details::Options::deduce_tree(matches)?;
71             return Ok(Self::Details(details));
72         }
73 
74         if flag.matches(&flags::ONE_LINE) {
75             let _ = matches.has(&flags::ONE_LINE)?;
76             return Ok(Self::Lines);
77         }
78 
79         let grid = grid::Options::deduce(matches)?;
80         Ok(Self::Grid(grid))
81     }
82 
strict_check_long_flags(matches: &MatchedFlags<'_>) -> Result<(), OptionsError>83     fn strict_check_long_flags(matches: &MatchedFlags<'_>) -> Result<(), OptionsError> {
84         // If --long hasn’t been passed, then check if we need to warn the
85         // user about flags that won’t have any effect.
86         if matches.is_strict() {
87             for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
88                              &flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP, &flags::NUMERIC ] {
89                 if matches.has(option)? {
90                     return Err(OptionsError::Useless(*option, false, &flags::LONG));
91                 }
92             }
93 
94             if matches.has(&flags::GIT)? {
95                 return Err(OptionsError::Useless(&flags::GIT, false, &flags::LONG));
96             }
97             else if matches.has(&flags::LEVEL)? && ! matches.has(&flags::RECURSE)? && ! matches.has(&flags::TREE)? {
98                 return Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
99             }
100         }
101 
102         Ok(())
103     }
104 }
105 
106 
107 impl grid::Options {
deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError>108     fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
109         let grid = grid::Options {
110             across: matches.has(&flags::ACROSS)?,
111         };
112 
113         Ok(grid)
114     }
115 }
116 
117 
118 impl details::Options {
deduce_tree(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError>119     fn deduce_tree(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
120         let details = details::Options {
121             table: None,
122             header: false,
123             xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
124         };
125 
126         Ok(details)
127     }
128 
deduce_long<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError>129     fn deduce_long<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
130         if matches.is_strict() {
131             if matches.has(&flags::ACROSS)? && ! matches.has(&flags::GRID)? {
132                 return Err(OptionsError::Useless(&flags::ACROSS, true, &flags::LONG));
133             }
134             else if matches.has(&flags::ONE_LINE)? {
135                 return Err(OptionsError::Useless(&flags::ONE_LINE, true, &flags::LONG));
136             }
137         }
138 
139         Ok(details::Options {
140             table: Some(TableOptions::deduce(matches, vars)?),
141             header: matches.has(&flags::HEADER)?,
142             xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
143         })
144     }
145 }
146 
147 
148 impl TerminalWidth {
deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError>149     fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
150         use crate::options::vars;
151 
152         if let Some(columns) = vars.get(vars::COLUMNS).and_then(|s| s.into_string().ok()) {
153             match columns.parse() {
154                 Ok(width) => {
155                     Ok(Self::Set(width))
156                 }
157                 Err(e) => {
158                     let source = NumberSource::Env(vars::COLUMNS);
159                     Err(OptionsError::FailedParse(columns, source, e))
160                 }
161             }
162         }
163         else {
164             Ok(Self::Automatic)
165         }
166     }
167 }
168 
169 
170 impl RowThreshold {
deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError>171     fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
172         use crate::options::vars;
173 
174         if let Some(columns) = vars.get(vars::EXA_GRID_ROWS).and_then(|s| s.into_string().ok()) {
175             match columns.parse() {
176                 Ok(rows) => {
177                     Ok(Self::MinimumRows(rows))
178                 }
179                 Err(e) => {
180                     let source = NumberSource::Env(vars::EXA_GRID_ROWS);
181                     Err(OptionsError::FailedParse(columns, source, e))
182                 }
183             }
184         }
185         else {
186             Ok(Self::AlwaysGrid)
187         }
188     }
189 }
190 
191 
192 impl TableOptions {
deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError>193     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
194         let time_format = TimeFormat::deduce(matches, vars)?;
195         let size_format = SizeFormat::deduce(matches)?;
196         let user_format = UserFormat::deduce(matches)?;
197         let columns = Columns::deduce(matches)?;
198         Ok(Self { time_format, size_format, columns , user_format})
199     }
200 }
201 
202 
203 impl Columns {
deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError>204     fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
205         let time_types = TimeTypes::deduce(matches)?;
206         let git = matches.has(&flags::GIT)?;
207 
208         let blocks = matches.has(&flags::BLOCKS)?;
209         let group  = matches.has(&flags::GROUP)?;
210         let inode  = matches.has(&flags::INODE)?;
211         let links  = matches.has(&flags::LINKS)?;
212         let octal  = matches.has(&flags::OCTAL)?;
213 
214         let permissions = ! matches.has(&flags::NO_PERMISSIONS)?;
215         let filesize =    ! matches.has(&flags::NO_FILESIZE)?;
216         let user =        ! matches.has(&flags::NO_USER)?;
217 
218         Ok(Self { time_types, git, octal, blocks, group, inode, links, permissions, filesize, user })
219     }
220 }
221 
222 
223 impl SizeFormat {
224 
225     /// Determine which file size to use in the file size column based on
226     /// the user’s options.
227     ///
228     /// The default mode is to use the decimal prefixes, as they are the
229     /// most commonly-understood, and don’t involve trying to parse large
230     /// strings of digits in your head. Changing the format to anything else
231     /// involves the `--binary` or `--bytes` flags, and these conflict with
232     /// each other.
deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError>233     fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
234         let flag = matches.has_where(|f| f.matches(&flags::BINARY) || f.matches(&flags::BYTES))?;
235 
236         Ok(match flag {
237             Some(f) if f.matches(&flags::BINARY)  => Self::BinaryBytes,
238             Some(f) if f.matches(&flags::BYTES)   => Self::JustBytes,
239             _                                     => Self::DecimalBytes,
240         })
241     }
242 }
243 
244 
245 impl TimeFormat {
246 
247     /// Determine how time should be formatted in timestamp columns.
deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError>248     fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
249         let word =
250             if let Some(w) = matches.get(&flags::TIME_STYLE)? {
251                 w.to_os_string()
252             }
253             else {
254                 use crate::options::vars;
255                 match vars.get(vars::TIME_STYLE) {
256                     Some(ref t) if ! t.is_empty()  => t.clone(),
257                     _                              => return Ok(Self::DefaultFormat)
258                 }
259             };
260 
261         if &word == "default" {
262             Ok(Self::DefaultFormat)
263         }
264         else if &word == "iso" {
265             Ok(Self::ISOFormat)
266         }
267         else if &word == "long-iso" {
268             Ok(Self::LongISO)
269         }
270         else if &word == "full-iso" {
271             Ok(Self::FullISO)
272         }
273         else {
274             Err(OptionsError::BadArgument(&flags::TIME_STYLE, word))
275         }
276     }
277 }
278 
279 
280 impl UserFormat {
deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError>281     fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
282         let flag = matches.has(&flags::NUMERIC)?;
283         Ok(if flag { Self::Numeric } else { Self::Name })
284     }
285 }
286 
287 
288 impl TimeTypes {
289 
290     /// Determine which of a file’s time fields should be displayed for it
291     /// based on the user’s options.
292     ///
293     /// There are two separate ways to pick which fields to show: with a
294     /// flag (such as `--modified`) or with a parameter (such as
295     /// `--time=modified`). An error is signaled if both ways are used.
296     ///
297     /// It’s valid to show more than one column by passing in more than one
298     /// option, but passing *no* options means that the user just wants to
299     /// see the default set.
deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError>300     fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
301         let possible_word = matches.get(&flags::TIME)?;
302         let modified = matches.has(&flags::MODIFIED)?;
303         let changed  = matches.has(&flags::CHANGED)?;
304         let accessed = matches.has(&flags::ACCESSED)?;
305         let created  = matches.has(&flags::CREATED)?;
306 
307         let no_time = matches.has(&flags::NO_TIME)?;
308 
309         let time_types = if no_time {
310             Self { modified: false, changed: false, accessed: false, created: false }
311         } else if let Some(word) = possible_word {
312             if modified {
313                 return Err(OptionsError::Useless(&flags::MODIFIED, true, &flags::TIME));
314             }
315             else if changed {
316                 return Err(OptionsError::Useless(&flags::CHANGED, true, &flags::TIME));
317             }
318             else if accessed {
319                 return Err(OptionsError::Useless(&flags::ACCESSED, true, &flags::TIME));
320             }
321             else if created {
322                 return Err(OptionsError::Useless(&flags::CREATED, true, &flags::TIME));
323             }
324             else if word == "mod" || word == "modified" {
325                 Self { modified: true,  changed: false, accessed: false, created: false }
326             }
327             else if word == "ch" || word == "changed" {
328                 Self { modified: false, changed: true,  accessed: false, created: false }
329             }
330             else if word == "acc" || word == "accessed" {
331                 Self { modified: false, changed: false, accessed: true,  created: false }
332             }
333             else if word == "cr" || word == "created" {
334                 Self { modified: false, changed: false, accessed: false, created: true  }
335             }
336             else {
337                 return Err(OptionsError::BadArgument(&flags::TIME, word.into()));
338             }
339         }
340         else if modified || changed || accessed || created {
341             Self { modified, changed, accessed, created }
342         }
343         else {
344             Self::default()
345         };
346 
347         Ok(time_types)
348     }
349 }
350 
351 
352 #[cfg(test)]
353 mod test {
354     use super::*;
355     use std::ffi::OsString;
356     use crate::options::flags;
357     use crate::options::parser::{Flag, Arg};
358 
359     use crate::options::test::parse_for_test;
360     use crate::options::test::Strictnesses::*;
361 
362     static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES,    &flags::TIME_STYLE,
363                                    &flags::TIME,   &flags::MODIFIED, &flags::CHANGED,
364                                    &flags::CREATED, &flags::ACCESSED,
365                                    &flags::HEADER, &flags::GROUP,  &flags::INODE, &flags::GIT,
366                                    &flags::LINKS,  &flags::BLOCKS, &flags::LONG,  &flags::LEVEL,
367                                    &flags::GRID,   &flags::ACROSS, &flags::ONE_LINE, &flags::TREE,
368                                    &flags::NUMERIC ];
369 
370     macro_rules! test {
371 
372         ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
373             /// Macro that writes a test.
374             /// If testing both strictnesses, they’ll both be done in the same function.
375             #[test]
376             fn $name() {
377                 for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
378                     assert_eq!(result, $result);
379                 }
380             }
381         };
382 
383         ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => {
384             /// Special macro for testing Err results.
385             /// This is needed because sometimes the Ok type doesn’t implement PartialEq.
386             #[test]
387             fn $name() {
388                 for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
389                     assert_eq!(result.unwrap_err(), $result);
390                 }
391             }
392         };
393 
394         ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => like $pat:pat) => {
395             /// More general macro for testing against a pattern.
396             /// Instead of using PartialEq, this just tests if it matches a pat.
397             #[test]
398             fn $name() {
399                 for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
400                     println!("Testing {:?}", result);
401                     match result {
402                         $pat => assert!(true),
403                         _    => assert!(false),
404                     }
405                 }
406             }
407         };
408 
409 
410         ($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => err $result:expr) => {
411             /// Like above, but with $vars.
412             #[test]
413             fn $name() {
414                 for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {
415                     assert_eq!(result.unwrap_err(), $result);
416                 }
417             }
418         };
419 
420         ($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => like $pat:pat) => {
421             /// Like further above, but with $vars.
422             #[test]
423             fn $name() {
424                 for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {
425                     println!("Testing {:?}", result);
426                     match result {
427                         $pat => assert!(true),
428                         _    => assert!(false),
429                     }
430                 }
431             }
432         };
433     }
434 
435 
436     mod size_formats {
437         use super::*;
438 
439         // Default behaviour
440         test!(empty:   SizeFormat <- [];                       Both => Ok(SizeFormat::DecimalBytes));
441 
442         // Individual flags
443         test!(binary:  SizeFormat <- ["--binary"];             Both => Ok(SizeFormat::BinaryBytes));
444         test!(bytes:   SizeFormat <- ["--bytes"];              Both => Ok(SizeFormat::JustBytes));
445 
446         // Overriding
447         test!(both_1:  SizeFormat <- ["--binary", "--binary"];  Last => Ok(SizeFormat::BinaryBytes));
448         test!(both_2:  SizeFormat <- ["--bytes",  "--binary"];  Last => Ok(SizeFormat::BinaryBytes));
449         test!(both_3:  SizeFormat <- ["--binary", "--bytes"];   Last => Ok(SizeFormat::JustBytes));
450         test!(both_4:  SizeFormat <- ["--bytes",  "--bytes"];   Last => Ok(SizeFormat::JustBytes));
451 
452         test!(both_5:  SizeFormat <- ["--binary", "--binary"];  Complain => err OptionsError::Duplicate(Flag::Long("binary"), Flag::Long("binary")));
453         test!(both_6:  SizeFormat <- ["--bytes",  "--binary"];  Complain => err OptionsError::Duplicate(Flag::Long("bytes"),  Flag::Long("binary")));
454         test!(both_7:  SizeFormat <- ["--binary", "--bytes"];   Complain => err OptionsError::Duplicate(Flag::Long("binary"), Flag::Long("bytes")));
455         test!(both_8:  SizeFormat <- ["--bytes",  "--bytes"];   Complain => err OptionsError::Duplicate(Flag::Long("bytes"),  Flag::Long("bytes")));
456     }
457 
458 
459     mod time_formats {
460         use super::*;
461 
462         // These tests use pattern matching because TimeFormat doesn’t
463         // implement PartialEq.
464 
465         // Default behaviour
466         test!(empty:     TimeFormat <- [], None;                            Both => like Ok(TimeFormat::DefaultFormat));
467 
468         // Individual settings
469         test!(default:   TimeFormat <- ["--time-style=default"], None;      Both => like Ok(TimeFormat::DefaultFormat));
470         test!(iso:       TimeFormat <- ["--time-style", "iso"], None;       Both => like Ok(TimeFormat::ISOFormat));
471         test!(long_iso:  TimeFormat <- ["--time-style=long-iso"], None;     Both => like Ok(TimeFormat::LongISO));
472         test!(full_iso:  TimeFormat <- ["--time-style", "full-iso"], None;  Both => like Ok(TimeFormat::FullISO));
473 
474         // Overriding
475         test!(actually:  TimeFormat <- ["--time-style=default", "--time-style", "iso"], None;  Last => like Ok(TimeFormat::ISOFormat));
476         test!(actual_2:  TimeFormat <- ["--time-style=default", "--time-style", "iso"], None;  Complain => err OptionsError::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")));
477 
478         test!(nevermind: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"], None;  Last => like Ok(TimeFormat::FullISO));
479         test!(nevermore: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"], None;  Complain => err OptionsError::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")));
480 
481         // Errors
482         test!(daily:     TimeFormat <- ["--time-style=24-hour"], None;  Both => err OptionsError::BadArgument(&flags::TIME_STYLE, OsString::from("24-hour")));
483 
484         // `TIME_STYLE` environment variable is defined.
485         // If the time-style argument is not given, `TIME_STYLE` is used.
486         test!(use_env:     TimeFormat <- [], Some("long-iso".into());  Both => like Ok(TimeFormat::LongISO));
487 
488         // If the time-style argument is given, `TIME_STYLE` is overriding.
489         test!(override_env:     TimeFormat <- ["--time-style=full-iso"], Some("long-iso".into());  Both => like Ok(TimeFormat::FullISO));
490     }
491 
492 
493     mod time_types {
494         use super::*;
495 
496         // Default behaviour
497         test!(empty:     TimeTypes <- [];                      Both => Ok(TimeTypes::default()));
498 
499         // Modified
500         test!(modified:  TimeTypes <- ["--modified"];          Both => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));
501         test!(m:         TimeTypes <- ["-m"];                  Both => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));
502         test!(time_mod:  TimeTypes <- ["--time=modified"];     Both => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));
503         test!(t_m:       TimeTypes <- ["-tmod"];               Both => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));
504 
505         // Changed
506         #[cfg(target_family = "unix")]
507         test!(changed:   TimeTypes <- ["--changed"];           Both => Ok(TimeTypes { modified: false, changed: true,  accessed: false, created: false }));
508         #[cfg(target_family = "unix")]
509         test!(time_ch:   TimeTypes <- ["--time=changed"];      Both => Ok(TimeTypes { modified: false, changed: true,  accessed: false, created: false }));
510         #[cfg(target_family = "unix")]
511         test!(t_ch:    TimeTypes <- ["-t", "ch"];              Both => Ok(TimeTypes { modified: false, changed: true,  accessed: false, created: false }));
512 
513         // Accessed
514         test!(acc:       TimeTypes <- ["--accessed"];          Both => Ok(TimeTypes { modified: false, changed: false, accessed: true,  created: false }));
515         test!(a:         TimeTypes <- ["-u"];                  Both => Ok(TimeTypes { modified: false, changed: false, accessed: true,  created: false }));
516         test!(time_acc:  TimeTypes <- ["--time", "accessed"];  Both => Ok(TimeTypes { modified: false, changed: false, accessed: true,  created: false }));
517         test!(time_a:    TimeTypes <- ["-t", "acc"];           Both => Ok(TimeTypes { modified: false, changed: false, accessed: true,  created: false }));
518 
519         // Created
520         test!(cr:        TimeTypes <- ["--created"];           Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true  }));
521         test!(c:         TimeTypes <- ["-U"];                  Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true  }));
522         test!(time_cr:   TimeTypes <- ["--time=created"];      Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true  }));
523         test!(t_cr:      TimeTypes <- ["-tcr"];                Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true  }));
524 
525         // Multiples
526         test!(time_uu:   TimeTypes <- ["-u", "--modified"];    Both => Ok(TimeTypes { modified: true,  changed: false, accessed: true,  created: false }));
527 
528 
529         // Errors
530         test!(time_tea:  TimeTypes <- ["--time=tea"];          Both => err OptionsError::BadArgument(&flags::TIME, OsString::from("tea")));
531         test!(t_ea:      TimeTypes <- ["-tea"];                Both => err OptionsError::BadArgument(&flags::TIME, OsString::from("ea")));
532 
533         // Overriding
534         test!(overridden:   TimeTypes <- ["-tcr", "-tmod"];    Last => Ok(TimeTypes { modified: true,  changed: false, accessed: false, created: false }));
535         test!(overridden_2: TimeTypes <- ["-tcr", "-tmod"];    Complain => err OptionsError::Duplicate(Flag::Short(b't'), Flag::Short(b't')));
536     }
537 
538 
539     mod views {
540         use super::*;
541 
542         use crate::output::grid::Options as GridOptions;
543 
544 
545         // Default
546         test!(empty:         Mode <- [], None;            Both => like Ok(Mode::Grid(_)));
547 
548         // Grid views
549         test!(original_g:    Mode <- ["-G"], None;        Both => like Ok(Mode::Grid(GridOptions { across: false, .. })));
550         test!(grid:          Mode <- ["--grid"], None;    Both => like Ok(Mode::Grid(GridOptions { across: false, .. })));
551         test!(across:        Mode <- ["--across"], None;  Both => like Ok(Mode::Grid(GridOptions { across: true,  .. })));
552         test!(gracross:      Mode <- ["-xG"], None;       Both => like Ok(Mode::Grid(GridOptions { across: true,  .. })));
553 
554         // Lines views
555         test!(lines:         Mode <- ["--oneline"], None;     Both => like Ok(Mode::Lines));
556         test!(prima:         Mode <- ["-1"], None;            Both => like Ok(Mode::Lines));
557 
558         // Details views
559         test!(long:          Mode <- ["--long"], None;    Both => like Ok(Mode::Details(_)));
560         test!(ell:           Mode <- ["-l"], None;        Both => like Ok(Mode::Details(_)));
561 
562         // Grid-details views
563         test!(lid:           Mode <- ["--long", "--grid"], None;  Both => like Ok(Mode::GridDetails(_)));
564         test!(leg:           Mode <- ["-lG"], None;               Both => like Ok(Mode::GridDetails(_)));
565 
566         // Options that do nothing with --long
567         test!(long_across:   Mode <- ["--long", "--across"],   None;  Last => like Ok(Mode::Details(_)));
568 
569         // Options that do nothing without --long
570         test!(just_header:   Mode <- ["--header"],   None;  Last => like Ok(Mode::Grid(_)));
571         test!(just_group:    Mode <- ["--group"],    None;  Last => like Ok(Mode::Grid(_)));
572         test!(just_inode:    Mode <- ["--inode"],    None;  Last => like Ok(Mode::Grid(_)));
573         test!(just_links:    Mode <- ["--links"],    None;  Last => like Ok(Mode::Grid(_)));
574         test!(just_blocks:   Mode <- ["--blocks"],   None;  Last => like Ok(Mode::Grid(_)));
575         test!(just_binary:   Mode <- ["--binary"],   None;  Last => like Ok(Mode::Grid(_)));
576         test!(just_bytes:    Mode <- ["--bytes"],    None;  Last => like Ok(Mode::Grid(_)));
577         test!(just_numeric:  Mode <- ["--numeric"],  None;  Last => like Ok(Mode::Grid(_)));
578 
579         #[cfg(feature = "git")]
580         test!(just_git:      Mode <- ["--git"],    None;  Last => like Ok(Mode::Grid(_)));
581 
582         test!(just_header_2: Mode <- ["--header"],   None;  Complain => err OptionsError::Useless(&flags::HEADER,  false, &flags::LONG));
583         test!(just_group_2:  Mode <- ["--group"],    None;  Complain => err OptionsError::Useless(&flags::GROUP,   false, &flags::LONG));
584         test!(just_inode_2:  Mode <- ["--inode"],    None;  Complain => err OptionsError::Useless(&flags::INODE,   false, &flags::LONG));
585         test!(just_links_2:  Mode <- ["--links"],    None;  Complain => err OptionsError::Useless(&flags::LINKS,   false, &flags::LONG));
586         test!(just_blocks_2: Mode <- ["--blocks"],   None;  Complain => err OptionsError::Useless(&flags::BLOCKS,  false, &flags::LONG));
587         test!(just_binary_2: Mode <- ["--binary"],   None;  Complain => err OptionsError::Useless(&flags::BINARY,  false, &flags::LONG));
588         test!(just_bytes_2:  Mode <- ["--bytes"],    None;  Complain => err OptionsError::Useless(&flags::BYTES,   false, &flags::LONG));
589         test!(just_numeric2: Mode <- ["--numeric"],  None;  Complain => err OptionsError::Useless(&flags::NUMERIC, false, &flags::LONG));
590 
591         #[cfg(feature = "git")]
592         test!(just_git_2:    Mode <- ["--git"],    None;  Complain => err OptionsError::Useless(&flags::GIT,    false, &flags::LONG));
593 
594         // Contradictions and combinations
595         test!(lgo:           Mode <- ["--long", "--grid", "--oneline"], None;  Both => like Ok(Mode::Lines));
596         test!(lgt:           Mode <- ["--long", "--grid", "--tree"],    None;  Both => like Ok(Mode::Details(_)));
597         test!(tgl:           Mode <- ["--tree", "--grid", "--long"],    None;  Both => like Ok(Mode::GridDetails(_)));
598         test!(tlg:           Mode <- ["--tree", "--long", "--grid"],    None;  Both => like Ok(Mode::GridDetails(_)));
599         test!(ot:            Mode <- ["--oneline", "--tree"],           None;  Both => like Ok(Mode::Details(_)));
600         test!(og:            Mode <- ["--oneline", "--grid"],           None;  Both => like Ok(Mode::Grid(_)));
601         test!(tg:            Mode <- ["--tree", "--grid"],              None;  Both => like Ok(Mode::Grid(_)));
602     }
603 }
604