use clap::{ArgMatches, ValueEnum};
use crate::output::TerminalWidth::Automatic;
use crate::fs::feature::xattr;
use crate::options::parser::{CodeContent, ColorScaleModeArgs};
use crate::options::{NumberSource, OptionsError, Vars, vars};
use crate::output::TerminalWidth::Set;
use crate::output::color_scale::{ColorScaleMode, ColorScaleOptions};
use crate::output::file_name::Options as FileStyle;
use crate::output::grid_details::{self, RowThreshold};
use crate::output::table::{
Columns, FlagsFormat, GroupFormat, Options as TableOptions, SizeFormat, TimeTypes, UserFormat,
};
use crate::output::time::TimeFormat;
use crate::output::{Mode, TerminalWidth, View, code, details, grid};
use super::parser::{ColorScaleArgs, TimeArgs};
impl View {
pub fn deduce<V: Vars>(
matches: &ArgMatches,
vars: &V,
strict: bool,
) -> Result<Self, OptionsError> {
let width = TerminalWidth::deduce(matches, vars)?;
let is_tty = width.actual_terminal_width().is_some();
let mode = Mode::deduce(matches, vars, is_tty, strict)?;
let deref_links = matches.get_flag("dereference");
let follow_links = matches.get_flag("follow-symlinks");
let total_size = matches.get_flag("total-size");
let file_style = FileStyle::deduce(matches, vars, is_tty)?;
Ok(Self {
mode,
width,
file_style,
deref_links,
follow_links,
total_size,
})
}
}
impl Mode {
pub fn deduce<V: Vars>(
matches: &ArgMatches,
vars: &V,
is_tty: bool,
strict: bool,
) -> Result<Self, OptionsError> {
if let Some(content) = matches.get_one::<CodeContent>("code").copied() {
return Ok(Self::Code(code::Options { content }));
}
let long = matches.get_flag("long");
let oneline = matches.get_flag("oneline");
let grid = matches.get_flag("grid");
let tree = matches.get_flag("tree");
if !long && strict {
Self::strict_check_long_flags(matches)?;
}
if !(long || oneline || grid || tree) {
if is_tty {
let grid = grid::Options::deduce(matches);
return Ok(Self::Grid(grid));
}
return Ok(Self::Lines);
}
if long {
let details = details::Options::deduce_long(matches, vars, strict)?;
if grid {
let row_threshold = RowThreshold::deduce(vars)?;
let grid_details = grid_details::Options {
details,
row_threshold,
};
return Ok(Self::GridDetails(grid_details));
}
return Ok(Self::Details(details));
}
if tree {
let details = details::Options::deduce_tree(matches, vars);
return Ok(Self::Details(details));
}
if oneline {
return Ok(Self::Lines);
}
let grid = grid::Options::deduce(matches);
Ok(Self::Grid(grid))
}
fn strict_check_long_flags(matches: &ArgMatches) -> Result<(), OptionsError> {
for flag in &[
"binary",
"bytes",
"inode",
"links",
"header",
"blocksize",
"time",
"group",
"numeric",
"mounts",
"loc",
] {
if matches.contains_id(flag) {
return Err(OptionsError::Useless(flag, false, "long"));
}
}
if matches.get_flag("git") && !matches.get_flag("no-git") {
return Err(OptionsError::Useless("git", false, "long"));
} else if matches.contains_id("level")
&& !matches.get_flag("recurse")
&& !matches.get_flag("tree")
{
return Err(OptionsError::Useless2("level", "recurse", "tree"));
}
Ok(())
}
}
impl grid::Options {
fn deduce(matches: &ArgMatches) -> Self {
grid::Options {
across: matches.get_flag("across"),
}
}
}
impl details::Options {
fn deduce_tree<V: Vars>(matches: &ArgMatches, vars: &V) -> Self {
details::Options {
table: None,
header: false,
xattr: xattr::ENABLED && matches.get_flag("extended"),
secattr: xattr::ENABLED && matches.get_flag("security-context"),
mounts: matches.get_flag("mounts"),
color_scale: ColorScaleOptions::deduce(matches, vars),
follow_links: matches.get_flag("follow-symlinks"),
}
}
fn deduce_long<V: Vars>(
matches: &ArgMatches,
vars: &V,
strict: bool,
) -> Result<Self, OptionsError> {
if strict {
if matches.get_flag("across") && !matches.get_flag("grid") {
return Err(OptionsError::Useless("across", true, "long"));
} else if matches.get_flag("oneline") {
return Err(OptionsError::Useless("one-line", true, "long"));
}
}
Ok(details::Options {
table: Some(TableOptions::deduce(matches, vars)?),
header: matches.get_flag("header"),
xattr: xattr::ENABLED && matches.get_flag("extended"),
secattr: xattr::ENABLED && matches.get_flag("security-context"),
mounts: matches.get_flag("mounts"),
color_scale: ColorScaleOptions::deduce(matches, vars),
follow_links: matches.get_flag("follow-symlinks"),
})
}
}
impl TerminalWidth {
fn deduce<V: Vars>(matches: &ArgMatches, vars: &V) -> Result<Self, OptionsError> {
if let Some(&width) = matches.get_one("width") {
if width >= 1 {
Ok(Set(width))
} else {
Ok(Automatic)
}
} else if let Some(columns) = vars.get(vars::COLUMNS).and_then(|s| s.into_string().ok()) {
match columns.parse() {
Ok(width) => Ok(Set(width)),
Err(e) => {
let source = NumberSource::Env(vars::COLUMNS);
Err(OptionsError::FailedParse(columns, source, e))
}
}
} else {
Ok(Automatic)
}
}
}
impl RowThreshold {
fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
if let Some(columns) = vars
.get_with_fallback(vars::EZA_GRID_ROWS, 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.source(vars::EZA_GRID_ROWS, vars::EXA_GRID_ROWS)
.unwrap(),
);
Err(OptionsError::FailedParse(columns, source, e))
}
}
} else {
Ok(Self::AlwaysGrid)
}
}
}
impl TableOptions {
fn deduce<V: Vars>(matches: &ArgMatches, vars: &V) -> Result<Self, OptionsError> {
let time_format = TimeFormat::deduce(matches, vars);
let flags_format = FlagsFormat::deduce(vars);
let size_format = SizeFormat::deduce(matches);
let user_format = UserFormat::deduce(matches);
let group_format = GroupFormat::deduce(matches);
let columns = Columns::deduce(matches, vars)?;
Ok(Self {
size_format,
time_format,
user_format,
group_format,
flags_format,
columns,
})
}
}
impl Columns {
fn deduce<V: Vars>(matches: &ArgMatches, vars: &V) -> Result<Self, OptionsError> {
let time_types = TimeTypes::deduce(matches)?;
let no_git_env = vars
.get_with_fallback(vars::EXA_OVERRIDE_GIT, vars::EZA_OVERRIDE_GIT)
.is_some();
let git = matches.get_flag("git") && !matches.get_flag("no-git") && !no_git_env;
let subdir_git_repos =
matches.get_flag("git-repos") && !matches.get_flag("no-git") && !no_git_env;
let subdir_git_repos_no_stat = !subdir_git_repos
&& matches.get_flag("git-repos-no-status")
&& !matches.get_flag("no-git")
&& !no_git_env;
let file_flags = matches.get_flag("file-flags");
let blocksize = matches.get_flag("blocksize");
let group = matches.get_flag("group");
let inode = matches.get_flag("inode");
let links = matches.get_flag("links");
let octal = matches.get_flag("octal-permissions");
let security_context = xattr::ENABLED && matches.get_flag("security-context");
let permissions = !matches.get_flag("no-permissions");
let filesize = !matches.get_flag("no-filesize");
let user = !matches.get_flag("no-user");
let loc = matches.get_one::<CodeContent>("loc").copied();
Ok(Self {
time_types,
inode,
links,
blocksize,
group,
git,
subdir_git_repos,
subdir_git_repos_no_stat,
octal,
security_context,
file_flags,
permissions,
filesize,
user,
loc,
})
}
}
impl SizeFormat {
fn deduce(matches: &ArgMatches) -> Self {
use SizeFormat::*;
if matches.get_flag("binary") {
BinaryBytes
} else if matches.get_flag("bytes") {
JustBytes
} else {
DecimalBytes
}
}
}
const FORMAT_STYLE_FIELDS: [&str; 6] = [
"default",
"iso",
"long-iso",
"full-iso",
"relative",
"+<CUSTOM_FORMAT>",
];
impl TimeFormat {
pub fn try_from_str(value: &str) -> Result<Self, String> {
use nu_ansi_term::Color::*;
let error_header = format!(
"invalid value '{}' for '{}'\n [possible values: {}]\n\n",
Yellow.paint(value),
White.paint("--time-style <STYLE>"),
FORMAT_STYLE_FIELDS
.map(|s| Green.paint(s).to_string())
.join(", ")
);
let error_footer = format!(
"\n\nFor more information, try '{}'.\n",
White.paint("--help"),
);
let fmt = match value {
"default" => return Ok(TimeFormat::DefaultFormat),
"iso" => return Ok(TimeFormat::ISOFormat),
"long-iso" => return Ok(TimeFormat::LongISO),
"full-iso" => return Ok(TimeFormat::FullISO),
"relative" => return Ok(TimeFormat::Relative),
s if !s.starts_with('+') => {
let error_middle = format!(
"{}{}",
"Please start the format with a plus sign (+) to indicate a custom format.\n",
"For example: \"+%Y-%m-%d %H:%M:%S\"",
);
return Err(format!("{error_header}{error_middle}{error_footer}"));
}
s => s,
};
let mut lines = fmt.strip_prefix('+').unwrap().lines();
let non_recent = match lines.next() {
None | Some("") => {
let error_middle = format!(
"{}{}",
"Custom timestamp format is empty, ",
"please supply a chrono format string after the +."
);
return Err(format!("{error_header}{error_middle}{error_footer}"));
}
Some(non_recent) => non_recent,
};
let recent = match lines.next() {
Some("") => {
let error_middle = format!(
"{}{}",
"Custom timestamp format for recent files is empty, ",
"please supply a chrono format string at the second line."
);
return Err(format!("{error_header}{error_middle}{error_footer}"));
}
recent => recent.map(std::string::ToString::to_string),
};
Ok(TimeFormat::Custom {
non_recent: String::from(non_recent),
recent,
})
}
}
impl TimeFormat {
fn deduce<V: Vars>(matches: &ArgMatches, vars: &V) -> Self {
if let Some(arg) = matches.get_one::<TimeFormat>("time-style") {
arg.clone()
} else {
match vars.get(vars::TIME_STYLE) {
Some(t) if !t.is_empty() => TimeFormat::try_from_str(t.to_str().unwrap_or(""))
.unwrap_or(TimeFormat::DefaultFormat),
_ => Self::DefaultFormat,
}
}
}
}
impl UserFormat {
fn deduce(matches: &ArgMatches) -> Self {
if matches.get_flag("numeric") {
Self::Numeric
} else {
Self::Name
}
}
}
impl GroupFormat {
fn deduce(matches: &ArgMatches) -> Self {
if matches.get_flag("smart-group") {
Self::Smart
} else {
Self::Regular
}
}
}
impl TimeTypes {
fn deduce(matches: &ArgMatches) -> Result<Self, OptionsError> {
let possible_word = matches.get_one::<TimeArgs>("time");
let modified = matches.get_flag("modified");
let changed = matches.get_flag("changed");
let accessed = matches.get_flag("accessed");
let created = matches.get_flag("created");
let no_time = matches.get_flag("no-time");
#[rustfmt::skip]
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("modified", true, "time"));
} else if changed {
return Err(OptionsError::Useless("changed", true, "time"));
} else if accessed {
return Err(OptionsError::Useless("accessed", true, "time"));
} else if created {
return Err(OptionsError::Useless("created", true, "time"));
} else if *word == TimeArgs::Modified {
Self { modified: true, changed: false, accessed: false, created: false }
} else if *word == TimeArgs::Changed {
Self { modified: false, changed: true, accessed: false, created: false }
} else if *word == TimeArgs::Accessed {
Self { modified: false, changed: false, accessed: true, created: false }
} else if *word == TimeArgs::Created {
Self { modified: false, changed: false, accessed: false, created: true }
} else {
return Err(OptionsError::BadArgument("time", word.to_possible_value().unwrap().get_name().into()));
}
} else if modified || changed || accessed || created {
Self {
modified,
changed,
accessed,
created,
}
} else {
Self::default()
};
Ok(time_types)
}
}
impl ColorScaleOptions {
pub fn deduce<V: Vars>(matches: &ArgMatches, vars: &V) -> Self {
let min_luminance =
match vars.get_with_fallback(vars::EZA_MIN_LUMINANCE, vars::EXA_MIN_LUMINANCE) {
Some(var) => match var.to_string_lossy().parse() {
Ok(luminance) if (-100..=100).contains(&luminance) => luminance,
_ => 40,
},
None => 40,
};
let mode = match matches.get_one("color-scale-mode").unwrap() {
ColorScaleModeArgs::Fixed => ColorScaleMode::Fixed,
ColorScaleModeArgs::Gradient => ColorScaleMode::Gradient,
};
let mut options = ColorScaleOptions {
mode,
min_luminance,
size: false,
age: false,
};
let Some(words) = matches.get_many("color-scale") else {
return options;
};
for word in words {
match word {
ColorScaleArgs::All => {
options.size = true;
options.age = true;
}
ColorScaleArgs::Age => {
options.age = true;
}
ColorScaleArgs::Size => {
options.size = true;
}
}
}
options
}
}
#[cfg(test)]
mod tests {
use crate::options::parser::test::mock_cli;
use crate::options::vars::test::MockVars;
use std::ffi::OsString;
use std::num::ParseIntError;
use super::*;
#[test]
fn deduce_time_types_no_time() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec!["--no-time"])),
Ok(TimeTypes {
modified: false,
..TimeTypes::default()
})
);
}
#[test]
fn deduce_time_types_default() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec![""])),
Ok(TimeTypes::default())
);
}
#[test]
fn deduce_time_types_modified_word() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec!["--time", "modified"])),
Ok(TimeTypes {
modified: true,
..TimeTypes::default()
})
);
}
#[test]
fn deduce_time_types_accessed_word() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec!["--time", "accessed"])),
Ok(TimeTypes {
accessed: true,
modified: false,
..TimeTypes::default()
})
);
}
#[test]
fn deduce_time_types_changed_word() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec!["--time", "changed"])),
Ok(TimeTypes {
modified: false,
changed: true,
..TimeTypes::default()
})
);
}
#[test]
fn deduce_time_types_created_word() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec!["--time", "created"])),
Ok(TimeTypes {
modified: false,
created: true,
..TimeTypes::default()
})
);
}
#[test]
fn deduce_time_types_modified() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec!["--modified"])),
Ok(TimeTypes {
modified: true,
..TimeTypes::default()
})
);
}
#[test]
fn deduce_time_types_accessed() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec!["--accessed"])),
Ok(TimeTypes {
accessed: true,
modified: false,
..TimeTypes::default()
})
);
}
#[test]
fn deduce_time_types_changed() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec!["--changed"])),
Ok(TimeTypes {
modified: false,
changed: true,
..TimeTypes::default()
})
);
}
#[test]
fn deduce_time_types_created() {
assert_eq!(
TimeTypes::deduce(&mock_cli(vec!["--created"])),
Ok(TimeTypes {
modified: false,
created: true,
..TimeTypes::default()
})
);
}
#[test]
fn deduce_group_format_on() {
assert_eq!(
GroupFormat::deduce(&mock_cli(vec!["--smart-group"])),
GroupFormat::Smart
);
}
#[test]
fn deduce_group_format_off() {
assert_eq!(
GroupFormat::deduce(&mock_cli(vec![""])),
GroupFormat::Regular
);
}
#[test]
fn deduce_user_format_on() {
assert_eq!(
UserFormat::deduce(&mock_cli(vec!["--numeric"])),
UserFormat::Numeric
);
}
#[test]
fn deduce_user_format_off() {
assert_eq!(UserFormat::deduce(&mock_cli(vec![""])), UserFormat::Name);
}
#[test]
fn deduce_size_format_off() {
assert_eq!(
SizeFormat::deduce(&mock_cli(vec![""])),
SizeFormat::DecimalBytes
);
}
#[test]
fn deduce_user_format_bytes() {
assert_eq!(
SizeFormat::deduce(&mock_cli(vec!["--bytes"])),
SizeFormat::JustBytes
);
}
#[test]
fn deduce_user_format_binary() {
assert_eq!(
SizeFormat::deduce(&mock_cli(vec!["--binary"])),
SizeFormat::BinaryBytes
);
}
#[test]
fn deduce_grid_options() {
assert_eq!(
grid::Options::deduce(&mock_cli(vec!["--across"])),
grid::Options { across: true }
);
}
#[test]
fn deduce_time_style_iso_env() {
let mut vars = MockVars::default();
vars.set(vars::TIME_STYLE, &OsString::from("iso"));
assert_eq!(
TimeFormat::deduce(&mock_cli(vec![""]), &vars),
TimeFormat::ISOFormat
);
}
#[test]
fn deduce_time_style_iso_arg() {
let vars = MockVars::default();
assert_eq!(
TimeFormat::deduce(&mock_cli(vec!["--time-style", "iso"]), &vars),
TimeFormat::ISOFormat
);
}
#[test]
fn deduce_time_style_long_iso_env() {
let mut vars = MockVars::default();
vars.set(vars::TIME_STYLE, &OsString::from("long-iso"));
assert_eq!(
TimeFormat::deduce(&mock_cli(vec![""]), &vars),
TimeFormat::LongISO
);
}
#[test]
fn deduce_time_style_long_iso_arg() {
let vars = MockVars::default();
assert_eq!(
TimeFormat::deduce(&mock_cli(vec!["--time-style", "long-iso"]), &vars),
TimeFormat::LongISO
);
}
#[test]
fn deduce_time_style_full_iso_env() {
let mut vars = MockVars::default();
vars.set(vars::TIME_STYLE, &OsString::from("full-iso"));
assert_eq!(
TimeFormat::deduce(&mock_cli(vec![""]), &vars),
TimeFormat::FullISO
);
}
#[test]
fn deduce_time_style_full_iso_arg() {
let vars = MockVars::default();
assert_eq!(
TimeFormat::deduce(&mock_cli(vec!["--time-style", "full-iso"]), &vars),
TimeFormat::FullISO
);
}
#[test]
fn deduce_time_style_relative_env() {
let mut vars = MockVars::default();
vars.set(vars::TIME_STYLE, &OsString::from("relative"));
assert_eq!(
TimeFormat::deduce(&mock_cli(vec![""]), &vars),
TimeFormat::Relative
);
}
#[test]
fn deduce_time_style_relative_arg() {
let vars = MockVars::default();
assert_eq!(
TimeFormat::deduce(&mock_cli(vec!["--time-style", "relative"]), &vars),
TimeFormat::Relative
);
}
#[test]
fn deduce_time_style_custom_env() {
let mut vars = MockVars::default();
vars.set(vars::TIME_STYLE, &OsString::from("+%Y-%b-%d"));
assert_eq!(
TimeFormat::deduce(&mock_cli(vec![""]), &vars),
TimeFormat::Custom {
non_recent: String::from("%Y-%b-%d"),
recent: None
}
);
}
#[test]
fn deduce_time_style_custom_arg() {
assert_eq!(
TimeFormat::deduce(
&mock_cli(vec!["--time-style", "+%Y-%b-%d"]),
&MockVars::default()
),
TimeFormat::Custom {
non_recent: String::from("%Y-%b-%d"),
recent: None
}
);
}
#[test]
fn deduce_time_style_non_recent_and_recent() {
assert_eq!(
TimeFormat::deduce(
&mock_cli(vec!["--time-style", "+%Y-%m-%d %H\n--%m-%d %H:%M"]),
&MockVars::default()
),
TimeFormat::Custom {
non_recent: String::from("%Y-%m-%d %H"),
recent: Some(String::from("--%m-%d %H:%M"))
}
);
}
#[test]
fn deduce_color_scale_size_age_luminance_40_gradient() {
assert_eq!(
ColorScaleOptions::deduce(
&mock_cli(vec!["--color-scale", "size,age"]),
&MockVars::default()
),
ColorScaleOptions {
mode: ColorScaleMode::Gradient,
min_luminance: 40,
size: true,
age: true,
}
);
}
#[test]
fn deduce_color_scale_size_luminance_60_gradient() {
let mut vars = MockVars::default();
vars.set(vars::EZA_MIN_LUMINANCE, &OsString::from("60"));
assert_eq!(
ColorScaleOptions::deduce(&mock_cli(vec!["--color-scale", "size"]), &vars),
ColorScaleOptions {
mode: ColorScaleMode::Gradient,
min_luminance: 60,
size: true,
age: false,
}
);
}
#[test]
fn deduce_color_scale_age_luminance_60_fixed() {
let mut vars = MockVars::default();
vars.set(vars::EZA_MIN_LUMINANCE, &OsString::from("60"));
assert_eq!(
ColorScaleOptions::deduce(
&mock_cli(vec!["--color-scale", "age", "--color-scale-mode", "fixed"]),
&vars
),
ColorScaleOptions {
mode: ColorScaleMode::Fixed,
min_luminance: 60,
size: false,
age: true,
}
);
}
#[test]
fn deduce_color_scale_size_age_luminance_99_fixed() {
let mut vars = MockVars::default();
vars.set(vars::EZA_MIN_LUMINANCE, &OsString::from("99"));
assert_eq!(
ColorScaleOptions::deduce(
&mock_cli(vec![
"--color-scale",
"size,age",
"--color-scale-mode",
"fixed"
]),
&vars
),
ColorScaleOptions {
mode: ColorScaleMode::Fixed,
min_luminance: 99,
size: true,
age: true,
}
);
}
#[test]
fn deduce_mode_grid() {
assert_eq!(
Mode::deduce(
&mock_cli(vec!["--grid"]),
&MockVars::default(),
false,
false
),
Ok(Mode::Grid(grid::Options { across: false }))
);
}
#[test]
fn deduce_mode_grid_across() {
assert_eq!(
Mode::deduce(
&mock_cli(vec!["--grid", "--across"]),
&MockVars::default(),
false,
false
),
Ok(Mode::Grid(grid::Options { across: true }))
);
}
#[test]
fn deduce_details_options_tree() {
let cli = mock_cli(vec!["--tree"]);
assert_eq!(
details::Options::deduce_tree(&cli, &MockVars::default()),
details::Options {
table: None,
header: false,
xattr: false,
secattr: false,
mounts: false,
color_scale: ColorScaleOptions::deduce(&cli, &MockVars::default()),
follow_links: false,
}
);
}
#[test]
fn deduce_details_options_tree_mounts() {
let cli = mock_cli(vec!["--tree", "--mounts"]);
assert_eq!(
details::Options::deduce_tree(&cli, &MockVars::default()),
details::Options {
table: None,
header: false,
xattr: false,
secattr: false,
mounts: true,
color_scale: ColorScaleOptions::deduce(&cli, &MockVars::default()),
follow_links: false,
}
);
}
#[test]
fn deduce_details_options_tree_xattr() {
let cli = mock_cli(vec!["--tree", "--extended"]);
assert_eq!(
details::Options::deduce_tree(&cli, &MockVars::default()),
details::Options {
table: None,
header: false,
xattr: xattr::ENABLED,
secattr: false,
mounts: false,
color_scale: ColorScaleOptions::deduce(&cli, &MockVars::default()),
follow_links: false,
}
);
}
#[test]
fn deduce_details_options_tree_secattr() {
let cli = mock_cli(vec!["--tree", "--context"]);
assert_eq!(
details::Options::deduce_tree(&cli, &MockVars::default()),
details::Options {
table: None,
header: false,
xattr: false,
secattr: xattr::ENABLED,
mounts: false,
color_scale: ColorScaleOptions::deduce(&cli, &MockVars::default()),
follow_links: false,
}
);
}
#[test]
fn deduce_details_long_strict_across() {
assert_eq!(
details::Options::deduce_long(
&mock_cli(vec!["--long", "--across"]),
&MockVars::default(),
true
),
Err(OptionsError::Useless("across", true, "long"))
);
}
#[test]
fn deduce_details_long_strict_one_line() {
assert_eq!(
details::Options::deduce_long(
&mock_cli(vec!["--long", "--oneline"]),
&MockVars::default(),
true
),
Err(OptionsError::Useless("one-line", true, "long"))
);
}
#[test]
fn deduce_terminal_width_automatic() {
assert_eq!(
TerminalWidth::deduce(&mock_cli(vec![""]), &MockVars::default()),
Ok(Automatic)
);
}
#[test]
fn deduce_terminal_width_set_arg() {
assert_eq!(
TerminalWidth::deduce(&mock_cli(vec!["--width", "80"]), &MockVars::default()),
Ok(Set(80))
);
}
#[test]
fn deduce_terminal_width_set_env() {
let mut vars = MockVars::default();
vars.set(vars::COLUMNS, &OsString::from("80"));
assert_eq!(
TerminalWidth::deduce(&mock_cli(vec![""]), &vars),
Ok(Set(80))
);
}
#[test]
fn deduce_terminal_width_set_env_bad() {
let mut vars = MockVars::default();
vars.set(vars::COLUMNS, &OsString::from("bad"));
let e: Result<i64, ParseIntError> =
vars.get(vars::COLUMNS).unwrap().to_string_lossy().parse();
assert_eq!(
TerminalWidth::deduce(&mock_cli(vec![""]), &vars),
Err(OptionsError::FailedParse(
String::from("bad"),
NumberSource::Env(vars::COLUMNS),
e.unwrap_err()
))
);
}
#[test]
fn deduce_mode_code_default_is_both() {
assert_eq!(
Mode::deduce(
&mock_cli(vec!["--code"]),
&MockVars::default(),
false,
false
),
Ok(Mode::Code(code::Options {
content: CodeContent::Both
}))
);
}
#[test]
fn deduce_mode_code_lines() {
assert_eq!(
Mode::deduce(
&mock_cli(vec!["--code=lines"]),
&MockVars::default(),
false,
false
),
Ok(Mode::Code(code::Options {
content: CodeContent::Lines
}))
);
}
#[test]
fn deduce_columns_loc_percent() {
assert_eq!(
Columns::deduce(&mock_cli(vec!["--loc=percent"]), &MockVars::default())
.unwrap()
.loc,
Some(CodeContent::Percent)
);
}
#[test]
fn deduce_columns_loc_bare_is_both() {
assert_eq!(
Columns::deduce(&mock_cli(vec!["--loc"]), &MockVars::default())
.unwrap()
.loc,
Some(CodeContent::Both)
);
}
#[test]
fn deduce_columns_loc_absent() {
assert_eq!(
Columns::deduce(&mock_cli(vec![""]), &MockVars::default())
.unwrap()
.loc,
None
);
}
}