Skip to main content

ls_key/
list.rs

1use std::path::{Path, PathBuf};
2use std::fs::metadata;
3use std::borrow::Cow;
4use walkdir::{DirEntry, WalkDir, Error as WalkDirError};
5use ansi_term::Colour;
6
7#[derive(Debug, Clone, PartialEq)]
8pub enum FileType {
9    File,
10    Dir,
11}
12
13
14#[derive(Debug, Clone, PartialEq)]
15pub struct Entry {
16    pub path: PathBuf,
17    pub file_type: FileType,
18    pub key: Option<usize>
19}
20
21// Can't alphabetyize PathBuf case insensitively, so we convert to String then back again.
22pub fn alphabetize_paths_vec(paths_vec: Vec<PathBuf>) -> Vec<PathBuf> {
23    let mut strings_vec: Vec<String> = vec![];
24    let alphabetized_paths_vec: Vec<PathBuf> = {
25        let mut _alphabetized_paths_vec: Vec<PathBuf> = vec![];
26        for path in paths_vec.iter() {
27            let path = path.clone();
28            let path_string = path.into_os_string().into_string().unwrap();
29            strings_vec.push(path_string);
30        }
31
32        strings_vec.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
33
34        for string in strings_vec.iter() {
35            _alphabetized_paths_vec.push(PathBuf::from(string));
36        }
37        _alphabetized_paths_vec
38    };
39
40    alphabetized_paths_vec
41}
42
43pub fn alphabetize_entry(a: &Entry, b: &Entry) -> std::cmp::Ordering {
44    let paths_vec: Vec<PathBuf> = vec![a.path.clone(), b.path.clone()];
45    let paths_vec = alphabetize_paths_vec(paths_vec.clone());
46
47    if &a.path == &b.path {
48        std::cmp::Ordering::Equal
49    } else if paths_vec.iter().nth(0).unwrap() == &b.path {
50        std::cmp::Ordering::Greater
51    } else {
52         std::cmp::Ordering::Less
53    }
54}
55
56
57#[cfg(test)]
58mod test_entries_sort {
59    use super::*;
60    #[test]
61    fn sort_entries() {
62        let a = Entry {
63            path: PathBuf::from("/a"),
64            file_type: FileType::File,
65            key: None,
66        };
67
68        let b = Entry {
69            path: PathBuf::from("/B"),
70            file_type: FileType::File,
71            key: None,
72        };
73
74
75        let c = Entry {
76            path: PathBuf::from("/c"),
77            file_type: FileType::File,
78            key: None,
79        };
80
81        let mut entries = vec![b.clone(), a.clone(), c.clone()];
82
83        entries.sort_by(|a, b| alphabetize_entry(a, b));
84
85        assert_eq!(
86            entries,
87            vec![a, b, c]
88        )
89    }
90}
91
92#[derive(Debug, Clone, PartialEq, Default)]
93pub struct List {
94    pub files: Vec<Entry>,
95    pub parent_path: PathBuf,
96    pub path_history: Vec<PathBuf>,
97    pub filter: Option<Vec<usize>>
98}
99
100impl List {
101    pub fn new<P: AsRef<Path>>(path: P) -> Self {
102        let mut list: List = Default::default();
103        list.parent_path = path.as_ref().to_path_buf();
104        list.path_history.push(list.parent_path.clone());
105
106        list
107    }
108
109    // Update due to going into a new directory.
110    pub fn update<P: AsRef<Path>>(mut self, path: P) -> Self {
111        let old_path_history = self.path_history;
112        let old_parent_path = self.parent_path;
113        let p = path.as_ref().to_str().unwrap();
114        let np: String = basename(p, '/').into_owned();
115        let basename = Path::new(&np);
116        let list: List = Default::default();
117        self = list;
118        self.path_history = old_path_history;
119        self.parent_path = old_parent_path.join(basename);
120        self.path_history.push(self.parent_path.clone());
121
122        self
123    }
124
125    pub fn list_skip_hidden(mut self) -> Result<Self, std::io::Error> {
126        let list: List = Default::default();
127        let walker = WalkDir::new(&self.parent_path).max_depth(1).into_iter();
128        for entry in walker.filter_entry(|e| !list.clone().skip(e)) {
129                self = list_maker(entry, self)?;
130        }
131
132        Ok(self)
133    }
134
135    pub fn list_include_hidden(mut self) -> Result<Self, std::io::Error> {
136        let mut _list: List = Default::default();
137        for entry in WalkDir::new(&self.parent_path).max_depth(1) {
138                self = list_maker(entry, self)?;
139        }
140
141        Ok(self)
142    }
143
144    fn replace_shortest_path(mut self, pathbuf: PathBuf) -> Self {
145        let path = pathbuf.into_boxed_path();
146        let depth_from_root_dir = path.iter().count();
147
148        let parent_dir_count = self.parent_path.iter().count();
149        if depth_from_root_dir < parent_dir_count {
150            self.parent_path = path.to_path_buf();
151        }
152
153        self
154    }
155
156    pub fn order_and_sort_list(&mut self, sort: bool, filter: bool) -> Vec<Entry> {
157        let mut all_files = self.files.clone();
158        let previous_path = self.path_history.iter().last().unwrap();
159        if sort {
160            all_files.sort_by(|a, b| alphabetize_entry(a, b));
161        }
162        all_files.insert(
163            0,
164            Entry {
165                path: previous_path.to_path_buf(),
166                file_type: FileType::Dir,
167                key: None
168            }
169        );
170
171        // Add keys
172        let mut n = 0;
173        let mut final_all_files: Vec<Entry> = vec![];
174        for mut x in all_files.into_iter() {
175            x.key = Some(n);
176            final_all_files.push(x.clone());
177            n += 1;
178        }
179
180        //let few_ms = std::time::Duration::from_millis(1000);
181        //std::thread::sleep(few_ms);
182
183        // Filter entries
184        if filter {
185
186           fn filter_thing(x: &Entry, filter: &Option<Vec<usize>>) -> bool {
187               if let Some(fl) = filter {
188                   if let Some(key) = x.key {
189                       fl.iter().find(|n| &key == *n).is_some()
190                   } else {
191                       true
192                   }
193               } else {
194                   true
195               }
196           }
197
198           let find_in_range = final_all_files.clone().into_iter().filter(|x|
199               // !filter.as_ref().unwrap().iter().find(|n| &x.key.unwrap() == *n).is_some()
200               filter_thing(x, &self.filter)
201           );
202
203            let files: Vec<Entry> = find_in_range.collect();
204
205            let entry_test = Entry {
206                path: PathBuf::from("entry_test"),
207                file_type: FileType::File,
208                key: Some(3)
209
210            };
211
212            if files != vec![entry_test] {
213                //let few_ms = std::time::Duration::from_millis(1000);
214                //std::thread::sleep(few_ms);
215            } else {
216                //let few_ms = std::time::Duration::from_millis(1000);
217                //std::thread::sleep(few_ms);
218            }
219            final_all_files = files;
220        } else {
221        }
222
223        final_all_files
224    }
225
226    pub fn get_file_by_key(&self, key: usize, sort: bool) -> Option<PathBuf> {
227        let all_files = order_and_sort_list(&self, sort);
228        let all_files = all_files.iter();
229
230        for entry in all_files.clone() {
231            //println!("{} [{}]", entry.display(), n);
232            //p
233            let n = entry.key.unwrap();
234            //let parent_file_name = file_or_dir_name(&self.parent_path);
235            if n == key {
236                let path = entry.path.to_path_buf();
237                return self.clone().full_entry_path(path);
238            }
239        }
240
241        return None
242    }
243
244    // Caution ahead, side-effect: set parent dir field.
245    fn skip(self, entry: &DirEntry) -> bool {
246
247        //if is_parent_dir(entry) {
248        //    return true
249        //}
250        entry.file_name()
251             .to_str()
252             .map(|s| s.starts_with("."))
253             .unwrap_or(false)
254    }
255
256    fn full_entry_path(self, path: PathBuf) -> Option<PathBuf> {
257        let p = self.parent_path;
258        Some(p.join(path.as_path()))
259    }
260}
261
262fn basename<'a>(path: &'a str, sep: char) -> Cow<'a, str> {
263    let mut pieces = path.rsplit(sep);
264    match pieces.next() {
265        Some(p) => p.into(),
266        None => path.into(),
267    }
268}
269
270fn file_or_dir_name(path: &PathBuf) -> Option<PathBuf> {
271    let path = path.as_path();
272    let path = path.file_name();
273
274    if let Some(p) = path {
275        Some(Path::new(&p).to_path_buf())
276    } else {
277        None
278    }
279}
280
281fn list_maker(entry: Result<DirEntry, WalkDirError>, mut list: List) -> Result<List, std::io::Error> {
282    match entry {
283        Ok(entry) => {
284            let entry = entry.path();
285            let parent_file_name = file_or_dir_name(&list.parent_path);
286
287                match metadata(entry) {
288                    Ok(md) => {
289                       let path = entry.to_path_buf();
290                       let short_path = file_or_dir_name(&path);
291                       if md.is_file() {
292                           list = list.replace_shortest_path(path);
293                           if let Some(p) = short_path {
294
295                               if Some(p.clone()) != parent_file_name {
296                                   list.files.push(
297                                       Entry {
298                                           path: p,
299                                           file_type: FileType::File,
300                                           key: None
301                                       }
302                                    );
303                               }
304                           }
305                       } else if md.is_dir() {
306                           list = list.replace_shortest_path(path);
307                           if let Some(p) = short_path {
308                               if Some(p.clone()) != parent_file_name {
309                                   list.files.push(
310                                       Entry {
311                                           path: p,
312                                           file_type: FileType::Dir,
313                                           key: None
314                                       }
315                                    );
316                               }
317                           }
318                       }
319                    },
320                    Err(_) => ()
321                }
322        },
323        Err(_) => ()
324    }
325
326    Ok(list)
327}
328
329pub fn is_file<P: AsRef<Path>>(path: P) -> bool {
330    metadata(path).unwrap().is_file()
331}
332
333pub fn is_dir<P: AsRef<Path>>(path: P) -> bool {
334    metadata(path).unwrap().is_dir()
335}
336
337//pub fn go_back_compoenent_display() {
338//    let _previous_path = previous_path.as_path();
339//    let components = _previous_path.components();
340//    let component = components.last().unwrap();
341//    let last = component.as_os_str();
342//}
343
344pub fn key_entries(entries: Vec<Entry>) -> Vec<String> {
345    let mut entries_keyed: Vec<String> = vec![];
346    for entry in entries.clone() {
347        let n = entry.key.unwrap();
348        let entry = match entry.file_type {
349            FileType::File => {
350                let entry = entry.path.to_str().unwrap();
351                let entry = format!(r#"{} [{}]"#, entry, n);
352                Colour::White.bold().paint(entry).to_string()
353            },
354            FileType::Dir => {
355                if n == 0 {
356                    let _path = entry.path.clone();
357                    let _path = _path.as_path();
358                    let os_str = _path.iter().last().unwrap();
359                    let entry_str = os_str.to_str().unwrap();
360                    if entry_str != "/" {
361                          let entry_string = format!("../{}", entry_str);
362                          Colour::Blue.bold().paint(entry_string).to_string()
363                    } else {
364                        "/".to_string()
365                    }
366                } else {
367                    let entry_str = entry.path.to_str().unwrap();
368                    let entry = format!(r#"{} [{}]"#, entry_str, n);
369                    Colour::Blue.bold().paint(entry).to_string()
370                }
371            },
372        };
373        if entry != "/".to_string() {
374             entries_keyed.push(entry);
375        }
376    }
377
378    entries_keyed
379}
380
381pub fn order_and_sort_list(list: &List, sort: bool) -> Vec<Entry> {
382    let mut all_files = list.files.clone();
383    let previous_path = list.path_history.iter().last().unwrap();
384    if sort {
385        all_files.sort_by(|a, b| alphabetize_entry(a, b));
386        //all_files = alphabetize_paths_vec(all_files.clone());
387    }
388    all_files.insert(
389        0,
390        Entry {
391            path: previous_path.to_path_buf(),
392            file_type: FileType::Dir,
393            key: None
394        }
395    );
396
397    //all_files.iter().map(|mut x|
398    //    (0..count).for_each(|n| all_files.into_iter().nth(n).unwrap().key = Some(n))
399    //);
400    //
401
402    let mut n = 0;
403    let mut final_all_files: Vec<Entry> = vec![];
404    for mut x in all_files.into_iter() {
405        x.key = Some(n);
406        final_all_files.push(x.clone());
407        n += 1;
408    }
409
410    //(0..count).for_each(|n| (all_files.iter().nth(n).unwrap() = Some(n)));
411
412    final_all_files
413}
414
415pub fn print_list_with_keys(list: List) -> Result<(), std::io::Error> {
416    let all_files = order_and_sort_list(&list, true);
417    let mut n = 0;
418    for entry in all_files {
419        println!("{} [{}]", entry.path.display(), n);
420        n += 1;
421    }
422
423    Ok(())
424}
425
426pub mod fuzzy_score {
427    use super::Entry;
428    use fuzzy_matcher;
429    use fuzzy_matcher::FuzzyMatcher;
430    use fuzzy_matcher::skim::SkimMatcherV2;
431
432    #[derive(Debug, Clone, PartialEq)]
433    pub enum Score {
434         Files((Entry, Option<(i64, Vec<usize>)>)),
435    }
436
437    impl Score {
438        pub fn score(&self) -> (Entry, Option<(i64, Vec<usize>)>) {
439            match self.clone() {
440                Score::Files(score) => score,
441            }
442        }
443    }
444
445    #[derive(Debug, Clone, PartialEq)]
446    pub struct Scores {
447        pub files: Vec<Score>,
448    }
449
450    pub fn score(compare_to: &str, guess: &str) -> Option<(i64, Vec<usize>)> {
451        let matcher = SkimMatcherV2::default();
452        matcher.fuzzy_indices(compare_to, guess)
453    }
454
455    pub fn order(a: &Score, b: &Score) -> std::cmp::Ordering {
456        let a_score = a.score().0.path;
457        let b_score = b.score().0.path;
458
459        if a_score == b_score {
460            std::cmp::Ordering::Equal
461        } else if a_score > b_score {
462            std::cmp::Ordering::Less
463        } else {
464            std::cmp::Ordering::Greater
465        }
466    }
467}
468
469#[cfg(test)]
470mod fuzzy_tests {
471    use std::path::PathBuf;
472    use super::*;
473    #[test]
474    #[ignore]//docker
475    fn score() {
476        let res = super::fuzzy_score::score("abc", "abx");
477        assert_eq!(res, None);
478        let (score, indices) = super::fuzzy_score::score("axbycz", "xyz").unwrap();
479        assert_eq!(indices, [1, 3, 5]);
480        assert_eq!(score, 39);
481        let (score, indices) = super::fuzzy_score::score("axbycz", "abc").unwrap();
482        assert_eq!(indices, [0, 2, 4]);
483        assert_eq!(score, 55);
484        let (score, indices) = super::fuzzy_score::score("unignore_play_test", "uigp").unwrap();
485        assert_eq!(indices, [0, 2, 3, 9]);
486        assert_eq!(score, 78);
487    }
488
489    #[test]
490    #[ignore]//docker
491    fn sort_score() {
492        let guess = "xyz";
493        let file_a = "xayb";
494        let file_b = "xyazabc";
495        let file_c = "xyza";
496        let file_d = "afd";
497
498        let res_a = super::fuzzy_score::score(file_a, guess);
499        let res_b = super::fuzzy_score::score(file_b, guess);
500        let res_c = super::fuzzy_score::score(file_c, guess);
501        let res_d = super::fuzzy_score::score(file_d, guess);
502
503        let mut scores = super::fuzzy_score::Scores {
504            files: vec![
505                     super::fuzzy_score::Score::Files(
506                         (
507                             Entry {
508                                 path: PathBuf::from(file_a),
509                                 file_type: FileType::File,
510                                 key: None
511                             },
512                             res_a.clone()
513                         )
514                     ),
515                     super::fuzzy_score::Score::Files(
516                         (
517                             Entry {
518                                 path: PathBuf::from(file_b),
519                                 file_type: FileType::File,
520                                 key: None
521                             },
522                             res_b.clone()
523                         )
524                     ),
525                     super::fuzzy_score::Score::Files(
526                         (
527                             Entry {
528                                 path: PathBuf::from(file_c),
529                                 file_type: FileType::File,
530                                 key: None
531                             },
532                             res_c.clone()
533                         )
534                     ),
535                     super::fuzzy_score::Score::Files(
536                         (
537                             Entry {
538                                 path: PathBuf::from(file_d),
539                                 file_type: FileType::File,
540                                 key: None
541                             },
542                             res_d.clone()
543                         )
544                     ),
545                 ],
546        };
547
548        let pre_sort = scores.clone();
549        scores.files.sort_by(|a, b| fuzzy_score::order(a, b));
550        let post_sort = scores.clone();
551        assert_ne!(
552            pre_sort,
553            post_sort
554        );
555        //scores.dirs.sort_by(|a, b| fuzzy_score::order(a, b));
556
557        assert_ne!(
558            pre_sort.files,
559            post_sort.files
560        );
561
562        assert_eq!(
563             scores.files.iter().count(),
564             4
565        );
566
567        assert_eq!(
568            scores.files.iter().nth(0).unwrap().score().0.path,
569            PathBuf::from("xyza")
570        );
571
572        assert_eq!(
573            scores.files.iter().nth(1).unwrap().score().0.path,
574            PathBuf::from("xyazabc")
575        );
576
577        assert_eq!(
578            scores.files.iter().nth(2).unwrap().score().0.path,
579            PathBuf::from("xayb")
580        )
581    }
582}