1
0

view.rs 35 KB

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