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