use crate::fs::DotFilter;
use crate::fs::filter::{
FileFilter, GroupDirs, IgnorePatterns, SortCase, SortField, SymlinkMode, VcsIgnore,
};
use crate::options::parser::MatchedFlags;
use crate::options::{OptionsError, flags};
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),
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 matches.has(flags::NO_DIRS_FIRST) || matches.has(flags::NO_DIRS_LAST) {
return Self::None;
}
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) -> Self {
use crate::fs::sort_registry::SortFieldDef;
let Some(word) = matches.get(flags::SORT) else {
return Self::default();
};
if word == "version" {
return Self::Name(SortCase::AaBbCc);
}
SortFieldDef::field_from_name(word).expect("Clap rejects invalid --sort values")
}
}
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);
let dot_entries_flag = matches.has(flags::DOT_ENTRIES);
let show_dotfiles = count >= 1;
let show_dot_entries = count >= 2 || dot_entries_flag;
if show_dot_entries && matches.has(flags::TREE) {
return Err(OptionsError::TreeAllAll);
}
Ok(Self {
show_dotfiles,
show_dot_entries,
})
}
}
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 Some(inputs) = matches.get(flag) else {
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 <- []; SortField::default());
test!(one_arg: SortField <- ["--sort=mod"]; SortField::ModifiedDate);
test!(one_long: SortField <- ["--sort=size"]; SortField::Size);
test!(one_short: SortField <- ["-saccessed"]; SortField::AccessedDate);
test!(lowercase: SortField <- ["--sort", "name"]; SortField::Name(SortCase::AaBbCc));
test!(uppercase: SortField <- ["--sort", "Name"]; SortField::Name(SortCase::ABCabc));
test!(old: SortField <- ["--sort", "new"]; SortField::ModifiedDate);
test!(oldest: SortField <- ["--sort=newest"]; SortField::ModifiedDate);
test!(new: SortField <- ["--sort", "old"]; SortField::ModifiedAge);
test!(newest: SortField <- ["--sort=oldest"]; SortField::ModifiedAge);
test!(age: SortField <- ["-sage"]; SortField::ModifiedAge);
test!(mix_hidden_lowercase: SortField <- ["--sort", ".name"]; SortField::NameMixHidden(SortCase::AaBbCc));
test!(mix_hidden_uppercase: SortField <- ["--sort", ".Name"]; 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"]; SortField::ModifiedDate);
test!(overridden_2: SortField <- ["--sort", "none", "--sort=Extension"]; SortField::Extension(SortCase::ABCabc));
#[cfg(unix)]
mod metadata_sorts {
use super::*;
test!(permissions: SortField <- ["--sort=permissions"]; SortField::Permissions);
test!(perms_mode: SortField <- ["--sort=mode"]; SortField::Permissions);
test!(perms_octal: SortField <- ["--sort=octal"]; SortField::Permissions);
test!(size_filesize: SortField <- ["--sort=filesize"]; SortField::Size);
#[test]
fn perms_rejected_as_sort_field() {
let cmd = crate::options::parser::build_command();
assert!(cmd.try_get_matches_from(["lx", "--sort=perms"]).is_err());
}
test!(blocks: SortField <- ["--sort=blocks"]; SortField::Blocks);
test!(links: SortField <- ["--sort=links"]; SortField::HardLinks);
test!(flags: SortField <- ["--sort=flags"]; SortField::Flags);
test!(user_lower: SortField <- ["--sort=user"]; SortField::User(SortCase::AaBbCc));
test!(user_upper: SortField <- ["--sort=User"]; SortField::User(SortCase::ABCabc));
test!(group_lower: SortField <- ["--sort=group"]; SortField::Group(SortCase::AaBbCc));
test!(group_upper: SortField <- ["--sort=Group"]; SortField::Group(SortCase::ABCabc));
test!(uid: SortField <- ["--sort=uid"]; SortField::Uid);
test!(gid: SortField <- ["--sort=gid"]; SortField::Gid);
test!(vcs: SortField <- ["--sort=vcs"]; SortField::VcsStatusSort);
test!(version: SortField <- ["--sort=version"]; SortField::Name(SortCase::AaBbCc));
}
}
mod dot_filters {
use super::*;
const JUST_FILES: DotFilter = DotFilter {
show_dotfiles: false,
show_dot_entries: false,
};
const DOTFILES: DotFilter = DotFilter {
show_dotfiles: true,
show_dot_entries: false,
};
const DOTFILES_AND_DOTS: DotFilter = DotFilter {
show_dotfiles: true,
show_dot_entries: true,
};
const DOT_ENTRIES_ONLY: DotFilter = DotFilter {
show_dotfiles: false,
show_dot_entries: true,
};
test!(empty: DotFilter <- []; Ok(JUST_FILES));
test!(all: DotFilter <- ["--all"]; Ok(DOTFILES));
test!(all_all: DotFilter <- ["--all", "-a"]; Ok(DOTFILES_AND_DOTS));
test!(all_all_2: DotFilter <- ["-aa"]; Ok(DOTFILES_AND_DOTS));
test!(all_all_3: DotFilter <- ["-aaa"]; Ok(DOTFILES_AND_DOTS));
test!(dot_entries: DotFilter <- ["--dot-entries"]; Ok(DOT_ENTRIES_ONLY));
test!(dot_entries_alone: DotFilter <- ["--dot-entries"]; Ok(DOT_ENTRIES_ONLY));
test!(all_and_dot_entries: DotFilter <- ["-a", "--dot-entries"]; Ok(DOTFILES_AND_DOTS));
test!(tree_a: DotFilter <- ["-Ta"]; Ok(DOTFILES));
test!(tree_aa: DotFilter <- ["-Taa"]; Err(OptionsError::TreeAllAll));
test!(tree_aaa: DotFilter <- ["-Taaa"]; Err(OptionsError::TreeAllAll));
test!(tree_dot_entries: DotFilter <- ["-T", "--dot-entries"]; 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);
}
}