use super::{
Error,
pest::{DependencyParser, Rule},
};
use crate::build_profile::BuildProfile;
use pest::Parser;
use pest::iterators::Pair;
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq)]
pub struct BuildProfileConstraint {
pub negated: bool,
pub build_profile: BuildProfile,
}
impl std::fmt::Display for BuildProfileConstraint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}",
if self.negated { "!" } else { "" },
self.build_profile
)
}
}
impl TryFrom<Pair<'_, Rule>> for BuildProfileConstraint {
type Error = Error;
fn try_from(token: Pair<'_, Rule>) -> Result<Self, Error> {
let mut negated: bool = false;
let mut build_profile: Option<String> = None;
for token in token.into_inner() {
match token.as_rule() {
Rule::not => {
if negated {
return Err(Error::InvalidBuildProfileConstraint);
}
negated = true;
}
Rule::build_profile_name => {
build_profile = Some(token.as_str().to_owned());
}
_ => continue,
};
}
let Some(build_profile) = build_profile else {
return Err(Error::InvalidBuildProfileConstraint);
};
Ok(BuildProfileConstraint {
negated,
build_profile: build_profile.parse()?,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BuildProfileConstraints {
pub build_profiles: Vec<BuildProfileConstraint>,
}
impl std::fmt::Display for BuildProfileConstraints {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.build_profiles
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(" ")
)
}
}
impl TryFrom<Pair<'_, Rule>> for BuildProfileConstraints {
type Error = Error;
fn try_from(token: Pair<'_, Rule>) -> Result<Self, Error> {
let mut constraints = BuildProfileConstraints {
build_profiles: vec![],
};
for token in token.into_inner() {
match token.as_rule() {
Rule::build_profile_constraint => {}
_ => continue,
};
constraints.build_profiles.push(token.try_into()?);
}
Ok(constraints)
}
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct BuildProfileRestrictionFormula {
pub build_profile_constraints: Vec<BuildProfileConstraints>,
}
impl From<Vec<BuildProfileConstraints>> for BuildProfileRestrictionFormula {
fn from(constraints: Vec<BuildProfileConstraints>) -> Self {
Self {
build_profile_constraints: constraints,
}
}
}
impl std::fmt::Display for BuildProfileRestrictionFormula {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.build_profile_constraints
.iter()
.map(|v| format!("<{v}>"))
.collect::<Vec<_>>()
.join(" ")
)
}
}
impl TryFrom<Pair<'_, Rule>> for BuildProfileRestrictionFormula {
type Error = Error;
fn try_from(token: Pair<'_, Rule>) -> Result<Self, Error> {
let mut ret = Self::default();
for constraints in token.into_inner() {
match constraints.as_rule() {
Rule::build_profile_constraints => {}
_ => continue,
};
ret.build_profile_constraints.push(constraints.try_into()?);
}
Ok(ret)
}
}
impl FromStr for BuildProfileRestrictionFormula {
type Err = Error;
fn from_str(v: &str) -> Result<Self, Error> {
let Some(token) =
DependencyParser::parse(Rule::build_profile_restriction_formula, v)?.next()
else {
return Ok(Default::default());
};
token.try_into()
}
}
impl BuildProfileConstraint {
pub fn matches(&self, build_profile: &BuildProfile) -> bool {
let matches = self.build_profile == *build_profile;
if self.negated { !matches } else { matches }
}
}
impl BuildProfileConstraints {
pub fn matches(&self, build_profiles: &[BuildProfile]) -> bool {
self.build_profiles.iter().any(|bpc| {
build_profiles.iter().all(|bp| bpc.matches(bp))
})
}
}
impl BuildProfileRestrictionFormula {
pub fn matches(&self, build_profile: &[BuildProfile]) -> bool {
self.build_profile_constraints
.iter()
.all(|bpc| bpc.matches(build_profile))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_profiles_syntax() {
let bprf: BuildProfileRestrictionFormula = "<!cross> <!nocheck>".parse().unwrap();
assert_eq!(2, bprf.build_profile_constraints.len());
assert_eq!(1, bprf.build_profile_constraints[0].build_profiles.len());
assert_eq!(1, bprf.build_profile_constraints[1].build_profiles.len());
}
#[test]
fn test_build_profile_matches() {
let bprf: BuildProfileRestrictionFormula = "<!cross> <!nocheck>".parse().unwrap();
let cross: BuildProfile = "cross".parse().unwrap();
let nocheck: BuildProfile = "nocheck".parse().unwrap();
let nodoc: BuildProfile = "nodoc".parse().unwrap();
assert!(!bprf.matches(&[cross]));
assert!(!bprf.matches(&[nocheck]));
assert!(bprf.matches(&[nodoc]));
}
#[test]
fn test_build_profile_muiltiple() {
let bprf: BuildProfileRestrictionFormula = "<!cross !nodoc>".parse().unwrap();
assert!(!bprf.matches(&[BuildProfile::Cross, BuildProfile::NoDoc]));
assert!(bprf.matches(&[BuildProfile::NoDoc]));
assert!(bprf.matches(&[BuildProfile::Cross]));
let bprf: BuildProfileRestrictionFormula = "<!cross !nodoc> <!nogolang>".parse().unwrap();
assert!(!bprf.matches(&[BuildProfile::Cross, BuildProfile::NoDoc]));
assert!(bprf.matches(&[BuildProfile::NoDoc]));
assert!(bprf.matches(&[BuildProfile::Cross]));
assert!(!bprf.matches(&[BuildProfile::NoGolang]));
}
}
#[cfg(feature = "serde")]
mod serde {
use super::BuildProfileRestrictionFormula;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};
impl Serialize for BuildProfileRestrictionFormula {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
String::serialize(&self.to_string(), serializer)
}
}
impl<'de> Deserialize<'de> for BuildProfileRestrictionFormula {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
s.parse().map_err(|e| D::Error::custom(format!("{e:?}")))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::control;
use std::io::{BufReader, Cursor};
#[test]
fn serde_build_profile_restriction_formula() {
#[derive(Clone, Debug, PartialEq, Deserialize)]
struct Test {
#[serde(rename = "Build-Profiles")]
build_profiles: BuildProfileRestrictionFormula,
}
let test: Test = control::de::from_reader(&mut BufReader::new(Cursor::new(
"\
Build-Profiles: <foo> <bar>
",
)))
.unwrap();
assert_eq!(test.build_profiles.build_profile_constraints.len(), 2);
}
}
}