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