Skip to main content

ex_cli/
sorter.rs

1use crate::cli::order::{DirKind, OrderKind};
2use crate::config::Config;
3use crate::fs::file::File;
4use itertools::{EitherOrBoth, Itertools, ZipLongest};
5use std::cmp::Ordering;
6use std::path::Components;
7
8pub struct Sorter<'a> {
9    config: &'a Config,
10}
11
12impl<'a> Sorter<'a> {
13    pub fn new(config: &'a Config) -> Self {
14        Self { config }
15    }
16
17    pub fn sort_files(&self, files: &mut Vec<File>) {
18        files.sort_unstable_by(|x, y| self.cmp_files(x, y));
19    }
20
21    fn cmp_files(&self, left: &File, right: &File) -> Ordering {
22        for order in self.config.sort_order() {
23            let result = match order {
24                OrderKind::Dir => Self::cmp_dirs(left, right),
25                OrderKind::Group => Self::cmp_groups(left, right),
26                OrderKind::Name => Self::cmp_names(left, right),
27                OrderKind::Ext => Self::cmp_exts(left, right),
28                OrderKind::Size(dir) => Self::cmp_sizes(left, right, dir),
29                OrderKind::Time(dir) => Self::cmp_times(left, right, dir),
30            };
31            if result != Ordering::Equal {
32                return result;
33            }
34        }
35        Ordering::Equal
36    }
37
38    fn cmp_dirs(left: &File, right: &File) -> Ordering {
39        for pair in Self::zip_dirs(left, right) {
40            let result = match pair {
41                EitherOrBoth::Both(left, right) => {
42                    let left = left.as_os_str().to_str().unwrap_or_default();
43                    let right = right.as_os_str().to_str().unwrap_or_default();
44                    Self::cmp_strings(left, right)
45                }
46                EitherOrBoth::Left(_) => Ordering::Greater,
47                EitherOrBoth::Right(_) => Ordering::Less,
48            };
49            if result != Ordering::Equal {
50                return result;
51            }
52        }
53        Self::cmp_groups(left, right)
54    }
55
56    fn zip_dirs<'b>(left: &'b File, right: &'b File) -> ZipLongest<Components<'b>, Components<'b>> {
57        let left = left.abs_dir.components();
58        let right = right.abs_dir.components();
59        left.zip_longest(right)
60    }
61
62    fn cmp_groups(left: &File, right: &File) -> Ordering {
63        let left = left.group_dir_before_file();
64        let right = right.group_dir_before_file();
65        left.cmp(&right)
66    }
67
68    fn cmp_names(left: &File, right: &File) -> Ordering {
69        Self::cmp_strings(&left.file_name, &right.file_name)
70    }
71
72    fn cmp_exts(left: &File, right: &File) -> Ordering {
73        Self::cmp_strings(&left.file_ext, &right.file_ext)
74    }
75
76    fn cmp_strings(left: &str, right: &str) -> Ordering {
77        let result = natord::compare_ignore_case(left, right);
78        if result != Ordering::Equal {
79            return result;
80        }
81        let result = natord::compare(left, right);
82        if result != Ordering::Equal {
83            return result;
84        }
85        Ordering::Equal
86    }
87
88    fn cmp_sizes(left: &File, right: &File, dir: &DirKind) -> Ordering {
89        let result = left.file_size.cmp(&right.file_size);
90        match dir {
91            DirKind::Asc => result,
92            DirKind::Desc => result.reverse(),
93        }
94    }
95
96    fn cmp_times(left: &File, right: &File, dir: &DirKind) -> Ordering {
97        let result = left.file_time.cmp(&right.file_time);
98        match dir {
99            DirKind::Asc => result,
100            DirKind::Desc => result.reverse(),
101        }
102    }
103}
104
105// noinspection RsLift
106#[cfg(test)]
107mod tests {
108    use crate::cli::file::FileKind;
109    use crate::cli::order::{DirKind, OrderKind};
110    use crate::config::Config;
111    use crate::fs::file::File;
112    use crate::sorter::Sorter;
113    use chrono::{DateTime, Utc};
114    use pretty_assertions::assert_eq;
115    use std::ffi::OsStr;
116    use std::path::{PathBuf, MAIN_SEPARATOR_STR};
117
118    #[test]
119    fn test_files_are_sorted_by_path() {
120        let expected = string_from(vec![
121            "index",
122            "cheese/",
123            "cheese/index",
124            "cheese/english/",
125            "cheese/english/cheddar.xy",
126            "cheese/english/stilton.ij",
127            "cheese/french/",
128            "cheese/french/bleu.xy",
129            "cheese/french/brie.ij",
130            "fruit/",
131            "fruit/citrus/",
132            "fruit/citrus/lemon.xy",
133            "fruit/citrus/orange.ij",
134            "fruit/other/",
135            "fruit/other/apple.xy",
136            "fruit/other/banana.ij",
137            "fruit/other/cherry.xy",
138            "fruit/other/date.ij",
139            "fruit.zip/",
140            "fruit.zip/citrus/",
141            "fruit.zip/citrus/lemon.xy",
142            "fruit.zip/citrus/orange.ij",
143            "fruit.zip/other/",
144            "fruit.zip/other/apple.xy",
145            "fruit.zip/other/banana.ij",
146            "fruit.zip/other/cherry.xy",
147            "fruit.zip/other/date.ij",
148        ]);
149        let files = create_files();
150        let paths = sort_files(files, vec![OrderKind::Dir, OrderKind::Name]);
151        assert_eq!(expected, paths);
152    }
153
154    #[test]
155    fn test_files_are_sorted_by_dir_and_size() {
156        let expected = string_from(vec![
157            "index",                      //    42
158            "cheese/",                    //     0
159            "cheese/index",               //    99
160            "cheese/english/",            //     0
161            "cheese/english/stilton.ij",  //    91
162            "cheese/english/cheddar.xy",  //  6363
163            "cheese/french/",             //     0
164            "cheese/french/brie.ij",      //  8681
165            "cheese/french/bleu.xy",      // 20122
166            "fruit/",                     //     0
167            "fruit/citrus/",              //     0
168            "fruit/citrus/lemon.xy",      //   108
169            "fruit/citrus/orange.ij",     //   173
170            "fruit/other/",               //     0
171            "fruit/other/date.ij",        //     6
172            "fruit/other/banana.ij",      //    73
173            "fruit/other/apple.xy",       //   467
174            "fruit/other/cherry.xy",      //   795
175            "fruit.zip/",                 //     0
176            "fruit.zip/citrus/",          //     0
177            "fruit.zip/citrus/lemon.xy",  //   108
178            "fruit.zip/citrus/orange.ij", //   173
179            "fruit.zip/other/",           //     0
180            "fruit.zip/other/date.ij",    //     6
181            "fruit.zip/other/banana.ij",  //    73
182            "fruit.zip/other/apple.xy",   //   467
183            "fruit.zip/other/cherry.xy",  //   795
184        ]);
185        let files = create_files();
186        let paths = sort_files(files, vec![OrderKind::Dir, OrderKind::Size(DirKind::Asc), OrderKind::Name]);
187        assert_eq!(expected, paths);
188    }
189
190    #[test]
191    fn test_files_are_sorted_by_dir_and_time() {
192        let expected = string_from(vec![
193            "index",                      // 2023-07-05 13:05:42
194            "cheese/",                    // 2023-01-16 11:25:51
195            "cheese/index",               // 2022-12-23 21:43:18
196            "cheese/english/",            // 2023-08-04 10:57:26
197            "cheese/english/cheddar.xy",  // 2022-09-10 04:03:38
198            "cheese/english/stilton.ij",  // 2023-04-07 03:08:29
199            "cheese/french/",             // 2022-10-06 23:36:27
200            "cheese/french/bleu.xy",      // 2022-10-28 05:49:07
201            "cheese/french/brie.ij",      // 2023-02-28 11:01:59
202            "fruit/",                     // 2023-04-18 17:09:31
203            "fruit/citrus/",              // 2023-07-13 08:58:00
204            "fruit/citrus/orange.ij",     // 2023-03-27 05:45:33
205            "fruit/citrus/lemon.xy",      // 2023-06-29 21:16:08
206            "fruit/other/",               // 2023-08-14 01:53:00
207            "fruit/other/date.ij",        // 2022-11-25 14:42:53
208            "fruit/other/cherry.xy",      // 2022-12-19 02:12:38
209            "fruit/other/apple.xy",       // 2023-05-10 11:24:22
210            "fruit/other/banana.ij",      // 2023-06-10 02:06:38
211            "fruit.zip/",                 // 2023-04-18 17:09:31
212            "fruit.zip/citrus/",          // 2023-07-13 08:58:00
213            "fruit.zip/citrus/orange.ij", // 2023-03-27 05:45:33
214            "fruit.zip/citrus/lemon.xy",  // 2023-06-29 21:16:08
215            "fruit.zip/other/",           // 2023-08-14 01:53:00
216            "fruit.zip/other/date.ij",    // 2022-11-25 14:42:53
217            "fruit.zip/other/cherry.xy",  // 2022-12-19 02:12:38
218            "fruit.zip/other/apple.xy",   // 2023-05-10 11:24:22
219            "fruit.zip/other/banana.ij",  // 2023-06-10 02:06:38
220        ]);
221        let files = create_files();
222        let paths = sort_files(files, vec![OrderKind::Dir, OrderKind::Time(DirKind::Asc), OrderKind::Name]);
223        assert_eq!(expected, paths);
224    }
225
226    #[test]
227    fn test_files_are_sorted_by_name() {
228        let expected = string_from(vec![
229            "cheese/",                    // -
230            "cheese/english/",            // -
231            "cheese/french/",             // -
232            "fruit/",                     // -
233            "fruit/citrus/",              // -
234            "fruit/other/",               // -
235            "fruit.zip/",                 // -
236            "fruit.zip/citrus/",          // -
237            "fruit.zip/other/",           // -
238            "fruit/other/apple.xy",       // apple.xy
239            "fruit.zip/other/apple.xy",   // apple.xy
240            "fruit/other/banana.ij",      // banana.ij
241            "fruit.zip/other/banana.ij",  // banana.ij
242            "cheese/french/bleu.xy",      // bleu.xy
243            "cheese/french/brie.ij",      // brie.ij
244            "cheese/english/cheddar.xy",  // cheddar.xy
245            "fruit/other/cherry.xy",      // cherry.xy
246            "fruit.zip/other/cherry.xy",  // cherry.xy
247            "fruit/other/date.ij",        // date.ij
248            "fruit.zip/other/date.ij",    // date.ij
249            "index",                      // index
250            "cheese/index",               // index
251            "fruit/citrus/lemon.xy",      // lemon.xy
252            "fruit.zip/citrus/lemon.xy",  // lemon.xy
253            "fruit/citrus/orange.ij",     // orange.ij
254            "fruit.zip/citrus/orange.ij", // orange.ij
255            "cheese/english/stilton.ij",  // stilton.ij
256        ]);
257        let files = create_files();
258        let paths = sort_files(files, vec![OrderKind::Name, OrderKind::Dir]);
259        assert_eq!(expected, paths);
260    }
261
262    #[test]
263    fn test_files_are_sorted_by_ext() {
264        let expected = string_from(vec![
265            "index",                      // -
266            "cheese/",                    // -
267            "cheese/index",               // -
268            "cheese/english/",            // -
269            "cheese/french/",             // -
270            "fruit/",                     // -
271            "fruit/citrus/",              // -
272            "fruit/other/",               // -
273            "fruit.zip/",                 // -
274            "fruit.zip/citrus/",          // -
275            "fruit.zip/other/",           // -
276            "cheese/english/stilton.ij",  // .ij
277            "cheese/french/brie.ij",      // .ij
278            "fruit/citrus/orange.ij",     // .ij
279            "fruit/other/banana.ij",      // .ij
280            "fruit/other/date.ij",        // .ij
281            "fruit.zip/citrus/orange.ij", // .ij
282            "fruit.zip/other/banana.ij",  // .ij
283            "fruit.zip/other/date.ij",    // .ij
284            "cheese/english/cheddar.xy",  // .xy
285            "cheese/french/bleu.xy",      // .xy
286            "fruit/citrus/lemon.xy",      // .xy
287            "fruit/other/apple.xy",       // .xy
288            "fruit/other/cherry.xy",      // .xy
289            "fruit.zip/citrus/lemon.xy",  // .xy
290            "fruit.zip/other/apple.xy",   // .xy
291            "fruit.zip/other/cherry.xy",  // .xy
292        ]);
293        let files = create_files();
294        let paths = sort_files(files, vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name]);
295        assert_eq!(expected, paths);
296    }
297
298    #[test]
299    fn test_files_are_sorted_by_ext_and_size() {
300        let expected = string_from(vec![
301            "cheese/",                    // -       0
302            "cheese/english/",            // -       0
303            "cheese/french/",             // -       0
304            "fruit/",                     // -       0
305            "fruit/citrus/",              // -       0
306            "fruit/other/",               // -       0
307            "fruit.zip/",                 // -       0
308            "fruit.zip/citrus/",          // -       0
309            "fruit.zip/other/",           // -       0
310            "index",                      // -      42
311            "cheese/index",               // -      99
312            "fruit/other/date.ij",        // .ij     6
313            "fruit.zip/other/date.ij",    // .ij     6
314            "fruit/other/banana.ij",      // .ij    73
315            "fruit.zip/other/banana.ij",  // .ij    73
316            "cheese/english/stilton.ij",  // .ij    91
317            "fruit/citrus/orange.ij",     // .ij   173
318            "fruit.zip/citrus/orange.ij", // .ij   173
319            "cheese/french/brie.ij",      // .ij  8681
320            "fruit/citrus/lemon.xy",      // .xy   108
321            "fruit.zip/citrus/lemon.xy",  // .xy   108
322            "fruit/other/apple.xy",       // .xy   467
323            "fruit.zip/other/apple.xy",   // .xy   467
324            "fruit/other/cherry.xy",      // .xy   795
325            "fruit.zip/other/cherry.xy",  // .xy   795
326            "cheese/english/cheddar.xy",  // .xy  6363
327            "cheese/french/bleu.xy",      // .xy 20122
328        ]);
329        let files = create_files();
330        let paths = sort_files(files, vec![OrderKind::Ext, OrderKind::Size(DirKind::Asc), OrderKind::Dir, OrderKind::Name]);
331        assert_eq!(expected, paths);
332    }
333
334    #[test]
335    fn test_files_are_sorted_by_size_asc() {
336        let expected = string_from(vec![
337            "cheese/",                    //     0
338            "cheese/english/",            //     0
339            "cheese/french/",             //     0
340            "fruit/",                     //     0
341            "fruit/citrus/",              //     0
342            "fruit/other/",               //     0
343            "fruit.zip/",                 //     0
344            "fruit.zip/citrus/",          //     0
345            "fruit.zip/other/",           //     0
346            "fruit/other/date.ij",        //     6
347            "fruit.zip/other/date.ij",    //     6
348            "index",                      //    42
349            "fruit/other/banana.ij",      //    73
350            "fruit.zip/other/banana.ij",  //    73
351            "cheese/english/stilton.ij",  //    91
352            "cheese/index",               //    99
353            "fruit/citrus/lemon.xy",      //   108
354            "fruit.zip/citrus/lemon.xy",  //   108
355            "fruit/citrus/orange.ij",     //   173
356            "fruit.zip/citrus/orange.ij", //   173
357            "fruit/other/apple.xy",       //   467
358            "fruit.zip/other/apple.xy",   //   467
359            "fruit/other/cherry.xy",      //   795
360            "fruit.zip/other/cherry.xy",  //   795
361            "cheese/english/cheddar.xy",  //  6363
362            "cheese/french/brie.ij",      //  8681
363            "cheese/french/bleu.xy",      // 20122
364        ]);
365        let files = create_files();
366        let paths = sort_files(files, vec![OrderKind::Size(DirKind::Asc), OrderKind::Dir, OrderKind::Name]);
367        assert_eq!(expected, paths);
368    }
369
370    #[test]
371    fn test_files_are_sorted_by_size_desc() {
372        let expected = string_from(vec![
373            "cheese/french/bleu.xy",      // 20122
374            "cheese/french/brie.ij",      //  8681
375            "cheese/english/cheddar.xy",  //  6363
376            "fruit/other/cherry.xy",      //   795
377            "fruit.zip/other/cherry.xy",  //   795
378            "fruit/other/apple.xy",       //   467
379            "fruit.zip/other/apple.xy",   //   467
380            "fruit/citrus/orange.ij",     //   173
381            "fruit.zip/citrus/orange.ij", //   173
382            "fruit/citrus/lemon.xy",      //   108
383            "fruit.zip/citrus/lemon.xy",  //   108
384            "cheese/index",               //    99
385            "cheese/english/stilton.ij",  //    91
386            "fruit/other/banana.ij",      //    73
387            "fruit.zip/other/banana.ij",  //    73
388            "index",                      //    42
389            "fruit/other/date.ij",        //     6
390            "fruit.zip/other/date.ij",    //     6
391            "cheese/",                    //     0
392            "cheese/english/",            //     0
393            "cheese/french/",             //     0
394            "fruit/",                     //     0
395            "fruit/citrus/",              //     0
396            "fruit/other/",               //     0
397            "fruit.zip/",                 //     0
398            "fruit.zip/citrus/",          //     0
399            "fruit.zip/other/",           //     0
400        ]);
401        let files = create_files();
402        let paths = sort_files(files, vec![OrderKind::Size(DirKind::Desc), OrderKind::Dir, OrderKind::Name]);
403        assert_eq!(expected, paths);
404    }
405
406    #[test]
407    fn test_files_are_sorted_by_time_asc() {
408        let expected = string_from(vec![
409            "cheese/english/cheddar.xy",  // 2022-09-10 04:03:38
410            "cheese/french/",             // 2022-10-06 23:36:27
411            "cheese/french/bleu.xy",      // 2022-10-28 05:49:07
412            "fruit/other/date.ij",        // 2022-11-25 14:42:53
413            "fruit.zip/other/date.ij",    // 2022-11-25 14:42:53
414            "fruit/other/cherry.xy",      // 2022-12-19 02:12:38
415            "fruit.zip/other/cherry.xy",  // 2022-12-19 02:12:38
416            "cheese/index",               // 2022-12-23 21:43:18
417            "cheese/",                    // 2023-01-16 11:25:51
418            "cheese/french/brie.ij",      // 2023-02-28 11:01:59
419            "fruit/citrus/orange.ij",     // 2023-03-27 05:45:33
420            "fruit.zip/citrus/orange.ij", // 2023-03-27 05:45:33
421            "cheese/english/stilton.ij",  // 2023-04-07 03:08:29
422            "fruit/",                     // 2023-04-18 17:09:31
423            "fruit.zip/",                 // 2023-04-18 17:09:31
424            "fruit/other/apple.xy",       // 2023-05-10 11:24:22
425            "fruit.zip/other/apple.xy",   // 2023-05-10 11:24:22
426            "fruit/other/banana.ij",      // 2023-06-10 02:06:38
427            "fruit.zip/other/banana.ij",  // 2023-06-10 02:06:38
428            "fruit/citrus/lemon.xy",      // 2023-06-29 21:16:08
429            "fruit.zip/citrus/lemon.xy",  // 2023-06-29 21:16:08
430            "index",                      // 2023-07-05 13:05:42
431            "fruit/citrus/",              // 2023-07-13 08:58:00
432            "fruit.zip/citrus/",          // 2023-07-13 08:58:00
433            "cheese/english/",            // 2023-08-04 10:57:26
434            "fruit/other/",               // 2023-08-14 01:53:00
435            "fruit.zip/other/",           // 2023-08-14 01:53:00
436        ]);
437        let files = create_files();
438        let paths = sort_files(files, vec![OrderKind::Time(DirKind::Asc), OrderKind::Dir, OrderKind::Name]);
439        assert_eq!(expected, paths);
440    }
441
442    #[test]
443    fn test_files_are_sorted_by_time_desc() {
444        let expected = string_from(vec![
445            "fruit/other/",               // 2023-08-14 01:53:00
446            "fruit.zip/other/",           // 2023-08-14 01:53:00
447            "cheese/english/",            // 2023-08-04 10:57:26
448            "fruit/citrus/",              // 2023-07-13 08:58:00
449            "fruit.zip/citrus/",          // 2023-07-13 08:58:00
450            "index",                      // 2023-07-05 13:05:42
451            "fruit/citrus/lemon.xy",      // 2023-06-29 21:16:08
452            "fruit.zip/citrus/lemon.xy",  // 2023-06-29 21:16:08
453            "fruit/other/banana.ij",      // 2023-06-10 02:06:38
454            "fruit.zip/other/banana.ij",  // 2023-06-10 02:06:38
455            "fruit/other/apple.xy",       // 2023-05-10 11:24:22
456            "fruit.zip/other/apple.xy",   // 2023-05-10 11:24:22
457            "fruit/",                     // 2023-04-18 17:09:31
458            "fruit.zip/",                 // 2023-04-18 17:09:31
459            "cheese/english/stilton.ij",  // 2023-04-07 03:08:29
460            "fruit/citrus/orange.ij",     // 2023-03-27 05:45:33
461            "fruit.zip/citrus/orange.ij", // 2023-03-27 05:45:33
462            "cheese/french/brie.ij",      // 2023-02-28 11:01:59
463            "cheese/",                    // 2023-01-16 11:25:51
464            "cheese/index",               // 2022-12-23 21:43:18
465            "fruit/other/cherry.xy",      // 2022-12-19 02:12:38
466            "fruit.zip/other/cherry.xy",  // 2022-12-19 02:12:38
467            "fruit/other/date.ij",        // 2022-11-25 14:42:53
468            "fruit.zip/other/date.ij",    // 2022-11-25 14:42:53
469            "cheese/french/bleu.xy",      // 2022-10-28 05:49:07
470            "cheese/french/",             // 2022-10-06 23:36:27
471            "cheese/english/cheddar.xy",  // 2022-09-10 04:03:38
472        ]);
473        let files = create_files();
474        let paths = sort_files(files, vec![OrderKind::Time(DirKind::Desc), OrderKind::Dir, OrderKind::Name]);
475        assert_eq!(expected, paths);
476    }
477
478    #[test]
479    fn test_files_are_sorted_by_group() {
480        let expected = string_from(vec![
481            "cheese/",                    // dir
482            "cheese/english/",            // dir
483            "cheese/french/",             // dir
484            "fruit/",                     // dir
485            "fruit/citrus/",              // dir
486            "fruit/other/",               // dir
487            "fruit.zip/",                 // dir
488            "fruit.zip/citrus/",          // dir
489            "fruit.zip/other/",           // dir
490            "index",                      // file
491            "cheese/index",               // file
492            "cheese/english/cheddar.xy",  // file
493            "cheese/english/stilton.ij",  // file
494            "cheese/french/bleu.xy",      // file
495            "cheese/french/brie.ij",      // file
496            "fruit/citrus/lemon.xy",      // file
497            "fruit/citrus/orange.ij",     // file
498            "fruit/other/apple.xy",       // file
499            "fruit/other/banana.ij",      // file
500            "fruit/other/cherry.xy",      // file
501            "fruit/other/date.ij",        // file
502            "fruit.zip/citrus/lemon.xy",  // file
503            "fruit.zip/citrus/orange.ij", // file
504            "fruit.zip/other/apple.xy",   // file
505            "fruit.zip/other/banana.ij",  // file
506            "fruit.zip/other/cherry.xy",  // file
507            "fruit.zip/other/date.ij",    // file
508        ]);
509        let files = create_files();
510        let paths = sort_files(files, vec![OrderKind::Group, OrderKind::Dir, OrderKind::Name]);
511        assert_eq!(expected, paths);
512    }
513
514    #[test]
515    fn test_files_are_sorted_by_case_insensitive_path() {
516        let expected = string_from(vec![
517            "AAA.DD",
518            "AAA.dd",
519            "aaa.DD",
520            "aaa.dd",
521            "AAA.EE",
522            "AAA.ee",
523            "aaa.EE",
524            "aaa.ee",
525            "AAA.FF",
526            "AAA.ff",
527            "aaa.FF",
528            "aaa.ff",
529            "BBB.DD",
530            "BBB.dd",
531            "bbb.DD",
532            "bbb.dd",
533            "BBB.EE",
534            "BBB.ee",
535            "bbb.EE",
536            "bbb.ee",
537            "BBB.FF",
538            "BBB.ff",
539            "bbb.FF",
540            "bbb.ff",
541            "CCC.DD",
542            "CCC.dd",
543            "ccc.DD",
544            "ccc.dd",
545            "CCC.EE",
546            "CCC.ee",
547            "ccc.EE",
548            "ccc.ee",
549            "CCC.FF",
550            "CCC.ff",
551            "ccc.FF",
552            "ccc.ff",
553        ]);
554        let files = create_case();
555        let paths = sort_files(files, vec![OrderKind::Dir, OrderKind::Name]);
556        assert_eq!(expected, paths);
557    }
558
559    #[test]
560    fn test_files_are_sorted_by_case_insensitive_ext() {
561        let expected = string_from(vec![
562            "AAA.DD",
563            "aaa.DD",
564            "BBB.DD",
565            "bbb.DD",
566            "CCC.DD",
567            "ccc.DD",
568            "AAA.dd",
569            "aaa.dd",
570            "BBB.dd",
571            "bbb.dd",
572            "CCC.dd",
573            "ccc.dd",
574            "AAA.EE",
575            "aaa.EE",
576            "BBB.EE",
577            "bbb.EE",
578            "CCC.EE",
579            "ccc.EE",
580            "AAA.ee",
581            "aaa.ee",
582            "BBB.ee",
583            "bbb.ee",
584            "CCC.ee",
585            "ccc.ee",
586            "AAA.FF",
587            "aaa.FF",
588            "BBB.FF",
589            "bbb.FF",
590            "CCC.FF",
591            "ccc.FF",
592            "AAA.ff",
593            "aaa.ff",
594            "BBB.ff",
595            "bbb.ff",
596            "CCC.ff",
597            "ccc.ff",
598        ]);
599        let files = create_case();
600        let paths = sort_files(files, vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name]);
601        assert_eq!(expected, paths);
602    }
603
604    #[test]
605    fn test_files_are_sorted_before_dirs() {
606        let expected = string_from(vec![
607            "xyz",
608            "abc/",
609            "abc/xyz",
610            "abc/def/",
611            "abc/def/xyz",
612        ]);
613        let files = create_folder();
614        let paths = sort_files(files, vec![OrderKind::Dir, OrderKind::Name]);
615        assert_eq!(expected, paths);
616    }
617
618    #[test]
619    fn test_numeric_files_are_sorted_by_dir() {
620        let expected = string_from(vec![
621            "9/",
622            "9/9/",
623            "9/9/9/",
624            "9/9/10/",
625            "9/10/",
626            "9/10/9/",
627            "9/10/10/",
628            "10/",
629            "10/9/",
630            "10/9/9/",
631            "10/9/10/",
632            "10/10/",
633            "10/10/9/",
634            "10/10/10/",
635        ]);
636        let files = create_numeric1();
637        let paths = sort_files(files, vec![OrderKind::Dir, OrderKind::Name]);
638        assert_eq!(expected, paths);
639    }
640
641    #[test]
642    fn test_numeric_files_are_sorted_by_name() {
643        let expected = string_from(vec![
644            "9",
645            "9_9",
646            "9_9_9",
647            "9_9_10",
648            "9_10",
649            "9_10_9",
650            "9_10_10",
651            "10",
652            "10_9",
653            "10_9_9",
654            "10_9_10",
655            "10_10",
656            "10_10_9",
657            "10_10_10",
658        ]);
659        let files = create_numeric2();
660        let paths = sort_files(files, vec![OrderKind::Dir, OrderKind::Name]);
661        assert_eq!(expected, paths);
662    }
663
664    #[test]
665    fn test_numeric_files_are_sorted_by_ext() {
666        let expected = string_from(vec![
667            "file.9",
668            "file.9_9",
669            "file.9_9_9",
670            "file.9_9_10",
671            "file.9_10",
672            "file.9_10_9",
673            "file.9_10_10",
674            "file.10",
675            "file.10_9",
676            "file.10_9_9",
677            "file.10_9_10",
678            "file.10_10",
679            "file.10_10_9",
680            "file.10_10_10",
681        ]);
682        let files = create_numeric3();
683        let paths = sort_files(files, vec![OrderKind::Ext, OrderKind::Dir, OrderKind::Name]);
684        assert_eq!(expected, paths);
685    }
686
687    fn create_files() -> Vec<File> {
688        vec![
689            create_file(FileKind::Other, "2023-07-05T13:05:42Z", 42, "index"),
690            create_file(FileKind::Dir, "2023-01-16T11:25:51Z", 0, "cheese"),
691            create_file(FileKind::Other, "2022-12-23T21:43:18Z", 99, "cheese/index"),
692            create_file(FileKind::Dir, "2023-08-04T10:57:26Z", 0, "cheese/english"),
693            create_file(FileKind::Other, "2022-09-10T04:03:38Z", 6363, "cheese/english/cheddar.xy"),
694            create_file(FileKind::Other, "2023-04-07T03:08:29Z", 91, "cheese/english/stilton.ij"),
695            create_file(FileKind::Dir, "2022-10-06T23:36:27Z", 0, "cheese/french"),
696            create_file(FileKind::Other, "2022-10-28T05:49:07Z", 20122, "cheese/french/bleu.xy"),
697            create_file(FileKind::Other, "2023-02-28T11:01:59Z", 8681, "cheese/french/brie.ij"),
698            create_file(FileKind::Dir, "2023-04-18T17:09:31Z", 0, "fruit"),
699            create_file(FileKind::Dir, "2023-07-13T08:58:00Z", 0, "fruit/citrus"),
700            create_file(FileKind::Other, "2023-06-29T21:16:08Z", 108, "fruit/citrus/lemon.xy"),
701            create_file(FileKind::Other, "2023-03-27T05:45:33Z", 173, "fruit/citrus/orange.ij"),
702            create_file(FileKind::Dir, "2023-08-14T01:53:00Z", 0, "fruit/other"),
703            create_file(FileKind::Other, "2023-05-10T11:24:22Z", 467, "fruit/other/apple.xy"),
704            create_file(FileKind::Other, "2023-06-10T02:06:38Z", 73, "fruit/other/banana.ij"),
705            create_file(FileKind::Other, "2022-12-19T02:12:38Z", 795, "fruit/other/cherry.xy"),
706            create_file(FileKind::Other, "2022-11-25T14:42:53Z", 6, "fruit/other/date.ij"),
707            create_file(FileKind::Dir, "2023-04-18T17:09:31Z", 0, "fruit.zip"),
708            create_file(FileKind::Dir, "2023-07-13T08:58:00Z", 0, "fruit.zip/citrus"),
709            create_file(FileKind::Other, "2023-06-29T21:16:08Z", 108, "fruit.zip/citrus/lemon.xy"),
710            create_file(FileKind::Other, "2023-03-27T05:45:33Z", 173, "fruit.zip/citrus/orange.ij"),
711            create_file(FileKind::Dir, "2023-08-14T01:53:00Z", 0, "fruit.zip/other"),
712            create_file(FileKind::Other, "2023-05-10T11:24:22Z", 467, "fruit.zip/other/apple.xy"),
713            create_file(FileKind::Other, "2023-06-10T02:06:38Z", 73, "fruit.zip/other/banana.ij"),
714            create_file(FileKind::Other, "2022-12-19T02:12:38Z", 795, "fruit.zip/other/cherry.xy"),
715            create_file(FileKind::Other, "2022-11-25T14:42:53Z", 6, "fruit.zip/other/date.ij"),
716        ]
717    }
718
719    fn create_case() -> Vec<File> {
720        vec![
721            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "aaa.dd"),
722            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "aaa.DD"),
723            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "aaa.ee"),
724            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "aaa.EE"),
725            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "aaa.ff"),
726            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "aaa.FF"),
727            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "AAA.dd"),
728            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "AAA.DD"),
729            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "AAA.ee"),
730            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "AAA.EE"),
731            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "AAA.ff"),
732            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "AAA.FF"),
733            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "bbb.dd"),
734            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "bbb.DD"),
735            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "bbb.ee"),
736            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "bbb.EE"),
737            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "bbb.ff"),
738            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "bbb.FF"),
739            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "BBB.dd"),
740            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "BBB.DD"),
741            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "BBB.ee"),
742            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "BBB.EE"),
743            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "BBB.ff"),
744            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "BBB.FF"),
745            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "ccc.dd"),
746            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "ccc.DD"),
747            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "ccc.ee"),
748            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "ccc.EE"),
749            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "ccc.ff"),
750            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "ccc.FF"),
751            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "CCC.dd"),
752            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "CCC.DD"),
753            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "CCC.ee"),
754            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "CCC.EE"),
755            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "CCC.ff"),
756            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "CCC.FF"),
757        ]
758    }
759
760    fn create_folder() -> Vec<File> {
761        vec![
762            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "abc"),
763            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "abc/def"),
764            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "abc/def/xyz"),
765            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "abc/xyz"),
766            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "xyz"),
767        ]
768    }
769
770    fn create_numeric1() -> Vec<File> {
771        vec![
772            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "10"),
773            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "10/10"),
774            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "10/10/10"),
775            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "10/10/9"),
776            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "10/9"),
777            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "10/9/10"),
778            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "10/9/9"),
779            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "9"),
780            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "9/10"),
781            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "9/10/10"),
782            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "9/10/9"),
783            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "9/9"),
784            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "9/9/10"),
785            create_file(FileKind::Dir, "1970-01-01T00:00:00Z", 0, "9/9/9"),
786        ]
787    }
788
789    fn create_numeric2() -> Vec<File> {
790        vec![
791            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "10"),
792            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "10_10"),
793            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "10_10_10"),
794            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "10_10_9"),
795            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "10_9"),
796            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "10_9_10"),
797            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "10_9_9"),
798            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "9"),
799            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "9_10"),
800            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "9_10_10"),
801            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "9_10_9"),
802            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "9_9"),
803            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "9_9_10"),
804            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "9_9_9"),
805        ]
806    }
807
808    fn create_numeric3() -> Vec<File> {
809        vec![
810            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.10"),
811            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.10_10"),
812            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.10_10_10"),
813            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.10_10_9"),
814            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.10_9"),
815            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.10_9_10"),
816            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.10_9_9"),
817            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.9"),
818            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.9_10"),
819            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.9_10_10"),
820            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.9_10_9"),
821            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.9_9"),
822            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.9_9_10"),
823            create_file(FileKind::Other, "1970-01-01T00:00:00Z", 0, "file.9_9_9"),
824        ]
825    }
826
827    fn create_file(
828        file_type: FileKind,
829        file_time: &str,
830        file_size: u64,
831        rel_path: &str,
832    ) -> File {
833        let abs_path = PathBuf::from("/root/").join(rel_path);
834        let rel_path = PathBuf::from(rel_path);
835        let file_depth = rel_path.components().count();
836        let file_time = DateTime::parse_from_rfc3339(file_time).unwrap().with_timezone(&Utc);
837        if file_type == FileKind::Dir {
838            let file_name = String::from("");
839            let file_ext = String::from("");
840            File::new(abs_path, rel_path, file_depth, None, file_name, file_ext, file_type)
841                .with_size(file_size)
842                .with_time(file_time)
843        } else {
844            let abs_dir = PathBuf::from(abs_path.parent().unwrap());
845            let rel_dir = PathBuf::from(rel_path.parent().unwrap());
846            let file_name = String::from(abs_path.file_name().and_then(OsStr::to_str).unwrap());
847            let file_ext = String::from(abs_path.extension().and_then(OsStr::to_str).unwrap_or_default());
848            File::new(abs_dir, rel_dir, file_depth, None, file_name, file_ext, file_type)
849                .with_size(file_size)
850                .with_time(file_time)
851        }
852    }
853
854    fn sort_files(mut files: Vec<File>, order: Vec<OrderKind>) -> Vec<String> {
855        let config = create_config(order);
856        let sorter = Sorter::new(&config);
857        sorter.sort_files(&mut files);
858        files.into_iter().map(format_file).collect()
859    }
860
861    fn format_file(file: File) -> String {
862        file.rel_dir
863            .join(file.file_name)
864            .to_str()
865            .map(|x| String::from(x))
866            .map(|x| x.replace(MAIN_SEPARATOR_STR, "/"))
867            .unwrap()
868    }
869
870    fn create_config(order: Vec<OrderKind>) -> Config {
871        Config::default().with_sort_order(order)
872    }
873
874    fn string_from(values: Vec<&str>) -> Vec<String> {
875        values.into_iter().map(String::from).collect()
876    }
877}