mod convert;
mod path;
use regex::Regex;
use std::fmt;
#[derive(Debug)]
pub enum Error {
Parse(Vec<String>), Regex(regex::Error),
}
impl From<regex::Error> for Error {
fn from(e: regex::Error) -> Self {
Error::Regex(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Parse(msgs) => write!(f, "{}", msgs.join("; ")),
Error::Regex(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Regex(e) => Some(e),
_ => None,
}
}
}
pub fn path_matches(path: &str, files: &[&str]) -> Result<Option<usize>, Error> {
if files.is_empty() {
return Ok(None);
}
let pattern = match path.chars().next() {
Some(c) if matches!(c, '?' | '+' | '!') => &path[c.len_utf8()..],
_ => path,
};
let re = compile_pattern(pattern)?;
Ok(files.iter().enumerate().find(|(_, file)| re.is_match(file)).map(|(i, _)| i))
}
pub fn paths_match(paths: &[&str], files: &[&str]) -> Result<Option<(usize, usize)>, Error> {
if paths.is_empty() || files.is_empty() {
return Ok(None);
}
let compiled: Vec<(bool, Regex)> = paths
.iter()
.map(|p| {
let (negated, rest) = match p.chars().next() {
Some('!') => (true, &p[1..]),
_ => (false, *p),
};
let pattern = match rest.chars().next() {
Some(c) if matches!(c, '?' | '+') => &rest[c.len_utf8()..],
_ => rest,
};
compile_pattern(pattern).map(|re| (negated, re))
})
.collect::<Result<_, _>>()?;
for (fi, file) in files.iter().enumerate() {
let mut matched_by: Option<usize> = None;
for (pi, (negated, re)) in compiled.iter().enumerate() {
if re.is_match(file) {
matched_by = if *negated { None } else { Some(pi) };
}
}
if let Some(pi) = matched_by {
return Ok(Some((pi, fi)));
}
}
Ok(None)
}
fn compile_pattern(pattern: &str) -> Result<Regex, Error> {
let parsed_path = match path::parse(pattern) {
Ok(p) => p,
Err(errs) => {
let msgs = errs.into_iter().map(|e| e.to_string()).collect();
return Err(Error::Parse(msgs));
}
};
Ok(convert::path_to_regex(&parsed_path)?)
}