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