use crate::error::{Error, Result};
use crate::indicator::Indicator;
#[derive(Debug, Clone)]
pub struct ProjectType {
pub name: String,
pub description: String,
pub indicators: Vec<Indicator>,
pub build_excludes: Vec<String>,
pub(crate) matchers: Vec<Matcher>,
}
impl ProjectType {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
indicators: Vec<Indicator>,
build_excludes: Vec<&str>,
) -> Self {
ProjectType {
name: name.into(),
description: description.into(),
indicators,
build_excludes: build_excludes.into_iter().map(String::from).collect(),
matchers: Vec::new(),
}
}
pub(crate) fn compile(&mut self) -> Result<()> {
let mut matchers = Vec::with_capacity(self.indicators.len());
for (i, ind) in self.indicators.iter().enumerate() {
matchers.push(Matcher::compile(ind, &self.name, i)?);
}
self.matchers = matchers;
Ok(())
}
pub(crate) fn match_listing(&self, files: &[String], subdirs: &[String]) -> Option<String> {
for (i, m) in self.matchers.iter().enumerate() {
if m.matches(files, subdirs) {
return Some(self.indicators[i].to_string());
}
}
None
}
}
#[derive(Debug, Clone)]
pub(crate) enum Matcher {
File(String),
Glob(glob::Pattern),
SubdirGlob(glob::Pattern),
#[cfg(feature = "cel")]
Cel(crate::cel::Program),
}
impl Matcher {
fn compile(ind: &Indicator, type_name: &str, index: usize) -> Result<Self> {
match ind {
Indicator::HasFile(s) => Ok(Matcher::File(s.clone())),
Indicator::HasGlob(s) => Ok(Matcher::Glob(compile_glob(s, type_name, index)?)),
Indicator::HasSubdirGlob(s) => {
Ok(Matcher::SubdirGlob(compile_glob(s, type_name, index)?))
}
Indicator::Cel(expr) => compile_cel(expr, type_name, index),
}
}
fn matches(&self, files: &[String], subdirs: &[String]) -> bool {
match self {
Matcher::File(want) => files.iter().any(|n| n.eq_ignore_ascii_case(want)),
Matcher::Glob(p) => files.iter().any(|n| p.matches(n)),
Matcher::SubdirGlob(p) => subdirs.iter().any(|n| p.matches(n)),
#[cfg(feature = "cel")]
Matcher::Cel(prog) => prog.eval(files, subdirs),
}
}
}
fn compile_glob(pattern: &str, type_name: &str, index: usize) -> Result<glob::Pattern> {
glob::Pattern::new(pattern).map_err(|e| Error::Config {
name: type_name.to_string(),
reason: format!("indicator[{index}] bad glob {pattern:?}: {e}"),
})
}
#[cfg(feature = "cel")]
fn compile_cel(expr: &str, type_name: &str, index: usize) -> Result<Matcher> {
crate::cel::Program::compile(expr)
.map(Matcher::Cel)
.map_err(|reason| Error::Cel {
name: type_name.to_string(),
index,
reason,
})
}
#[cfg(not(feature = "cel"))]
fn compile_cel(_expr: &str, type_name: &str, _index: usize) -> Result<Matcher> {
Err(Error::CelFeatureDisabled {
name: type_name.to_string(),
})
}