use crate::{
errors::{FilterExpressionParseErrors, ParseSingleError},
parsing::{
new_span, parse, DisplayParsedRegex, DisplayParsedString, ExprResult, GenericGlob,
ParsedExpr, SetDef,
},
};
use guppy::{
graph::{cargo::BuildPlatform, PackageGraph},
PackageId,
};
use miette::SourceSpan;
use nextest_metadata::{RustBinaryId, RustTestBinaryKind};
use recursion::{Collapsible, CollapsibleExt, MappableFrame, PartiallyApplied};
use std::{collections::HashSet, fmt};
#[derive(Debug, Clone)]
pub enum NameMatcher {
Equal { value: String, implicit: bool },
Contains { value: String, implicit: bool },
Glob { glob: GenericGlob, implicit: bool },
Regex(regex::Regex),
}
impl NameMatcher {
pub(crate) fn implicit_equal(value: String) -> Self {
Self::Equal {
value,
implicit: true,
}
}
pub(crate) fn implicit_contains(value: String) -> Self {
Self::Contains {
value,
implicit: true,
}
}
}
impl PartialEq for NameMatcher {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::Contains {
value: s1,
implicit: default1,
},
Self::Contains {
value: s2,
implicit: default2,
},
) => s1 == s2 && default1 == default2,
(
Self::Equal {
value: s1,
implicit: default1,
},
Self::Equal {
value: s2,
implicit: default2,
},
) => s1 == s2 && default1 == default2,
(Self::Regex(r1), Self::Regex(r2)) => r1.as_str() == r2.as_str(),
(Self::Glob { glob: g1, .. }, Self::Glob { glob: g2, .. }) => {
g1.regex().as_str() == g2.regex().as_str()
}
_ => false,
}
}
}
impl Eq for NameMatcher {}
impl fmt::Display for NameMatcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Equal { value, implicit } => write!(
f,
"{}{}",
if *implicit { "" } else { "=" },
DisplayParsedString(value)
),
Self::Contains { value, implicit } => write!(
f,
"{}{}",
if *implicit { "" } else { "~" },
DisplayParsedString(value)
),
Self::Glob { glob, implicit } => write!(
f,
"{}{}",
if *implicit { "" } else { "#" },
DisplayParsedString(glob.as_str())
),
Self::Regex(r) => write!(f, "/{}/", DisplayParsedRegex(r)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FilteringSet {
Packages(HashSet<PackageId>),
Kind(NameMatcher, SourceSpan),
Platform(BuildPlatform, SourceSpan),
Binary(NameMatcher, SourceSpan),
BinaryId(NameMatcher, SourceSpan),
Test(NameMatcher, SourceSpan),
All,
None,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct BinaryQuery<'a> {
pub package_id: &'a PackageId,
pub binary_id: &'a RustBinaryId,
pub binary_name: &'a str,
pub kind: &'a RustTestBinaryKind,
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 struct FilteringExpr {
pub input: String,
pub parsed: ParsedExpr,
pub compiled: CompiledExpr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompiledExpr {
Not(Box<CompiledExpr>),
Union(Box<CompiledExpr>, Box<CompiledExpr>),
Intersection(Box<CompiledExpr>, Box<CompiledExpr>),
Set(FilteringSet),
}
impl NameMatcher {
pub(crate) fn is_match(&self, input: &str) -> bool {
match self {
Self::Equal { value, .. } => value == input,
Self::Contains { value, .. } => input.contains(value),
Self::Glob { glob, .. } => glob.is_match(input),
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::BinaryId(matcher, _) => matcher.is_match(query.binary_query.binary_id.as_str()),
Self::Platform(platform, _) => query.binary_query.platform == *platform,
Self::Kind(matcher, _) => matcher.is_match(query.binary_query.kind.as_str()),
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::BinaryId(matcher, _) => Some(matcher.is_match(query.binary_id.as_str())),
Self::Platform(platform, _) => Some(query.platform == *platform),
Self::Kind(matcher, _) => Some(matcher.is_match(query.kind.as_str())),
Self::Packages(packages) => Some(packages.contains(query.package_id)),
}
}
}
impl FilteringExpr {
pub fn parse(input: String, graph: &PackageGraph) -> Result<Self, FilterExpressionParseErrors> {
let mut errors = Vec::new();
match parse(new_span(&input, &mut errors)) {
Ok(parsed_expr) => {
if !errors.is_empty() {
return Err(FilterExpressionParseErrors::new(input.clone(), errors));
}
match parsed_expr {
ExprResult::Valid(parsed) => {
let compiled =
crate::compile::compile(&parsed, graph).map_err(|errors| {
FilterExpressionParseErrors::new(input.clone(), errors)
})?;
Ok(Self {
input,
parsed,
compiled,
})
}
_ => {
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 ExprFrame::*;
Wrapped(&self.compiled).collapse_frames(|layer: ExprFrame<&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),
Difference(a, b) => a.logic_and(b.logic_not()),
Parens(a) => a,
}
})
}
pub fn matches_test(&self, query: &TestQuery<'_>) -> bool {
use ExprFrame::*;
Wrapped(&self.compiled).collapse_frames(|layer: ExprFrame<&FilteringSet, bool>| match layer
{
Set(set) => set.matches_test(query),
Not(a) => !a,
Union(a, b) => a || b,
Intersection(a, b) => a && b,
Difference(a, b) => a && !b,
Parens(a) => a,
})
}
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 ExprFrame<Set, A> {
Not(A),
Union(A, A),
Intersection(A, A),
Difference(A, A),
Parens(A),
Set(Set),
}
impl<Set> MappableFrame for ExprFrame<Set, PartiallyApplied> {
type Frame<Next> = ExprFrame<Set, Next>;
fn map_frame<A, B>(input: Self::Frame<A>, mut f: impl FnMut(A) -> B) -> Self::Frame<B> {
use ExprFrame::*;
match input {
Not(a) => Not(f(a)),
Union(a, b) => Union(f(a), f(b)),
Intersection(a, b) => Intersection(f(a), f(b)),
Difference(a, b) => Difference(f(a), f(b)),
Parens(a) => Parens(f(a)),
Set(f) => Set(f),
}
}
}
pub(crate) struct Wrapped<T>(pub(crate) T);
impl<'a> Collapsible for Wrapped<&'a CompiledExpr> {
type FrameToken = ExprFrame<&'a FilteringSet, PartiallyApplied>;
fn into_frame(self) -> <Self::FrameToken as MappableFrame>::Frame<Self> {
match self.0 {
CompiledExpr::Not(a) => ExprFrame::Not(Wrapped(a.as_ref())),
CompiledExpr::Union(a, b) => ExprFrame::Union(Wrapped(a.as_ref()), Wrapped(b.as_ref())),
CompiledExpr::Intersection(a, b) => {
ExprFrame::Intersection(Wrapped(a.as_ref()), Wrapped(b.as_ref()))
}
CompiledExpr::Set(f) => ExprFrame::Set(f),
}
}
}
impl<'a> Collapsible for Wrapped<&'a ParsedExpr> {
type FrameToken = ExprFrame<&'a SetDef, PartiallyApplied>;
fn into_frame(self) -> <Self::FrameToken as MappableFrame>::Frame<Self> {
match self.0 {
ParsedExpr::Not(_, a) => ExprFrame::Not(Wrapped(a.as_ref())),
ParsedExpr::Union(_, a, b) => {
ExprFrame::Union(Wrapped(a.as_ref()), Wrapped(b.as_ref()))
}
ParsedExpr::Intersection(_, a, b) => {
ExprFrame::Intersection(Wrapped(a.as_ref()), Wrapped(b.as_ref()))
}
ParsedExpr::Difference(_, a, b) => {
ExprFrame::Difference(Wrapped(a.as_ref()), Wrapped(b.as_ref()))
}
ParsedExpr::Parens(a) => ExprFrame::Parens(Wrapped(a.as_ref())),
ParsedExpr::Set(f) => ExprFrame::Set(f),
}
}
}