use crate::fs::filter::{
FileFilter, FileFilterFlags, GitIgnore, IgnorePatterns, SortCase, SortField,
};
use crate::fs::DotFilter;
use crate::options::parser::MatchedFlags;
use crate::options::{flags, OptionsError};
impl FileFilter {
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
use FileFilterFlags as FFF;
let mut filter_flags: Vec<FileFilterFlags> = vec![];
for (has, flag) in &[
(matches.has(&flags::REVERSE)?, FFF::Reverse),
(matches.has(&flags::ONLY_DIRS)?, FFF::OnlyDirs),
(matches.has(&flags::ONLY_FILES)?, FFF::OnlyFiles),
(matches.has(&flags::NO_SYMLINKS)?, FFF::NoSymlinks),
(matches.has(&flags::SHOW_SYMLINKS)?, FFF::ShowSymlinks),
(matches.has(&flags::DIRS_LAST)?, FFF::ListDirsLast),
(matches.has(&flags::DIRS_FIRST)?, FFF::ListDirsFirst),
] {
if *has {
filter_flags.push(flag.clone());
}
}
#[rustfmt::skip]
return Ok(Self {
no_symlinks: filter_flags.contains(&FFF::NoSymlinks),
show_symlinks: filter_flags.contains(&FFF::ShowSymlinks),
flags: filter_flags,
sort_field: SortField::deduce(matches)?,
dot_filter: DotFilter::deduce(matches)?,
ignore_patterns: IgnorePatterns::deduce(matches)?,
git_ignore: GitIgnore::deduce(matches)?,
});
}
}
impl SortField {
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let Some(word) = matches.get(&flags::SORT)? else {
return Ok(Self::default());
};
let Some(word) = word.to_str() else {
return Err(OptionsError::BadArgument(&flags::SORT, word.into()));
};
let field = match word {
"name" | "filename" => Self::Name(SortCase::AaBbCc),
"Name" | "Filename" => Self::Name(SortCase::ABCabc),
".name" | ".filename" => Self::NameMixHidden(SortCase::AaBbCc),
".Name" | ".Filename" => Self::NameMixHidden(SortCase::ABCabc),
"size" | "filesize" => Self::Size,
"ext" | "extension" => Self::Extension(SortCase::AaBbCc),
"Ext" | "Extension" => Self::Extension(SortCase::ABCabc),
"date" | "time" | "mod" | "modified" | "new" | "newest" => Self::ModifiedDate,
"age" | "old" | "oldest" => Self::ModifiedAge,
"ch" | "changed" => Self::ChangedDate,
"acc" | "accessed" => Self::AccessedDate,
"cr" | "created" => Self::CreatedDate,
#[cfg(unix)]
"inode" => Self::FileInode,
"type" => Self::FileType,
"none" => Self::Unsorted,
_ => {
return Err(OptionsError::BadArgument(&flags::SORT, word.into()));
}
};
Ok(field)
}
}
impl Default for SortField {
fn default() -> Self {
Self::Name(SortCase::AaBbCc)
}
}
impl DotFilter {
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let all_count = matches.count(&flags::ALL);
let has_almost_all = matches.has(&flags::ALMOST_ALL)?;
match (all_count, has_almost_all) {
(0, false) => Ok(Self::JustFiles),
(1, _) | (0, true) => Ok(Self::Dotfiles),
(c, _) => {
if matches.count(&flags::TREE) > 0 {
Err(OptionsError::TreeAllAll)
} else if matches.is_strict() && c > 2 {
Err(OptionsError::Conflict(&flags::ALL, &flags::ALL))
} else {
Ok(Self::DotfilesAndDots)
}
}
}
}
}
impl IgnorePatterns {
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let Some(inputs) = matches.get(&flags::IGNORE_GLOB)? else {
return Ok(Self::empty());
};
let (patterns, mut errors) = Self::parse_from_iter(inputs.to_string_lossy().split('|'));
match errors.pop() {
Some(e) => Err(e.into()),
None => Ok(patterns),
}
}
}
impl GitIgnore {
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
if matches.has(&flags::GIT_IGNORE)? {
Ok(Self::CheckAndIgnore)
} else {
Ok(Self::Off)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::options::flags;
use crate::options::parser::Flag;
use std::ffi::OsString;
macro_rules! test {
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
#[test]
fn $name() {
use crate::options::parser::Arg;
use crate::options::test::parse_for_test;
use crate::options::test::Strictnesses::*;
static TEST_ARGS: &[&Arg] = &[
&flags::SORT,
&flags::ALL,
&flags::ALMOST_ALL,
&flags::TREE,
&flags::IGNORE_GLOB,
&flags::GIT_IGNORE,
];
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| {
$type::deduce(mf)
}) {
assert_eq!(result, $result);
}
}
};
}
mod sort_fields {
use super::*;
test!(empty: SortField <- []; Both => Ok(SortField::default()));
test!(one_arg: SortField <- ["--sort=mod"]; Both => Ok(SortField::ModifiedDate));
test!(one_long: SortField <- ["--sort=size"]; Both => Ok(SortField::Size));
test!(one_short: SortField <- ["-saccessed"]; Both => Ok(SortField::AccessedDate));
test!(lowercase: SortField <- ["--sort", "name"]; Both => Ok(SortField::Name(SortCase::AaBbCc)));
test!(uppercase: SortField <- ["--sort", "Name"]; Both => Ok(SortField::Name(SortCase::ABCabc)));
test!(old: SortField <- ["--sort", "new"]; Both => Ok(SortField::ModifiedDate));
test!(oldest: SortField <- ["--sort=newest"]; Both => Ok(SortField::ModifiedDate));
test!(new: SortField <- ["--sort", "old"]; Both => Ok(SortField::ModifiedAge));
test!(newest: SortField <- ["--sort=oldest"]; Both => Ok(SortField::ModifiedAge));
test!(age: SortField <- ["-sage"]; Both => Ok(SortField::ModifiedAge));
test!(mix_hidden_lowercase: SortField <- ["--sort", ".name"]; Both => Ok(SortField::NameMixHidden(SortCase::AaBbCc)));
test!(mix_hidden_uppercase: SortField <- ["--sort", ".Name"]; Both => Ok(SortField::NameMixHidden(SortCase::ABCabc)));
test!(error: SortField <- ["--sort=colour"]; Both => Err(OptionsError::BadArgument(&flags::SORT, OsString::from("colour"))));
test!(overridden: SortField <- ["--sort=cr", "--sort", "mod"]; Last => Ok(SortField::ModifiedDate));
test!(overridden_2: SortField <- ["--sort", "none", "--sort=Extension"]; Last => Ok(SortField::Extension(SortCase::ABCabc)));
test!(overridden_3: SortField <- ["--sort=cr", "--sort", "mod"]; Complain => Err(OptionsError::Duplicate(Flag::Long("sort"), Flag::Long("sort"))));
test!(overridden_4: SortField <- ["--sort", "none", "--sort=Extension"]; Complain => Err(OptionsError::Duplicate(Flag::Long("sort"), Flag::Long("sort"))));
}
mod dot_filters {
use super::*;
test!(empty: DotFilter <- []; Both => Ok(DotFilter::JustFiles));
test!(all: DotFilter <- ["--all"]; Both => Ok(DotFilter::Dotfiles));
test!(all_all: DotFilter <- ["--all", "-a"]; Both => Ok(DotFilter::DotfilesAndDots));
test!(all_all_2: DotFilter <- ["-aa"]; Both => Ok(DotFilter::DotfilesAndDots));
test!(all_all_3: DotFilter <- ["-aaa"]; Last => Ok(DotFilter::DotfilesAndDots));
test!(all_all_4: DotFilter <- ["-aaa"]; Complain => Err(OptionsError::Conflict(&flags::ALL, &flags::ALL)));
test!(tree_a: DotFilter <- ["-Ta"]; Both => Ok(DotFilter::Dotfiles));
test!(tree_aa: DotFilter <- ["-Taa"]; Both => Err(OptionsError::TreeAllAll));
test!(tree_aaa: DotFilter <- ["-Taaa"]; Both => Err(OptionsError::TreeAllAll));
test!(almost_all: DotFilter <- ["--almost-all"]; Both => Ok(DotFilter::Dotfiles));
test!(almost_all_all: DotFilter <- ["-Aa"]; Both => Ok(DotFilter::Dotfiles));
test!(almost_all_all_2: DotFilter <- ["-Aaa"]; Both => Ok(DotFilter::DotfilesAndDots));
}
mod ignore_patterns {
use super::*;
use std::iter::FromIterator;
fn pat(string: &'static str) -> glob::Pattern {
glob::Pattern::new(string).unwrap()
}
test!(none: IgnorePatterns <- []; Both => Ok(IgnorePatterns::empty()));
test!(one: IgnorePatterns <- ["--ignore-glob", "*.ogg"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg") ])));
test!(two: IgnorePatterns <- ["--ignore-glob=*.ogg|*.MP3"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg"), pat("*.MP3") ])));
test!(loads: IgnorePatterns <- ["-I*|?|.|*"]; Both => Ok(IgnorePatterns::from_iter(vec![ pat("*"), pat("?"), pat("."), pat("*") ])));
test!(overridden: IgnorePatterns <- ["-I=*.ogg", "-I", "*.mp3"]; Last => Ok(IgnorePatterns::from_iter(vec![ pat("*.mp3") ])));
test!(overridden_2: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Last => Ok(IgnorePatterns::from_iter(vec![ pat("*.MP3") ])));
test!(overridden_3: IgnorePatterns <- ["-I=*.ogg", "-I", "*.mp3"]; Complain => Err(OptionsError::Duplicate(Flag::Short(b'I'), Flag::Short(b'I'))));
test!(overridden_4: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Complain => Err(OptionsError::Duplicate(Flag::Short(b'I'), Flag::Short(b'I'))));
}
mod git_ignores {
use super::*;
test!(off: GitIgnore <- []; Both => Ok(GitIgnore::Off));
test!(on: GitIgnore <- ["--git-ignore"]; Both => Ok(GitIgnore::CheckAndIgnore));
}
}