use std::{error, fmt, str::FromStr};
use globset::{Error as GlobError, Glob, GlobBuilder, GlobMatcher};
use crate::version::{self, PartialVersion, VersionError};
use super::Runtime;
#[derive(Debug)]
pub enum ConstraintError {
GlobError(GlobError),
VersionError(version::VersionError),
}
impl fmt::Display for ConstraintError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use ConstraintError::*;
match self {
GlobError(ref error) => write!(fmt, "could not parse constraint: {error}",),
VersionError(ref error) => write!(
fmt,
"could not parse version constraint {text:?}: {error}",
text = error.text().unwrap_or("<unknown>")
),
}
}
}
impl error::Error for ConstraintError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
ConstraintError::GlobError(ref error) => Some(error),
ConstraintError::VersionError(ref error) => Some(error),
}
}
}
impl From<GlobError> for ConstraintError {
fn from(error: GlobError) -> ConstraintError {
ConstraintError::GlobError(error)
}
}
impl From<version::VersionError> for ConstraintError {
fn from(error: version::VersionError) -> ConstraintError {
ConstraintError::VersionError(error)
}
}
#[derive(Clone, Debug)]
pub enum Constraint {
BinDir(GlobMatcher),
Version(PartialVersion),
Either(Box<Constraint>, Box<Constraint>),
Both(Box<Constraint>, Box<Constraint>),
Not(Box<Constraint>),
Anything,
Nothing,
}
impl Constraint {
pub fn path(pattern: &str) -> Result<Self, GlobError> {
Ok(Self::BinDir(
GlobBuilder::new(pattern)
.literal_separator(true)
.empty_alternates(true)
.build()?
.compile_matcher(),
))
}
pub fn glob(glob: &Glob) -> Self {
Self::BinDir(glob.compile_matcher())
}
pub fn version(version: &str) -> Result<Self, VersionError> {
Ok(Self::Version(version.parse()?))
}
pub fn any<C: IntoIterator<Item = Constraint>>(constraints: C) -> Self {
constraints
.into_iter()
.reduce(|a, b| a | b)
.unwrap_or(Self::Nothing)
}
pub fn all<C: IntoIterator<Item = Constraint>>(constraints: C) -> Self {
constraints
.into_iter()
.reduce(|a, b| a & b)
.unwrap_or(Self::Anything)
}
pub fn matches(&self, runtime: &Runtime) -> bool {
match self {
Self::BinDir(matcher) => matcher.is_match(&runtime.bindir),
Self::Version(version) => version.compatible(runtime.version),
Self::Either(ca, cb) => ca.matches(runtime) || cb.matches(runtime),
Self::Both(ca, cb) => ca.matches(runtime) && cb.matches(runtime),
Self::Not(constraint) => !constraint.matches(runtime),
Self::Anything => true,
Self::Nothing => false,
}
}
}
impl std::ops::Not for Constraint {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Self::Anything => Self::Nothing,
Self::Nothing => Self::Anything,
Self::Not(constraint) => *constraint,
_ => Self::Not(Box::new(self)),
}
}
}
impl std::ops::BitOr for Constraint {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Self::Anything, _) | (_, Self::Anything) => Self::Anything,
(Self::Nothing, c) | (c, Self::Nothing) => c,
(ca, cb) => Self::Either(Box::new(ca), Box::new(cb)),
}
}
}
impl std::ops::BitAnd for Constraint {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Self::Anything, c) | (c, Self::Anything) => c,
(Self::Nothing, _) | (_, Self::Nothing) => Self::Nothing,
(ca, cb) => Self::Both(Box::new(ca), Box::new(cb)),
}
}
}
impl From<PartialVersion> for Constraint {
fn from(version: PartialVersion) -> Self {
Self::Version(version)
}
}
impl FromStr for Constraint {
type Err = ConstraintError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.contains(std::path::MAIN_SEPARATOR) {
Ok(Self::path(s)?)
} else {
Ok(Self::version(s)?)
}
}
}
#[cfg(test)]
mod tests {
use super::Constraint;
use super::PartialVersion;
const CONSTRAINT: Constraint = Constraint::Version(PartialVersion::Post10m(13));
#[test]
fn test_not() {
let c1 = Constraint::Version(PartialVersion::Post10m(13));
assert!(matches!(c1, Constraint::Version(_)));
let c2 = !c1;
assert!(matches!(c2, Constraint::Not(_)));
let c3 = !c2;
assert!(matches!(c3, Constraint::Version(_)));
}
#[test]
fn test_not_anything_and_nothing() {
let c1 = Constraint::Anything;
let c2 = !c1;
assert!(matches!(c2, Constraint::Nothing));
let c3 = !c2;
assert!(matches!(c3, Constraint::Anything));
}
#[test]
fn test_or() {
assert!(matches!(
Constraint::Anything | CONSTRAINT.clone(),
Constraint::Anything
));
assert!(matches!(
CONSTRAINT.clone() | Constraint::Anything,
Constraint::Anything
));
assert!(matches!(
Constraint::Nothing | CONSTRAINT.clone(),
Constraint::Version(_)
));
assert!(matches!(
CONSTRAINT.clone() | Constraint::Nothing,
Constraint::Version(_)
));
}
#[test]
fn test_or_anything_and_nothing() {
assert!(matches!(
Constraint::Anything | Constraint::Anything,
Constraint::Anything
));
assert!(matches!(
Constraint::Nothing | Constraint::Anything,
Constraint::Anything
));
assert!(matches!(
Constraint::Anything | Constraint::Nothing,
Constraint::Anything
));
}
#[test]
fn test_and() {
assert!(matches!(
Constraint::Anything & CONSTRAINT.clone(),
Constraint::Version(_)
));
assert!(matches!(
CONSTRAINT.clone() & Constraint::Anything,
Constraint::Version(_)
));
assert!(matches!(
Constraint::Nothing & CONSTRAINT.clone(),
Constraint::Nothing
));
assert!(matches!(
CONSTRAINT.clone() & Constraint::Nothing,
Constraint::Nothing
));
}
#[test]
fn test_and_anything_and_nothing() {
assert!(matches!(
Constraint::Anything & Constraint::Anything,
Constraint::Anything
));
assert!(matches!(
Constraint::Nothing & Constraint::Anything,
Constraint::Nothing
));
assert!(matches!(
Constraint::Anything & Constraint::Nothing,
Constraint::Nothing
));
}
#[test]
fn test_any() {
assert!(matches!(Constraint::any([]), Constraint::Nothing));
assert!(matches!(
Constraint::any([
Constraint::Anything,
Constraint::Nothing,
Constraint::Nothing
]),
Constraint::Anything
));
assert!(matches!(
Constraint::any([Constraint::Nothing, CONSTRAINT.clone(), Constraint::Nothing]),
Constraint::Version(_)
));
assert!(matches!(
Constraint::any([
Constraint::Anything,
CONSTRAINT.clone(),
Constraint::Nothing
]),
Constraint::Anything
));
assert!(matches!(
Constraint::any([CONSTRAINT.clone(), CONSTRAINT.clone()]),
Constraint::Either(ca, cb)
if matches!(*ca, Constraint::Version(_))
&& matches!(*cb, Constraint::Version(_))
));
}
#[test]
fn test_all() {
assert!(matches!(Constraint::all([]), Constraint::Anything));
assert!(matches!(
Constraint::all([
Constraint::Anything,
Constraint::Anything,
Constraint::Anything
]),
Constraint::Anything
));
assert!(matches!(
Constraint::all([
Constraint::Anything,
CONSTRAINT.clone(),
Constraint::Anything,
]),
Constraint::Version(_),
));
assert!(matches!(
Constraint::all([
Constraint::Anything,
CONSTRAINT.clone(),
Constraint::Nothing,
]),
Constraint::Nothing,
));
assert!(matches!(
Constraint::all([CONSTRAINT.clone(), CONSTRAINT.clone()]),
Constraint::Both(ca, cb)
if matches!(*ca, Constraint::Version(_))
&& matches!(*cb, Constraint::Version(_))
));
}
}