1mod convert;
2mod path;
3
4use regex::Regex;
5use std::fmt;
6
7#[derive(Debug)]
8pub enum Error {
9 Parse(Vec<String>), Regex(regex::Error),
11}
12
13impl From<regex::Error> for Error {
14 fn from(e: regex::Error) -> Self {
15 Error::Regex(e)
16 }
17}
18
19impl fmt::Display for Error {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 Error::Parse(msgs) => write!(f, "{}", msgs.join("; ")),
23 Error::Regex(e) => write!(f, "{}", e),
24 }
25 }
26}
27
28impl std::error::Error for Error {
29 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
30 match self {
31 Error::Regex(e) => Some(e),
32 _ => None,
33 }
34 }
35}
36
37pub fn path_matches(path: &str, files: &[&str]) -> Result<Option<usize>, Error> {
38 if files.is_empty() {
39 return Ok(None);
40 }
41
42 let pattern = match path.chars().next() {
44 Some(c) if matches!(c, '?' | '+' | '!') => &path[c.len_utf8()..],
45 _ => path,
46 };
47
48 let re = compile_pattern(pattern)?;
49
50 Ok(files.iter().enumerate().find(|(_, file)| re.is_match(file)).map(|(i, _)| i))
52}
53
54pub fn paths_match(paths: &[&str], files: &[&str]) -> Result<Option<(usize, usize)>, Error> {
66 if paths.is_empty() || files.is_empty() {
67 return Ok(None);
68 }
69
70 let compiled: Vec<(bool, Regex)> = paths
72 .iter()
73 .map(|p| {
74 let (negated, rest) = match p.chars().next() {
75 Some('!') => (true, &p[1..]),
76 _ => (false, *p),
77 };
78 let pattern = match rest.chars().next() {
80 Some(c) if matches!(c, '?' | '+') => &rest[c.len_utf8()..],
81 _ => rest,
82 };
83 compile_pattern(pattern).map(|re| (negated, re))
84 })
85 .collect::<Result<_, _>>()?;
86
87 for (fi, file) in files.iter().enumerate() {
88 let mut matched_by: Option<usize> = None;
89 for (pi, (negated, re)) in compiled.iter().enumerate() {
90 if re.is_match(file) {
91 matched_by = if *negated { None } else { Some(pi) };
92 }
93 }
94 if let Some(pi) = matched_by {
95 return Ok(Some((pi, fi)));
96 }
97 }
98
99 Ok(None)
100}
101
102fn compile_pattern(pattern: &str) -> Result<Regex, Error> {
103 let parsed_path = match path::parse(pattern) {
105 Ok(p) => p,
106 Err(errs) => {
107 let msgs = errs.into_iter().map(|e| e.to_string()).collect();
108 return Err(Error::Parse(msgs));
109 }
110 };
111
112 Ok(convert::path_to_regex(&parsed_path)?)
114}