view.rs 30 KB

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