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), 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
364impl 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 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 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); assert_eq!(false, config.escape_path); 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); assert_eq!(true, config.escape_path); 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); assert_eq!(false, config.escape_path); 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"]; 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}