#![allow(clippy::pub_use)]
use std::cmp::Ordering;
use std::collections::HashMap;
use std::str::FromStr;
use crate::defs::{Mode, ParseError};
use crate::version::Version;
pub mod parser;
pub use crate::defs::{CalcResult, Calculable};
#[derive(Debug)]
enum BoolOpKind {
LessThan,
LessThanOrEqual,
Equal,
GreaterThanOrEqual,
GreaterThan,
}
impl BoolOpKind {
const LT: &'static str = "<";
const LE: &'static str = "<=";
const EQ: &'static str = "=";
const GT: &'static str = ">";
const GE: &'static str = ">=";
const LT_S: &'static str = "lt";
const LE_S: &'static str = "le";
const EQ_S: &'static str = "eq";
const GE_S: &'static str = "ge";
const GT_S: &'static str = "gt";
}
impl FromStr for BoolOpKind {
type Err = ParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
Self::LT | Self::LT_S => Ok(Self::LessThan),
Self::LE | Self::LE_S => Ok(Self::LessThanOrEqual),
Self::EQ | Self::EQ_S => Ok(Self::Equal),
Self::GE | Self::GE_S => Ok(Self::GreaterThanOrEqual),
Self::GT | Self::GT_S => Ok(Self::GreaterThan),
other => Err(ParseError::InvalidComparisonOperator(other.to_owned())),
}
}
}
#[derive(Debug)]
struct BoolOp {
op: BoolOpKind,
left: Box<dyn Calculable + 'static>,
right: Box<dyn Calculable + 'static>,
}
impl BoolOp {
fn new(op: BoolOpKind, left: Box<dyn Calculable>, right: Box<dyn Calculable>) -> Self {
Self { op, left, right }
}
}
impl Calculable for BoolOp {
fn get_value(&self, features: &HashMap<String, Version>) -> Result<CalcResult, ParseError> {
let left = self.left.get_value(features)?;
let right = self.right.get_value(features)?;
if let CalcResult::Version(ver_left) = left {
if let CalcResult::Version(ver_right) = right {
let ncomp = ver_left.cmp(&ver_right);
match self.op {
BoolOpKind::LessThan => Ok(CalcResult::Bool(ncomp == Ordering::Less)),
BoolOpKind::LessThanOrEqual => Ok(CalcResult::Bool(ncomp != Ordering::Greater)),
BoolOpKind::Equal => Ok(CalcResult::Bool(ncomp == Ordering::Equal)),
BoolOpKind::GreaterThanOrEqual => Ok(CalcResult::Bool(ncomp != Ordering::Less)),
BoolOpKind::GreaterThan => Ok(CalcResult::Bool(ncomp == Ordering::Greater)),
}
} else {
Err(ParseError::CannotCompare(
format!("{ver_left:?}"),
format!("{right:?}"),
))
}
} else {
Err(ParseError::Uncomparable(
format!("{left:?}"),
format!("{right:?}"),
))
}
}
}
#[derive(Debug)]
struct FeatureOp {
name: String,
}
impl FeatureOp {
fn new(name: &str) -> Self {
Self {
name: name.to_owned(),
}
}
}
impl Calculable for FeatureOp {
fn get_value(&self, features: &HashMap<String, Version>) -> Result<CalcResult, ParseError> {
Ok(features
.get(&self.name)
.map_or(CalcResult::Null, |value| CalcResult::Version(value.clone())))
}
}
#[derive(Debug)]
struct VersionOp {
value: Version,
}
impl VersionOp {
const fn from_version(version: Version) -> Self {
Self { value: version }
}
}
impl Calculable for VersionOp {
fn get_value(&self, _features: &HashMap<String, Version>) -> Result<CalcResult, ParseError> {
Ok(CalcResult::Version(self.value.clone()))
}
}
#[inline]
pub fn parse(expr: &str) -> Result<Mode, ParseError> {
parser::parse_expr(expr)
}
#[cfg(test)]
mod tests {
#![allow(clippy::panic)]
#![allow(clippy::panic_in_result_fn)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::use_debug)]
#![allow(clippy::wildcard_enum_match_arm)]
use std::collections::HashMap;
use std::error::Error;
use crate::defs::{CalcResult, Mode};
#[test]
fn test_parse_mode_simple_sign_no_space() -> Result<(), Box<dyn Error>> {
let mode = super::parse("hello<3.1")?;
let res = match mode {
Mode::Simple(res) => res,
other => panic!("{other:?}"),
};
match res.get_value(&HashMap::from([("hello".to_owned(), "2".parse()?)]))? {
CalcResult::Bool(true) => (),
other => panic!("{other:?}"),
};
match res.get_value(&HashMap::from([("hello".to_owned(), "4".parse()?)]))? {
CalcResult::Bool(false) => (),
other => panic!("{other:?}"),
};
res.get_value(&HashMap::new()).unwrap_err();
Ok(())
}
#[test]
fn test_parse_mode_simple_sign_space() -> Result<(), Box<dyn Error>> {
let mode = super::parse("hello < 3.1")?;
let res = match mode {
Mode::Simple(res) => res,
other => panic!("{other:?}"),
};
match res.get_value(&HashMap::from([("hello".to_owned(), "2".parse()?)]))? {
CalcResult::Bool(true) => (),
other => panic!("{other:?}"),
};
match res.get_value(&HashMap::from([("hello".to_owned(), "4".parse()?)]))? {
CalcResult::Bool(false) => (),
other => panic!("{other:?}"),
};
res.get_value(&HashMap::new()).unwrap_err();
Ok(())
}
#[test]
fn test_parse_mode_simple_word() -> Result<(), Box<dyn Error>> {
let mode = super::parse("hello lt 3.1")?;
let res = match mode {
Mode::Simple(res) => res,
other => panic!("{other:?}"),
};
match res.get_value(&HashMap::from([("hello".to_owned(), "2".parse()?)]))? {
CalcResult::Bool(true) => (),
other => panic!("{other:?}"),
};
match res.get_value(&HashMap::from([("hello".to_owned(), "4".parse()?)]))? {
CalcResult::Bool(false) => (),
other => panic!("{other:?}"),
};
res.get_value(&HashMap::new()).unwrap_err();
Ok(())
}
#[test]
fn test_parse_mode_single() -> Result<(), Box<dyn Error>> {
let mode = super::parse("hello")?;
let res = match mode {
Mode::Single(res) => res,
other => panic!("{other:?}"),
};
match res.get_value(&HashMap::from([("hello".to_owned(), "2".parse()?)]))? {
CalcResult::Version(ver) => assert_eq!(ver.as_ref(), "2"),
other => panic!("{other:?}"),
};
match res.get_value(&HashMap::new())? {
CalcResult::Null => (),
other => panic!("{other:?}"),
};
Ok(())
}
}