ex_cli/
config.rs

1use crate::error::{MyError, MyResult};
2use crate::fs::entry::Entry;
3use crate::fs::flags::FileFlags;
4use crate::fs::system::{System, EXEC_MASK};
5use crate::git::flags::GitFlags;
6use crate::util::calendar::Calendar;
7use chrono::{DateTime, Duration, TimeZone, Utc};
8use clap::parser::ValuesRef;
9use clap::{Arg, ArgAction, ArgMatches, Command};
10use clap_complete::Shell;
11use indexmap::IndexSet;
12use regex::{Match, Regex};
13use std::collections::HashSet;
14use std::fmt::Display;
15use std::num::ParseIntError;
16use std::ops::Sub;
17use std::process;
18
19#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
20pub enum DirKind {
21    Asc,
22    Desc,
23}
24
25#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
26pub enum OrderKind {
27    Dir,
28    Group,
29    Name,
30    Ext,
31    Size(DirKind),
32    Time(DirKind),
33}
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum RecentKind {
37    None,
38    Sec(i64),
39    Min(i64),
40    Hour(i64),
41    Day(i64),
42    Week(i64),
43    Month(i64),
44    Year(i64),
45}
46
47#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
48pub enum ExecKind {
49    None,
50    User,
51    Other,
52}
53
54#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
55pub enum FileKind {
56    File(ExecKind),
57    Dir,
58    Link(bool), // (true for resolved link)
59    Other,
60}
61
62pub struct Config {
63    pub curr_time: DateTime<Utc>,
64    pub min_depth: Option<usize>,
65    pub max_depth: Option<usize>,
66    pub show_indent: bool,
67    pub all_files: bool,
68    pub all_recurse: bool,
69    pub zip_expand: bool,
70    pub zip_password: Option<String>,
71    pub case_sensitive: Option<bool>,
72    pub order_files: Vec<OrderKind>,
73    pub order_name: bool,
74    pub filter_recent: RecentKind,
75    pub filter_types: Option<HashSet<FileKind>>,
76    pub filter_git: Option<GitFlags>,
77    pub show_total: bool,
78    #[cfg(debug_assertions)]
79    pub show_debug: bool,
80    pub show_pretty: bool,
81    pub show_utc: bool,
82    #[cfg(unix)]
83    pub show_owner: bool,
84    pub show_sig: bool,
85    pub only_path: bool,
86    pub escape_path: bool,
87    pub null_path: bool,
88    pub abs_path: bool,
89    #[cfg(windows)]
90    pub win_path: bool,
91    #[cfg(windows)]
92    pub win_ver: bool,
93    pub completion: Option<Shell>,
94    pub patterns: Vec<String>,
95}
96
97impl OrderKind {
98    fn from(ch: char) -> Option<Self> {
99        match ch {
100            'd' => Some(Self::Dir),
101            'n' => Some(Self::Name),
102            'e' => Some(Self::Ext),
103            's' => Some(Self::Size(DirKind::Asc)),
104            't' => Some(Self::Time(DirKind::Asc)),
105            _ => None,
106        }
107    }
108
109    fn ascending(self: Self) -> Self {
110        match self {
111            Self::Size(_) => Self::Size(DirKind::Asc),
112            Self::Time(_) => Self::Time(DirKind::Asc),
113            any => any,
114        }
115    }
116
117    fn descending(self: Self) -> Self {
118        match self {
119            Self::Size(_) => Self::Size(DirKind::Desc),
120            Self::Time(_) => Self::Time(DirKind::Desc),
121            any => any,
122        }
123    }
124}
125
126impl RecentKind {
127    fn from(kind: &str, count: i64) -> Self {
128        match kind {
129            "S" => Self::Sec(count),
130            "M" => Self::Min(count),
131            "H" | "h" => Self::Hour(count),
132            "d" => Self::Day(count),
133            "w" => Self::Week(count),
134            "m" => Self::Month(count),
135            "y" => Self::Year(count),
136            _ => Self::None,
137        }
138    }
139
140    pub fn subtract_from<Tz: TimeZone>(&self, curr_time: &DateTime<Utc>, zone: &Tz) -> Option<DateTime<Utc>> {
141        match self {
142            Self::None => None,
143            Self::Sec(count) => Some(curr_time.sub(Duration::seconds(*count))),
144            Self::Min(count) => Some(curr_time.sub(Duration::minutes(*count))),
145            Self::Hour(count) => Some(curr_time.sub(Duration::hours(*count))),
146            Self::Day(count) => Some(curr_time.sub(Duration::days(*count))),
147            Self::Week(count) => Some(curr_time.sub(Duration::weeks(*count))),
148            Self::Month(count) => Some(Calendar::from_time(curr_time, zone).subtract_month(*count, zone)),
149            Self::Year(count) => Some(Calendar::from_time(curr_time, zone).subtract_year(*count, zone)),
150        }
151    }
152
153    pub fn from_times<Tz: TimeZone>(
154        file_time: &DateTime<Utc>,
155        curr_time: &DateTime<Utc>,
156        curr_calendar: &Calendar,
157        zone: &Tz,
158    ) -> Self {
159        let delta = curr_time.signed_duration_since(file_time);
160        let count = delta.num_days();
161        if count > 0 {
162            let file_calendar = Calendar::from_time(file_time, zone);
163            if let Some(count) = file_calendar.num_years_to(&curr_calendar) {
164                return Self::Year(count);
165            }
166            if let Some(count) = file_calendar.num_months_to(&curr_calendar) {
167                return Self::Month(count);
168            }
169            return Self::Day(count);
170        }
171        let count = delta.num_hours();
172        if count > 0 {
173            return Self::Hour(count);
174        }
175        let count = delta.num_minutes();
176        if count > 0 {
177            return Self::Min(count);
178        }
179        let count = delta.num_seconds();
180        if count >= 0 {
181            return Self::Sec(count);
182        }
183        Self::None
184    }
185}
186
187impl FileKind {
188    pub fn from_char(file_type: char) -> Option<Vec<Self>> {
189        match file_type {
190            'f' => Some(vec![
191                Self::File(ExecKind::None),
192                Self::File(ExecKind::User),
193                Self::File(ExecKind::Other),
194            ]),
195            'e' => Some(vec![
196                Self::File(ExecKind::User),
197                Self::File(ExecKind::Other),
198            ]),
199            'd' => Some(vec![
200                Self::Dir,
201            ]),
202            'l' => Some(vec![
203                Self::Link(false),
204                Self::Link(true),
205            ]),
206            _ => None,
207        }
208    }
209
210    pub fn from_entry<S: System>(system: &S, entry: &dyn Entry) -> Self {
211        match entry.file_flags() {
212            FileFlags::File => Self::parse_file(system, entry),
213            FileFlags::Dir => Self::Dir,
214            FileFlags::Link => Self::Link(true),
215            FileFlags::Other => Self::Other,
216        }
217    }
218
219    #[cfg(unix)]
220    fn parse_file<S: System>(system: &S, entry: &dyn Entry) -> Self {
221        if (entry.file_mode() & system.get_mask(entry.owner_uid(), entry.owner_gid())) != 0 {
222            Self::File(ExecKind::User)
223        } else if (entry.file_mode() & EXEC_MASK) != 0 {
224            Self::File(ExecKind::Other)
225        } else {
226            Self::File(ExecKind::None)
227        }
228    }
229
230    #[cfg(not(unix))]
231    fn parse_file<S: System>(_system: &S, entry: &dyn Entry) -> Self {
232        if (entry.file_mode() & EXEC_MASK) != 0 {
233            Self::File(ExecKind::User)
234        } else {
235            Self::File(ExecKind::None)
236        }
237    }
238
239    pub fn dir_offset(&self) -> usize {
240        if *self == Self::Dir { 1 } else { 0 }
241    }
242}
243
244const RECURSE_SHORT: &'static str = "Find files in subdirectories";
245const DEPTH_SHORT: &'static str = "Find files to maximum depth M-N";
246const INDENT_SHORT: &'static str = "Indent files in subdirectories";
247const ALL_FILES_SHORT: &'static str = "Show .* and __*__ files (twice to recurse)";
248const ZIP_SHORT: &'static str = "Expand zip files";
249const PASSWORD_SHORT: &'static str = "Use zip password";
250const WITH_CASE_SHORT: &'static str = "Force case sensitive match";
251const NO_CASE_SHORT: &'static str = "Force case insensitive match";
252const ORDER_SHORT: &'static str = "Sort files by order [dnest][+-]...";
253const RECENT_SHORT: &'static str = "Include recent files [ymwdHMS][N]...";
254const TYPE_SHORT: &'static str = "Include files by type [fedl]...";
255const GIT_SHORT: &'static str = "Include files by Git status [xcamriu]...";
256const TOTAL_SHORT: &'static str = "Show total file size";
257#[cfg(debug_assertions)]
258const DEBUG_SHORT: &'static str = "Show debug information";
259const UTC_SHORT: &'static str = "Show times in UTC";
260#[cfg(unix)]
261const OWNER_SHORT: &'static str = "Show user and group";
262const SIG_SHORT: &'static str = "Show first four bytes of files";
263const ONLY_PATH_SHORT: &'static str = "Show paths only (repeat to show all attributes)";
264const NULL_PATH_SHORT: &'static str = "Show paths only (with null separator for xargs)";
265const ABS_PATH_SHORT: &'static str = "Show absolute paths";
266#[cfg(windows)]
267const WIN_PATH_SHORT: &'static str = "Show Windows paths";
268#[cfg(windows)]
269const WIN_VER_SHORT: &'static str = "Show Windows versions";
270#[cfg(debug_assertions)]
271const NOW_SHORT: &'static str = "Set current time for readme examples";
272#[cfg(debug_assertions)]
273const TERMINAL_SHORT: &'static str = "Force terminal output for readme examples";
274const COMPLETION_SHORT: &'static str = "Create completion script";
275const PATTERNS_SHORT: &'static str = "File matching patterns";
276
277const RECURSE_LONG: &'static str = "\
278Find files in subdirectories";
279const DEPTH_LONG: &'static str = "\
280Find files to maximum depth:
281Use \"-d4\" or \"-d-4\" to find files up to depth 4
282Use \"-d2-4\" to find files at depth 2, 3 or 4
283Use \"-d2-\" to find files at depth 2 and beyond";
284const INDENT_LONG: &'static str = "\
285Indent files in subdirectories";
286const ALL_FILES_LONG: &'static str = "\
287Show all files:
288Use \"-a\" to show hidden files and directories
289Use \"-aa\" to recurse into hidden directories
290Include Unix hidden files like \".bashrc\"
291Include Python cache directories \"__pycache__\"";
292const ZIP_LONG: &'static str = "\
293Expand zip files (*.zip, *.7z, *.tar, *.tar.gz)";
294const PASSWORD_LONG: &'static str = "\
295Use zip password (may be stored in shell command history!)";
296const WITH_CASE_LONG: &'static str = "\
297Force case sensitive match on Windows";
298const NO_CASE_LONG: &'static str = "\
299Force case insensitive match on Linux";
300const ORDER_LONG: &'static str = "\
301Sort files by order:
302Use \"-od\" to sort files by directory
303Use \"-on\" to sort files by filename
304Use \"-oe\" to sort files by extension
305Use \"-os\" to sort files by size (increasing)
306Use \"-os-\" to sort files by size (decreasing)
307Use \"-ot\" to sort files by time (increasing)
308Use \"-ot-\" to sort files by time (decreasing)
309Use \"-oest\" to sort files by extension then size then time";
310const RECENT_LONG: &'static str = "\
311Include recent files:
312Use \"-ry10\" to include files up to ten years old
313Use \"-rm6\" to include files up to six months old
314Use \"-rw2\" to include files up to two weeks old
315Use \"-rd\" to include files up to one day old
316Use \"-rH\" to include files up to one hour old
317Use \"-rM5\" to include files up to five minutes old
318Use \"-rS10\" to include files up to ten seconds old";
319const TYPE_LONG: &'static str = "\
320Include files by type:
321Use \"-tf\" to include files
322Use \"-te\" to include executables
323Use \"-td\" to include directories
324Use \"-tl\" to include links";
325const GIT_LONG: &'static str = "\
326Include files by Git status:
327Use \"-gx\" to include all files (with untracked and ignored)
328Use \"-gc\" to include cached files (not untracked or ignored)
329Use \"-ga\" to include only added files
330Use \"-gm\" to include only modified files
331Use \"-gr\" to include only renamed files
332Use \"-gu\" to include only untracked files
333Use \"-gi\" to include only ignored files (according to .gitignore)";
334const TOTAL_LONG: &'static str = "\
335Show total file size, and number of files and directories";
336#[cfg(debug_assertions)]
337const DEBUG_LONG: &'static str = "\
338Show debug information, including file depth";
339const UTC_LONG: &'static str = "\
340Show times in UTC";
341#[cfg(unix)]
342const OWNER_LONG: &'static str = "\
343Show user and group on Linux";
344const SIG_LONG: &'static str = "\
345Show first four bytes (signature) of files";
346const ONLY_PATH_LONG: &'static str = "\
347Show paths only:
348Use \"-x\" to show paths only
349Use \"-xx\" to show all attributes (pretty printing enabled)
350Use \"-xxx\" to show all attributes (pretty printing disabled)
351By default show all attributes when writing to the console
352By default show escaped paths when writing to a file";
353const NULL_PATH_LONG: &'static str = "\
354Show paths only with null separator for xargs";
355const ABS_PATH_LONG: &'static str = "\
356Show absolute paths";
357#[cfg(windows)]
358const WIN_PATH_LONG: &'static str = "\
359Show Windows paths:
360C:\\Path\\file.txt not /c/Path/file.txt in Bash";
361#[cfg(windows)]
362const WIN_VER_LONG: &'static str = "\
363Show Windows versions (*.exe and *.dll only)";
364#[cfg(debug_assertions)]
365const NOW_LONG: &'static str = "\
366Set current time for readme examples, e.g. \"2024-01-01T00:00:00Z\"";
367#[cfg(debug_assertions)]
368const TERMINAL_LONG: &'static str = "\
369Force terminal output for readme examples; disables piped output";
370const COMPLETION_LONG: &'static str = "\
371Create completion script:
372Use \"--completion bash\" to create script for Bash
373Use \"--completion elvish\" to create script for Elvish
374Use \"--completion fish\" to create script for Fish
375Use \"--completion powershell\" to create script for PowerShell
376Use \"--completion zsh\" to create script for Zsh";
377const PATTERNS_LONG: &'static str = "\
378File matching patterns";
379
380// noinspection RsLift
381impl Config {
382    pub fn new(name: String, args: Vec<String>, piped: bool) -> MyResult<Self> {
383        let mut command = Self::create_command(name.clone());
384        let matches = Self::create_matches(&mut command, args)?;
385        let config = Self::create_config(&mut command, matches, piped)?;
386        if let Some(completion) = config.completion {
387            Self::create_completion(&mut command, name, completion);
388            process::exit(1);
389        }
390        Ok(config)
391    }
392
393    pub fn default() -> Self {
394        Self {
395            curr_time: Self::create_epoch(),
396            min_depth: None,
397            max_depth: None,
398            show_indent: false,
399            all_files: false,
400            all_recurse: false,
401            zip_expand: false,
402            zip_password: None,
403            case_sensitive: None,
404            order_files: Vec::new(),
405            order_name: false,
406            filter_recent: RecentKind::None,
407            filter_types: None,
408            filter_git: None,
409            show_total: false,
410            #[cfg(debug_assertions)]
411            show_debug: false,
412            show_pretty: false,
413            show_utc: false,
414            #[cfg(unix)]
415            show_owner: false,
416            show_sig: false,
417            only_path: false,
418            escape_path: false,
419            null_path: false,
420            abs_path: false,
421            #[cfg(windows)]
422            win_path: false,
423            #[cfg(windows)]
424            win_ver: false,
425            completion: None,
426            patterns: Vec::new(),
427        }
428    }
429
430    fn create_epoch() -> DateTime<Utc> {
431        Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()
432    }
433
434    pub fn start_time<Tz: TimeZone>(&self, zone: &Tz) -> Option<DateTime<Utc>> {
435        self.filter_recent.subtract_from(&self.curr_time, zone)
436    }
437
438    fn create_command(name: String) -> Command {
439        let mut index = 0;
440        let command = Command::new(name)
441            .version(clap::crate_version!())
442            .about(clap::crate_description!())
443            .author(clap::crate_authors!());
444        let command = command
445            .arg(Self::create_arg("recurse", &mut index)
446            .long("recurse")
447            .short('s')
448            .action(ArgAction::SetTrue)
449            .help(RECURSE_SHORT)
450            .long_help(RECURSE_LONG));
451        let command = command
452            .arg(Self::create_arg("depth", &mut index)
453            .long("depth")
454            .short('d')
455            .value_name("DEPTH")
456            .action(ArgAction::Set)
457            .help(DEPTH_SHORT)
458            .long_help(DEPTH_LONG));
459        let command = command
460            .arg(Self::create_arg("indent", &mut index)
461            .long("indent")
462            .short('i')
463            .action(ArgAction::SetTrue)
464            .help(INDENT_SHORT)
465            .long_help(INDENT_LONG));
466        let command = command
467            .arg(Self::create_arg("all-files", &mut index)
468            .long("all-files")
469            .short('a')
470            .action(ArgAction::Count)
471            .help(ALL_FILES_SHORT)
472            .long_help(ALL_FILES_LONG));
473        let command = command
474            .arg(Self::create_arg("zip", &mut index)
475            .long("zip")
476            .short('z')
477            .action(ArgAction::SetTrue)
478            .help(ZIP_SHORT)
479            .long_help(ZIP_LONG));
480        let command = command
481            .arg(Self::create_arg("password", &mut index)
482            .long("password")
483            .action(ArgAction::Set)
484            .help(PASSWORD_SHORT)
485            .long_help(PASSWORD_LONG));
486        let command = command
487            .arg(Self::create_arg("case", &mut index)
488            .long("case")
489            .action(ArgAction::SetTrue)
490            .help(WITH_CASE_SHORT)
491            .long_help(WITH_CASE_LONG));
492        let command = command
493            .arg(Self::create_arg("no-case", &mut index)
494            .long("no-case")
495            .action(ArgAction::SetTrue)
496            .help(NO_CASE_SHORT)
497            .long_help(NO_CASE_LONG));
498        let command = command
499            .arg(Self::create_arg("order", &mut index)
500            .long("order")
501            .short('o')
502            .value_name("ORDER")
503            .action(ArgAction::Set)
504            .help(ORDER_SHORT)
505            .long_help(ORDER_LONG));
506        let command = command
507            .arg(Self::create_arg("recent", &mut index)
508            .long("recent")
509            .short('r')
510            .value_name("RECENT")
511            .action(ArgAction::Set)
512            .help(RECENT_SHORT)
513            .long_help(RECENT_LONG));
514        let command = command
515            .arg(Self::create_arg("type", &mut index)
516            .long("type")
517            .short('t')
518            .value_name("TYPE")
519            .action(ArgAction::Append)
520            .help(TYPE_SHORT)
521            .long_help(TYPE_LONG));
522        let command = command
523            .arg(Self::create_arg("git", &mut index)
524            .long("git")
525            .short('g')
526            .value_name("GIT")
527            .action(ArgAction::Append)
528            .help(GIT_SHORT)
529            .long_help(GIT_LONG));
530        let command = command
531            .arg(Self::create_arg("total", &mut index)
532            .long("total")
533            .action(ArgAction::SetTrue)
534            .help(TOTAL_SHORT)
535            .long_help(TOTAL_LONG));
536        #[cfg(debug_assertions)]
537        let command = command
538            .arg(Self::create_arg("debug", &mut index)
539            .long("debug")
540            .action(ArgAction::SetTrue)
541            .help(DEBUG_SHORT)
542            .long_help(DEBUG_LONG));
543        let command = command
544            .arg(Self::create_arg("utc", &mut index)
545            .long("utc")
546            .short('u')
547            .action(ArgAction::SetTrue)
548            .help(UTC_SHORT)
549            .long_help(UTC_LONG));
550        #[cfg(unix)]
551        let command = command
552            .arg(Self::create_arg("owner", &mut index)
553            .long("owner")
554            .action(ArgAction::SetTrue)
555            .help(OWNER_SHORT)
556            .long_help(OWNER_LONG));
557        let command = command
558            .arg(Self::create_arg("sig", &mut index)
559            .long("sig")
560            .action(ArgAction::SetTrue)
561            .help(SIG_SHORT)
562            .long_help(SIG_LONG));
563        let command = command
564            .arg(Self::create_arg("only-path", &mut index)
565            .long("only-path")
566            .short('x')
567            .action(ArgAction::Count)
568            .help(ONLY_PATH_SHORT)
569            .long_help(ONLY_PATH_LONG));
570        let command = command
571            .arg(Self::create_arg("null-path", &mut index)
572            .long("null-path")
573            .action(ArgAction::SetTrue)
574            .help(NULL_PATH_SHORT)
575            .long_help(NULL_PATH_LONG));
576        let command = command
577            .arg(Self::create_arg("abs-path", &mut index)
578            .long("abs-path")
579            .short('q')
580            .action(ArgAction::SetTrue)
581            .help(ABS_PATH_SHORT)
582            .long_help(ABS_PATH_LONG));
583        #[cfg(windows)]
584        let command = command
585            .arg(Self::create_arg("win-path", &mut index)
586            .long("win-path")
587            .short('w')
588            .action(ArgAction::SetTrue)
589            .help(WIN_PATH_SHORT)
590            .long_help(WIN_PATH_LONG));
591        #[cfg(windows)]
592        let command = command
593            .arg(Self::create_arg("win-ver", &mut index)
594            .long("win-ver")
595            .short('v')
596            .action(ArgAction::SetTrue)
597            .help(WIN_VER_SHORT)
598            .long_help(WIN_VER_LONG));
599        #[cfg(debug_assertions)]
600        let command = command
601            .arg(Self::create_arg("now", &mut index)
602            .long("now")
603            .value_name("TIME")
604            .action(ArgAction::Set)
605            .help(NOW_SHORT)
606            .long_help(NOW_LONG));
607        #[cfg(debug_assertions)]
608        let command = command
609            .arg(Self::create_arg("terminal", &mut index)
610            .long("terminal")
611            .action(ArgAction::SetTrue)
612            .help(TERMINAL_SHORT)
613            .long_help(TERMINAL_LONG));
614        let command = command
615            .arg(Self::create_arg("completion", &mut index)
616            .long("completion")
617            .value_name("SHELL")
618            .action(ArgAction::Set)
619            .value_parser(["bash", "elvish", "fish", "powershell", "zsh"])
620            .hide_possible_values(true)
621            .help(COMPLETION_SHORT)
622            .long_help(COMPLETION_LONG));
623        let command = command
624            .arg(Self::create_arg("patterns", &mut index)
625            .action(ArgAction::Append)
626            .default_value(".")
627            .help(PATTERNS_SHORT)
628            .long_help(PATTERNS_LONG));
629        command
630    }
631
632    fn create_arg(name: &'static str, index: &mut usize) -> Arg {
633        *index += 1;
634        Arg::new(name).display_order(*index)
635    }
636
637    fn create_matches(command: &mut Command, args: Vec<String>) -> clap::error::Result<ArgMatches> {
638        match command.try_get_matches_from_mut(args) {
639            Ok(found) => Ok(found),
640            Err(error) => match error.kind() {
641                clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
642                    let error = error.to_string();
643                    let error = error.trim_end();
644                    eprintln!("{error}");
645                    process::exit(1);
646                },
647                _ => Err(error),
648            }
649        }
650    }
651
652    fn create_config(command: &mut Command, matches: ArgMatches, piped: bool) -> MyResult<Self> {
653        #[cfg(debug_assertions)]
654        let curr_time = Self::parse_time(matches.get_one("now"))?;
655        #[cfg(not(debug_assertions))]
656        let curr_time = Utc::now();
657        let (min_depth, max_depth) = Self::parse_depth(command, matches.get_one("depth"), matches.get_flag("recurse"))?;
658        let show_indent = matches.get_flag("indent");
659        let (all_files, all_recurse) = Self::parse_all(matches.get_count("all-files"));
660        let zip_expand = matches.get_flag("zip");
661        let zip_password = matches.get_one("password").cloned();
662        let case_sensitive = Self::parse_case(matches.get_flag("case"), matches.get_flag("no-case"));
663        let (order_files, order_name) = Self::parse_order(command, matches.get_one("order"), max_depth, show_indent)?;
664        let filter_recent = Self::parse_recent(command, matches.get_one("recent"))?;
665        let filter_types = Self::parse_types(command, matches.get_many("type"))?;
666        let filter_git = Self::parse_git(command, matches.get_many("git"))?;
667        let show_total = matches.get_flag("total");
668        #[cfg(debug_assertions)]
669        let show_debug = matches.get_flag("debug");
670        let show_utc = matches.get_flag("utc");
671        #[cfg(unix)]
672        let show_owner = matches.get_flag("owner");
673        let show_sig = matches.get_flag("sig");
674        let only_path = matches.get_count("only-path");
675        let null_path = matches.get_flag("null-path");
676        #[cfg(debug_assertions)]
677        let piped = piped && !matches.get_flag("terminal");
678        let (show_pretty, only_path, escape_path) = Self::parse_only(only_path, null_path, piped);
679        let abs_path = matches.get_flag("abs-path");
680        #[cfg(windows)]
681        let win_path = matches.get_flag("win-path");
682        #[cfg(windows)]
683        let win_ver = matches.get_flag("win-ver");
684        let completion = Self::parse_completion(command, matches.get_one("completion"))?;
685        let patterns = Self::parse_values(matches.get_many("patterns"));
686        let config = Self {
687            curr_time,
688            min_depth,
689            max_depth,
690            show_indent,
691            all_files,
692            all_recurse,
693            zip_expand,
694            zip_password,
695            case_sensitive,
696            order_files,
697            order_name,
698            filter_recent,
699            filter_types,
700            filter_git,
701            show_total,
702            #[cfg(debug_assertions)]
703            show_debug,
704            show_pretty,
705            show_utc,
706            #[cfg(unix)]
707            show_owner,
708            show_sig,
709            only_path,
710            escape_path,
711            null_path,
712            abs_path,
713            #[cfg(windows)]
714            win_path,
715            #[cfg(windows)]
716            win_ver,
717            completion,
718            patterns,
719        };
720        Ok(config)
721    }
722
723    #[cfg(debug_assertions)]
724    fn parse_time(value: Option<&String>) -> MyResult<DateTime<Utc>> {
725        if let Some(value) = value {
726            let time = DateTime::parse_from_rfc3339(value)?;
727            Ok(time.to_utc())
728        } else {
729            Ok(Utc::now())
730        }
731    }
732
733    fn parse_depth(
734        command: &mut Command,
735        value: Option<&String>,
736        recurse: bool,
737    ) -> MyResult<(Option<usize>, Option<usize>)> {
738        if recurse {
739            Ok((Some(1), None))
740        } else if let Some(value) = value {
741            match value.parse::<usize>() {
742                Ok(value) => Ok((Some(1), Some(value + 1))),
743                Err(_) => {
744                    let re = Regex::new("^(\\d+)?-(\\d+)?$")?;
745                    match re.captures(value) {
746                        Some(captures) => {
747                            let min_depth = Self::parse_integer(captures.get(1))?;
748                            let max_depth = Self::parse_integer(captures.get(2))?;
749                            let min_depth = min_depth.map(|x| x + 1).or(Some(1));
750                            let max_depth = max_depth.map(|x| x + 1);
751                            Ok((min_depth, max_depth))
752                        }
753                        None => Err(Self::make_error(command, "depth", value)),
754                    }
755                }
756            }
757        } else {
758            Ok((Some(1), Some(1)))
759        }
760    }
761
762    fn parse_integer(matched: Option<Match>) -> Result<Option<usize>, ParseIntError> {
763        match matched {
764            Some(m) => m.as_str().parse().map(|x| Some(x)),
765            None => Ok(None),
766        }
767    }
768
769    fn parse_all(all_files: u8) -> (bool, bool) {
770        // Returns all_files, all_recurse.
771        match all_files {
772            0 => (false, false),
773            1 => (true, false),
774            _ => (true, true),
775        }
776    }
777
778    fn parse_case(enabled: bool, disabled: bool) -> Option<bool> {
779        if enabled {
780            Some(true)
781        } else if disabled {
782            Some(false)
783        } else {
784            None
785        }
786    }
787
788    fn parse_order(
789        command: &mut Command,
790        value: Option<&String>,
791        max_depth: Option<usize>,
792        show_indent: bool,
793    ) -> MyResult<(Vec<OrderKind>, bool)> {
794        let mut order_files = IndexSet::new();
795        let mut order_name = false;
796        if show_indent {
797            order_files.insert(OrderKind::Dir);
798        }
799        if let Some(value) = value {
800            let mut order = None;
801            for ch in value.chars() {
802                if ch.is_alphabetic() {
803                    if let Some(order) = order {
804                        order_files.insert(order);
805                    }
806                    order = OrderKind::from(ch);
807                    if order == None {
808                        return Err(Self::make_error(command, "order", value));
809                    }
810                    if order == Some(OrderKind::Name) {
811                        order_name = true;
812                    }
813                } else if ch == '+' {
814                    order = order.map(|x| x.ascending());
815                } else if ch == '-' {
816                    order = order.map(|x| x.descending());
817                } else {
818                    return Err(Self::make_error(command, "order", value));
819                }
820            }
821            if let Some(order) = order {
822                order_files.insert(order);
823            }
824        }
825        if order_files.is_empty() {
826            if let Some(max_depth) = max_depth {
827                if max_depth <= 1 {
828                    order_files.insert(OrderKind::Group);
829                }
830            }
831        }
832        order_files.insert(OrderKind::Dir);
833        order_files.insert(OrderKind::Name);
834        Ok((order_files.into_iter().collect(), order_name))
835    }
836
837    fn parse_recent(command: &mut Command, value: Option<&String>) -> MyResult<RecentKind> {
838        if let Some(value) = value {
839            let re = Regex::new("^([ymwdhHMS])(\\d+)?$")?;
840            return match re.captures(value) {
841                Some(captures) => {
842                    let count = captures
843                        .get(2)
844                        .map(|x| x.as_str())
845                        .map(|x| x.parse())
846                        .unwrap_or(Ok(1))?;
847                    let recent = captures
848                        .get(1)
849                        .map(|x| x.as_str())
850                        .map(|x| RecentKind::from(x, count))
851                        .unwrap_or(RecentKind::None);
852                    Ok(recent)
853                }
854                None => Err(Self::make_error(command, "recent", value)),
855            }
856        }
857        Ok(RecentKind::None)
858    }
859
860    fn parse_types(command: &mut Command, values: Option<ValuesRef<String>>) -> MyResult<Option<HashSet<FileKind>>> {
861        if let Some(values) = values {
862            let mut results = HashSet::new();
863            for value in values.flat_map(|x| x.chars()) {
864                if let Some(result) = FileKind::from_char(value) {
865                    results.extend(result);
866                } else {
867                    return Err(Self::make_error(command, "type", value));
868                }
869            }
870            return Ok(Some(results));
871        }
872        Ok(None)
873    }
874
875    fn parse_git(command: &mut Command, values: Option<ValuesRef<String>>) -> MyResult<Option<GitFlags>> {
876        if let Some(values) = values {
877            let mut flags = GitFlags::new();
878            for value in values.flat_map(|x| x.chars()) {
879                match value {
880                    'x' => flags.everything = true,
881                    'c' => flags.cached = true,
882                    'a' => flags.added = true,
883                    'm' => flags.modified = true,
884                    'r' => flags.renamed = true,
885                    'u' => flags.untracked = true,
886                    'i' => flags.ignored = true,
887                    _ => return Err(Self::make_error(command, "git", value)),
888                }
889            }
890            return Ok(Some(flags));
891        }
892        Ok(None)
893    }
894
895    fn parse_only(only_path: u8, null_path: bool, piped: bool) -> (bool, bool, bool) {
896        // Returns show_pretty, only_path, escape_path.
897        if null_path {
898            (false, true, false)
899        } else {
900            match only_path {
901                0 => (true, piped, piped),
902                1 => (false, true, false),
903                2 => (true, false, false),
904                _ => (false, false, false),
905            }
906        }
907    }
908
909    fn parse_values(values: Option<ValuesRef<String>>) -> Vec<String> {
910        values.unwrap_or_default().map(String::to_string).collect()
911    }
912
913    fn parse_completion(command: &mut Command, value: Option<&String>) -> MyResult<Option<Shell>> {
914        let value = value.map(String::as_ref);
915        match value {
916            Some("bash") => Ok(Some(Shell::Bash)),
917            Some("elvish") => Ok(Some(Shell::Elvish)),
918            Some("fish") => Ok(Some(Shell::Fish)),
919            Some("powershell") => Ok(Some(Shell::PowerShell)),
920            Some("ps") => Ok(Some(Shell::PowerShell)),
921            Some("zsh") => Ok(Some(Shell::Zsh)),
922            Some(value) => Err(Self::make_error(command, "completion", value)),
923            None => Ok(None),
924        }
925    }
926
927    fn create_completion(command: &mut Command, name: String, value: Shell) {
928        let mut stdout = std::io::stdout();
929        clap_complete::generate(value, command, name, &mut stdout);
930    }
931
932    fn make_error<T: Display>(command: &mut Command, option: &str, value: T) -> MyError {
933        let message = format!("Invalid {option} option: {value}");
934        let error = command.error(clap::error::ErrorKind::ValueValidation, message);
935        MyError::Clap(error)
936    }
937
938    #[cfg(windows)]
939    pub fn want_decrypt(&self) -> bool {
940        self.show_sig || self.win_ver
941    }
942
943    #[cfg(not(windows))]
944    pub fn want_decrypt(&self) -> bool {
945        self.show_sig
946    }
947}
948
949#[cfg(test)]
950#[allow(unexpected_cfgs)]
951mod tests {
952    use super::*;
953    use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
954    use pretty_assertions::assert_eq;
955    use std::hash::Hash;
956
957    #[test]
958    fn test_depth_is_handled() {
959        let args = vec!["ex"];
960        let config = create_config(args);
961        assert_eq!(Some(1), config.min_depth);
962        assert_eq!(Some(1), config.max_depth);
963
964        let args = vec!["ex", "-d4"];
965        let config = create_config(args);
966        assert_eq!(Some(1), config.min_depth);
967        assert_eq!(Some(5), config.max_depth);
968
969        let args = vec!["ex", "-d-4"];
970        let config = create_config(args);
971        assert_eq!(Some(1), config.min_depth);
972        assert_eq!(Some(5), config.max_depth);
973
974        let args = vec!["ex", "-d2-4"];
975        let config = create_config(args);
976        assert_eq!(Some(3), config.min_depth);
977        assert_eq!(Some(5), config.max_depth);
978
979        let args = vec!["ex", "-d2-"];
980        let config = create_config(args);
981        assert_eq!(Some(3), config.min_depth);
982        assert_eq!(None, config.max_depth);
983
984        let args = vec!["ex", "-s"];
985        let config = create_config(args);
986        assert_eq!(Some(1), config.min_depth);
987        assert_eq!(None, config.max_depth);
988
989        let args = vec!["ex", "-s", "-d4"];
990        let config = create_config(args);
991        assert_eq!(Some(1), config.min_depth);
992        assert_eq!(None, config.max_depth);
993
994        let args = vec!["ex", "--recurse"];
995        let config = create_config(args);
996        assert_eq!(Some(1), config.min_depth);
997        assert_eq!(None, config.max_depth);
998
999        let args = vec!["ex", "--depth=4"];
1000        let config = create_config(args);
1001        assert_eq!(Some(1), config.min_depth);
1002        assert_eq!(Some(5), config.max_depth);
1003    }
1004
1005    #[test]
1006    fn test_indent_is_handled() {
1007        let args = vec!["ex"];
1008        let config = create_config(args);
1009        assert_eq!(false, config.show_indent);
1010
1011        let args = vec!["ex", "-i"];
1012        let config = create_config(args);
1013        assert_eq!(true, config.show_indent);
1014
1015        let args = vec!["ex", "--indent"];
1016        let config = create_config(args);
1017        assert_eq!(true, config.show_indent);
1018    }
1019
1020    #[test]
1021    fn test_all_files_is_handled() {
1022        let args = vec!["ex"];
1023        let config = create_config(args);
1024        assert_eq!(false, config.all_files);
1025        assert_eq!(false, config.all_recurse);
1026
1027        let args = vec!["ex", "-a"];
1028        let config = create_config(args);
1029        assert_eq!(true, config.all_files);
1030        assert_eq!(false, config.all_recurse);
1031
1032        let args = vec!["ex", "-aa"];
1033        let config = create_config(args);
1034        assert_eq!(true, config.all_files);
1035        assert_eq!(true, config.all_recurse);
1036
1037        let args = vec!["ex", "-a", "-a"];
1038        let config = create_config(args);
1039        assert_eq!(true, config.all_files);
1040        assert_eq!(true, config.all_recurse);
1041
1042        let args = vec!["ex", "--all-files"];
1043        let config = create_config(args);
1044        assert_eq!(true, config.all_files);
1045        assert_eq!(false, config.all_recurse);
1046    }
1047
1048    #[test]
1049    fn test_zip_is_handled() {
1050        let args = vec!["ex"];
1051        let config = create_config(args);
1052        assert_eq!(false, config.zip_expand);
1053
1054        let args = vec!["ex", "-z"];
1055        let config = create_config(args);
1056        assert_eq!(true, config.zip_expand);
1057
1058        let args = vec!["ex", "--zip"];
1059        let config = create_config(args);
1060        assert_eq!(true, config.zip_expand);
1061    }
1062
1063    #[test]
1064    fn test_password_is_handled() {
1065        let args = vec!["ex"];
1066        let config = create_config(args);
1067        assert_eq!(None, config.zip_password);
1068
1069        let args = vec!["ex", "--password", "secret"];
1070        let config = create_config(args);
1071        assert_eq!(Some(String::from("secret")), config.zip_password);
1072    }
1073
1074    #[test]
1075    fn test_case_is_handled() {
1076        let args = vec!["ex"];
1077        let config = create_config(args);
1078        assert_eq!(None, config.case_sensitive);
1079
1080        let args = vec!["ex", "--case"];
1081        let config = create_config(args);
1082        assert_eq!(Some(true), config.case_sensitive);
1083
1084        let args = vec!["ex", "--case", "--no-case"];
1085        let config = create_config(args);
1086        assert_eq!(Some(true), config.case_sensitive);
1087
1088        let args = vec!["ex", "--no-case"];
1089        let config = create_config(args);
1090        assert_eq!(Some(false), config.case_sensitive);
1091
1092        let args = vec!["ex", "--no-case", "--case"];
1093        let config = create_config(args);
1094        assert_eq!(Some(true), config.case_sensitive);
1095    }
1096
1097    #[test]
1098    fn test_order_is_handled() {
1099        let expected = vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name];
1100        let args = vec!["ex"];
1101        let config = create_config(args);
1102        assert_eq!(expected, config.order_files);
1103        assert_eq!(false, config.order_name);
1104
1105        let expected = vec![OrderKind::Dir, OrderKind::Name];
1106        let args = vec!["ex", "-od"];
1107        let config = create_config(args);
1108        assert_eq!(expected, config.order_files);
1109        assert_eq!(false, config.order_name);
1110
1111        let expected = vec![OrderKind::Name, OrderKind::Dir];
1112        let args = vec!["ex", "-on"];
1113        let config = create_config(args);
1114        assert_eq!(expected, config.order_files);
1115        assert_eq!(true, config.order_name);
1116
1117        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
1118        let args = vec!["ex", "-oe"];
1119        let config = create_config(args);
1120        assert_eq!(expected, config.order_files);
1121        assert_eq!(false, config.order_name);
1122
1123        let expected = vec![OrderKind::Size(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
1124        let args = vec!["ex", "-os"];
1125        let config = create_config(args);
1126        assert_eq!(expected, config.order_files);
1127        assert_eq!(false, config.order_name);
1128
1129        let expected = vec![OrderKind::Size(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
1130        let args = vec!["ex", "-os+"];
1131        let config = create_config(args);
1132        assert_eq!(expected, config.order_files);
1133        assert_eq!(false, config.order_name);
1134
1135        let expected = vec![OrderKind::Size(DirKind::Desc), OrderKind::Dir, OrderKind::Name];
1136        let args = vec!["ex", "-os-"];
1137        let config = create_config(args);
1138        assert_eq!(expected, config.order_files);
1139        assert_eq!(false, config.order_name);
1140
1141        let expected = vec![OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
1142        let args = vec!["ex", "-ot"];
1143        let config = create_config(args);
1144        assert_eq!(expected, config.order_files);
1145        assert_eq!(false, config.order_name);
1146
1147        let expected = vec![OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
1148        let args = vec!["ex", "-ot+"];
1149        let config = create_config(args);
1150        assert_eq!(expected, config.order_files);
1151        assert_eq!(false, config.order_name);
1152
1153        let expected = vec![OrderKind::Time(DirKind::Desc), OrderKind::Dir, OrderKind::Name];
1154        let args = vec!["ex", "-ot-"];
1155        let config = create_config(args);
1156        assert_eq!(expected, config.order_files);
1157        assert_eq!(false, config.order_name);
1158
1159        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Time(DirKind::Asc), OrderKind::Name];
1160        let args = vec!["ex", "--order=dest"];
1161        let config = create_config(args);
1162        assert_eq!(expected, config.order_files);
1163        assert_eq!(false, config.order_name);
1164
1165        let expected = vec![OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
1166        let args = vec!["ex", "--order=est"];
1167        let config = create_config(args);
1168        assert_eq!(expected, config.order_files);
1169        assert_eq!(false, config.order_name);
1170
1171        let expected = vec![OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
1172        let args = vec!["ex", "--order=e+s+t+"];
1173        let config = create_config(args);
1174        assert_eq!(expected, config.order_files);
1175        assert_eq!(false, config.order_name);
1176
1177        let expected = vec![OrderKind::Ext, OrderKind::Size(DirKind::Desc), OrderKind::Time(DirKind::Desc), OrderKind::Dir, OrderKind::Name];
1178        let args = vec!["ex", "--order=e-s-t-"];
1179        let config = create_config(args);
1180        assert_eq!(expected, config.order_files);
1181        assert_eq!(false, config.order_name);
1182    }
1183
1184    #[test]
1185    fn test_order_defaults_to_group_with_no_recursion() {
1186        let expected = vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name];
1187        let args = vec!["ex"];
1188        let config = create_config(args);
1189        assert_eq!(expected, config.order_files);
1190        assert_eq!(false, config.order_name);
1191
1192        let expected = vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name];
1193        let args = vec!["ex", "-d0"];
1194        let config = create_config(args);
1195        assert_eq!(expected, config.order_files);
1196        assert_eq!(false, config.order_name);
1197
1198        let expected = vec![OrderKind::Dir, OrderKind::Name];
1199        let args = vec!["ex", "-d1"];
1200        let config = create_config(args);
1201        assert_eq!(expected, config.order_files);
1202        assert_eq!(false, config.order_name);
1203
1204        let expected = vec![OrderKind::Dir, OrderKind::Name];
1205        let args = vec!["ex", "-d2"];
1206        let config = create_config(args);
1207        assert_eq!(expected, config.order_files);
1208        assert_eq!(false, config.order_name);
1209
1210        let expected = vec![OrderKind::Dir, OrderKind::Name];
1211        let args = vec!["ex", "-s"];
1212        let config = create_config(args);
1213        assert_eq!(expected, config.order_files);
1214        assert_eq!(false, config.order_name);
1215
1216        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
1217        let args = vec!["ex", "-oe"];
1218        let config = create_config(args);
1219        assert_eq!(expected, config.order_files);
1220        assert_eq!(false, config.order_name);
1221
1222        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
1223        let args = vec!["ex", "-oe", "-d0"];
1224        let config = create_config(args);
1225        assert_eq!(expected, config.order_files);
1226        assert_eq!(false, config.order_name);
1227
1228        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
1229        let args = vec!["ex", "-oe", "-d1"];
1230        let config = create_config(args);
1231        assert_eq!(expected, config.order_files);
1232        assert_eq!(false, config.order_name);
1233
1234        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
1235        let args = vec!["ex", "-oe", "-d2"];
1236        let config = create_config(args);
1237        assert_eq!(expected, config.order_files);
1238        assert_eq!(false, config.order_name);
1239
1240        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
1241        let args = vec!["ex", "-oe", "-s"];
1242        let config = create_config(args);
1243        assert_eq!(expected, config.order_files);
1244        assert_eq!(false, config.order_name);
1245    }
1246
1247    #[test]
1248    fn test_order_defaults_to_directory_with_indent() {
1249        let expected = vec![OrderKind::Dir, OrderKind::Name];
1250        let args = vec!["ex", "-i"];
1251        let config = create_config(args);
1252        assert_eq!(expected, config.order_files);
1253        assert_eq!(false, config.order_name);
1254
1255        let expected = vec![OrderKind::Dir, OrderKind::Name];
1256        let args = vec!["ex", "-i", "-d0"];
1257        let config = create_config(args);
1258        assert_eq!(expected, config.order_files);
1259        assert_eq!(false, config.order_name);
1260
1261        let expected = vec![OrderKind::Dir, OrderKind::Name];
1262        let args = vec!["ex", "-i", "-d1"];
1263        let config = create_config(args);
1264        assert_eq!(expected, config.order_files);
1265        assert_eq!(false, config.order_name);
1266
1267        let expected = vec![OrderKind::Dir, OrderKind::Name];
1268        let args = vec!["ex", "-i", "-d2"];
1269        let config = create_config(args);
1270        assert_eq!(expected, config.order_files);
1271        assert_eq!(false, config.order_name);
1272
1273        let expected = vec![OrderKind::Dir, OrderKind::Name];
1274        let args = vec!["ex", "-i", "-s"];
1275        let config = create_config(args);
1276        assert_eq!(expected, config.order_files);
1277        assert_eq!(false, config.order_name);
1278
1279        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
1280        let args = vec!["ex", "-i", "-oe"];
1281        let config = create_config(args);
1282        assert_eq!(expected, config.order_files);
1283        assert_eq!(false, config.order_name);
1284
1285        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
1286        let args = vec!["ex", "-i", "-oe", "-d0"];
1287        let config = create_config(args);
1288        assert_eq!(expected, config.order_files);
1289        assert_eq!(false, config.order_name);
1290
1291        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
1292        let args = vec!["ex", "-i", "-oe", "-d1"];
1293        let config = create_config(args);
1294        assert_eq!(expected, config.order_files);
1295        assert_eq!(false, config.order_name);
1296
1297        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
1298        let args = vec!["ex", "-i", "-oe", "-d2"];
1299        let config = create_config(args);
1300        assert_eq!(expected, config.order_files);
1301        assert_eq!(false, config.order_name);
1302
1303        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
1304        let args = vec!["ex", "-i", "-oe", "-s"];
1305        let config = create_config(args);
1306        assert_eq!(expected, config.order_files);
1307        assert_eq!(false, config.order_name);
1308    }
1309
1310    #[test]
1311    fn test_recent_is_handled() {
1312        let args = vec!["ex"];
1313        let config = create_config(args);
1314        assert_eq!(RecentKind::None, config.filter_recent);
1315
1316        let args = vec!["ex", "-ry10"];
1317        let config = create_config(args);
1318        assert_eq!(RecentKind::Year(10), config.filter_recent);
1319
1320        let args = vec!["ex", "-rm6"];
1321        let config = create_config(args);
1322        assert_eq!(RecentKind::Month(6), config.filter_recent);
1323
1324        let args = vec!["ex", "-rw2"];
1325        let config = create_config(args);
1326        assert_eq!(RecentKind::Week(2), config.filter_recent);
1327
1328        let args = vec!["ex", "-rd"];
1329        let config = create_config(args);
1330        assert_eq!(RecentKind::Day(1), config.filter_recent);
1331
1332        let args = vec!["ex", "--recent=h"];
1333        let config = create_config(args);
1334        assert_eq!(RecentKind::Hour(1), config.filter_recent);
1335
1336        let args = vec!["ex", "--recent=H"];
1337        let config = create_config(args);
1338        assert_eq!(RecentKind::Hour(1), config.filter_recent);
1339
1340        let args = vec!["ex", "--recent=M5"];
1341        let config = create_config(args);
1342        assert_eq!(RecentKind::Min(5), config.filter_recent);
1343
1344        let args = vec!["ex", "--recent=S10"];
1345        let config = create_config(args);
1346        assert_eq!(RecentKind::Sec(10), config.filter_recent);
1347    }
1348
1349    #[test]
1350    fn test_none_is_subtracted_from_time() {
1351        let now = create_time(2023, 7, 1, 0, 0, 0);
1352        assert_eq!(None, RecentKind::None.subtract_from(&now, &Utc));
1353    }
1354
1355    #[test]
1356    fn test_second_is_subtracted_from_time() {
1357        let now = create_time(2023, 7, 1, 0, 0, 0);
1358        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Sec(0).subtract_from(&now, &Utc));
1359        assert_eq!(Some(create_time(2023, 6, 30, 23, 59, 59)), RecentKind::Sec(1).subtract_from(&now, &Utc));
1360        assert_eq!(Some(create_time(2023, 6, 30, 23, 59, 58)), RecentKind::Sec(2).subtract_from(&now, &Utc));
1361    }
1362
1363    #[test]
1364    fn test_minute_is_subtracted_from_time() {
1365        let now = create_time(2023, 7, 1, 0, 0, 0);
1366        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Min(0).subtract_from(&now, &Utc));
1367        assert_eq!(Some(create_time(2023, 6, 30, 23, 59, 0)), RecentKind::Min(1).subtract_from(&now, &Utc));
1368        assert_eq!(Some(create_time(2023, 6, 30, 23, 58, 0)), RecentKind::Min(2).subtract_from(&now, &Utc));
1369    }
1370
1371    #[test]
1372    fn test_hour_is_subtracted_from_time() {
1373        let now = create_time(2023, 7, 1, 0, 0, 0);
1374        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Hour(0).subtract_from(&now, &Utc));
1375        assert_eq!(Some(create_time(2023, 6, 30, 23, 0, 0)), RecentKind::Hour(1).subtract_from(&now, &Utc));
1376        assert_eq!(Some(create_time(2023, 6, 30, 22, 0, 0)), RecentKind::Hour(2).subtract_from(&now, &Utc));
1377    }
1378
1379    #[test]
1380    fn test_day_is_subtracted_from_time() {
1381        let now = create_time(2023, 7, 1, 0, 0, 0);
1382        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Day(0).subtract_from(&now, &Utc));
1383        assert_eq!(Some(create_time(2023, 6, 30, 0, 0, 0)), RecentKind::Day(1).subtract_from(&now, &Utc));
1384        assert_eq!(Some(create_time(2023, 6, 29, 0, 0, 0)), RecentKind::Day(2).subtract_from(&now, &Utc));
1385    }
1386
1387    #[test]
1388    fn test_week_is_subtracted_from_time() {
1389        let now = create_time(2023, 7, 1, 0, 0, 0);
1390        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Week(0).subtract_from(&now, &Utc));
1391        assert_eq!(Some(create_time(2023, 6, 24, 0, 0, 0)), RecentKind::Week(1).subtract_from(&now, &Utc));
1392        assert_eq!(Some(create_time(2023, 6, 17, 0, 0, 0)), RecentKind::Week(2).subtract_from(&now, &Utc));
1393    }
1394
1395    #[test]
1396    fn test_month_is_subtracted_from_time() {
1397        let now = create_time(2023, 3, 31, 0, 0, 0);
1398        assert_eq!(Some(create_time(2023, 3, 31, 0, 0, 0)), RecentKind::Month(0).subtract_from(&now, &Utc));
1399        assert_eq!(Some(create_time(2023, 3, 1, 0, 0, 0)), RecentKind::Month(1).subtract_from(&now, &Utc));
1400        assert_eq!(Some(create_time(2023, 1, 31, 0, 0, 0)), RecentKind::Month(2).subtract_from(&now, &Utc));
1401        assert_eq!(Some(create_time(2022, 12, 31, 0, 0, 0)), RecentKind::Month(3).subtract_from(&now, &Utc));
1402        assert_eq!(Some(create_time(2022, 12, 1, 0, 0, 0)), RecentKind::Month(4).subtract_from(&now, &Utc));
1403        assert_eq!(Some(create_time(2022, 10, 31, 0, 0, 0)), RecentKind::Month(5).subtract_from(&now, &Utc));
1404    }
1405
1406    #[test]
1407    fn test_year_is_subtracted_from_time() {
1408        let now = create_time(2024, 2, 29, 0, 0, 0);
1409        assert_eq!(Some(create_time(2024, 2, 29, 0, 0, 0)), RecentKind::Year(0).subtract_from(&now, &Utc));
1410        assert_eq!(Some(create_time(2023, 3, 1, 0, 0, 0)), RecentKind::Year(1).subtract_from(&now, &Utc));
1411        assert_eq!(Some(create_time(2022, 3, 1, 0, 0, 0)), RecentKind::Year(2).subtract_from(&now, &Utc));
1412        assert_eq!(Some(create_time(2021, 3, 1, 0, 0, 0)), RecentKind::Year(3).subtract_from(&now, &Utc));
1413        assert_eq!(Some(create_time(2020, 2, 29, 0, 0, 0)), RecentKind::Year(4).subtract_from(&now, &Utc));
1414    }
1415
1416    #[test]
1417    fn test_type_is_handled() {
1418        let args = vec!["ex"];
1419        let config = create_config(args);
1420        assert_eq!(None, config.filter_types);
1421
1422        let args = vec!["ex", "-tf"];
1423        let config = create_config(args);
1424        let expected = create_set(&[
1425            FileKind::File(ExecKind::None),
1426            FileKind::File(ExecKind::User),
1427            FileKind::File(ExecKind::Other),
1428        ]);
1429        assert_eq!(Some(expected), config.filter_types);
1430
1431        let args = vec!["ex", "-te"];
1432        let config = create_config(args);
1433        let expected = create_set(&[
1434            FileKind::File(ExecKind::User),
1435            FileKind::File(ExecKind::Other),
1436        ]);
1437        assert_eq!(Some(expected), config.filter_types);
1438
1439        let args = vec!["ex", "-td"];
1440        let config = create_config(args);
1441        let expected = create_set(&[FileKind::Dir]);
1442        assert_eq!(Some(expected), config.filter_types);
1443
1444        let args = vec!["ex", "-tf", "-td"];
1445        let config = create_config(args);
1446        let expected = create_set(&[
1447            FileKind::File(ExecKind::None),
1448            FileKind::File(ExecKind::User),
1449            FileKind::File(ExecKind::Other),
1450            FileKind::Dir,
1451        ]);
1452        assert_eq!(Some(expected), config.filter_types);
1453
1454        let args = vec!["ex", "-tfd"];
1455        let config = create_config(args);
1456        let expected = create_set(&[
1457            FileKind::File(ExecKind::None),
1458            FileKind::File(ExecKind::User),
1459            FileKind::File(ExecKind::Other),
1460            FileKind::Dir,
1461        ]);
1462        assert_eq!(Some(expected), config.filter_types);
1463
1464        let args = vec!["ex", "--type=l"];
1465        let config = create_config(args);
1466        let expected = create_set(&[
1467            FileKind::Link(false),
1468            FileKind::Link(true),
1469        ]);
1470        assert_eq!(Some(expected), config.filter_types);
1471    }
1472
1473    #[test]
1474    fn test_git_is_handled() {
1475        let args = vec!["ex"];
1476        let config = create_config(args);
1477        assert_eq!(None, config.filter_git);
1478
1479        let expected = GitFlags::new().with_everything(true);
1480        let args = vec!["ex", "-gx"];
1481        let config = create_config(args);
1482        assert_eq!(Some(expected), config.filter_git);
1483
1484        let expected = GitFlags::new().with_cached(true);
1485        let args = vec!["ex", "-gc"];
1486        let config = create_config(args);
1487        assert_eq!(Some(expected), config.filter_git);
1488
1489        let expected = GitFlags::new().with_added(true);
1490        let args = vec!["ex", "-ga"];
1491        let config = create_config(args);
1492        assert_eq!(Some(expected), config.filter_git);
1493
1494        let expected = GitFlags::new().with_modified(true);
1495        let args = vec!["ex", "-gm"];
1496        let config = create_config(args);
1497        assert_eq!(Some(expected), config.filter_git);
1498
1499        let expected = GitFlags::new().with_renamed(true);
1500        let args = vec!["ex", "-gr"];
1501        let config = create_config(args);
1502        assert_eq!(Some(expected), config.filter_git);
1503
1504        let expected = GitFlags::new().with_untracked(true);
1505        let args = vec!["ex", "-gu"];
1506        let config = create_config(args);
1507        assert_eq!(Some(expected), config.filter_git);
1508
1509        let expected = GitFlags::new().with_ignored(true);
1510        let args = vec!["ex", "-gi"];
1511        let config = create_config(args);
1512        assert_eq!(Some(expected), config.filter_git);
1513
1514        let expected = GitFlags::new().with_added(true).with_modified(true).with_renamed(true);
1515        let args = vec!["ex", "--git=amr"];
1516        let config = create_config(args);
1517        assert_eq!(Some(expected), config.filter_git);
1518    }
1519
1520    #[test]
1521    fn test_total_is_handled() {
1522        let args = vec!["ex"];
1523        let config = create_config(args);
1524        assert_eq!(false, config.show_total);
1525
1526        let args = vec!["ex", "--total"];
1527        let config = create_config(args);
1528        assert_eq!(true, config.show_total);
1529    }
1530
1531    #[test]
1532    fn test_utc_is_handled() {
1533        let args = vec!["ex"];
1534        let config = create_config(args);
1535        assert_eq!(false, config.show_utc);
1536
1537        let args = vec!["ex", "-u"];
1538        let config = create_config(args);
1539        assert_eq!(true, config.show_utc);
1540
1541        let args = vec!["ex", "--utc"];
1542        let config = create_config(args);
1543        assert_eq!(true, config.show_utc);
1544    }
1545
1546    #[test]
1547    #[cfg(unix)]
1548    fn test_owner_is_handled() {
1549        let args = vec!["ex"];
1550        let config = create_config(args);
1551        assert_eq!(false, config.show_owner);
1552
1553        let args = vec!["ex", "--owner"];
1554        let config = create_config(args);
1555        assert_eq!(true, config.show_owner);
1556    }
1557
1558    #[test]
1559    fn test_data_is_handled() {
1560        let args = vec!["ex"];
1561        let config = create_config(args);
1562        assert_eq!(false, config.show_sig);
1563
1564        let args = vec!["ex", "--sig"];
1565        let config = create_config(args);
1566        assert_eq!(true, config.show_sig);
1567    }
1568
1569    #[test]
1570    fn test_only_path_is_handled_with_terminal() {
1571        let args = vec!["ex"];
1572        let config = create_config_with_piped(args, false);
1573        assert_eq!(true, config.show_pretty);
1574        assert_eq!(false, config.only_path); // (false if terminal)
1575        assert_eq!(false, config.escape_path); // (false if terminal)
1576        assert_eq!(false, config.null_path);
1577
1578        let args = vec!["ex", "-x"];
1579        let config = create_config_with_piped(args, false);
1580        assert_eq!(false, config.show_pretty);
1581        assert_eq!(true, config.only_path);
1582        assert_eq!(false, config.escape_path);
1583        assert_eq!(false, config.null_path);
1584
1585        let args = vec!["ex", "-x", "-x"];
1586        let config = create_config_with_piped(args, false);
1587        assert_eq!(true, config.show_pretty);
1588        assert_eq!(false, config.only_path);
1589        assert_eq!(false, config.escape_path);
1590        assert_eq!(false, config.null_path);
1591
1592        let args = vec!["ex", "-xxx"];
1593        let config = create_config_with_piped(args, false);
1594        assert_eq!(false, config.show_pretty);
1595        assert_eq!(false, config.only_path);
1596        assert_eq!(false, config.escape_path);
1597        assert_eq!(false, config.null_path);
1598
1599        let args = vec!["ex", "--only-path"];
1600        let config = create_config_with_piped(args, false);
1601        assert_eq!(false, config.show_pretty);
1602        assert_eq!(true, config.only_path);
1603        assert_eq!(false, config.escape_path);
1604        assert_eq!(false, config.null_path);
1605    }
1606
1607    #[test]
1608    fn test_only_path_is_handled_with_piped() {
1609        let args = vec!["ex"];
1610        let config = create_config_with_piped(args, true);
1611        assert_eq!(true, config.show_pretty);
1612        assert_eq!(true, config.only_path); // (true if piped)
1613        assert_eq!(true, config.escape_path); // (true if piped)
1614        assert_eq!(false, config.null_path);
1615
1616        let args = vec!["ex", "-x"];
1617        let config = create_config_with_piped(args, true);
1618        assert_eq!(false, config.show_pretty);
1619        assert_eq!(true, config.only_path);
1620        assert_eq!(false, config.escape_path);
1621        assert_eq!(false, config.null_path);
1622
1623        let args = vec!["ex", "-x", "-x"];
1624        let config = create_config_with_piped(args, true);
1625        assert_eq!(true, config.show_pretty);
1626        assert_eq!(false, config.only_path);
1627        assert_eq!(false, config.escape_path);
1628        assert_eq!(false, config.null_path);
1629
1630        let args = vec!["ex", "-xxx"];
1631        let config = create_config_with_piped(args, true);
1632        assert_eq!(false, config.show_pretty);
1633        assert_eq!(false, config.only_path);
1634        assert_eq!(false, config.escape_path);
1635        assert_eq!(false, config.null_path);
1636
1637        let args = vec!["ex", "--only-path"];
1638        let config = create_config_with_piped(args, true);
1639        assert_eq!(false, config.show_pretty);
1640        assert_eq!(true, config.only_path);
1641        assert_eq!(false, config.escape_path);
1642        assert_eq!(false, config.null_path);
1643    }
1644
1645    #[test]
1646    #[cfg(debug_assertions)]
1647    fn test_only_path_is_handled_with_override() {
1648        let args = vec!["ex", "--terminal"];
1649        let config = create_config_with_piped(args, true);
1650        assert_eq!(true, config.show_pretty);
1651        assert_eq!(false, config.only_path); // (false if terminal)
1652        assert_eq!(false, config.escape_path); // (false if terminal)
1653        assert_eq!(false, config.null_path);
1654
1655        let args = vec!["ex", "--terminal", "-x"];
1656        let config = create_config_with_piped(args, true);
1657        assert_eq!(false, config.show_pretty);
1658        assert_eq!(true, config.only_path);
1659        assert_eq!(false, config.escape_path);
1660        assert_eq!(false, config.null_path);
1661
1662        let args = vec!["ex", "--terminal", "-x", "-x"];
1663        let config = create_config_with_piped(args, true);
1664        assert_eq!(true, config.show_pretty);
1665        assert_eq!(false, config.only_path);
1666        assert_eq!(false, config.escape_path);
1667        assert_eq!(false, config.null_path);
1668
1669        let args = vec!["ex", "--terminal", "-xxx"];
1670        let config = create_config_with_piped(args, true);
1671        assert_eq!(false, config.show_pretty);
1672        assert_eq!(false, config.only_path);
1673        assert_eq!(false, config.escape_path);
1674        assert_eq!(false, config.null_path);
1675
1676        let args = vec!["ex", "--terminal", "--only-path"];
1677        let config = create_config_with_piped(args, true);
1678        assert_eq!(false, config.show_pretty);
1679        assert_eq!(true, config.only_path);
1680        assert_eq!(false, config.escape_path);
1681        assert_eq!(false, config.null_path);
1682    }
1683
1684    #[test]
1685    fn test_null_path_is_handled() {
1686        let args = vec!["ex"];
1687        let config = create_config_with_piped(args, false);
1688        assert_eq!(true, config.show_pretty);
1689        assert_eq!(false, config.only_path);
1690        assert_eq!(false, config.escape_path);
1691        assert_eq!(false, config.null_path);
1692
1693        let args = vec!["ex", "-z"]; // (reassigned "-z")
1694        let config = create_config_with_piped(args, false);
1695        assert_eq!(true, config.show_pretty);
1696        assert_eq!(false, config.only_path);
1697        assert_eq!(false, config.escape_path);
1698        assert_eq!(false, config.null_path);
1699
1700        let args = vec!["ex", "--null-path"];
1701        let config = create_config_with_piped(args, false);
1702        assert_eq!(false, config.show_pretty);
1703        assert_eq!(true, config.only_path);
1704        assert_eq!(false, config.escape_path);
1705        assert_eq!(true, config.null_path);
1706    }
1707
1708    #[test]
1709    fn test_abs_path_is_handled() {
1710        let args = vec!["ex"];
1711        let config = create_config(args);
1712        assert_eq!(false, config.abs_path);
1713
1714        let args = vec!["ex", "-q"];
1715        let config = create_config(args);
1716        assert_eq!(true, config.abs_path);
1717
1718        let args = vec!["ex", "--abs-path"];
1719        let config = create_config(args);
1720        assert_eq!(true, config.abs_path);
1721    }
1722
1723    #[test]
1724    #[cfg(windows)]
1725    fn test_win_path_is_handled() {
1726        let args = vec!["ex"];
1727        let config = create_config(args);
1728        assert_eq!(false, config.win_path);
1729
1730        let args = vec!["ex", "-w"];
1731        let config = create_config(args);
1732        assert_eq!(true, config.win_path);
1733
1734        let args = vec!["ex", "--win-path"];
1735        let config = create_config(args);
1736        assert_eq!(true, config.win_path);
1737    }
1738
1739    #[test]
1740    #[cfg(windows)]
1741    fn test_win_ver_is_handled() {
1742        let args = vec!["ex"];
1743        let config = create_config(args);
1744        assert_eq!(false, config.win_ver);
1745
1746        let args = vec!["ex", "-v"];
1747        let config = create_config(args);
1748        assert_eq!(true, config.win_ver);
1749
1750        let args = vec!["ex", "--win-ver"];
1751        let config = create_config(args);
1752        assert_eq!(true, config.win_ver);
1753    }
1754
1755    #[test]
1756    fn test_patterns_are_handled() {
1757        let expected = vec!["."];
1758        let args = vec!["ex"];
1759        let config = create_config(args);
1760        assert_eq!(expected, config.patterns);
1761
1762        let expected = vec!["file1"];
1763        let args = vec!["ex", "file1"];
1764        let config = create_config(args);
1765        assert_eq!(expected, config.patterns);
1766
1767        let expected = vec!["file1", "file2"];
1768        let args = vec!["ex", "file1", "file2"];
1769        let config = create_config(args);
1770        assert_eq!(expected, config.patterns);
1771    }
1772
1773    #[test]
1774    #[should_panic(expected = "Invalid depth option: foo")]
1775    fn test_unexpected_depth_causes_error() {
1776        let args = vec!["ex", "-dfoo"];
1777        create_config(args);
1778    }
1779
1780    #[test]
1781    #[should_panic(expected = "Invalid order option: foo")]
1782    fn test_unexpected_order_causes_error() {
1783        let args = vec!["ex", "-ofoo"];
1784        create_config(args);
1785    }
1786
1787    #[test]
1788    #[should_panic(expected = "Invalid recent option: foo")]
1789    fn test_unexpected_recent_causes_error() {
1790        let args = vec!["ex", "-rfoo"];
1791        create_config(args);
1792    }
1793
1794    #[test]
1795    #[should_panic(expected = "Invalid type option: z")]
1796    fn test_unexpected_type_causes_error() {
1797        let args = vec!["ex", "-tfzd"];
1798        create_config(args);
1799    }
1800
1801    #[test]
1802    #[should_panic(expected = "Invalid git option: z")]
1803    fn test_unexpected_git_causes_error() {
1804        let args = vec!["ex", "-gazm"];
1805        create_config(args);
1806    }
1807
1808    fn create_config(args: Vec<&str>) -> Config {
1809        create_config_with_piped(args, false)
1810    }
1811
1812    fn create_config_with_piped(args: Vec<&str>, piped: bool) -> Config {
1813        let mut command = Config::create_command(String::from("ex"));
1814        let args = args.into_iter().map(String::from).collect();
1815        let matches = Config::create_matches(&mut command, args).unwrap_or_else(handle_error);
1816        let config = Config::create_config(&mut command, matches, piped).unwrap_or_else(handle_error);
1817        config
1818    }
1819
1820    fn create_time(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32) -> DateTime<Utc> {
1821        let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
1822        let time = NaiveTime::from_hms_opt(hour, minute, second).unwrap();
1823        let time = DateTime::from_naive_utc_and_offset(NaiveDateTime::new(date, time), Utc);
1824        time
1825    }
1826
1827    fn create_set<T: Clone + Eq + Hash>(values: &[T]) -> HashSet<T> {
1828        values.iter().map(T::clone).collect()
1829    }
1830
1831    fn handle_error<T, E: Display>(err: E)-> T {
1832        panic!("{}", err);
1833    }
1834
1835    impl Config {
1836        pub fn with_curr_time(
1837            mut self,
1838            year: i32,
1839            month: u32,
1840            day: u32,
1841            hour: u32,
1842            min: u32,
1843            sec: u32,
1844        ) -> Self {
1845            self.curr_time = Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap();
1846            self
1847        }
1848
1849        pub fn with_min_depth(mut self, min_depth: usize) -> Self {
1850            self.min_depth = Some(min_depth);
1851            self
1852        }
1853
1854        pub fn with_max_depth(mut self, max_depth: usize) -> Self {
1855            self.max_depth = Some(max_depth);
1856            self
1857        }
1858
1859        pub fn with_show_indent(mut self, show_indent: bool) -> Self {
1860            self.show_indent = show_indent;
1861            self
1862        }
1863
1864        #[cfg(disabled)]
1865        pub fn with_all_files(mut self, all_files: bool) -> Self {
1866            self.all_files = all_files;
1867            self
1868        }
1869
1870        #[cfg(disabled)]
1871        pub fn with_all_recurse(mut self, all_recurse: bool) -> Self {
1872            self.all_recurse = all_recurse;
1873            self
1874        }
1875
1876        pub fn with_zip_expand(mut self, zip_expand: bool) -> Self {
1877            self.zip_expand = zip_expand;
1878            self
1879        }
1880
1881        #[cfg(disabled)]
1882        pub fn with_zip_password(mut self, zip_password: Option<String>) -> Self {
1883            self.zip_password = zip_password;
1884            self
1885        }
1886
1887        pub fn with_case_sensitive(mut self, case_sensitive: bool) -> Self {
1888            self.case_sensitive = Some(case_sensitive);
1889            self
1890        }
1891
1892        pub fn with_order_files(mut self, order_files: Vec<OrderKind>) -> Self {
1893            self.order_files = order_files;
1894            self
1895        }
1896
1897        pub fn with_order_name(mut self, order_name: bool) -> Self {
1898            self.order_name = order_name;
1899            self
1900        }
1901
1902        pub fn with_filter_recent(mut self, filter_recent: RecentKind) -> Self {
1903            self.filter_recent = filter_recent;
1904            self
1905        }
1906
1907        pub fn with_filter_types(mut self, filter_types: Vec<FileKind>) -> Self {
1908            self.filter_types = Some(filter_types.into_iter().collect());
1909            self
1910        }
1911
1912        pub fn with_filter_git(mut self, filter_git: GitFlags) -> Self {
1913            self.filter_git = Some(filter_git);
1914            self
1915        }
1916
1917        pub fn with_show_total(mut self, show_total: bool) -> Self {
1918            self.show_total = show_total;
1919            self
1920        }
1921
1922        pub fn with_show_pretty(mut self, show_pretty: bool) -> Self {
1923            self.show_pretty = show_pretty;
1924            self
1925        }
1926
1927        pub fn with_show_utc(mut self, show_utc: bool) -> Self {
1928            self.show_utc = show_utc;
1929            self
1930        }
1931
1932        #[cfg(unix)]
1933        pub fn with_show_owner(mut self, show_owner: bool) -> Self {
1934            self.show_owner = show_owner;
1935            self
1936        }
1937
1938        pub fn with_show_sig(mut self, show_sig: bool) -> Self {
1939            self.show_sig = show_sig;
1940            self
1941        }
1942
1943        pub fn with_only_path(mut self, only_path: bool) -> Self {
1944            self.only_path = only_path;
1945            self
1946        }
1947
1948        pub fn with_escape_path(mut self, escape_path: bool) -> Self {
1949            self.escape_path = escape_path;
1950            self
1951        }
1952
1953        #[cfg(disabled)]
1954        pub fn with_null_path(mut self, null_path: bool) -> Self {
1955            self.null_path = null_path;
1956            self
1957        }
1958
1959        pub fn with_abs_path(mut self, abs_path: bool) -> Self {
1960            self.abs_path = abs_path;
1961            self
1962        }
1963
1964        #[cfg(windows)]
1965        #[cfg(disabled)]
1966        pub fn with_win_path(mut self, win_path: bool) -> Self {
1967            self.win_path = win_path;
1968            self
1969        }
1970
1971        #[cfg(windows)]
1972        pub fn with_win_ver(mut self, win_ver: bool) -> Self {
1973            self.win_ver = win_ver;
1974            self
1975        }
1976
1977        #[cfg(disabled)]
1978        pub fn with_completion(mut self, completion: Shell) -> Self {
1979            self.completion = Some(completion);
1980            self
1981        }
1982
1983        pub fn with_patterns(mut self, patterns: Vec<&str>) -> Self {
1984            self.patterns = patterns.into_iter().map(String::from).collect();
1985            self
1986        }
1987    }
1988}