use crate::fs::DotFilter;
use crate::fs::filter::{FileFilter, SortField, SortCase, IgnorePatterns, GitIgnore};
use crate::options::{flags, OptionsError};
use crate::options::parser::MatchedFlags;
impl FileFilter {
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
Ok(Self {
list_dirs_first: matches.has(&flags::DIRS_FIRST)?,
reverse: matches.has(&flags::REVERSE)?,
only_dirs: matches.has(&flags::ONLY_DIRS)?,
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 word = match matches.get(&flags::SORT)? {
Some(w) => w,
None => return Ok(Self::default()),
};
let word = match word.to_str() {
Some(w) => w,
None => 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 count = matches.count(&flags::ALL);
if count == 0 {
Ok(Self::JustFiles)
}
else if count == 1 {
Ok(Self::Dotfiles)
}
else if matches.count(&flags::TREE) > 0 {
Err(OptionsError::TreeAllAll)
}
else if count >= 3 && matches.is_strict() {
Err(OptionsError::Conflict(&flags::ALL, &flags::ALL))
}
else {
Ok(Self::DotfilesAndDots)
}
}
}
impl IgnorePatterns {
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let inputs = match matches.get(&flags::IGNORE_GLOB)? {
Some(is) => is,
None => 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 std::ffi::OsString;
use crate::options::flags;
use crate::options::parser::Flag;
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::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));
}
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));
}
}