ex_cli/
sorter.rs

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