tinc_build/
path_set.rs

1use std::collections::HashSet;
2use std::iter;
3
4use crate::types::ProtoPath;
5
6#[derive(Clone, Debug, Default)]
7pub(crate) struct PathSet {
8    paths: HashSet<String>,
9}
10
11impl PathSet {
12    pub(crate) fn contains(&self, full_path: &ProtoPath) -> bool {
13        let mut path_str = full_path.to_string();
14        if !path_str.starts_with(".") {
15            path_str = format!(".{}", path_str).to_string();
16        }
17        self.find_matching(&path_str).is_some()
18    }
19
20    pub(crate) fn insert(&mut self, path: impl std::fmt::Display) {
21        self.paths.insert(path.to_string());
22    }
23
24    fn find_matching(&self, full_path: &str) -> Option<String> {
25        sub_path_iter(full_path).find_map(|path| {
26            if self.paths.contains(path) {
27                Some(path.to_string())
28            } else {
29                None
30            }
31        })
32    }
33}
34
35fn sub_path_iter(full_path: &str) -> impl Iterator<Item = &str> {
36    // Get all combinations of prefixes/suffixes, along global path
37    iter::once(full_path)
38        .chain(suffixes(full_path))
39        .chain(prefixes(full_path))
40        .chain(iter::once("."))
41}
42
43fn prefixes(fq_path: &str) -> impl Iterator<Item = &str> {
44    iter::successors(Some(fq_path), |path| {
45        #[allow(unknown_lints, clippy::manual_split_once)]
46        path.rsplitn(2, '.').nth(1).filter(|path| !path.is_empty())
47    })
48    .skip(1)
49}
50
51fn suffixes(fq_path: &str) -> impl Iterator<Item = &str> {
52    iter::successors(Some(fq_path), |path| {
53        #[allow(unknown_lints, clippy::manual_split_once)]
54        path.splitn(2, '.').nth(1).filter(|path| !path.is_empty())
55    })
56    .skip(1)
57}
58
59#[cfg(test)]
60#[cfg_attr(coverage_nightly, coverage(off))]
61mod tests {
62    use crate::PathSet;
63    use crate::types::ProtoPath;
64
65    #[test]
66    fn test_path_set() {
67        let mut ps_a = PathSet::default();
68        ps_a.insert(".my_package.MessageA.field_a");
69
70        assert!(ps_a.contains(&ProtoPath::new(".my_package.MessageA.field_a")));
71        assert!(!ps_a.contains(&ProtoPath::new(".my_package.MessageA.field_b")));
72        assert!(!ps_a.contains(&ProtoPath::new(".my_package.MessageB.field_a")));
73        assert!(!ps_a.contains(&ProtoPath::new(".other_package.MessageA.field_a")));
74
75        let mut ps_b = PathSet::default();
76        ps_b.insert(".my_package.MessageA");
77
78        assert!(ps_b.contains(&ProtoPath::new(".my_package.MessageA.field_a")));
79        assert!(ps_b.contains(&ProtoPath::new(".my_package.MessageA.field_b")));
80        assert!(!ps_b.contains(&ProtoPath::new(".my_package.MessageB.field_a")));
81        assert!(!ps_b.contains(&ProtoPath::new(".other_package.MessageA.field_a")));
82
83        let mut ps_c = PathSet::default();
84        ps_c.insert(".my_package");
85
86        assert!(ps_c.contains(&ProtoPath::new(".my_package.MessageA.field_a")));
87        assert!(ps_c.contains(&ProtoPath::new(".my_package.MessageA.field_b")));
88        assert!(ps_c.contains(&ProtoPath::new(".my_package.MessageB.field_a")));
89        assert!(!ps_c.contains(&ProtoPath::new(".other_package.MessageA.field_a")));
90
91        let mut ps_d = PathSet::default();
92        ps_d.insert(".");
93
94        assert!(ps_d.contains(&ProtoPath::new(".my_package.MessageA.field_a")));
95        assert!(ps_d.contains(&ProtoPath::new(".my_package.MessageA.field_b")));
96        assert!(ps_d.contains(&ProtoPath::new(".my_package.MessageB.field_a")));
97        assert!(ps_d.contains(&ProtoPath::new(".other_package.MessageA.field_a")));
98    }
99}