use std::path::Path;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::patternset;
#[derive(Clone)]
pub struct RuleSet {
rules: Vec<Rule>,
matcher: patternset::Matcher,
}
impl RuleSet {
pub fn new(rules: Vec<Rule>) -> Self {
let mut builder = patternset::Builder::new();
for rule in &rules {
builder.add(&rule.pattern);
}
let matcher = builder.build();
Self { rules, matcher }
}
pub fn matching_rule(&self, path: impl AsRef<Path>) -> Option<&Rule> {
self.matcher
.matching_patterns(path)
.iter()
.max()
.map(|&idx| &self.rules[idx])
}
pub fn owners(&self, path: impl AsRef<Path>) -> Option<&[Owner]> {
return self.matching_rule(path).and_then(|rule| {
if rule.owners.is_empty() {
None
} else {
Some(rule.owners.as_ref())
}
});
}
pub fn all_matching_rules(&self, path: impl AsRef<Path>) -> Vec<(usize, &Rule)> {
self.matcher
.matching_patterns(path)
.iter()
.map(|&idx| (idx, &self.rules[idx]))
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rule {
pub pattern: String,
pub owners: Vec<Owner>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Owner {
pub value: String,
pub kind: OwnerKind,
}
impl Owner {
pub fn new(value: String, kind: OwnerKind) -> Self {
Self { value, kind }
}
}
static EMAIL_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"\A[A-Z0-9a-z\._'%\+\-]+@[A-Za-z0-9\.\-]+\.[A-Za-z]{2,6}\z").unwrap());
static USERNAME_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\A@[a-zA-Z0-9\-_]+\z").unwrap());
static TEAM_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"\A@[a-zA-Z0-9\-]+/[a-zA-Z0-9\-_]+\z").unwrap());
#[derive(Debug, Clone)]
pub struct InvalidOwnerError {
value: String,
}
impl std::fmt::Display for InvalidOwnerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid owner: {}", self.value)
}
}
impl std::error::Error for InvalidOwnerError {}
impl TryFrom<String> for Owner {
type Error = InvalidOwnerError;
fn try_from(value: String) -> Result<Self, Self::Error> {
if EMAIL_REGEX.is_match(&value) {
Ok(Self::new(value, OwnerKind::Email))
} else if USERNAME_REGEX.is_match(&value) {
Ok(Self::new(value, OwnerKind::User))
} else if TEAM_REGEX.is_match(&value) {
Ok(Self::new(value, OwnerKind::Team))
} else {
Err(InvalidOwnerError { value })
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OwnerKind {
User,
Team,
Email,
}