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