path_lookup/
lib.rs

1use std::{collections::HashSet, env, fs};
2
3/// Returns all exectuabqles found in $PATH as a HashSet<String>.
4/// There is no performance penalty compared to iteration.
5pub fn get_executables() -> HashSet<String> {
6    let serialized_paths = env::var("PATH").expect("Could not read PATH variable");
7    let deserialized_paths = serialized_paths.split(":");
8    let mut results: HashSet<String> = HashSet::new();
9
10    for path in deserialized_paths {
11        if let Ok(entries) = fs::read_dir(path) {
12            for entry in entries {
13                if !entry.is_ok() {
14                    continue;
15                }
16
17                let path = entry.unwrap().path();
18                let command_name = path
19                    .file_name()
20                    .expect("Could not get file name")
21                    .to_os_string()
22                    .into_string()
23                    .expect("Could not convert file name to string");
24
25                if !path.is_file() {
26                    continue;
27                }
28
29                if is_executable::is_executable(path) {
30                    results.insert(command_name);
31                }
32            }
33        }
34    }
35
36    results
37}
38
39/// Returns an iterator of the exectuables found in $PATH.
40/// Note that it still allocates memory because it is necessary to
41/// deduplicate the results.
42pub fn iterate_executables() -> impl Iterator<Item = String> {
43    // I spent significant amount of time realizing that allocating a data structure
44    // for deduplication purposes is necessary, thus iteration without memory allocation
45    // is not possible.
46
47    // This function is functionally redundant, but it can save time by signaling
48    // that is is already the optimal iteration algorithm. It also leaves the door
49    // open for any future optimization without breaking changes.
50
51    get_executables().into_iter()
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn returns_commands_that_definitely_exist_on_the_system() {
60        let result = get_executables();
61        assert!(result.contains("ls"));
62        assert!(result.contains("mkdir"));
63        assert!(result.contains("kill"));
64        assert!(result.contains("man"));
65    }
66
67    #[test]
68    fn does_not_return_commands_that_dont_exist_in_the_system() {
69        let result = get_executables();
70        assert!(!result.contains("asdfsdfadsfasdfdasfadsfadsfadsfafafdsfasf"));
71    }
72
73    #[test]
74    fn iteration_and_hashmap_have_the_same_results() {
75        let mut results_from_iterator: HashSet<String> = HashSet::new();
76        for command in iterate_executables() {
77            results_from_iterator.insert(command);
78        }
79
80        assert_eq!(results_from_iterator, get_executables());
81    }
82}