bbox_core/
file_search.rs

1use ignore::{types::TypesBuilder, WalkBuilder};
2use std::cmp;
3use std::path::{Path, PathBuf};
4
5#[cfg(any(unix, windows))]
6const SAME_FS_SUPPORTED: bool = true;
7
8#[cfg(not(any(unix, windows)))]
9const SAME_FS_SUPPORTED: bool = false;
10
11/// Find files with given pattern ignoring hidden directories or similar
12pub fn search<P: AsRef<Path>>(basedir: P, pattern: &str) -> Vec<PathBuf> {
13    let mut builder = TypesBuilder::new();
14    builder.add("files", pattern).expect("Invalid file pattern");
15    let types = builder
16        .select("files")
17        .build()
18        .expect("Invalid Types definition");
19    let walker = WalkBuilder::new(basedir)
20        .follow_links(true)
21        .same_file_system(SAME_FS_SUPPORTED)
22        .types(types)
23        .build();
24    let mut files = Vec::new();
25    for entry in walker.flatten() {
26        if !entry.file_type().expect("stdin not supported").is_dir() {
27            files.push(entry.path().to_path_buf());
28        }
29    }
30    files
31}
32
33/// Given a vector of paths, calculate the path
34/// that is the longest common prefix.
35pub fn longest_common_prefix(paths: &Vec<PathBuf>) -> PathBuf {
36    if paths.is_empty() {
37        return PathBuf::new();
38    }
39    let path0 = &paths[0];
40    let mut len = path0.components().count();
41    for path in paths {
42        len = cmp::min(
43            len,
44            path.components()
45                .take(len)
46                .zip(path0.components())
47                .take_while(|&(a, b)| a == b)
48                .count(),
49        );
50    }
51    let common: Vec<_> = path0
52        .components()
53        .take(len)
54        .map(|comp| comp.as_os_str())
55        .collect();
56    common.iter().collect()
57}
58
59#[cfg(test)]
60mod test {
61    use super::*;
62
63    #[test]
64    fn empty_lcp() {
65        assert_eq!(longest_common_prefix(&vec![]), PathBuf::new());
66    }
67
68    #[test]
69    fn single_lcp() {
70        assert_eq!(
71            longest_common_prefix(&vec![PathBuf::from("/a/b")]),
72            PathBuf::from("/a/b")
73        );
74    }
75
76    #[test]
77    fn no_lcp() {
78        assert_eq!(
79            longest_common_prefix(&vec![
80                PathBuf::from("/a"),
81                PathBuf::from("/b"),
82                PathBuf::from("/c")
83            ]),
84            PathBuf::from("/")
85        );
86    }
87
88    #[test]
89    fn valid_lcp() {
90        assert_eq!(
91            longest_common_prefix(&vec![
92                PathBuf::from("/a/b/a"),
93                PathBuf::from("/a/b/b"),
94                PathBuf::from("/a/b/c")
95            ]),
96            PathBuf::from("/a/b")
97        );
98    }
99
100    #[test]
101    fn valid_is_shortest_lcp() {
102        assert_eq!(
103            longest_common_prefix(&vec![
104                PathBuf::from("/a/b/a"),
105                PathBuf::from("/a/b"),
106                PathBuf::from("/a/b/c")
107            ]),
108            PathBuf::from("/a/b")
109        );
110    }
111
112    #[test]
113    fn lcp_files() {
114        assert_eq!(
115            longest_common_prefix(&vec![
116                PathBuf::from("/a/b/c/f1.x"),
117                PathBuf::from("/a/b/f2.x"),
118                PathBuf::from("/a/f3.x")
119            ]),
120            PathBuf::from("/a")
121        );
122        assert_eq!(
123            longest_common_prefix(&vec![
124                PathBuf::from("/a/f3.x"),
125                PathBuf::from("/a/b/c/f1.x"),
126                PathBuf::from("/a/b/f2.x")
127            ]),
128            PathBuf::from("/a")
129        );
130        assert_eq!(
131            longest_common_prefix(&vec![PathBuf::from("/a/f1.x"),]),
132            PathBuf::from("/a/f1.x") // Strip file names, if you want the path
133        );
134    }
135}