diff --git a/rust/hg-core/src/matchers.rs b/rust/hg-core/src/matchers.rs --- a/rust/hg-core/src/matchers.rs +++ b/rust/hg-core/src/matchers.rs @@ -7,8 +7,11 @@ //! Structs and types for matching files and directories. -use crate::utils::hg_path::{HgPath, HgPathBuf}; +use crate::utils::hg_path::HgPath; +use crate::DirsMultiset; use std::collections::HashSet; +use std::iter::FromIterator; +use std::ops::Deref; pub enum VisitChildrenSet<'a> { /// Don't visit anything @@ -114,3 +117,92 @@ false } } + +/// Matches the input files exactly. They are interpreted as paths, not +/// patterns. +/// +///``` +/// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath }; +/// +/// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")]; +/// let matcher = FileMatcher::new(&files); +/// +/// assert_eq!(true, matcher.matches(HgPath::new(b"a.txt"))); +/// assert_eq!(false, matcher.matches(HgPath::new(b"b.txt"))); +/// assert_eq!(false, matcher.matches(HgPath::new(b"main.c"))); +/// assert_eq!(true, matcher.matches(HgPath::new(br"re:.*\.c$"))); +/// ``` +#[derive(Debug)] +pub struct FileMatcher<'a> { + files: HashSet<&'a HgPath>, + dirs: DirsMultiset, +} + +impl<'a> FileMatcher<'a> { + pub fn new(files: &'a [impl AsRef]) -> Self { + Self { + files: HashSet::from_iter(files.iter().map(|f| f.as_ref())), + dirs: DirsMultiset::from_manifest(files), + } + } + fn inner_matches(&self, filename: impl AsRef) -> bool { + self.files.contains(filename.as_ref()) + } +} + +impl<'a> Matcher for FileMatcher<'a> { + fn file_set(&self) -> Option<&HashSet<&HgPath>> { + Some(&self.files) + } + fn exact_match(&self, filename: impl AsRef) -> bool { + self.inner_matches(filename) + } + fn matches(&self, filename: impl AsRef) -> bool { + self.inner_matches(filename) + } + fn visit_children_set( + &self, + directory: impl AsRef, + ) -> VisitChildrenSet { + if self.files.len() == 0 || !self.dirs.contains(directory.as_ref()) { + return VisitChildrenSet::Empty; + } + let minus = self + .dirs + .iter() + .filter_map(|k| { + if k == HgPath::new(b"") { + None + } else { + Some(k.deref()) + } + }) + .collect(); + let candidates = self.files.union(&minus); + + let candidates: Vec<&HgPath> = if directory.as_ref().len() == 0 { + candidates.map(|f| *f).collect() + } else { + let mut d = directory.as_ref().to_owned(); + d.push(b'/'); + candidates + .into_iter() + .filter_map(|c| c.relative_to(&d)) + .collect() + }; + + let ret = candidates + .into_iter() + .filter_map(|c| if !c.contains(b'/') { Some(c) } else { None }) + .collect(); + + VisitChildrenSet::Set(ret) + } + fn matches_everything(&self) -> bool { + false + } + fn is_exact(&self) -> bool { + true + } +} +