use crate::fs::feature::xattr;
use crate::options::{flags, OptionsError, NumberSource, Vars};
use crate::options::parser::MatchedFlags;
use crate::output::{View, Mode, TerminalWidth, grid, details};
use crate::output::grid_details::{self, RowThreshold};
use crate::output::file_name::Options as FileStyle;
use crate::output::table::{TimeTypes, SizeFormat, UserFormat, Columns, Options as TableOptions};
use crate::output::time::TimeFormat;
impl View {
pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
let mode = Mode::deduce(matches, vars)?;
let width = TerminalWidth::deduce(vars)?;
let file_style = FileStyle::deduce(matches, vars)?;
Ok(Self { mode, width, file_style })
}
}
impl Mode {
pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
let flag = matches.has_where_any(|f| f.matches(&flags::LONG) || f.matches(&flags::ONE_LINE)
|| f.matches(&flags::GRID) || f.matches(&flags::TREE));
let flag = if let Some(f) = flag { f } else {
Self::strict_check_long_flags(matches)?;
let grid = grid::Options::deduce(matches)?;
return Ok(Self::Grid(grid));
};
if flag.matches(&flags::LONG)
|| (flag.matches(&flags::TREE) && matches.has(&flags::LONG)?)
|| (flag.matches(&flags::GRID) && matches.has(&flags::LONG)?)
{
let _ = matches.has(&flags::LONG)?;
let details = details::Options::deduce_long(matches, vars)?;
let flag = matches.has_where_any(|f| f.matches(&flags::GRID) || f.matches(&flags::TREE));
if flag.is_some() && flag.unwrap().matches(&flags::GRID) {
let _ = matches.has(&flags::GRID)?;
let grid = grid::Options::deduce(matches)?;
let row_threshold = RowThreshold::deduce(vars)?;
let grid_details = grid_details::Options { grid, details, row_threshold };
return Ok(Self::GridDetails(grid_details));
}
return Ok(Self::Details(details));
}
Self::strict_check_long_flags(matches)?;
if flag.matches(&flags::TREE) {
let _ = matches.has(&flags::TREE)?;
let details = details::Options::deduce_tree(matches)?;
return Ok(Self::Details(details));
}
if flag.matches(&flags::ONE_LINE) {
let _ = matches.has(&flags::ONE_LINE)?;
return Ok(Self::Lines);
}
let grid = grid::Options::deduce(matches)?;
Ok(Self::Grid(grid))
}
fn strict_check_long_flags(matches: &MatchedFlags<'_>) -> Result<(), OptionsError> {
if matches.is_strict() {
for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
&flags::HEADER, &flags::BLOCKS, &flags::TIME, &flags::GROUP, &flags::NUMERIC ] {
if matches.has(option)? {
return Err(OptionsError::Useless(*option, false, &flags::LONG));
}
}
if matches.has(&flags::GIT)? {
return Err(OptionsError::Useless(&flags::GIT, false, &flags::LONG));
}
else if matches.has(&flags::LEVEL)? && ! matches.has(&flags::RECURSE)? && ! matches.has(&flags::TREE)? {
return Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
}
}
Ok(())
}
}
impl grid::Options {
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let grid = grid::Options {
across: matches.has(&flags::ACROSS)?,
};
Ok(grid)
}
}
impl details::Options {
fn deduce_tree(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let details = details::Options {
table: None,
header: false,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
};
Ok(details)
}
fn deduce_long<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
if matches.is_strict() {
if matches.has(&flags::ACROSS)? && ! matches.has(&flags::GRID)? {
return Err(OptionsError::Useless(&flags::ACROSS, true, &flags::LONG));
}
else if matches.has(&flags::ONE_LINE)? {
return Err(OptionsError::Useless(&flags::ONE_LINE, true, &flags::LONG));
}
}
Ok(details::Options {
table: Some(TableOptions::deduce(matches, vars)?),
header: matches.has(&flags::HEADER)?,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
})
}
}
impl TerminalWidth {
fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
use crate::options::vars;
if let Some(columns) = vars.get(vars::COLUMNS).and_then(|s| s.into_string().ok()) {
match columns.parse() {
Ok(width) => {
Ok(Self::Set(width))
}
Err(e) => {
let source = NumberSource::Env(vars::COLUMNS);
Err(OptionsError::FailedParse(columns, source, e))
}
}
}
else {
Ok(Self::Automatic)
}
}
}
impl RowThreshold {
fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
use crate::options::vars;
if let Some(columns) = vars.get(vars::EXA_GRID_ROWS).and_then(|s| s.into_string().ok()) {
match columns.parse() {
Ok(rows) => {
Ok(Self::MinimumRows(rows))
}
Err(e) => {
let source = NumberSource::Env(vars::EXA_GRID_ROWS);
Err(OptionsError::FailedParse(columns, source, e))
}
}
}
else {
Ok(Self::AlwaysGrid)
}
}
}
impl TableOptions {
fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
let time_format = TimeFormat::deduce(matches, vars)?;
let size_format = SizeFormat::deduce(matches)?;
let user_format = UserFormat::deduce(matches)?;
let columns = Columns::deduce(matches)?;
Ok(Self { size_format, time_format, user_format, columns })
}
}
impl Columns {
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let time_types = TimeTypes::deduce(matches)?;
let git = matches.has(&flags::GIT)?;
let subdir_git_repos = matches.has(&flags::GIT_REPOS)?;
let subdir_git_repos_no_stat = !subdir_git_repos && matches.has(&flags::GIT_REPOS_NO_STAT)?;
let blocks = matches.has(&flags::BLOCKS)?;
let group = matches.has(&flags::GROUP)?;
let inode = matches.has(&flags::INODE)?;
let links = matches.has(&flags::LINKS)?;
let octal = matches.has(&flags::OCTAL)?;
let security_context = xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?;
let permissions = ! matches.has(&flags::NO_PERMISSIONS)?;
let filesize = ! matches.has(&flags::NO_FILESIZE)?;
let user = ! matches.has(&flags::NO_USER)?;
Ok(Self { time_types, inode, links, blocks, group, git, subdir_git_repos, subdir_git_repos_no_stat, octal, security_context, permissions, filesize, user })
}
}
impl SizeFormat {
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let flag = matches.has_where(|f| f.matches(&flags::BINARY) || f.matches(&flags::BYTES))?;
Ok(match flag {
Some(f) if f.matches(&flags::BINARY) => Self::BinaryBytes,
Some(f) if f.matches(&flags::BYTES) => Self::JustBytes,
_ => Self::DecimalBytes,
})
}
}
impl TimeFormat {
fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
let word =
if let Some(w) = matches.get(&flags::TIME_STYLE)? {
w.to_os_string()
}
else {
use crate::options::vars;
match vars.get(vars::TIME_STYLE) {
Some(ref t) if ! t.is_empty() => t.clone(),
_ => return Ok(Self::DefaultFormat)
}
};
match word.to_string_lossy().as_ref() {
"default" => Ok(Self::DefaultFormat),
"relative" => Ok(Self::Relative),
"iso" => Ok(Self::ISOFormat),
"long-iso" => Ok(Self::LongISO),
"full-iso" => Ok(Self::FullISO),
_ => Err(OptionsError::BadArgument(&flags::TIME_STYLE, word))
}
}
}
impl UserFormat {
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let flag = matches.has(&flags::NUMERIC)?;
Ok(if flag { Self::Numeric } else { Self::Name })
}
}
impl TimeTypes {
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let possible_word = matches.get(&flags::TIME)?;
let modified = matches.has(&flags::MODIFIED)?;
let changed = matches.has(&flags::CHANGED)?;
let accessed = matches.has(&flags::ACCESSED)?;
let created = matches.has(&flags::CREATED)?;
let no_time = matches.has(&flags::NO_TIME)?;
let time_types = if no_time {
Self { modified: false, changed: false, accessed: false, created: false }
} else if let Some(word) = possible_word {
if modified {
return Err(OptionsError::Useless(&flags::MODIFIED, true, &flags::TIME));
}
else if changed {
return Err(OptionsError::Useless(&flags::CHANGED, true, &flags::TIME));
}
else if accessed {
return Err(OptionsError::Useless(&flags::ACCESSED, true, &flags::TIME));
}
else if created {
return Err(OptionsError::Useless(&flags::CREATED, true, &flags::TIME));
}
else if word == "mod" || word == "modified" {
Self { modified: true, changed: false, accessed: false, created: false }
}
else if word == "ch" || word == "changed" {
Self { modified: false, changed: true, accessed: false, created: false }
}
else if word == "acc" || word == "accessed" {
Self { modified: false, changed: false, accessed: true, created: false }
}
else if word == "cr" || word == "created" {
Self { modified: false, changed: false, accessed: false, created: true }
}
else {
return Err(OptionsError::BadArgument(&flags::TIME, word.into()));
}
}
else if modified || changed || accessed || created {
Self { modified, changed, accessed, created }
}
else {
Self::default()
};
Ok(time_types)
}
}
#[cfg(test)]
mod test {
use super::*;
use std::ffi::OsString;
use crate::options::flags;
use crate::options::parser::{Flag, Arg};
use crate::options::test::parse_for_test;
use crate::options::test::Strictnesses::*;
static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE,
&flags::TIME, &flags::MODIFIED, &flags::CHANGED,
&flags::CREATED, &flags::ACCESSED,
&flags::HEADER, &flags::GROUP, &flags::INODE, &flags::GIT,
&flags::LINKS, &flags::BLOCKS, &flags::LONG, &flags::LEVEL,
&flags::GRID, &flags::ACROSS, &flags::ONE_LINE, &flags::TREE,
&flags::NUMERIC ];
macro_rules! test {
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
assert_eq!(result, $result);
}
}
};
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
assert_eq!(result.unwrap_err(), $result);
}
}
};
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => like $pat:pat) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
println!("Testing {:?}", result);
match result {
$pat => assert!(true),
_ => assert!(false),
}
}
}
};
($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => err $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {
assert_eq!(result.unwrap_err(), $result);
}
}
};
($name:ident: $type:ident <- $inputs:expr, $vars:expr; $stricts:expr => like $pat:pat) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, &$vars)) {
println!("Testing {:?}", result);
match result {
$pat => assert!(true),
_ => assert!(false),
}
}
}
};
}
mod size_formats {
use super::*;
test!(empty: SizeFormat <- []; Both => Ok(SizeFormat::DecimalBytes));
test!(binary: SizeFormat <- ["--binary"]; Both => Ok(SizeFormat::BinaryBytes));
test!(bytes: SizeFormat <- ["--bytes"]; Both => Ok(SizeFormat::JustBytes));
test!(both_1: SizeFormat <- ["--binary", "--binary"]; Last => Ok(SizeFormat::BinaryBytes));
test!(both_2: SizeFormat <- ["--bytes", "--binary"]; Last => Ok(SizeFormat::BinaryBytes));
test!(both_3: SizeFormat <- ["--binary", "--bytes"]; Last => Ok(SizeFormat::JustBytes));
test!(both_4: SizeFormat <- ["--bytes", "--bytes"]; Last => Ok(SizeFormat::JustBytes));
test!(both_5: SizeFormat <- ["--binary", "--binary"]; Complain => err OptionsError::Duplicate(Flag::Long("binary"), Flag::Long("binary")));
test!(both_6: SizeFormat <- ["--bytes", "--binary"]; Complain => err OptionsError::Duplicate(Flag::Long("bytes"), Flag::Long("binary")));
test!(both_7: SizeFormat <- ["--binary", "--bytes"]; Complain => err OptionsError::Duplicate(Flag::Long("binary"), Flag::Long("bytes")));
test!(both_8: SizeFormat <- ["--bytes", "--bytes"]; Complain => err OptionsError::Duplicate(Flag::Long("bytes"), Flag::Long("bytes")));
}
mod time_formats {
use super::*;
test!(empty: TimeFormat <- [], None; Both => like Ok(TimeFormat::DefaultFormat));
test!(default: TimeFormat <- ["--time-style=default"], None; Both => like Ok(TimeFormat::DefaultFormat));
test!(iso: TimeFormat <- ["--time-style", "iso"], None; Both => like Ok(TimeFormat::ISOFormat));
test!(relative: TimeFormat <- ["--time-style", "relative"], None; Both => like Ok(TimeFormat::Relative));
test!(long_iso: TimeFormat <- ["--time-style=long-iso"], None; Both => like Ok(TimeFormat::LongISO));
test!(full_iso: TimeFormat <- ["--time-style", "full-iso"], None; Both => like Ok(TimeFormat::FullISO));
test!(actually: TimeFormat <- ["--time-style=default", "--time-style", "iso"], None; Last => like Ok(TimeFormat::ISOFormat));
test!(actual_2: TimeFormat <- ["--time-style=default", "--time-style", "iso"], None; Complain => err OptionsError::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")));
test!(nevermind: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"], None; Last => like Ok(TimeFormat::FullISO));
test!(nevermore: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"], None; Complain => err OptionsError::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")));
test!(daily: TimeFormat <- ["--time-style=24-hour"], None; Both => err OptionsError::BadArgument(&flags::TIME_STYLE, OsString::from("24-hour")));
test!(use_env: TimeFormat <- [], Some("long-iso".into()); Both => like Ok(TimeFormat::LongISO));
test!(override_env: TimeFormat <- ["--time-style=full-iso"], Some("long-iso".into()); Both => like Ok(TimeFormat::FullISO));
}
mod time_types {
use super::*;
test!(empty: TimeTypes <- []; Both => Ok(TimeTypes::default()));
test!(modified: TimeTypes <- ["--modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
test!(m: TimeTypes <- ["-m"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
test!(time_mod: TimeTypes <- ["--time=modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
test!(t_m: TimeTypes <- ["-tmod"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
#[cfg(target_family = "unix")]
test!(changed: TimeTypes <- ["--changed"]; Both => Ok(TimeTypes { modified: false, changed: true, accessed: false, created: false }));
#[cfg(target_family = "unix")]
test!(time_ch: TimeTypes <- ["--time=changed"]; Both => Ok(TimeTypes { modified: false, changed: true, accessed: false, created: false }));
#[cfg(target_family = "unix")]
test!(t_ch: TimeTypes <- ["-t", "ch"]; Both => Ok(TimeTypes { modified: false, changed: true, accessed: false, created: false }));
test!(acc: TimeTypes <- ["--accessed"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false }));
test!(a: TimeTypes <- ["-u"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false }));
test!(time_acc: TimeTypes <- ["--time", "accessed"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false }));
test!(time_a: TimeTypes <- ["-t", "acc"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false }));
test!(cr: TimeTypes <- ["--created"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true }));
test!(c: TimeTypes <- ["-U"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true }));
test!(time_cr: TimeTypes <- ["--time=created"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true }));
test!(t_cr: TimeTypes <- ["-tcr"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true }));
test!(time_uu: TimeTypes <- ["-u", "--modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: true, created: false }));
test!(time_tea: TimeTypes <- ["--time=tea"]; Both => err OptionsError::BadArgument(&flags::TIME, OsString::from("tea")));
test!(t_ea: TimeTypes <- ["-tea"]; Both => err OptionsError::BadArgument(&flags::TIME, OsString::from("ea")));
test!(overridden: TimeTypes <- ["-tcr", "-tmod"]; Last => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
test!(overridden_2: TimeTypes <- ["-tcr", "-tmod"]; Complain => err OptionsError::Duplicate(Flag::Short(b't'), Flag::Short(b't')));
}
mod views {
use super::*;
use crate::output::grid::Options as GridOptions;
test!(empty: Mode <- [], None; Both => like Ok(Mode::Grid(_)));
test!(original_g: Mode <- ["-G"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, .. })));
test!(grid: Mode <- ["--grid"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, .. })));
test!(across: Mode <- ["--across"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, .. })));
test!(gracross: Mode <- ["-xG"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, .. })));
test!(lines: Mode <- ["--oneline"], None; Both => like Ok(Mode::Lines));
test!(prima: Mode <- ["-1"], None; Both => like Ok(Mode::Lines));
test!(long: Mode <- ["--long"], None; Both => like Ok(Mode::Details(_)));
test!(ell: Mode <- ["-l"], None; Both => like Ok(Mode::Details(_)));
test!(lid: Mode <- ["--long", "--grid"], None; Both => like Ok(Mode::GridDetails(_)));
test!(leg: Mode <- ["-lG"], None; Both => like Ok(Mode::GridDetails(_)));
test!(long_across: Mode <- ["--long", "--across"], None; Last => like Ok(Mode::Details(_)));
test!(just_header: Mode <- ["--header"], None; Last => like Ok(Mode::Grid(_)));
test!(just_group: Mode <- ["--group"], None; Last => like Ok(Mode::Grid(_)));
test!(just_inode: Mode <- ["--inode"], None; Last => like Ok(Mode::Grid(_)));
test!(just_links: Mode <- ["--links"], None; Last => like Ok(Mode::Grid(_)));
test!(just_blocks: Mode <- ["--blocks"], None; Last => like Ok(Mode::Grid(_)));
test!(just_binary: Mode <- ["--binary"], None; Last => like Ok(Mode::Grid(_)));
test!(just_bytes: Mode <- ["--bytes"], None; Last => like Ok(Mode::Grid(_)));
test!(just_numeric: Mode <- ["--numeric"], None; Last => like Ok(Mode::Grid(_)));
#[cfg(feature = "git")]
test!(just_git: Mode <- ["--git"], None; Last => like Ok(Mode::Grid(_)));
test!(just_header_2: Mode <- ["--header"], None; Complain => err OptionsError::Useless(&flags::HEADER, false, &flags::LONG));
test!(just_group_2: Mode <- ["--group"], None; Complain => err OptionsError::Useless(&flags::GROUP, false, &flags::LONG));
test!(just_inode_2: Mode <- ["--inode"], None; Complain => err OptionsError::Useless(&flags::INODE, false, &flags::LONG));
test!(just_links_2: Mode <- ["--links"], None; Complain => err OptionsError::Useless(&flags::LINKS, false, &flags::LONG));
test!(just_blocks_2: Mode <- ["--blocks"], None; Complain => err OptionsError::Useless(&flags::BLOCKS, false, &flags::LONG));
test!(just_binary_2: Mode <- ["--binary"], None; Complain => err OptionsError::Useless(&flags::BINARY, false, &flags::LONG));
test!(just_bytes_2: Mode <- ["--bytes"], None; Complain => err OptionsError::Useless(&flags::BYTES, false, &flags::LONG));
test!(just_numeric2: Mode <- ["--numeric"], None; Complain => err OptionsError::Useless(&flags::NUMERIC, false, &flags::LONG));
#[cfg(feature = "git")]
test!(just_git_2: Mode <- ["--git"], None; Complain => err OptionsError::Useless(&flags::GIT, false, &flags::LONG));
test!(lgo: Mode <- ["--long", "--grid", "--oneline"], None; Both => like Ok(Mode::Lines));
test!(lgt: Mode <- ["--long", "--grid", "--tree"], None; Both => like Ok(Mode::Details(_)));
test!(tgl: Mode <- ["--tree", "--grid", "--long"], None; Both => like Ok(Mode::GridDetails(_)));
test!(tlg: Mode <- ["--tree", "--long", "--grid"], None; Both => like Ok(Mode::GridDetails(_)));
test!(ot: Mode <- ["--oneline", "--tree"], None; Both => like Ok(Mode::Details(_)));
test!(og: Mode <- ["--oneline", "--grid"], None; Both => like Ok(Mode::Grid(_)));
test!(tg: Mode <- ["--tree", "--grid"], None; Both => like Ok(Mode::Grid(_)));
}
}