rust/hg-core/src/matchers.rs
branchstable
changeset 51564 f5c367dc6541
parent 51471 5633de951d34
child 51566 529a655874fb
equal deleted inserted replaced
51563:b861d913e7ec 51564:f5c367dc6541
    33 
    33 
    34 #[derive(Debug, PartialEq)]
    34 #[derive(Debug, PartialEq)]
    35 pub enum VisitChildrenSet {
    35 pub enum VisitChildrenSet {
    36     /// Don't visit anything
    36     /// Don't visit anything
    37     Empty,
    37     Empty,
    38     /// Only visit this directory
    38     /// Visit this directory and probably its children
    39     This,
    39     This,
    40     /// Visit this directory and these subdirectories
    40     /// Only visit the children (both files and directories) if they
       
    41     /// are mentioned in this set. (empty set corresponds to [Empty])
    41     /// TODO Should we implement a `NonEmptyHashSet`?
    42     /// TODO Should we implement a `NonEmptyHashSet`?
    42     Set(HashSet<HgPathBuf>),
    43     Set(HashSet<HgPathBuf>),
    43     /// Visit this directory and all subdirectories
    44     /// Visit this directory and all subdirectories
       
    45     /// (you can stop asking about the children set)
    44     Recursive,
    46     Recursive,
    45 }
    47 }
    46 
    48 
    47 pub trait Matcher: core::fmt::Debug {
    49 pub trait Matcher: core::fmt::Debug {
    48     /// Explicitly listed files
    50     /// Explicitly listed files
  1103 
  1105 
  1104 #[cfg(test)]
  1106 #[cfg(test)]
  1105 mod tests {
  1107 mod tests {
  1106     use super::*;
  1108     use super::*;
  1107     use pretty_assertions::assert_eq;
  1109     use pretty_assertions::assert_eq;
       
  1110     use std::collections::BTreeMap;
       
  1111     use std::collections::BTreeSet;
       
  1112     use std::fmt::Debug;
  1108     use std::path::Path;
  1113     use std::path::Path;
  1109 
  1114 
  1110     #[test]
  1115     #[test]
  1111     fn test_roots_and_dirs() {
  1116     fn test_roots_and_dirs() {
  1112         let pats = vec![
  1117         let pats = vec![
  2117         assert_eq!(
  2122         assert_eq!(
  2118             matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
  2123             matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
  2119             VisitChildrenSet::This
  2124             VisitChildrenSet::This
  2120         );
  2125         );
  2121     }
  2126     }
  2122 }
  2127 
       
  2128     mod invariants {
       
  2129         pub mod visit_children_set {
       
  2130 
       
  2131             use crate::{
       
  2132                 matchers::{tests::Tree, Matcher, VisitChildrenSet},
       
  2133                 utils::hg_path::HgPath,
       
  2134             };
       
  2135 
       
  2136             #[allow(dead_code)]
       
  2137             #[derive(Debug)]
       
  2138             struct Error<'a, M> {
       
  2139                 matcher: &'a M,
       
  2140                 path: &'a HgPath,
       
  2141                 matching: &'a Tree,
       
  2142                 visit_children_set: &'a VisitChildrenSet,
       
  2143             }
       
  2144 
       
  2145             fn holds(matching: &Tree, vcs: &VisitChildrenSet) -> bool {
       
  2146                 match vcs {
       
  2147                     VisitChildrenSet::Empty => matching.is_empty(),
       
  2148                     VisitChildrenSet::This => {
       
  2149                         // `This` does not come with any obligations.
       
  2150                         true
       
  2151                     }
       
  2152                     VisitChildrenSet::Recursive => {
       
  2153                         // `Recursive` does not come with any correctness
       
  2154                         // obligations.
       
  2155                         // It instructs the caller to stop calling
       
  2156                         // `visit_children_set` for all
       
  2157                         // descendants, so may have negative performance
       
  2158                         // implications, but we're not testing against that
       
  2159                         // here.
       
  2160                         true
       
  2161                     }
       
  2162                     VisitChildrenSet::Set(allowed_children) => {
       
  2163                         // `allowed_children` does not distinguish between
       
  2164                         // files and directories: if it's not included, it
       
  2165                         // must not be matched.
       
  2166                         for k in matching.dirs.keys() {
       
  2167                             if !(allowed_children.contains(k)) {
       
  2168                                 return false;
       
  2169                             }
       
  2170                         }
       
  2171                         for k in matching.files.iter() {
       
  2172                             if !(allowed_children.contains(k)) {
       
  2173                                 return false;
       
  2174                             }
       
  2175                         }
       
  2176                         true
       
  2177                     }
       
  2178                 }
       
  2179             }
       
  2180 
       
  2181             pub fn check<M: Matcher + std::fmt::Debug>(
       
  2182                 matcher: &M,
       
  2183                 path: &HgPath,
       
  2184                 matching: &Tree,
       
  2185                 visit_children_set: &VisitChildrenSet,
       
  2186             ) {
       
  2187                 if !holds(matching, visit_children_set) {
       
  2188                     panic!(
       
  2189                         "{:#?}",
       
  2190                         Error {
       
  2191                             matcher,
       
  2192                             path,
       
  2193                             visit_children_set,
       
  2194                             matching
       
  2195                         }
       
  2196                     )
       
  2197                 }
       
  2198             }
       
  2199         }
       
  2200     }
       
  2201 
       
  2202     #[derive(Debug, Clone)]
       
  2203     pub struct Tree {
       
  2204         files: BTreeSet<HgPathBuf>,
       
  2205         dirs: BTreeMap<HgPathBuf, Tree>,
       
  2206     }
       
  2207 
       
  2208     impl Tree {
       
  2209         fn len(&self) -> usize {
       
  2210             let mut n = 0;
       
  2211             n += self.files.len();
       
  2212             for d in self.dirs.values() {
       
  2213                 n += d.len();
       
  2214             }
       
  2215             n
       
  2216         }
       
  2217 
       
  2218         fn is_empty(&self) -> bool {
       
  2219             self.files.is_empty() && self.dirs.is_empty()
       
  2220         }
       
  2221 
       
  2222         fn filter_and_check<M: Matcher + Debug>(
       
  2223             &self,
       
  2224             m: &M,
       
  2225             path: &HgPath,
       
  2226         ) -> Self {
       
  2227             let files: BTreeSet<HgPathBuf> = self
       
  2228                 .files
       
  2229                 .iter()
       
  2230                 .filter(|v| m.matches(&path.join(v)))
       
  2231                 .map(|f| f.to_owned())
       
  2232                 .collect();
       
  2233             let dirs: BTreeMap<HgPathBuf, Tree> = self
       
  2234                 .dirs
       
  2235                 .iter()
       
  2236                 .filter_map(|(k, v)| {
       
  2237                     let path = path.join(k);
       
  2238                     let v = v.filter_and_check(m, &path);
       
  2239                     if v.is_empty() {
       
  2240                         None
       
  2241                     } else {
       
  2242                         Some((k.to_owned(), v))
       
  2243                     }
       
  2244                 })
       
  2245                 .collect();
       
  2246             let matching = Self { files, dirs };
       
  2247             let vcs = m.visit_children_set(path);
       
  2248             invariants::visit_children_set::check(m, path, &matching, &vcs);
       
  2249             matching
       
  2250         }
       
  2251 
       
  2252         fn check_matcher<M: Matcher + Debug>(
       
  2253             &self,
       
  2254             m: &M,
       
  2255             expect_count: usize,
       
  2256         ) {
       
  2257             let res = self.filter_and_check(m, &HgPathBuf::new());
       
  2258             if expect_count != res.len() {
       
  2259                 eprintln!(
       
  2260                     "warning: expected {} matches, got {} for {:#?}",
       
  2261                     expect_count,
       
  2262                     res.len(),
       
  2263                     m
       
  2264                 );
       
  2265             }
       
  2266         }
       
  2267     }
       
  2268 
       
  2269     fn mkdir(children: &[(&[u8], &Tree)]) -> Tree {
       
  2270         let p = HgPathBuf::from_bytes;
       
  2271         let names = [
       
  2272             p(b"a"),
       
  2273             p(b"b.txt"),
       
  2274             p(b"file.txt"),
       
  2275             p(b"c.c"),
       
  2276             p(b"c.h"),
       
  2277             p(b"dir1"),
       
  2278             p(b"dir2"),
       
  2279             p(b"subdir"),
       
  2280         ];
       
  2281         let files: BTreeSet<HgPathBuf> = BTreeSet::from(names);
       
  2282         let dirs = children
       
  2283             .iter()
       
  2284             .map(|(name, t)| (p(name), (*t).clone()))
       
  2285             .collect();
       
  2286         Tree { files, dirs }
       
  2287     }
       
  2288 
       
  2289     fn make_example_tree() -> Tree {
       
  2290         let leaf = mkdir(&[]);
       
  2291         let abc = mkdir(&[(b"d", &leaf)]);
       
  2292         let ab = mkdir(&[(b"c", &abc)]);
       
  2293         let a = mkdir(&[(b"b", &ab)]);
       
  2294         let dir = mkdir(&[(b"subdir", &leaf), (b"subdir.c", &leaf)]);
       
  2295         mkdir(&[(b"dir", &dir), (b"dir1", &dir), (b"dir2", &dir), (b"a", &a)])
       
  2296     }
       
  2297 
       
  2298     #[test]
       
  2299     fn test_pattern_matcher_visit_children_set() {
       
  2300         let tree = make_example_tree();
       
  2301         let _pattern_dir1_glob_c =
       
  2302             PatternMatcher::new(vec![IgnorePattern::new(
       
  2303                 PatternSyntax::Glob,
       
  2304                 b"dir1/*.c",
       
  2305                 Path::new(""),
       
  2306             )])
       
  2307             .unwrap();
       
  2308         let pattern_dir1 = || {
       
  2309             PatternMatcher::new(vec![IgnorePattern::new(
       
  2310                 PatternSyntax::Path,
       
  2311                 b"dir1",
       
  2312                 Path::new(""),
       
  2313             )])
       
  2314             .unwrap()
       
  2315         };
       
  2316         let pattern_dir1_a = PatternMatcher::new(vec![IgnorePattern::new(
       
  2317             PatternSyntax::Glob,
       
  2318             b"dir1/a",
       
  2319             Path::new(""),
       
  2320         )])
       
  2321         .unwrap();
       
  2322         let pattern_relglob_c = || {
       
  2323             PatternMatcher::new(vec![IgnorePattern::new(
       
  2324                 PatternSyntax::RelGlob,
       
  2325                 b"*.c",
       
  2326                 Path::new(""),
       
  2327             )])
       
  2328             .unwrap()
       
  2329         };
       
  2330         //        // TODO: re-enable this test when the corresponding bug is
       
  2331         // fixed        if false {
       
  2332         //            tree.check_matcher(&pattern_dir1_glob_c);
       
  2333         //        }
       
  2334         let files = vec![HgPathBuf::from_bytes(b"dir/subdir/b.txt")];
       
  2335         let file_dir_subdir_b = FileMatcher::new(files).unwrap();
       
  2336 
       
  2337         let files = vec![
       
  2338             HgPathBuf::from_bytes(b"file.txt"),
       
  2339             HgPathBuf::from_bytes(b"a/file.txt"),
       
  2340             HgPathBuf::from_bytes(b"a/b/file.txt"),
       
  2341             // No file in a/b/c
       
  2342             HgPathBuf::from_bytes(b"a/b/c/d/file.txt"),
       
  2343         ];
       
  2344         let file_abcdfile = FileMatcher::new(files).unwrap();
       
  2345         let _rootfilesin_dir = PatternMatcher::new(vec![IgnorePattern::new(
       
  2346             PatternSyntax::RootFiles,
       
  2347             b"dir",
       
  2348             Path::new(""),
       
  2349         )])
       
  2350         .unwrap();
       
  2351 
       
  2352         let pattern_filepath_dir_subdir =
       
  2353             PatternMatcher::new(vec![IgnorePattern::new(
       
  2354                 PatternSyntax::FilePath,
       
  2355                 b"dir/subdir",
       
  2356                 Path::new(""),
       
  2357             )])
       
  2358             .unwrap();
       
  2359 
       
  2360         let include_dir_subdir =
       
  2361             IncludeMatcher::new(vec![IgnorePattern::new(
       
  2362                 PatternSyntax::RelPath,
       
  2363                 b"dir/subdir",
       
  2364                 Path::new(""),
       
  2365             )])
       
  2366             .unwrap();
       
  2367 
       
  2368         let more_includematchers = [
       
  2369             IncludeMatcher::new(vec![IgnorePattern::new(
       
  2370                 PatternSyntax::Glob,
       
  2371                 b"dir/s*",
       
  2372                 Path::new(""),
       
  2373             )])
       
  2374             .unwrap(),
       
  2375             // Test multiple patterns
       
  2376             IncludeMatcher::new(vec![
       
  2377                 IgnorePattern::new(
       
  2378                     PatternSyntax::RelPath,
       
  2379                     b"dir",
       
  2380                     Path::new(""),
       
  2381                 ),
       
  2382                 IgnorePattern::new(PatternSyntax::Glob, b"s*", Path::new("")),
       
  2383             ])
       
  2384             .unwrap(),
       
  2385             // Test multiple patterns
       
  2386             IncludeMatcher::new(vec![IgnorePattern::new(
       
  2387                 PatternSyntax::Glob,
       
  2388                 b"**/*.c",
       
  2389                 Path::new(""),
       
  2390             )])
       
  2391             .unwrap(),
       
  2392         ];
       
  2393 
       
  2394         tree.check_matcher(&pattern_dir1(), 25);
       
  2395         tree.check_matcher(&pattern_dir1_a, 1);
       
  2396         tree.check_matcher(&pattern_relglob_c(), 14);
       
  2397         tree.check_matcher(&AlwaysMatcher, 112);
       
  2398         tree.check_matcher(&NeverMatcher, 0);
       
  2399         tree.check_matcher(
       
  2400             &IntersectionMatcher::new(
       
  2401                 Box::new(pattern_relglob_c()),
       
  2402                 Box::new(pattern_dir1()),
       
  2403             ),
       
  2404             3,
       
  2405         );
       
  2406         tree.check_matcher(
       
  2407             &UnionMatcher::new(vec![
       
  2408                 Box::new(pattern_relglob_c()),
       
  2409                 Box::new(pattern_dir1()),
       
  2410             ]),
       
  2411             36,
       
  2412         );
       
  2413         tree.check_matcher(
       
  2414             &DifferenceMatcher::new(
       
  2415                 Box::new(pattern_relglob_c()),
       
  2416                 Box::new(pattern_dir1()),
       
  2417             ),
       
  2418             11,
       
  2419         );
       
  2420         tree.check_matcher(&file_dir_subdir_b, 1);
       
  2421         tree.check_matcher(&file_abcdfile, 4);
       
  2422         //        // TODO: re-enable this test when the corresponding bug is
       
  2423         // fixed
       
  2424         //
       
  2425         //        if false {
       
  2426         //            tree.check_matcher(&rootfilesin_dir, 6);
       
  2427         //        }
       
  2428         tree.check_matcher(&pattern_filepath_dir_subdir, 1);
       
  2429         tree.check_matcher(&include_dir_subdir, 9);
       
  2430         tree.check_matcher(&more_includematchers[0], 17);
       
  2431         tree.check_matcher(&more_includematchers[1], 25);
       
  2432         tree.check_matcher(&more_includematchers[2], 35);
       
  2433     }
       
  2434 }