leaper/
lib.rs

1use clap::{command, Arg, ArgMatches};
2use std::fs::read_dir;
3use std::path::{Path, PathBuf};
4
5/// Handle and return CL arguments
6pub fn args_handler() -> ArgMatches {
7    let match_results: ArgMatches = command!()
8        .about("A simple CLI tool to quickly leap to a directory")
9        .arg(Arg::new("target").required(true))
10        .arg(
11            Arg::new("up")
12                .action(clap::ArgAction::SetTrue)
13                .short('u')
14                .long("up")
15                .help("Leap upwards to Parent directories without following any subfolders"),
16        )
17        .arg(
18            Arg::new("path")
19                .action(clap::ArgAction::SetTrue)
20                .short('p')
21                .long("path")
22                .help("Return Path without leaping"),
23        )
24        .get_matches();
25
26    match_results
27}
28
29/// Return all entries for given location
30pub fn get_entries(path: &Path, is_upward: bool) -> Option<Vec<PathBuf>> {
31    dir_collect_entries(path, is_upward)
32}
33
34/// Find target and return if found
35pub fn find(mut entries: Vec<PathBuf>, target: &str, is_upward: bool) -> Option<PathBuf> {
36    let mut found = find_target(&entries, target);
37
38    while found.is_none() {
39        if let Some(unsearched_entries) = follow(&entries, is_upward) {
40            found = find_target(&unsearched_entries, target);
41            entries = unsearched_entries;
42        } else {
43            // No new entries
44            return None;
45        }
46    }
47
48    if found.is_some() {
49        return Some(found.unwrap().to_path_buf());
50    }
51
52    None
53}
54
55/// Follow directories and return unsearched entries
56pub fn follow(entries: &[PathBuf], is_upward: bool) -> Option<Vec<PathBuf>> {
57    let mut unsearched_entries: Vec<PathBuf> = Vec::new();
58
59    // Ignore directories and files from entries, already established that target was not found
60    for e in entries {
61        // Follow directories and add their elements to unsearched_entries
62        if e.is_dir() {
63            if is_upward {
64                if e.parent().is_some() {
65                    return Some(vec![e.parent().unwrap().to_path_buf()]);
66                }
67            }
68
69            if let Some(sub_entries) = get_entries(e.as_path(), is_upward) {
70                for se in sub_entries {
71                    unsearched_entries.push(se);
72                }
73            }
74        }
75    }
76
77    if unsearched_entries.is_empty() {
78        return None;
79    }
80
81    Some(unsearched_entries)
82}
83
84fn find_target(entries: &[PathBuf], target: &str) -> Option<PathBuf> {
85    for e in entries {
86        if e.to_string_lossy().ends_with(target) {
87            return Some(e.to_path_buf());
88        }
89    }
90
91    None
92}
93
94fn dir_collect_entries(mut input_dir: &Path, upward: bool) -> Option<Vec<PathBuf>> {
95    if input_dir.parent().is_none() {
96        return None;
97    }
98
99    input_dir = match upward {
100        true => input_dir.parent().unwrap(),
101        false => input_dir,
102    };
103
104    let mut entries = Vec::new();
105
106    match read_dir(input_dir) {
107        Ok(dirs) => {
108            for entry in dirs {
109                match entry {
110                    Ok(e) => {
111                        entries.push(e.path());
112                    }
113                    Err(err) => println!("{}", err),
114                }
115            }
116        }
117        Err(err) => {
118            println!("{}", err);
119        }
120    }
121
122    Some(entries)
123}