use callisto_model::{Ecosystem, PackageId};
use globset::{Glob, GlobMatcher};
#[derive(Clone, Debug)]
pub struct PackagePattern {
raw: String,
ecosystem: Option<Ecosystem>,
matcher: GlobMatcher,
}
impl PackagePattern {
pub fn parse(s: &str) -> Result<Self, globset::Error> {
let (ecosystem, rest) = match s.split_once(':') {
Some((prefix, rest)) => match Ecosystem::from_prefix(prefix) {
Some(eco) => (Some(eco), rest),
None => (None, s),
},
None => (None, s),
};
let glob = Glob::new(rest)?;
Ok(Self {
raw: s.to_string(),
ecosystem,
matcher: glob.compile_matcher(),
})
}
pub fn ecosystem(&self) -> Option<Ecosystem> {
self.ecosystem
}
pub fn matches(&self, id: &PackageId) -> bool {
self.matches_in_ecosystems(id.name(), id.ecosystem().as_slice())
}
pub fn matches_in_ecosystems(&self, name: &str, ecosystems: &[Ecosystem]) -> bool {
if let Some(want) = self.ecosystem {
if !ecosystems.contains(&want) {
return false;
}
}
self.matcher.is_match(name)
}
pub fn as_str(&self) -> &str {
&self.raw
}
}
impl std::fmt::Display for PackagePattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.raw)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ecosystem_prefixed_pattern_matches_only_that_ecosystem() {
let pattern = PackagePattern::parse("cargo:internal-*").expect("valid glob");
let cargo_pkg = PackageId::parse("cargo:internal-foo").expect("valid package id");
let npm_pkg = PackageId::parse("npm:internal-foo").expect("valid package id");
assert!(
pattern.matches(&cargo_pkg),
"'cargo:internal-*' must match the Cargo package 'internal-foo'"
);
assert!(
!pattern.matches(&npm_pkg),
"'cargo:internal-*' must NOT match the npm package 'internal-foo'"
);
}
}