use crate::fs::DotFilter;
use crate::fs::filter::{FileFilter, SortField, SortCase, GroupDirs, IgnorePatterns, SymlinkMode, VcsIgnore};
use crate::options::{flags, OptionsError};
use crate::options::parser::MatchedFlags;
impl FileFilter {
pub fn deduce(matches: &MatchedFlags) -> Result<Self, OptionsError> {
Ok(Self {
group_dirs: GroupDirs::deduce(matches),
reverse: matches.has(flags::REVERSE),
only_dirs: matches.has(flags::ONLY_DIRS),
only_files: matches.has(flags::ONLY_FILES),
sort_field: SortField::deduce(matches)?,
sort_by_total_size: matches.has(flags::TOTAL_SIZE),
dot_filter: DotFilter::deduce(matches)?,
ignore_patterns: IgnorePatterns::deduce(matches)?,
prune_patterns: IgnorePatterns::deduce_from(matches, flags::PRUNE)?,
vcs_ignore: VcsIgnore::deduce(matches),
symlink_mode: SymlinkMode::deduce(matches),
})
}
}
impl GroupDirs {
fn deduce(matches: &MatchedFlags) -> Self {
if let Some(word) = matches.get(flags::GROUP_DIRS) {
return match word {
"first" => Self::First,
"last" => Self::Last,
"none" => Self::None,
_ => unreachable!("Clap rejects invalid --group-dirs values"),
};
}
if matches.has(flags::DIRS_FIRST) {
return Self::First;
}
if matches.has(flags::DIRS_LAST) {
return Self::Last;
}
Self::None
}
}
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 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
}
_ => unreachable!("Clap rejects invalid --sort values"),
};
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.has(flags::TREE) {
Err(OptionsError::TreeAllAll)
}
else {
Ok(Self::DotfilesAndDots)
}
}
}
impl IgnorePatterns {
pub fn deduce(matches: &MatchedFlags) -> Result<Self, OptionsError> {
Self::deduce_from(matches, flags::IGNORE_GLOB)
}
pub fn deduce_from(matches: &MatchedFlags, flag: &str) -> Result<Self, OptionsError> {
let inputs = match matches.get(flag) {
Some(is) => is,
None => return Ok(Self::empty()),
};
let (patterns, mut errors) = Self::parse_from_iter(inputs.split('|'));
match errors.pop() {
Some(e) => Err(e.into()),
None => Ok(patterns),
}
}
}
impl VcsIgnore {
pub fn deduce(matches: &MatchedFlags) -> Self {
if matches.has(flags::VCS_IGNORE) {
Self::CheckAndIgnore
}
else {
Self::Off
}
}
}
impl SymlinkMode {
pub fn deduce(matches: &MatchedFlags) -> Self {
match matches.get(flags::SYMLINKS) {
Some("hide") => Self::Hide,
Some("follow") => Self::Follow,
_ => Self::Show,
}
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! test {
($name:ident: $type:ident <- $inputs:expr; $result:expr) => {
#[test]
fn $name() {
use crate::options::test::parse_for_test;
for result in parse_for_test($inputs.as_ref(), |mf| $type::deduce(mf)) {
assert_eq!(result, $result);
}
}
};
}
mod sort_fields {
use super::*;
test!(empty: SortField <- []; Ok(SortField::default()));
test!(one_arg: SortField <- ["--sort=mod"]; Ok(SortField::ModifiedDate));
test!(one_long: SortField <- ["--sort=size"]; Ok(SortField::Size));
test!(one_short: SortField <- ["-saccessed"]; Ok(SortField::AccessedDate));
test!(lowercase: SortField <- ["--sort", "name"]; Ok(SortField::Name(SortCase::AaBbCc)));
test!(uppercase: SortField <- ["--sort", "Name"]; Ok(SortField::Name(SortCase::ABCabc)));
test!(old: SortField <- ["--sort", "new"]; Ok(SortField::ModifiedDate));
test!(oldest: SortField <- ["--sort=newest"]; Ok(SortField::ModifiedDate));
test!(new: SortField <- ["--sort", "old"]; Ok(SortField::ModifiedAge));
test!(newest: SortField <- ["--sort=oldest"]; Ok(SortField::ModifiedAge));
test!(age: SortField <- ["-sage"]; Ok(SortField::ModifiedAge));
test!(mix_hidden_lowercase: SortField <- ["--sort", ".name"]; Ok(SortField::NameMixHidden(SortCase::AaBbCc)));
test!(mix_hidden_uppercase: SortField <- ["--sort", ".Name"]; Ok(SortField::NameMixHidden(SortCase::ABCabc)));
#[test]
fn error() {
let cmd = crate::options::parser::build_command();
assert!(cmd.try_get_matches_from(["lx", "--sort=colour"]).is_err());
}
test!(overridden: SortField <- ["--sort=cr", "--sort", "mod"]; Ok(SortField::ModifiedDate));
test!(overridden_2: SortField <- ["--sort", "none", "--sort=Extension"]; Ok(SortField::Extension(SortCase::ABCabc)));
}
mod dot_filters {
use super::*;
test!(empty: DotFilter <- []; Ok(DotFilter::JustFiles));
test!(all: DotFilter <- ["--all"]; Ok(DotFilter::Dotfiles));
test!(all_all: DotFilter <- ["--all", "-a"]; Ok(DotFilter::DotfilesAndDots));
test!(all_all_2: DotFilter <- ["-aa"]; Ok(DotFilter::DotfilesAndDots));
test!(all_all_3: DotFilter <- ["-aaa"]; Ok(DotFilter::DotfilesAndDots));
test!(tree_a: DotFilter <- ["-Ta"]; Ok(DotFilter::Dotfiles));
test!(tree_aa: DotFilter <- ["-Taa"]; Err(OptionsError::TreeAllAll));
test!(tree_aaa: DotFilter <- ["-Taaa"]; 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 <- []; Ok(IgnorePatterns::empty()));
test!(one: IgnorePatterns <- ["--ignore-glob", "*.ogg"]; Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg") ])));
test!(two: IgnorePatterns <- ["--ignore-glob=*.ogg|*.MP3"]; Ok(IgnorePatterns::from_iter(vec![ pat("*.ogg"), pat("*.MP3") ])));
test!(loads: IgnorePatterns <- ["-I*|?|.|*"]; Ok(IgnorePatterns::from_iter(vec![ pat("*"), pat("?"), pat("."), pat("*") ])));
test!(overridden: IgnorePatterns <- ["-I=*.ogg", "-I", "*.mp3"]; Ok(IgnorePatterns::from_iter(vec![ pat("*.mp3") ])));
test!(overridden_2: IgnorePatterns <- ["-I", "*.OGG", "-I*.MP3"]; Ok(IgnorePatterns::from_iter(vec![ pat("*.MP3") ])));
}
mod vcs_ignores {
use super::*;
test!(off: VcsIgnore <- []; VcsIgnore::Off);
test!(vcs_flag: VcsIgnore <- ["--vcs-ignore"]; VcsIgnore::CheckAndIgnore);
}
}