view.rs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. use output::Colours;
  2. use output::{View, Mode, grid, details};
  3. use output::grid_details::{self, RowThreshold};
  4. use output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
  5. use output::file_name::{Classify, FileStyle};
  6. use output::time::TimeFormat;
  7. use options::{flags, Misfire, Vars};
  8. use options::parser::MatchedFlags;
  9. use fs::feature::xattr;
  10. use info::filetype::FileExtensions;
  11. impl View {
  12. /// Determine which view to use and all of that view’s arguments.
  13. pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<View, Misfire> {
  14. let mode = Mode::deduce(matches, vars)?;
  15. let colours = Colours::deduce(matches)?;
  16. let style = FileStyle::deduce(matches)?;
  17. Ok(View { mode, colours, style })
  18. }
  19. }
  20. impl Mode {
  21. /// Determine the mode from the command-line arguments.
  22. pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Mode, Misfire> {
  23. use options::misfire::Misfire::*;
  24. let long = || {
  25. if matches.has(&flags::ACROSS)? && !matches.has(&flags::GRID)? {
  26. Err(Useless(&flags::ACROSS, true, &flags::LONG))
  27. }
  28. else if matches.has(&flags::ONE_LINE)? {
  29. Err(Useless(&flags::ONE_LINE, true, &flags::LONG))
  30. }
  31. else {
  32. Ok(details::Options {
  33. table: Some(TableOptions::deduce(matches)?),
  34. header: matches.has(&flags::HEADER)?,
  35. xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
  36. })
  37. }
  38. };
  39. let other_options_scan = || {
  40. if let Some(width) = TerminalWidth::deduce(vars)?.width() {
  41. if matches.has(&flags::ONE_LINE)? {
  42. if matches.has(&flags::ACROSS)? {
  43. Err(Useless(&flags::ACROSS, true, &flags::ONE_LINE))
  44. }
  45. else {
  46. Ok(Mode::Lines)
  47. }
  48. }
  49. else if matches.has(&flags::TREE)? {
  50. let details = details::Options {
  51. table: None,
  52. header: false,
  53. xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
  54. };
  55. Ok(Mode::Details(details))
  56. }
  57. else {
  58. let grid = grid::Options {
  59. across: matches.has(&flags::ACROSS)?,
  60. console_width: width,
  61. };
  62. Ok(Mode::Grid(grid))
  63. }
  64. }
  65. else {
  66. // If the terminal width couldn’t be matched for some reason, such
  67. // as the program’s stdout being connected to a file, then
  68. // fallback to the lines view.
  69. if matches.has(&flags::TREE)? {
  70. let details = details::Options {
  71. table: None,
  72. header: false,
  73. xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
  74. };
  75. Ok(Mode::Details(details))
  76. }
  77. else {
  78. Ok(Mode::Lines)
  79. }
  80. }
  81. };
  82. if matches.has(&flags::LONG)? {
  83. let details = long()?;
  84. if matches.has(&flags::GRID)? {
  85. let other_options_mode = other_options_scan()?;
  86. if let Mode::Grid(grid) = other_options_mode {
  87. let row_threshold = RowThreshold::deduce(vars)?;
  88. return Ok(Mode::GridDetails(grid_details::Options { grid, details, row_threshold }));
  89. }
  90. else {
  91. return Ok(other_options_mode);
  92. }
  93. }
  94. else {
  95. return Ok(Mode::Details(details));
  96. }
  97. }
  98. // If --long hasn’t been passed, then check if we need to warn the
  99. // user about flags that won’t have any effect.
  100. if matches.is_strict() {
  101. for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
  102. &flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP ] {
  103. if matches.has(option)? {
  104. return Err(Useless(*option, false, &flags::LONG));
  105. }
  106. }
  107. if cfg!(feature="git") && matches.has(&flags::GIT)? {
  108. return Err(Useless(&flags::GIT, false, &flags::LONG));
  109. }
  110. else if matches.has(&flags::LEVEL)? && !matches.has(&flags::RECURSE)? && !matches.has(&flags::TREE)? {
  111. // TODO: I'm not sure if the code even gets this far.
  112. // There is an identical check in dir_action
  113. return Err(Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
  114. }
  115. }
  116. other_options_scan()
  117. }
  118. }
  119. /// The width of the terminal requested by the user.
  120. #[derive(PartialEq, Debug)]
  121. enum TerminalWidth {
  122. /// The user requested this specific number of columns.
  123. Set(usize),
  124. /// The terminal was found to have this number of columns.
  125. Terminal(usize),
  126. /// The user didn’t request any particular terminal width.
  127. Unset,
  128. }
  129. impl TerminalWidth {
  130. /// Determine a requested terminal width from the command-line arguments.
  131. ///
  132. /// Returns an error if a requested width doesn’t parse to an integer.
  133. fn deduce<V: Vars>(vars: &V) -> Result<TerminalWidth, Misfire> {
  134. if let Some(columns) = vars.get("COLUMNS").and_then(|s| s.into_string().ok()) {
  135. match columns.parse() {
  136. Ok(width) => Ok(TerminalWidth::Set(width)),
  137. Err(e) => Err(Misfire::FailedParse(e)),
  138. }
  139. }
  140. else if let Some(width) = *TERM_WIDTH {
  141. Ok(TerminalWidth::Terminal(width))
  142. }
  143. else {
  144. Ok(TerminalWidth::Unset)
  145. }
  146. }
  147. fn width(&self) -> Option<usize> {
  148. match *self {
  149. TerminalWidth::Set(width) |
  150. TerminalWidth::Terminal(width) => Some(width),
  151. TerminalWidth::Unset => None,
  152. }
  153. }
  154. }
  155. impl RowThreshold {
  156. /// Determine whether to use a row threshold based on the given
  157. /// environment variables.
  158. fn deduce<V: Vars>(vars: &V) -> Result<RowThreshold, Misfire> {
  159. if let Some(columns) = vars.get("EXA_GRID_ROWS").and_then(|s| s.into_string().ok()) {
  160. match columns.parse() {
  161. Ok(rows) => Ok(RowThreshold::MinimumRows(rows)),
  162. Err(e) => Err(Misfire::FailedParse(e)),
  163. }
  164. }
  165. else {
  166. Ok(RowThreshold::AlwaysGrid)
  167. }
  168. }
  169. }
  170. impl TableOptions {
  171. fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
  172. let env = Environment::load_all();
  173. let time_format = TimeFormat::deduce(matches)?;
  174. let size_format = SizeFormat::deduce(matches)?;
  175. let extra_columns = Columns::deduce(matches)?;
  176. Ok(TableOptions { env, time_format, size_format, extra_columns })
  177. }
  178. }
  179. impl Columns {
  180. fn deduce(matches: &MatchedFlags) -> Result<Self, Misfire> {
  181. let time_types = TimeTypes::deduce(matches)?;
  182. let git = cfg!(feature="git") && matches.has(&flags::GIT)?;
  183. let blocks = matches.has(&flags::BLOCKS)?;
  184. let group = matches.has(&flags::GROUP)?;
  185. let inode = matches.has(&flags::INODE)?;
  186. let links = matches.has(&flags::LINKS)?;
  187. Ok(Columns { time_types, git, blocks, group, inode, links })
  188. }
  189. }
  190. impl SizeFormat {
  191. /// Determine which file size to use in the file size column based on
  192. /// the user’s options.
  193. ///
  194. /// The default mode is to use the decimal prefixes, as they are the
  195. /// most commonly-understood, and don’t involve trying to parse large
  196. /// strings of digits in your head. Changing the format to anything else
  197. /// involves the `--binary` or `--bytes` flags, and these conflict with
  198. /// each other.
  199. fn deduce(matches: &MatchedFlags) -> Result<SizeFormat, Misfire> {
  200. let flag = matches.has_where(|f| f.matches(&flags::BINARY) || f.matches(&flags::BYTES))?;
  201. Ok(match flag {
  202. Some(f) if f.matches(&flags::BINARY) => SizeFormat::BinaryBytes,
  203. Some(f) if f.matches(&flags::BYTES) => SizeFormat::JustBytes,
  204. _ => SizeFormat::DecimalBytes,
  205. })
  206. }
  207. }
  208. const TIME_STYLES: &[&str] = &["default", "long-iso", "full-iso", "iso"];
  209. impl TimeFormat {
  210. /// Determine how time should be formatted in timestamp columns.
  211. fn deduce(matches: &MatchedFlags) -> Result<TimeFormat, Misfire> {
  212. pub use output::time::{DefaultFormat, ISOFormat};
  213. let word = match matches.get(&flags::TIME_STYLE)? {
  214. Some(w) => w,
  215. None => return Ok(TimeFormat::DefaultFormat(DefaultFormat::new())),
  216. };
  217. if word == "default" {
  218. Ok(TimeFormat::DefaultFormat(DefaultFormat::new()))
  219. }
  220. else if word == "iso" {
  221. Ok(TimeFormat::ISOFormat(ISOFormat::new()))
  222. }
  223. else if word == "long-iso" {
  224. Ok(TimeFormat::LongISO)
  225. }
  226. else if word == "full-iso" {
  227. Ok(TimeFormat::FullISO)
  228. }
  229. else {
  230. Err(Misfire::bad_argument(&flags::TIME_STYLE, word, TIME_STYLES))
  231. }
  232. }
  233. }
  234. static TIMES: &[&str] = &["modified", "accessed", "created"];
  235. impl TimeTypes {
  236. /// Determine which of a file’s time fields should be displayed for it
  237. /// based on the user’s options.
  238. ///
  239. /// There are two separate ways to pick which fields to show: with a
  240. /// flag (such as `--modified`) or with a parameter (such as
  241. /// `--time=modified`). An error is signaled if both ways are used.
  242. ///
  243. /// It’s valid to show more than one column by passing in more than one
  244. /// option, but passing *no* options means that the user just wants to
  245. /// see the default set.
  246. fn deduce(matches: &MatchedFlags) -> Result<TimeTypes, Misfire> {
  247. let possible_word = matches.get(&flags::TIME)?;
  248. let modified = matches.has(&flags::MODIFIED)?;
  249. let created = matches.has(&flags::CREATED)?;
  250. let accessed = matches.has(&flags::ACCESSED)?;
  251. if let Some(word) = possible_word {
  252. if modified {
  253. Err(Misfire::Useless(&flags::MODIFIED, true, &flags::TIME))
  254. }
  255. else if created {
  256. Err(Misfire::Useless(&flags::CREATED, true, &flags::TIME))
  257. }
  258. else if accessed {
  259. Err(Misfire::Useless(&flags::ACCESSED, true, &flags::TIME))
  260. }
  261. else if word == "mod" || word == "modified" {
  262. Ok(TimeTypes { accessed: false, modified: true, created: false })
  263. }
  264. else if word == "acc" || word == "accessed" {
  265. Ok(TimeTypes { accessed: true, modified: false, created: false })
  266. }
  267. else if word == "cr" || word == "created" {
  268. Ok(TimeTypes { accessed: false, modified: false, created: true })
  269. }
  270. else {
  271. Err(Misfire::bad_argument(&flags::TIME, word, TIMES))
  272. }
  273. }
  274. else if modified || created || accessed {
  275. Ok(TimeTypes { accessed, modified, created })
  276. }
  277. else {
  278. Ok(TimeTypes::default())
  279. }
  280. }
  281. }
  282. /// Under what circumstances we should display coloured, rather than plain,
  283. /// output to the terminal.
  284. ///
  285. /// By default, we want to display the colours when stdout can display them.
  286. /// Turning them on when output is going to, say, a pipe, would make programs
  287. /// such as `grep` or `more` not work properly. So the `Automatic` mode does
  288. /// this check and only displays colours when they can be truly appreciated.
  289. #[derive(PartialEq, Debug)]
  290. enum TerminalColours {
  291. /// Display them even when output isn’t going to a terminal.
  292. Always,
  293. /// Display them when output is going to a terminal, but not otherwise.
  294. Automatic,
  295. /// Never display them, even when output is going to a terminal.
  296. Never,
  297. }
  298. impl Default for TerminalColours {
  299. fn default() -> TerminalColours {
  300. TerminalColours::Automatic
  301. }
  302. }
  303. const COLOURS: &[&str] = &["always", "auto", "never"];
  304. impl TerminalColours {
  305. /// Determine which terminal colour conditions to use.
  306. fn deduce(matches: &MatchedFlags) -> Result<TerminalColours, Misfire> {
  307. let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
  308. Some(w) => w,
  309. None => return Ok(TerminalColours::default()),
  310. };
  311. if word == "always" {
  312. Ok(TerminalColours::Always)
  313. }
  314. else if word == "auto" || word == "automatic" {
  315. Ok(TerminalColours::Automatic)
  316. }
  317. else if word == "never" {
  318. Ok(TerminalColours::Never)
  319. }
  320. else {
  321. Err(Misfire::bad_argument(&flags::COLOR, word, COLOURS))
  322. }
  323. }
  324. }
  325. impl Colours {
  326. fn deduce(matches: &MatchedFlags) -> Result<Colours, Misfire> {
  327. use self::TerminalColours::*;
  328. let tc = TerminalColours::deduce(matches)?;
  329. if tc == Always || (tc == Automatic && TERM_WIDTH.is_some()) {
  330. let scale = matches.has(&flags::COLOR_SCALE)? || matches.has(&flags::COLOUR_SCALE)?;
  331. Ok(Colours::colourful(scale))
  332. }
  333. else {
  334. Ok(Colours::plain())
  335. }
  336. }
  337. }
  338. impl FileStyle {
  339. fn deduce(matches: &MatchedFlags) -> Result<FileStyle, Misfire> {
  340. let classify = Classify::deduce(matches)?;
  341. let exts = FileExtensions;
  342. Ok(FileStyle { classify, exts })
  343. }
  344. }
  345. impl Classify {
  346. fn deduce(matches: &MatchedFlags) -> Result<Classify, Misfire> {
  347. let flagged = matches.has(&flags::CLASSIFY)?;
  348. Ok(if flagged { Classify::AddFileIndicators }
  349. else { Classify::JustFilenames })
  350. }
  351. }
  352. // Gets, then caches, the width of the terminal that exa is running in.
  353. // This gets used multiple times above, with no real guarantee of order,
  354. // so it’s easier to just cache it the first time it runs.
  355. lazy_static! {
  356. static ref TERM_WIDTH: Option<usize> = {
  357. // All of stdin, stdout, and stderr could not be connected to a
  358. // terminal, but we’re only interested in stdout because it’s
  359. // where the output goes.
  360. use term_size::dimensions_stdout;
  361. dimensions_stdout().map(|t| t.0)
  362. };
  363. }
  364. #[cfg(test)]
  365. mod test {
  366. use super::*;
  367. use std::ffi::OsString;
  368. use options::flags;
  369. use options::parser::{Flag, Arg};
  370. use options::test::parse_for_test;
  371. use options::test::Strictnesses::*;
  372. pub fn os(input: &'static str) -> OsString {
  373. let mut os = OsString::new();
  374. os.push(input);
  375. os
  376. }
  377. static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE,
  378. &flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED,
  379. &flags::COLOR, &flags::COLOUR,
  380. &flags::HEADER, &flags::GROUP, &flags::INODE, &flags::GIT,
  381. &flags::LINKS, &flags::BLOCKS, &flags::LONG, &flags::LEVEL,
  382. &flags::GRID, &flags::ACROSS, &flags::ONE_LINE ];
  383. macro_rules! test {
  384. ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
  385. /// Macro that writes a test.
  386. /// If testing both strictnesses, they’ll both be done in the same function.
  387. #[test]
  388. fn $name() {
  389. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
  390. assert_eq!(result, $result);
  391. }
  392. }
  393. };
  394. ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => {
  395. /// Special macro for testing Err results.
  396. /// This is needed because sometimes the Ok type doesn’t implement PartialEq.
  397. #[test]
  398. fn $name() {
  399. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
  400. assert_eq!(result.unwrap_err(), $result);
  401. }
  402. }
  403. };
  404. ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => like $pat:pat) => {
  405. /// More general macro for testing against a pattern.
  406. /// Instead of using PartialEq, this just tests if it matches a pat.
  407. #[test]
  408. fn $name() {
  409. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
  410. println!("Testing {:?}", result);
  411. match result {
  412. $pat => assert!(true),
  413. _ => assert!(false),
  414. }
  415. }
  416. }
  417. };
  418. ($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => err $result:expr) => {
  419. /// Like above, but with $vars.
  420. #[test]
  421. fn $name() {
  422. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {
  423. assert_eq!(result.unwrap_err(), $result);
  424. }
  425. }
  426. };
  427. ($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => like $pat:pat) => {
  428. /// Like further above, but with $vars.
  429. #[test]
  430. fn $name() {
  431. for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {
  432. println!("Testing {:?}", result);
  433. match result {
  434. $pat => assert!(true),
  435. _ => assert!(false),
  436. }
  437. }
  438. }
  439. };
  440. }
  441. mod size_formats {
  442. use super::*;
  443. // Default behaviour
  444. test!(empty: SizeFormat <- []; Both => Ok(SizeFormat::DecimalBytes));
  445. // Individual flags
  446. test!(binary: SizeFormat <- ["--binary"]; Both => Ok(SizeFormat::BinaryBytes));
  447. test!(bytes: SizeFormat <- ["--bytes"]; Both => Ok(SizeFormat::JustBytes));
  448. // Overriding
  449. test!(both_1: SizeFormat <- ["--binary", "--binary"]; Last => Ok(SizeFormat::BinaryBytes));
  450. test!(both_2: SizeFormat <- ["--bytes", "--binary"]; Last => Ok(SizeFormat::BinaryBytes));
  451. test!(both_3: SizeFormat <- ["--binary", "--bytes"]; Last => Ok(SizeFormat::JustBytes));
  452. test!(both_4: SizeFormat <- ["--bytes", "--bytes"]; Last => Ok(SizeFormat::JustBytes));
  453. test!(both_5: SizeFormat <- ["--binary", "--binary"]; Complain => err Misfire::Duplicate(Flag::Long("binary"), Flag::Long("binary")));
  454. test!(both_6: SizeFormat <- ["--bytes", "--binary"]; Complain => err Misfire::Duplicate(Flag::Long("bytes"), Flag::Long("binary")));
  455. test!(both_7: SizeFormat <- ["--binary", "--bytes"]; Complain => err Misfire::Duplicate(Flag::Long("binary"), Flag::Long("bytes")));
  456. test!(both_8: SizeFormat <- ["--bytes", "--bytes"]; Complain => err Misfire::Duplicate(Flag::Long("bytes"), Flag::Long("bytes")));
  457. }
  458. mod time_formats {
  459. use super::*;
  460. use std::ffi::OsStr;
  461. // These tests use pattern matching because TimeFormat doesn’t
  462. // implement PartialEq.
  463. // Default behaviour
  464. test!(empty: TimeFormat <- []; Both => like Ok(TimeFormat::DefaultFormat(_)));
  465. // Individual settings
  466. test!(default: TimeFormat <- ["--time-style=default"]; Both => like Ok(TimeFormat::DefaultFormat(_)));
  467. test!(iso: TimeFormat <- ["--time-style", "iso"]; Both => like Ok(TimeFormat::ISOFormat(_)));
  468. test!(long_iso: TimeFormat <- ["--time-style=long-iso"]; Both => like Ok(TimeFormat::LongISO));
  469. test!(full_iso: TimeFormat <- ["--time-style", "full-iso"]; Both => like Ok(TimeFormat::FullISO));
  470. // Overriding
  471. test!(actually: TimeFormat <- ["--time-style=default", "--time-style", "iso"]; Last => like Ok(TimeFormat::ISOFormat(_)));
  472. test!(actual_2: TimeFormat <- ["--time-style=default", "--time-style", "iso"]; Complain => err Misfire::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")));
  473. test!(nevermind: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"]; Last => like Ok(TimeFormat::FullISO));
  474. test!(nevermore: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"]; Complain => err Misfire::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")));
  475. // Errors
  476. test!(daily: TimeFormat <- ["--time-style=24-hour"]; Both => err Misfire::bad_argument(&flags::TIME_STYLE, OsStr::new("24-hour"), TIME_STYLES));
  477. }
  478. mod time_types {
  479. use super::*;
  480. // Default behaviour
  481. test!(empty: TimeTypes <- []; Both => Ok(TimeTypes::default()));
  482. // Modified
  483. test!(modified: TimeTypes <- ["--modified"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
  484. test!(m: TimeTypes <- ["-m"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
  485. test!(time_mod: TimeTypes <- ["--time=modified"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
  486. test!(time_m: TimeTypes <- ["-tmod"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
  487. // Accessed
  488. test!(acc: TimeTypes <- ["--accessed"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
  489. test!(a: TimeTypes <- ["-u"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
  490. test!(time_acc: TimeTypes <- ["--time", "accessed"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
  491. test!(time_a: TimeTypes <- ["-t", "acc"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
  492. // Created
  493. test!(cr: TimeTypes <- ["--created"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
  494. test!(c: TimeTypes <- ["-U"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
  495. test!(time_cr: TimeTypes <- ["--time=created"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
  496. test!(time_c: TimeTypes <- ["-tcr"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
  497. // Multiples
  498. test!(time_uu: TimeTypes <- ["-uU"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: true }));
  499. // Errors
  500. test!(time_tea: TimeTypes <- ["--time=tea"]; Both => err Misfire::bad_argument(&flags::TIME, &os("tea"), super::TIMES));
  501. test!(time_ea: TimeTypes <- ["-tea"]; Both => err Misfire::bad_argument(&flags::TIME, &os("ea"), super::TIMES));
  502. // Overriding
  503. test!(overridden: TimeTypes <- ["-tcr", "-tmod"]; Last => Ok(TimeTypes { accessed: false, modified: true, created: false }));
  504. test!(overridden_2: TimeTypes <- ["-tcr", "-tmod"]; Complain => err Misfire::Duplicate(Flag::Short(b't'), Flag::Short(b't')));
  505. }
  506. mod colourses {
  507. use super::*;
  508. // Default
  509. test!(empty: TerminalColours <- []; Both => Ok(TerminalColours::default()));
  510. // --colour
  511. test!(u_always: TerminalColours <- ["--colour=always"]; Both => Ok(TerminalColours::Always));
  512. test!(u_auto: TerminalColours <- ["--colour", "auto"]; Both => Ok(TerminalColours::Automatic));
  513. test!(u_never: TerminalColours <- ["--colour=never"]; Both => Ok(TerminalColours::Never));
  514. // --color
  515. test!(no_u_always: TerminalColours <- ["--color", "always"]; Both => Ok(TerminalColours::Always));
  516. test!(no_u_auto: TerminalColours <- ["--color=auto"]; Both => Ok(TerminalColours::Automatic));
  517. test!(no_u_never: TerminalColours <- ["--color", "never"]; Both => Ok(TerminalColours::Never));
  518. // Errors
  519. test!(no_u_error: TerminalColours <- ["--color=upstream"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("upstream"), super::COLOURS)); // the error is for --color
  520. test!(u_error: TerminalColours <- ["--colour=lovers"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("lovers"), super::COLOURS)); // and so is this one!
  521. // Overriding
  522. test!(overridden_1: TerminalColours <- ["--colour=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
  523. test!(overridden_2: TerminalColours <- ["--color=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
  524. test!(overridden_3: TerminalColours <- ["--colour=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
  525. test!(overridden_4: TerminalColours <- ["--color=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
  526. test!(overridden_5: TerminalColours <- ["--colour=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("colour")));
  527. test!(overridden_6: TerminalColours <- ["--color=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("colour")));
  528. test!(overridden_7: TerminalColours <- ["--colour=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("color")));
  529. test!(overridden_8: TerminalColours <- ["--color=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("color")));
  530. }
  531. mod views {
  532. use super::*;
  533. use output::grid::Options as GridOptions;
  534. // Default
  535. test!(empty: Mode <- [], None; Both => like Ok(Mode::Grid(_)));
  536. // Grid views
  537. test!(original_g: Mode <- ["-G"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, console_width: _ })));
  538. test!(grid: Mode <- ["--grid"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, console_width: _ })));
  539. test!(across: Mode <- ["--across"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, console_width: _ })));
  540. test!(gracross: Mode <- ["-xG"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, console_width: _ })));
  541. // Lines views
  542. test!(lines: Mode <- ["--oneline"], None; Both => like Ok(Mode::Lines));
  543. test!(prima: Mode <- ["-1"], None; Both => like Ok(Mode::Lines));
  544. // Details views
  545. test!(long: Mode <- ["--long"], None; Both => like Ok(Mode::Details(_)));
  546. test!(ell: Mode <- ["-l"], None; Both => like Ok(Mode::Details(_)));
  547. // Grid-details views
  548. test!(lid: Mode <- ["--long", "--grid"], None; Both => like Ok(Mode::GridDetails(_)));
  549. test!(leg: Mode <- ["-lG"], None; Both => like Ok(Mode::GridDetails(_)));
  550. // Options that do nothing without --long
  551. test!(just_header: Mode <- ["--header"], None; Last => like Ok(Mode::Grid(_)));
  552. test!(just_group: Mode <- ["--group"], None; Last => like Ok(Mode::Grid(_)));
  553. test!(just_inode: Mode <- ["--inode"], None; Last => like Ok(Mode::Grid(_)));
  554. test!(just_links: Mode <- ["--links"], None; Last => like Ok(Mode::Grid(_)));
  555. test!(just_blocks: Mode <- ["--blocks"], None; Last => like Ok(Mode::Grid(_)));
  556. test!(just_binary: Mode <- ["--binary"], None; Last => like Ok(Mode::Grid(_)));
  557. test!(just_bytes: Mode <- ["--bytes"], None; Last => like Ok(Mode::Grid(_)));
  558. #[cfg(feature="git")]
  559. test!(just_git: Mode <- ["--git"], None; Last => like Ok(Mode::Grid(_)));
  560. test!(just_header_2: Mode <- ["--header"], None; Complain => err Misfire::Useless(&flags::HEADER, false, &flags::LONG));
  561. test!(just_group_2: Mode <- ["--group"], None; Complain => err Misfire::Useless(&flags::GROUP, false, &flags::LONG));
  562. test!(just_inode_2: Mode <- ["--inode"], None; Complain => err Misfire::Useless(&flags::INODE, false, &flags::LONG));
  563. test!(just_links_2: Mode <- ["--links"], None; Complain => err Misfire::Useless(&flags::LINKS, false, &flags::LONG));
  564. test!(just_blocks_2: Mode <- ["--blocks"], None; Complain => err Misfire::Useless(&flags::BLOCKS, false, &flags::LONG));
  565. test!(just_binary_2: Mode <- ["--binary"], None; Complain => err Misfire::Useless(&flags::BINARY, false, &flags::LONG));
  566. test!(just_bytes_2: Mode <- ["--bytes"], None; Complain => err Misfire::Useless(&flags::BYTES, false, &flags::LONG));
  567. #[cfg(feature="git")]
  568. test!(just_git_2: Mode <- ["--git"], None; Complain => err Misfire::Useless(&flags::GIT, false, &flags::LONG));
  569. }
  570. }