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