use std::fmt;
use strum::{AsRefStr, Display, EnumIter, EnumString, VariantNames};
use crate::pkg::Restrict as PkgRestrict;
use crate::restrict::dep::Restrict as DepRestrict;
use crate::types::Deque;
pub(crate) mod boolean;
pub mod dep;
pub mod depset;
pub mod ordered;
pub mod parse;
pub mod set;
pub mod str;
boolean::restrict_with_boolean! {Restrict,
True,
False,
Dep(DepRestrict),
Pkg(PkgRestrict),
Str(str::Restrict),
}
#[allow(clippy::derivable_impls)]
impl Default for Restrict {
fn default() -> Self {
Self::False
}
}
impl From<&Restrict> for Restrict {
fn from(r: &Restrict) -> Self {
r.clone()
}
}
pub trait TryIntoRestrict<C> {
fn try_into_restrict(self, context: &C) -> crate::Result<Restrict>;
}
impl<C> TryIntoRestrict<C> for &str {
fn try_into_restrict(self, _context: &C) -> crate::Result<Restrict> {
parse::dep(self)
}
}
impl<C, T: Into<Restrict>> TryIntoRestrict<C> for T {
fn try_into_restrict(self, _context: &C) -> crate::Result<Restrict> {
Ok(self.into())
}
}
impl Restrict {
pub fn iter_flatten(&self) -> IterFlatten<'_> {
IterFlatten([self].into_iter().collect())
}
}
#[derive(Debug)]
pub struct IterFlatten<'a>(Deque<&'a Restrict>);
impl<'a> Iterator for IterFlatten<'a> {
type Item = &'a Restrict;
fn next(&mut self) -> Option<Self::Item> {
while let Some(restrict) = self.0.pop_front() {
match restrict {
Restrict::And(vals) => self.0.extend_left(vals.iter().map(AsRef::as_ref)),
Restrict::Or(vals) => self.0.extend_left(vals.iter().map(AsRef::as_ref)),
Restrict::Xor(vals) => self.0.extend_left(vals.iter().map(AsRef::as_ref)),
_ => return Some(restrict),
}
}
None
}
}
macro_rules! restrict_match {
($r:expr, $obj:expr, $($matcher:pat $(if $pred:expr)* => $result:expr,)+) => {
match $r {
$($matcher $(if $pred)* => $result,)+
Self::True => true,
Self::False => false,
Self::And(vals) => vals.iter().all(|r| r.matches($obj)),
Self::Or(vals) => vals.iter().any(|r| r.matches($obj)),
Self::Xor(vals) => {
let mut curr: Option<bool>;
let mut prev: Option<bool> = None;
for r in vals {
curr = Some(r.matches($obj));
if prev.is_some() && curr != prev {
return true;
}
prev = curr
}
false
},
Self::Not(r) => !r.matches($obj),
_ => {
tracing::warn!("invalid restriction {:?} for matching {:?}", $r, $obj);
false
}
}
}
}
pub(crate) use restrict_match;
impl Restrict {
boolean::restrict_impl_boolean! {Self}
}
boolean::restrict_ops_boolean!(Restrict);
pub trait Restriction<T>: fmt::Debug {
fn matches(&self, object: T) -> bool;
}
impl Restriction<&String> for Restrict {
fn matches(&self, s: &String) -> bool {
restrict_match! {self, s,
Self::Dep(r) => r.matches(s.as_str()),
Self::Str(r) => r.matches(s.as_str()),
}
}
}
impl Restriction<&str> for Restrict {
fn matches(&self, s: &str) -> bool {
restrict_match! {self, s,
Self::Dep(r) => r.matches(s),
Self::Str(r) => r.matches(s),
}
}
}
#[derive(
AsRefStr,
Display,
EnumIter,
EnumString,
VariantNames,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Copy,
Clone,
)]
#[strum(serialize_all = "kebab-case")]
pub enum Scope {
Version,
Package,
Category,
Repo,
}
impl From<&Restrict> for Scope {
fn from(value: &Restrict) -> Self {
use dep::Restrict::{Category, Package, Version};
use str::Restrict::Equal;
let restrict_scope = |restrict: &Restrict| match restrict {
Restrict::Dep(Version(Some(_))) => Scope::Version,
Restrict::Dep(Package(Equal(_))) => Scope::Package,
Restrict::Dep(Package(_)) => Scope::Category,
Restrict::Dep(Category(_)) => Scope::Category,
_ => Scope::Repo,
};
match value {
Restrict::And(_) => value
.iter_flatten()
.map(restrict_scope)
.min()
.unwrap_or(Scope::Repo),
Restrict::Or(_) => value
.iter_flatten()
.map(restrict_scope)
.max()
.unwrap_or(Scope::Repo),
_ => restrict_scope(value),
}
}
}
#[cfg(test)]
mod tests {
use crate::dep::Dep;
use super::*;
#[test]
fn filtering() {
let dep_strs = vec!["cat/pkg", ">=cat/pkg-1", "=cat/pkg-1:2/3::repo"];
let deps: Vec<_> = dep_strs.iter().map(|s| s.parse().unwrap()).collect();
let filter = |r: Restrict, deps: Vec<Dep>| -> Vec<String> {
deps.into_iter()
.filter(|a| r.matches(a))
.map(|a| a.to_string())
.collect()
};
let r = Restrict::Dep(dep::Restrict::category("cat"));
assert_eq!(filter(r, deps.clone()), dep_strs);
let r = Restrict::Dep(dep::Restrict::Version(None));
assert_eq!(filter(r, deps.clone()), ["cat/pkg"]);
let dep = Dep::try_new("=cat/pkg-1").unwrap();
let r = Restrict::from(&dep);
assert_eq!(filter(r, deps.clone()), [">=cat/pkg-1", "=cat/pkg-1:2/3::repo"]);
let r = Restrict::True;
assert_eq!(filter(r, deps.clone()), dep_strs);
let r = Restrict::False;
assert!(filter(r, deps).is_empty());
}
}