Skip to main content

ex_cli/
config.rs

1use crate::cli::cli::Cli;
2use crate::cli::file::FileKind;
3use crate::cli::hidden::HiddenKind;
4use crate::cli::order::OrderKind;
5use crate::cli::recent::RecentKind;
6use crate::error::MyResult;
7use crate::git::flags::GitFlags;
8use chrono::{DateTime, TimeZone, Utc};
9use clap_builder::{ColorChoice, Parser};
10use clap_complete::Shell;
11use std::collections::HashSet;
12
13#[derive(Default)]
14pub struct Config {
15    cli: Cli,
16    piped: bool,
17    curr_time: DateTime<Utc>,
18    sort_order: Vec<OrderKind>,
19    sort_name: bool,
20}
21
22impl Config {
23    pub fn new<G: Fn() -> DateTime<Utc>>(
24        args: Vec<String>,
25        piped: bool,
26        factory: G,
27    ) -> MyResult<Self> {
28        let mut cli = Cli::try_parse_from(args)?;
29        let piped = piped && !cli.terminal();
30        let curr_time = cli.curr_time(factory);
31        let (sort_order, sort_name) = cli.sort_order();
32        let config = Self { cli, piped, curr_time, sort_order, sort_name };
33        Ok(config)
34    }
35
36    pub fn curr_time(&self) -> &DateTime<Utc> {
37        &self.curr_time
38    }
39
40    pub fn min_depth(&self) -> Option<usize> {
41        self.cli.min_depth()
42    }
43
44    pub fn max_depth(&self) -> Option<usize> {
45        self.cli.max_depth()
46    }
47
48    pub fn show_indent(&self) -> bool {
49        self.cli.show_indent
50    }
51
52    pub fn show_hidden(&self) -> HiddenKind {
53        match self.cli.show_hidden {
54            0 => HiddenKind::None,
55            1 => HiddenKind::Files,
56            _ => HiddenKind::Recurse,
57        }
58    }
59
60    pub fn zip_expand(&self) -> bool {
61        self.cli.zip_expand
62    }
63
64    pub fn zip_password(&self) -> &Option<String> {
65        &self.cli.zip_password
66    }
67
68    pub fn case_sensitive(&self) -> Option<bool> {
69        if self.cli.case_sensitive {
70            Some(true)
71        } else if self.cli.case_insensitive {
72            Some(false)
73        } else {
74            None
75        }
76    }
77
78    pub fn sort_order(&self) -> &Vec<OrderKind> {
79        &self.sort_order
80    }
81
82    pub fn sort_name(&self) -> bool {
83        self.sort_name
84    }
85
86    pub fn filter_recent(&self) -> RecentKind {
87        self.cli.filter_recent.unwrap_or_default()
88    }
89
90    pub fn filter_types(&self) -> Option<&HashSet<FileKind>> {
91        self.cli.filter_types.as_ref().map(|x| &x.inner)
92    }
93
94    pub fn filter_git(&self) -> Option<&GitFlags> {
95        self.cli.filter_git.as_ref()
96    }
97
98    #[cfg(debug_assertions)]
99    pub fn show_debug(&self) -> bool {
100        self.cli.show_debug
101    }
102
103    pub fn show_precise(&self) -> bool {
104        self.cli.show_precise
105    }
106
107    pub fn show_utc(&self) -> bool {
108        self.cli.show_utc
109    }
110
111    pub fn show_total(&self) -> bool {
112        self.cli.show_total
113    }
114
115    #[cfg(unix)]
116    pub fn show_owner(&self) -> bool {
117        self.cli.show_owner
118    }
119
120    pub fn show_crc(&self) -> bool {
121        self.cli.show_crc
122    }
123
124    pub fn show_sig(&self) -> bool {
125        self.cli.show_sig
126    }
127
128    pub fn only_path(&self) -> bool {
129        if self.cli.null_path {
130            true
131        } else {
132            match self.cli.only_path {
133                0 => self.piped,
134                1 => true,
135                _ => false,
136            }
137        }
138    }
139
140    pub fn escape_path(&self) -> bool {
141        if self.cli.null_path {
142            false
143        } else {
144            match self.cli.only_path {
145                0 => self.piped,
146                1 => false,
147                _ => false,
148            }
149        }
150    }
151
152    pub fn null_path(&self) -> bool {
153        self.cli.null_path
154    }
155
156    pub fn abs_path(&self) -> bool {
157        self.cli.abs_path
158    }
159
160    #[cfg(windows)]
161    pub fn win_path(&self) -> bool {
162        self.cli.win_path
163    }
164
165    #[cfg(windows)]
166    pub fn win_ver(&self) -> bool {
167        self.cli.win_ver
168    }
169
170    pub fn color(&self) -> Option<ColorChoice> {
171        self.cli.color
172    }
173
174    pub fn quiet(&self) -> bool {
175        self.cli.quiet
176    }
177
178    pub fn completion(&self) -> Option<Shell> {
179        self.cli.completion
180    }
181
182    pub fn patterns(&self) -> &Vec<String> {
183        &self.cli.patterns
184    }
185
186    pub fn start_time<Tz: TimeZone>(&self, zone: &Tz) -> Option<DateTime<Utc>> {
187        self.filter_recent().subtract_from(self.curr_time(), zone)
188    }
189
190    #[cfg(windows)]
191    pub fn want_decrypt(&self) -> bool {
192        self.show_sig() || self.win_ver()
193    }
194
195    #[cfg(not(windows))]
196    pub fn want_decrypt(&self) -> bool {
197        self.show_sig()
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use crate::cli::file::{ExecKind, FileKind, FileSet};
204    use crate::cli::hidden::HiddenKind;
205    use crate::cli::order::DirKind;
206    use crate::cli::recent::RecentKind;
207    use crate::config::{Config, OrderKind};
208    use crate::git::flags::GitFlags;
209    use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
210    use pretty_assertions::assert_eq;
211    use std::collections::HashSet;
212    use std::hash::Hash;
213
214    #[test]
215    fn test_depth_is_handled() {
216        let args = vec!["ex"];
217        let config = create_config(args);
218        assert_eq!(Some(1), config.min_depth());
219        assert_eq!(Some(1), config.max_depth());
220
221        let args = vec!["ex", "-d4"];
222        let config = create_config(args);
223        assert_eq!(Some(1), config.min_depth());
224        assert_eq!(Some(5), config.max_depth());
225
226        let args = vec!["ex", "-d-4"];
227        let config = create_config(args);
228        assert_eq!(Some(1), config.min_depth());
229        assert_eq!(Some(5), config.max_depth());
230
231        let args = vec!["ex", "-d2-4"];
232        let config = create_config(args);
233        assert_eq!(Some(3), config.min_depth());
234        assert_eq!(Some(5), config.max_depth());
235
236        let args = vec!["ex", "-d2-"];
237        let config = create_config(args);
238        assert_eq!(Some(3), config.min_depth());
239        assert_eq!(None, config.max_depth());
240
241        let args = vec!["ex", "-s"];
242        let config = create_config(args);
243        assert_eq!(Some(1), config.min_depth());
244        assert_eq!(None, config.max_depth());
245
246        let args = vec!["ex", "-s", "-d4"];
247        let config = create_config(args);
248        assert_eq!(Some(1), config.min_depth());
249        assert_eq!(None, config.max_depth());
250
251        let args = vec!["ex", "--recurse"];
252        let config = create_config(args);
253        assert_eq!(Some(1), config.min_depth());
254        assert_eq!(None, config.max_depth());
255
256        let args = vec!["ex", "--depth=4"];
257        let config = create_config(args);
258        assert_eq!(Some(1), config.min_depth());
259        assert_eq!(Some(5), config.max_depth());
260    }
261
262    #[test]
263    fn test_indent_is_handled() {
264        let args = vec!["ex"];
265        let config = create_config(args);
266        assert_eq!(false, config.show_indent());
267
268        let args = vec!["ex", "-i"];
269        let config = create_config(args);
270        assert_eq!(true, config.show_indent());
271
272        let args = vec!["ex", "--indent"];
273        let config = create_config(args);
274        assert_eq!(true, config.show_indent());
275    }
276
277    #[test]
278    fn test_all_files_is_handled() {
279        let args = vec!["ex"];
280        let config = create_config(args);
281        assert_eq!(HiddenKind::None, config.show_hidden());
282
283        let args = vec!["ex", "-a"];
284        let config = create_config(args);
285        assert_eq!(HiddenKind::Files, config.show_hidden());
286
287        let args = vec!["ex", "-aa"];
288        let config = create_config(args);
289        assert_eq!(HiddenKind::Recurse, config.show_hidden());
290
291        let args = vec!["ex", "-a", "-a"];
292        let config = create_config(args);
293        assert_eq!(HiddenKind::Recurse, config.show_hidden());
294
295        let args = vec!["ex", "--all-files"];
296        let config = create_config(args);
297        assert_eq!(HiddenKind::Files, config.show_hidden());
298    }
299
300    #[test]
301    fn test_zip_is_handled() {
302        let args = vec!["ex"];
303        let config = create_config(args);
304        assert_eq!(false, config.zip_expand());
305
306        let args = vec!["ex", "-z"];
307        let config = create_config(args);
308        assert_eq!(true, config.zip_expand());
309
310        let args = vec!["ex", "--zip"];
311        let config = create_config(args);
312        assert_eq!(true, config.zip_expand());
313    }
314
315    #[test]
316    fn test_password_is_handled() {
317        let args = vec!["ex"];
318        let config = create_config(args);
319        assert_eq!(&None, config.zip_password());
320
321        let args = vec!["ex", "--password", "secret"];
322        let config = create_config(args);
323        assert_eq!(&Some(String::from("secret")), config.zip_password());
324    }
325
326    #[test]
327    fn test_case_is_handled() {
328        let args = vec!["ex"];
329        let config = create_config(args);
330        assert_eq!(None, config.case_sensitive());
331
332        let args = vec!["ex", "--case"];
333        let config = create_config(args);
334        assert_eq!(Some(true), config.case_sensitive());
335
336        let args = vec!["ex", "--case", "--no-case"];
337        let config = create_config(args);
338        assert_eq!(Some(false), config.case_sensitive());
339
340        let args = vec!["ex", "--no-case"];
341        let config = create_config(args);
342        assert_eq!(Some(false), config.case_sensitive());
343
344        let args = vec!["ex", "--no-case", "--case"];
345        let config = create_config(args);
346        assert_eq!(Some(true), config.case_sensitive());
347    }
348
349    #[test]
350    fn test_order_is_handled() {
351        let expected = vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name];
352        let args = vec!["ex"];
353        let config = create_config(args);
354        assert_eq!(&expected, config.sort_order());
355        assert_eq!(false, config.sort_name());
356
357        let expected = vec![OrderKind::Dir, OrderKind::Name];
358        let args = vec!["ex", "-od"];
359        let config = create_config(args);
360        assert_eq!(&expected, config.sort_order());
361        assert_eq!(false, config.sort_name());
362
363        let expected = vec![OrderKind::Name, OrderKind::Dir];
364        let args = vec!["ex", "-on"];
365        let config = create_config(args);
366        assert_eq!(&expected, config.sort_order());
367        assert_eq!(true, config.sort_name());
368
369        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
370        let args = vec!["ex", "-oe"];
371        let config = create_config(args);
372        assert_eq!(&expected, config.sort_order());
373        assert_eq!(false, config.sort_name());
374
375        let expected = vec![OrderKind::Size(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
376        let args = vec!["ex", "-os"];
377        let config = create_config(args);
378        assert_eq!(&expected, config.sort_order());
379        assert_eq!(false, config.sort_name());
380
381        let expected = vec![OrderKind::Size(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
382        let args = vec!["ex", "-os+"];
383        let config = create_config(args);
384        assert_eq!(&expected, config.sort_order());
385        assert_eq!(false, config.sort_name());
386
387        let expected = vec![OrderKind::Size(DirKind::Desc), OrderKind::Dir, OrderKind::Name];
388        let args = vec!["ex", "-os-"];
389        let config = create_config(args);
390        assert_eq!(&expected, config.sort_order());
391        assert_eq!(false, config.sort_name());
392
393        let expected = vec![OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
394        let args = vec!["ex", "-ot"];
395        let config = create_config(args);
396        assert_eq!(&expected, config.sort_order());
397        assert_eq!(false, config.sort_name());
398
399        let expected = vec![OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
400        let args = vec!["ex", "-ot+"];
401        let config = create_config(args);
402        assert_eq!(&expected, config.sort_order());
403        assert_eq!(false, config.sort_name());
404
405        let expected = vec![OrderKind::Time(DirKind::Desc), OrderKind::Dir, OrderKind::Name];
406        let args = vec!["ex", "-ot-"];
407        let config = create_config(args);
408        assert_eq!(&expected, config.sort_order());
409        assert_eq!(false, config.sort_name());
410
411        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Time(DirKind::Asc), OrderKind::Name];
412        let args = vec!["ex", "--order=dest"];
413        let config = create_config(args);
414        assert_eq!(&expected, config.sort_order());
415        assert_eq!(false, config.sort_name());
416
417        let expected = vec![OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
418        let args = vec!["ex", "--order=est"];
419        let config = create_config(args);
420        assert_eq!(&expected, config.sort_order());
421        assert_eq!(false, config.sort_name());
422
423        let expected = vec![OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name];
424        let args = vec!["ex", "--order=e+s+t+"];
425        let config = create_config(args);
426        assert_eq!(&expected, config.sort_order());
427        assert_eq!(false, config.sort_name());
428
429        let expected = vec![OrderKind::Ext, OrderKind::Size(DirKind::Desc), OrderKind::Time(DirKind::Desc), OrderKind::Dir, OrderKind::Name];
430        let args = vec!["ex", "--order=e-s-t-"];
431        let config = create_config(args);
432        assert_eq!(&expected, config.sort_order());
433        assert_eq!(false, config.sort_name());
434    }
435
436    #[test]
437    fn test_order_defaults_to_group_with_no_recursion() {
438        let expected = vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name];
439        let args = vec!["ex"];
440        let config = create_config(args);
441        assert_eq!(&expected, config.sort_order());
442        assert_eq!(false, config.sort_name());
443
444        let expected = vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name];
445        let args = vec!["ex", "-d0"];
446        let config = create_config(args);
447        assert_eq!(&expected, config.sort_order());
448        assert_eq!(false, config.sort_name());
449
450        let expected = vec![OrderKind::Dir, OrderKind::Name];
451        let args = vec!["ex", "-d1"];
452        let config = create_config(args);
453        assert_eq!(&expected, config.sort_order());
454        assert_eq!(false, config.sort_name());
455
456        let expected = vec![OrderKind::Dir, OrderKind::Name];
457        let args = vec!["ex", "-d2"];
458        let config = create_config(args);
459        assert_eq!(&expected, config.sort_order());
460        assert_eq!(false, config.sort_name());
461
462        let expected = vec![OrderKind::Dir, OrderKind::Name];
463        let args = vec!["ex", "-s"];
464        let config = create_config(args);
465        assert_eq!(&expected, config.sort_order());
466        assert_eq!(false, config.sort_name());
467
468        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
469        let args = vec!["ex", "-oe"];
470        let config = create_config(args);
471        assert_eq!(&expected, config.sort_order());
472        assert_eq!(false, config.sort_name());
473
474        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
475        let args = vec!["ex", "-oe", "-d0"];
476        let config = create_config(args);
477        assert_eq!(&expected, config.sort_order());
478        assert_eq!(false, config.sort_name());
479
480        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
481        let args = vec!["ex", "-oe", "-d1"];
482        let config = create_config(args);
483        assert_eq!(&expected, config.sort_order());
484        assert_eq!(false, config.sort_name());
485
486        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
487        let args = vec!["ex", "-oe", "-d2"];
488        let config = create_config(args);
489        assert_eq!(&expected, config.sort_order());
490        assert_eq!(false, config.sort_name());
491
492        let expected = vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name];
493        let args = vec!["ex", "-oe", "-s"];
494        let config = create_config(args);
495        assert_eq!(&expected, config.sort_order());
496        assert_eq!(false, config.sort_name());
497    }
498
499    #[test]
500    fn test_order_defaults_to_directory_with_indent() {
501        let expected = vec![OrderKind::Dir, OrderKind::Name];
502        let args = vec!["ex", "-i"];
503        let config = create_config(args);
504        assert_eq!(&expected, config.sort_order());
505        assert_eq!(false, config.sort_name());
506
507        let expected = vec![OrderKind::Dir, OrderKind::Name];
508        let args = vec!["ex", "-i", "-d0"];
509        let config = create_config(args);
510        assert_eq!(&expected, config.sort_order());
511        assert_eq!(false, config.sort_name());
512
513        let expected = vec![OrderKind::Dir, OrderKind::Name];
514        let args = vec!["ex", "-i", "-d1"];
515        let config = create_config(args);
516        assert_eq!(&expected, config.sort_order());
517        assert_eq!(false, config.sort_name());
518
519        let expected = vec![OrderKind::Dir, OrderKind::Name];
520        let args = vec!["ex", "-i", "-d2"];
521        let config = create_config(args);
522        assert_eq!(&expected, config.sort_order());
523        assert_eq!(false, config.sort_name());
524
525        let expected = vec![OrderKind::Dir, OrderKind::Name];
526        let args = vec!["ex", "-i", "-s"];
527        let config = create_config(args);
528        assert_eq!(&expected, config.sort_order());
529        assert_eq!(false, config.sort_name());
530
531        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
532        let args = vec!["ex", "-i", "-oe"];
533        let config = create_config(args);
534        assert_eq!(&expected, config.sort_order());
535        assert_eq!(false, config.sort_name());
536
537        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
538        let args = vec!["ex", "-i", "-oe", "-d0"];
539        let config = create_config(args);
540        assert_eq!(&expected, config.sort_order());
541        assert_eq!(false, config.sort_name());
542
543        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
544        let args = vec!["ex", "-i", "-oe", "-d1"];
545        let config = create_config(args);
546        assert_eq!(&expected, config.sort_order());
547        assert_eq!(false, config.sort_name());
548
549        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
550        let args = vec!["ex", "-i", "-oe", "-d2"];
551        let config = create_config(args);
552        assert_eq!(&expected, config.sort_order());
553        assert_eq!(false, config.sort_name());
554
555        let expected = vec![OrderKind::Dir, OrderKind::Ext, OrderKind::Name];
556        let args = vec!["ex", "-i", "-oe", "-s"];
557        let config = create_config(args);
558        assert_eq!(&expected, config.sort_order());
559        assert_eq!(false, config.sort_name());
560    }
561
562    #[test]
563    fn test_recent_is_handled() {
564        let args = vec!["ex"];
565        let config = create_config(args);
566        assert_eq!(RecentKind::None, config.filter_recent());
567
568        let args = vec!["ex", "-ry10"];
569        let config = create_config(args);
570        assert_eq!(RecentKind::Year(10), config.filter_recent());
571
572        let args = vec!["ex", "-rm6"];
573        let config = create_config(args);
574        assert_eq!(RecentKind::Month(6), config.filter_recent());
575
576        let args = vec!["ex", "-rw2"];
577        let config = create_config(args);
578        assert_eq!(RecentKind::Week(2), config.filter_recent());
579
580        let args = vec!["ex", "-rd"];
581        let config = create_config(args);
582        assert_eq!(RecentKind::Day(1), config.filter_recent());
583
584        let args = vec!["ex", "--recent=h"];
585        let config = create_config(args);
586        assert_eq!(RecentKind::Hour(1), config.filter_recent());
587
588        let args = vec!["ex", "--recent=H"];
589        let config = create_config(args);
590        assert_eq!(RecentKind::Hour(1), config.filter_recent());
591
592        let args = vec!["ex", "--recent=M5"];
593        let config = create_config(args);
594        assert_eq!(RecentKind::Min(5), config.filter_recent());
595
596        let args = vec!["ex", "--recent=S10"];
597        let config = create_config(args);
598        assert_eq!(RecentKind::Sec(10), config.filter_recent());
599    }
600
601    #[test]
602    fn test_none_is_subtracted_from_time() {
603        let now = create_time(2023, 7, 1, 0, 0, 0);
604        assert_eq!(None, RecentKind::None.subtract_from(&now, &Utc));
605    }
606
607    #[test]
608    fn test_second_is_subtracted_from_time() {
609        let now = create_time(2023, 7, 1, 0, 0, 0);
610        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Sec(0).subtract_from(&now, &Utc));
611        assert_eq!(Some(create_time(2023, 6, 30, 23, 59, 59)), RecentKind::Sec(1).subtract_from(&now, &Utc));
612        assert_eq!(Some(create_time(2023, 6, 30, 23, 59, 58)), RecentKind::Sec(2).subtract_from(&now, &Utc));
613    }
614
615    #[test]
616    fn test_minute_is_subtracted_from_time() {
617        let now = create_time(2023, 7, 1, 0, 0, 0);
618        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Min(0).subtract_from(&now, &Utc));
619        assert_eq!(Some(create_time(2023, 6, 30, 23, 59, 0)), RecentKind::Min(1).subtract_from(&now, &Utc));
620        assert_eq!(Some(create_time(2023, 6, 30, 23, 58, 0)), RecentKind::Min(2).subtract_from(&now, &Utc));
621    }
622
623    #[test]
624    fn test_hour_is_subtracted_from_time() {
625        let now = create_time(2023, 7, 1, 0, 0, 0);
626        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Hour(0).subtract_from(&now, &Utc));
627        assert_eq!(Some(create_time(2023, 6, 30, 23, 0, 0)), RecentKind::Hour(1).subtract_from(&now, &Utc));
628        assert_eq!(Some(create_time(2023, 6, 30, 22, 0, 0)), RecentKind::Hour(2).subtract_from(&now, &Utc));
629    }
630
631    #[test]
632    fn test_day_is_subtracted_from_time() {
633        let now = create_time(2023, 7, 1, 0, 0, 0);
634        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Day(0).subtract_from(&now, &Utc));
635        assert_eq!(Some(create_time(2023, 6, 30, 0, 0, 0)), RecentKind::Day(1).subtract_from(&now, &Utc));
636        assert_eq!(Some(create_time(2023, 6, 29, 0, 0, 0)), RecentKind::Day(2).subtract_from(&now, &Utc));
637    }
638
639    #[test]
640    fn test_week_is_subtracted_from_time() {
641        let now = create_time(2023, 7, 1, 0, 0, 0);
642        assert_eq!(Some(create_time(2023, 7, 1, 0, 0, 0)), RecentKind::Week(0).subtract_from(&now, &Utc));
643        assert_eq!(Some(create_time(2023, 6, 24, 0, 0, 0)), RecentKind::Week(1).subtract_from(&now, &Utc));
644        assert_eq!(Some(create_time(2023, 6, 17, 0, 0, 0)), RecentKind::Week(2).subtract_from(&now, &Utc));
645    }
646
647    #[test]
648    fn test_month_is_subtracted_from_time() {
649        let now = create_time(2023, 3, 31, 0, 0, 0);
650        assert_eq!(Some(create_time(2023, 3, 31, 0, 0, 0)), RecentKind::Month(0).subtract_from(&now, &Utc));
651        assert_eq!(Some(create_time(2023, 3, 1, 0, 0, 0)), RecentKind::Month(1).subtract_from(&now, &Utc));
652        assert_eq!(Some(create_time(2023, 1, 31, 0, 0, 0)), RecentKind::Month(2).subtract_from(&now, &Utc));
653        assert_eq!(Some(create_time(2022, 12, 31, 0, 0, 0)), RecentKind::Month(3).subtract_from(&now, &Utc));
654        assert_eq!(Some(create_time(2022, 12, 1, 0, 0, 0)), RecentKind::Month(4).subtract_from(&now, &Utc));
655        assert_eq!(Some(create_time(2022, 10, 31, 0, 0, 0)), RecentKind::Month(5).subtract_from(&now, &Utc));
656    }
657
658    #[test]
659    fn test_year_is_subtracted_from_time() {
660        let now = create_time(2024, 2, 29, 0, 0, 0);
661        assert_eq!(Some(create_time(2024, 2, 29, 0, 0, 0)), RecentKind::Year(0).subtract_from(&now, &Utc));
662        assert_eq!(Some(create_time(2023, 3, 1, 0, 0, 0)), RecentKind::Year(1).subtract_from(&now, &Utc));
663        assert_eq!(Some(create_time(2022, 3, 1, 0, 0, 0)), RecentKind::Year(2).subtract_from(&now, &Utc));
664        assert_eq!(Some(create_time(2021, 3, 1, 0, 0, 0)), RecentKind::Year(3).subtract_from(&now, &Utc));
665        assert_eq!(Some(create_time(2020, 2, 29, 0, 0, 0)), RecentKind::Year(4).subtract_from(&now, &Utc));
666    }
667
668    #[test]
669    fn test_type_is_handled() {
670        let args = vec!["ex"];
671        let config = create_config(args);
672        assert_eq!(None, config.filter_types());
673
674        let args = vec!["ex", "-tf"];
675        let config = create_config(args);
676        let expected = create_set(&[
677            FileKind::File(ExecKind::None),
678            FileKind::File(ExecKind::User),
679            FileKind::File(ExecKind::Other),
680        ]);
681        assert_eq!(Some(&expected), config.filter_types());
682
683        let args = vec!["ex", "-te"];
684        let config = create_config(args);
685        let expected = create_set(&[
686            FileKind::File(ExecKind::User),
687            FileKind::File(ExecKind::Other),
688        ]);
689        assert_eq!(Some(&expected), config.filter_types());
690
691        let args = vec!["ex", "-td"];
692        let config = create_config(args);
693        let expected = create_set(&[FileKind::Dir]);
694        assert_eq!(Some(&expected), config.filter_types());
695
696        let args = vec!["ex", "-tfd"];
697        let config = create_config(args);
698        let expected = create_set(&[
699            FileKind::File(ExecKind::None),
700            FileKind::File(ExecKind::User),
701            FileKind::File(ExecKind::Other),
702            FileKind::Dir,
703        ]);
704        assert_eq!(Some(&expected), config.filter_types());
705
706        let args = vec!["ex", "--type=l"];
707        let config = create_config(args);
708        let expected = create_set(&[
709            FileKind::Link(false),
710            FileKind::Link(true),
711        ]);
712        assert_eq!(Some(&expected), config.filter_types());
713    }
714
715    #[test]
716    fn test_git_is_handled() {
717        let args = vec!["ex"];
718        let config = create_config(args);
719        assert_eq!(None, config.filter_git());
720
721        let expected = GitFlags::default().with_everything(true);
722        let args = vec!["ex", "-gx"];
723        let config = create_config(args);
724        assert_eq!(Some(&expected), config.filter_git());
725
726        let expected = GitFlags::default().with_cached(true);
727        let args = vec!["ex", "-gc"];
728        let config = create_config(args);
729        assert_eq!(Some(&expected), config.filter_git());
730
731        let expected = GitFlags::default().with_added(true);
732        let args = vec!["ex", "-ga"];
733        let config = create_config(args);
734        assert_eq!(Some(&expected), config.filter_git());
735
736        let expected = GitFlags::default().with_modified(true);
737        let args = vec!["ex", "-gm"];
738        let config = create_config(args);
739        assert_eq!(Some(&expected), config.filter_git());
740
741        let expected = GitFlags::default().with_renamed(true);
742        let args = vec!["ex", "-gr"];
743        let config = create_config(args);
744        assert_eq!(Some(&expected), config.filter_git());
745
746        let expected = GitFlags::default().with_untracked(true);
747        let args = vec!["ex", "-gu"];
748        let config = create_config(args);
749        assert_eq!(Some(&expected), config.filter_git());
750
751        let expected = GitFlags::default().with_ignored(true);
752        let args = vec!["ex", "-gi"];
753        let config = create_config(args);
754        assert_eq!(Some(&expected), config.filter_git());
755
756        let expected = GitFlags::default().with_added(true).with_modified(true).with_renamed(true);
757        let args = vec!["ex", "--git=amr"];
758        let config = create_config(args);
759        assert_eq!(Some(&expected), config.filter_git());
760    }
761
762    #[test]
763    fn test_precise_is_handled() {
764        let args = vec!["ex"];
765        let config = create_config(args);
766        assert_eq!(false, config.show_precise());
767
768        let args = vec!["ex", "-p"];
769        let config = create_config(args);
770        assert_eq!(true, config.show_precise());
771
772        let args = vec!["ex", "--precise"];
773        let config = create_config(args);
774        assert_eq!(true, config.show_precise());
775    }
776
777    #[test]
778    fn test_utc_is_handled() {
779        let args = vec!["ex"];
780        let config = create_config(args);
781        assert_eq!(false, config.show_utc());
782
783        let args = vec!["ex", "-u"];
784        let config = create_config(args);
785        assert_eq!(true, config.show_utc());
786
787        let args = vec!["ex", "--utc"];
788        let config = create_config(args);
789        assert_eq!(true, config.show_utc());
790    }
791
792    #[test]
793    fn test_total_is_handled() {
794        let args = vec!["ex"];
795        let config = create_config(args);
796        assert_eq!(false, config.show_total());
797
798        let args = vec!["ex", "--total"];
799        let config = create_config(args);
800        assert_eq!(true, config.show_total());
801    }
802
803    #[test]
804    #[cfg(unix)]
805    fn test_owner_is_handled() {
806        let args = vec!["ex"];
807        let config = create_config(args);
808        assert_eq!(false, config.show_owner());
809
810        let args = vec!["ex", "--owner"];
811        let config = create_config(args);
812        assert_eq!(true, config.show_owner());
813    }
814
815    #[test]
816    fn test_data_is_handled() {
817        let args = vec!["ex"];
818        let config = create_config(args);
819        assert_eq!(false, config.show_sig());
820
821        let args = vec!["ex", "--sig"];
822        let config = create_config(args);
823        assert_eq!(true, config.show_sig());
824    }
825
826    #[test]
827    fn test_only_path_is_handled_with_terminal() {
828        let args = vec!["ex"];
829        let config = create_config_with_piped(args, false);
830        assert_eq!(false, config.only_path()); // (false if terminal)
831        assert_eq!(false, config.escape_path()); // (false if terminal)
832        assert_eq!(false, config.null_path());
833
834        let args = vec!["ex", "-x"];
835        let config = create_config_with_piped(args, false);
836        assert_eq!(true, config.only_path());
837        assert_eq!(false, config.escape_path());
838        assert_eq!(false, config.null_path());
839
840        let args = vec!["ex", "-x", "-x"];
841        let config = create_config_with_piped(args, false);
842        assert_eq!(false, config.only_path());
843        assert_eq!(false, config.escape_path());
844        assert_eq!(false, config.null_path());
845
846        let args = vec!["ex", "-xx"];
847        let config = create_config_with_piped(args, false);
848        assert_eq!(false, config.only_path());
849        assert_eq!(false, config.escape_path());
850        assert_eq!(false, config.null_path());
851
852        let args = vec!["ex", "--only-path"];
853        let config = create_config_with_piped(args, false);
854        assert_eq!(true, config.only_path());
855        assert_eq!(false, config.escape_path());
856        assert_eq!(false, config.null_path());
857    }
858
859    #[test]
860    fn test_only_path_is_handled_with_piped() {
861        let args = vec!["ex"];
862        let config = create_config_with_piped(args, true);
863        assert_eq!(true, config.only_path()); // (true if piped)
864        assert_eq!(true, config.escape_path()); // (true if piped)
865        assert_eq!(false, config.null_path());
866
867        let args = vec!["ex", "-x"];
868        let config = create_config_with_piped(args, true);
869        assert_eq!(true, config.only_path());
870        assert_eq!(false, config.escape_path());
871        assert_eq!(false, config.null_path());
872
873        let args = vec!["ex", "-x", "-x"];
874        let config = create_config_with_piped(args, true);
875        assert_eq!(false, config.only_path());
876        assert_eq!(false, config.escape_path());
877        assert_eq!(false, config.null_path());
878
879        let args = vec!["ex", "-xx"];
880        let config = create_config_with_piped(args, true);
881        assert_eq!(false, config.only_path());
882        assert_eq!(false, config.escape_path());
883        assert_eq!(false, config.null_path());
884
885        let args = vec!["ex", "--only-path"];
886        let config = create_config_with_piped(args, true);
887        assert_eq!(true, config.only_path());
888        assert_eq!(false, config.escape_path());
889        assert_eq!(false, config.null_path());
890    }
891
892    #[test]
893    #[cfg(debug_assertions)]
894    fn test_only_path_is_handled_with_override() {
895        let args = vec!["ex", "--terminal"];
896        let config = create_config_with_piped(args, true);
897        assert_eq!(false, config.only_path()); // (false if terminal)
898        assert_eq!(false, config.escape_path()); // (false if terminal)
899        assert_eq!(false, config.null_path());
900
901        let args = vec!["ex", "--terminal", "-x"];
902        let config = create_config_with_piped(args, true);
903        assert_eq!(true, config.only_path());
904        assert_eq!(false, config.escape_path());
905        assert_eq!(false, config.null_path());
906
907        let args = vec!["ex", "--terminal", "-x", "-x"];
908        let config = create_config_with_piped(args, true);
909        assert_eq!(false, config.only_path());
910        assert_eq!(false, config.escape_path());
911        assert_eq!(false, config.null_path());
912
913        let args = vec!["ex", "--terminal", "-xx"];
914        let config = create_config_with_piped(args, true);
915        assert_eq!(false, config.only_path());
916        assert_eq!(false, config.escape_path());
917        assert_eq!(false, config.null_path());
918
919        let args = vec!["ex", "--terminal", "--only-path"];
920        let config = create_config_with_piped(args, true);
921        assert_eq!(true, config.only_path());
922        assert_eq!(false, config.escape_path());
923        assert_eq!(false, config.null_path());
924    }
925
926    #[test]
927    fn test_null_path_is_handled() {
928        let args = vec!["ex"];
929        let config = create_config_with_piped(args, false);
930        assert_eq!(false, config.only_path());
931        assert_eq!(false, config.escape_path());
932        assert_eq!(false, config.null_path());
933
934        let args = vec!["ex", "-z"]; // (reassigned "-z")
935        let config = create_config_with_piped(args, false);
936        assert_eq!(false, config.only_path());
937        assert_eq!(false, config.escape_path());
938        assert_eq!(false, config.null_path());
939
940        let args = vec!["ex", "--null-path"];
941        let config = create_config_with_piped(args, false);
942        assert_eq!(true, config.only_path());
943        assert_eq!(false, config.escape_path());
944        assert_eq!(true, config.null_path());
945    }
946
947    #[test]
948    fn test_abs_path_is_handled() {
949        let args = vec!["ex"];
950        let config = create_config(args);
951        assert_eq!(false, config.abs_path());
952
953        let args = vec!["ex", "-q"];
954        let config = create_config(args);
955        assert_eq!(true, config.abs_path());
956
957        let args = vec!["ex", "--abs-path"];
958        let config = create_config(args);
959        assert_eq!(true, config.abs_path());
960    }
961
962    #[test]
963    #[cfg(windows)]
964    fn test_win_path_is_handled() {
965        let args = vec!["ex"];
966        let config = create_config(args);
967        assert_eq!(false, config.win_path());
968
969        let args = vec!["ex", "-w"];
970        let config = create_config(args);
971        assert_eq!(true, config.win_path());
972
973        let args = vec!["ex", "--win-path"];
974        let config = create_config(args);
975        assert_eq!(true, config.win_path());
976    }
977
978    #[test]
979    #[cfg(windows)]
980    fn test_win_ver_is_handled() {
981        let args = vec!["ex"];
982        let config = create_config(args);
983        assert_eq!(false, config.win_ver());
984
985        let args = vec!["ex", "-v"];
986        let config = create_config(args);
987        assert_eq!(true, config.win_ver());
988
989        let args = vec!["ex", "--win-ver"];
990        let config = create_config(args);
991        assert_eq!(true, config.win_ver());
992    }
993
994    #[test]
995    fn test_patterns_are_handled() {
996        let expected = vec!["."];
997        let args = vec!["ex"];
998        let config = create_config(args);
999        assert_eq!(&expected, config.patterns());
1000
1001        let expected = vec!["file1"];
1002        let args = vec!["ex", "file1"];
1003        let config = create_config(args);
1004        assert_eq!(&expected, config.patterns());
1005
1006        let expected = vec!["file1", "file2"];
1007        let args = vec!["ex", "file1", "file2"];
1008        let config = create_config(args);
1009        assert_eq!(&expected, config.patterns());
1010    }
1011
1012    #[test]
1013    #[should_panic(expected = "Invalid depth option: foo")]
1014    fn test_unexpected_depth_causes_error() {
1015        let args = vec!["ex", "-dfoo"];
1016        create_config(args);
1017    }
1018
1019    #[test]
1020    #[should_panic(expected = "Invalid order option: foo")]
1021    fn test_unexpected_order_causes_error() {
1022        let args = vec!["ex", "-ofoo"];
1023        create_config(args);
1024    }
1025
1026    #[test]
1027    #[should_panic(expected = "Invalid recent option: foo")]
1028    fn test_unexpected_recent_causes_error() {
1029        let args = vec!["ex", "-rfoo"];
1030        create_config(args);
1031    }
1032
1033    #[test]
1034    #[should_panic(expected = "Invalid type option: z")]
1035    fn test_unexpected_type_causes_error() {
1036        let args = vec!["ex", "-tfzd"];
1037        create_config(args);
1038    }
1039
1040    #[test]
1041    #[should_panic(expected = "Invalid git option: z")]
1042    fn test_unexpected_git_causes_error() {
1043        let args = vec!["ex", "-gazm"];
1044        create_config(args);
1045    }
1046
1047    fn create_config(args: Vec<&str>) -> Config {
1048        create_config_with_piped(args, false)
1049    }
1050
1051    fn create_config_with_piped(args: Vec<&str>, piped: bool) -> Config {
1052        let args = args.into_iter().map(String::from).collect();
1053        Config::new(args, piped, DateTime::default).unwrap()
1054    }
1055
1056    fn create_time(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32) -> DateTime<Utc> {
1057        let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
1058        let time = NaiveTime::from_hms_opt(hour, minute, second).unwrap();
1059        let time = DateTime::from_naive_utc_and_offset(NaiveDateTime::new(date, time), Utc);
1060        time
1061    }
1062
1063    fn create_set<T: Clone + Eq + Hash>(values: &[T]) -> HashSet<T> {
1064        values.iter().map(T::clone).collect()
1065    }
1066
1067    impl Config {
1068        pub fn with_curr_time(
1069            mut self,
1070            year: i32,
1071            month: u32,
1072            day: u32,
1073            hour: u32,
1074            min: u32,
1075            sec: u32,
1076        ) -> Self {
1077            self.curr_time = Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap();
1078            self
1079        }
1080
1081        pub fn with_recurse_all(mut self, recurse_all: bool) -> Self {
1082            self.cli.recurse_all = recurse_all;
1083            self
1084        }
1085
1086        pub fn with_min_depth(mut self, min_depth: usize) -> Self {
1087            self.cli.recurse_depth.get_or_insert_default().min_depth = Some(min_depth);
1088            self
1089        }
1090
1091        pub fn with_max_depth(mut self, max_depth: usize) -> Self {
1092            self.cli.recurse_depth.get_or_insert_default().max_depth = Some(max_depth);
1093            self
1094        }
1095
1096        pub fn with_show_indent(mut self, show_indent: bool) -> Self {
1097            self.cli.show_indent = show_indent;
1098            self
1099        }
1100
1101        pub fn with_zip_expand(mut self, zip_expand: bool) -> Self {
1102            self.cli.zip_expand = zip_expand;
1103            self
1104        }
1105
1106        pub fn with_case_sensitive(mut self, case_sensitive: bool) -> Self {
1107            if case_sensitive {
1108                self.cli.case_sensitive = true;
1109                self.cli.case_insensitive = false;
1110            } else {
1111                self.cli.case_sensitive = false;
1112                self.cli.case_insensitive = true;
1113            }
1114            self
1115        }
1116
1117        pub fn with_sort_order(mut self, sort_order: Vec<OrderKind>) -> Self {
1118            self.sort_order = sort_order;
1119            self
1120        }
1121
1122        pub fn with_sort_name(mut self, sort_name: bool) -> Self {
1123            self.sort_name = sort_name;
1124            self
1125        }
1126
1127        pub fn with_filter_recent(mut self, filter_recent: RecentKind) -> Self {
1128            self.cli.filter_recent = Some(filter_recent);
1129            self
1130        }
1131
1132        pub fn with_filter_types(mut self, filter_types: Vec<FileKind>) -> Self {
1133            let filter_types = FileSet { inner: filter_types.into_iter().collect() };
1134            self.cli.filter_types = Some(filter_types);
1135            self
1136        }
1137
1138        pub fn with_filter_git(mut self, filter_git: GitFlags) -> Self {
1139            self.cli.filter_git = Some(filter_git);
1140            self
1141        }
1142
1143        pub fn with_show_precise(mut self, show_precise: bool) -> Self {
1144            self.cli.show_precise = show_precise;
1145            self
1146        }
1147
1148        pub fn with_show_utc(mut self, show_utc: bool) -> Self {
1149            self.cli.show_utc = show_utc;
1150            self
1151        }
1152
1153        pub fn with_show_total(mut self, show_total: bool) -> Self {
1154            self.cli.show_total = show_total;
1155            self
1156        }
1157
1158        #[cfg(unix)]
1159        pub fn with_show_owner(mut self, show_owner: bool) -> Self {
1160            self.cli.show_owner = show_owner;
1161            self
1162        }
1163
1164        pub fn with_show_crc(mut self, show_crc: bool) -> Self {
1165            self.cli.show_crc = show_crc;
1166            self
1167        }
1168
1169        pub fn with_show_sig(mut self, show_sig: bool) -> Self {
1170            self.cli.show_sig = show_sig;
1171            self
1172        }
1173
1174        pub fn with_only_path(mut self, only_path: bool) -> Self {
1175            self.cli.only_path = if only_path { 1 } else { 2 };
1176            self
1177        }
1178
1179        pub fn with_escape_path(mut self) -> Self {
1180            self.cli.only_path = 0;
1181            self.cli.null_path = false;
1182            self.piped = true;
1183            self
1184        }
1185
1186        pub fn with_abs_path(mut self, abs_path: bool) -> Self {
1187            self.cli.abs_path = abs_path;
1188            self
1189        }
1190
1191        #[cfg(windows)]
1192        pub fn with_win_ver(mut self, win_ver: bool) -> Self {
1193            self.cli.win_ver = win_ver;
1194            self
1195        }
1196
1197        pub fn with_patterns(mut self, patterns: Vec<&str>) -> Self {
1198            self.cli.patterns = patterns.into_iter().map(String::from).collect();
1199            self
1200        }
1201    }
1202}