use crate::{
errors::{FilterExpressionParseErrors, ParseSingleError, State},
parsing::{parse, Expr, ParsedExpr, SetDef, Span},
};
use guppy::{
graph::{cargo::BuildPlatform, PackageGraph},
PackageId,
};
use miette::SourceSpan;
use recursion::{
map_layer::{MapLayer, Project},
Collapse,
};
use std::{cell::RefCell, collections::HashSet};
#[derive(Debug, Clone)]
pub enum NameMatcher {
Equal(String),
Contains(String),
Regex(regex::Regex),
}
impl PartialEq for NameMatcher {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Contains(s1), Self::Contains(s2)) => s1 == s2,
(Self::Equal(s1), Self::Equal(s2)) => s1 == s2,
(Self::Regex(r1), Self::Regex(r2)) => r1.as_str() == r2.as_str(),
_ => false,
}
}
}
impl Eq for NameMatcher {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FilteringSet {
Packages(HashSet<PackageId>),
Kind(NameMatcher, SourceSpan),
Platform(BuildPlatform, SourceSpan),
Binary(NameMatcher, SourceSpan),
Test(NameMatcher, SourceSpan),
All,
None,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct BinaryQuery<'a> {
pub package_id: &'a PackageId,
pub binary_name: &'a str,
pub kind: &'a str,
pub platform: BuildPlatform,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct TestQuery<'a> {
pub binary_query: BinaryQuery<'a>,
pub test_name: &'a str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FilteringExpr {
Not(Box<FilteringExpr>),
Union(Box<FilteringExpr>, Box<FilteringExpr>),
Intersection(Box<FilteringExpr>, Box<FilteringExpr>),
Set(FilteringSet),
}
impl NameMatcher {
pub(crate) fn is_match(&self, input: &str) -> bool {
match self {
Self::Equal(text) => text == input,
Self::Contains(text) => input.contains(text),
Self::Regex(reg) => reg.is_match(input),
}
}
}
impl FilteringSet {
fn matches_test(&self, query: &TestQuery<'_>) -> bool {
match self {
Self::All => true,
Self::None => false,
Self::Test(matcher, _) => matcher.is_match(query.test_name),
Self::Binary(matcher, _) => matcher.is_match(query.binary_query.binary_name),
Self::Platform(platform, _) => query.binary_query.platform == *platform,
Self::Kind(matcher, _) => matcher.is_match(query.binary_query.kind),
Self::Packages(packages) => packages.contains(query.binary_query.package_id),
}
}
fn matches_binary(&self, query: &BinaryQuery<'_>) -> Option<bool> {
match self {
Self::All => Logic::top(),
Self::None => Logic::bottom(),
Self::Test(_, _) => None,
Self::Binary(matcher, _) => Some(matcher.is_match(query.binary_name)),
Self::Platform(platform, _) => Some(query.platform == *platform),
Self::Kind(matcher, _) => Some(matcher.is_match(query.kind)),
Self::Packages(packages) => Some(packages.contains(query.package_id)),
}
}
}
impl FilteringExpr {
pub fn parse(
input: &str,
graph: &PackageGraph,
) -> Result<FilteringExpr, FilterExpressionParseErrors> {
let errors = RefCell::new(Vec::new());
match parse(Span::new_extra(input, State::new(&errors))) {
Ok(parsed_expr) => {
let errors = errors.into_inner();
if !errors.is_empty() {
return Err(FilterExpressionParseErrors::new(input, errors));
}
match parsed_expr {
ParsedExpr::Valid(expr) => crate::compile::compile(&expr, graph)
.map_err(|errors| FilterExpressionParseErrors::new(input, errors)),
_ => {
Err(FilterExpressionParseErrors::new(
input,
vec![ParseSingleError::Unknown],
))
}
}
}
Err(_) => {
Err(FilterExpressionParseErrors::new(
input,
vec![ParseSingleError::Unknown],
))
}
}
}
pub fn matches_binary(&self, query: &BinaryQuery<'_>) -> Option<bool> {
use ExprLayer::*;
Wrapped(self).collapse_layers(|layer: ExprLayer<&FilteringSet, Option<bool>>| {
match layer {
Set(set) => set.matches_binary(query),
Not(a) => a.logic_not(),
Union(a, b) => a.logic_or(b),
Intersection(a, b) => a.logic_and(b),
}
})
}
pub fn matches_test(&self, query: &TestQuery<'_>) -> bool {
use ExprLayer::*;
Wrapped(self).collapse_layers(|layer: ExprLayer<&FilteringSet, bool>| match layer {
Set(set) => set.matches_test(query),
Not(a) => !a,
Union(a, b) => a || b,
Intersection(a, b) => a && b,
})
}
pub fn needs_deps(raw_expr: &str) -> bool {
raw_expr.contains("deps")
}
}
trait Logic {
fn top() -> Self;
fn bottom() -> Self;
fn logic_and(self, other: Self) -> Self;
fn logic_or(self, other: Self) -> Self;
fn logic_not(self) -> Self;
}
impl Logic for bool {
#[inline]
fn top() -> Self {
true
}
#[inline]
fn bottom() -> Self {
false
}
#[inline]
fn logic_and(self, other: Self) -> Self {
self && other
}
#[inline]
fn logic_or(self, other: Self) -> Self {
self || other
}
#[inline]
fn logic_not(self) -> Self {
!self
}
}
impl Logic for Option<bool> {
#[inline]
fn top() -> Self {
Some(true)
}
#[inline]
fn bottom() -> Self {
Some(false)
}
#[inline]
fn logic_and(self, other: Self) -> Self {
match (self, other) {
(Some(false), _) | (_, Some(false)) => Some(false),
(Some(true), Some(true)) => Some(true),
_ => None,
}
}
#[inline]
fn logic_or(self, other: Self) -> Self {
match (self, other) {
(Some(true), _) | (_, Some(true)) => Some(true),
(Some(false), Some(false)) => Some(false),
_ => None,
}
}
#[inline]
fn logic_not(self) -> Self {
self.map(|v| !v)
}
}
pub(crate) enum ExprLayer<Set, A> {
Not(A),
Union(A, A),
Intersection(A, A),
Set(Set),
}
impl<A, Set, B> MapLayer<B> for ExprLayer<Set, A> {
type Unwrapped = A;
type To = ExprLayer<Set, B>;
#[inline(always)]
fn map_layer<F: FnMut(Self::Unwrapped) -> B>(self, mut f: F) -> Self::To {
use ExprLayer::*;
match self {
Not(a) => Not(f(a)),
Union(a, b) => Union(f(a), f(b)),
Intersection(a, b) => Intersection(f(a), f(b)),
Set(f) => Set(f),
}
}
}
pub(crate) struct Wrapped<T>(pub(crate) T);
impl<'a> Project for Wrapped<&'a FilteringExpr> {
type To = ExprLayer<&'a FilteringSet, Wrapped<&'a FilteringExpr>>;
fn project(self) -> Self::To {
match self.0 {
FilteringExpr::Not(a) => ExprLayer::Not(Wrapped(a.as_ref())),
FilteringExpr::Union(a, b) => {
ExprLayer::Union(Wrapped(a.as_ref()), Wrapped(b.as_ref()))
}
FilteringExpr::Intersection(a, b) => {
ExprLayer::Intersection(Wrapped(a.as_ref()), Wrapped(b.as_ref()))
}
FilteringExpr::Set(f) => ExprLayer::Set(f),
}
}
}
impl<'a> Project for Wrapped<&'a Expr> {
type To = ExprLayer<&'a SetDef, Wrapped<&'a Expr>>;
fn project(self) -> Self::To {
match self.0 {
Expr::Not(a) => ExprLayer::Not(Wrapped(a.as_ref())),
Expr::Union(a, b) => ExprLayer::Union(Wrapped(a.as_ref()), Wrapped(b.as_ref())),
Expr::Intersection(a, b) => {
ExprLayer::Intersection(Wrapped(a.as_ref()), Wrapped(b.as_ref()))
}
Expr::Set(f) => ExprLayer::Set(f),
}
}
}