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