use std::cmp;
use std::collections::HashMap;
use std::error;
use std::fmt;
use std::str::FromStr;
use crate::version as fver;
quick_error! {
#[derive(Debug)]
enum ParseError {
CannotCompare(left: String, right: String) {
display("Cannot compare {} to {}", left, right)
}
InvalidComparisonOperator(op: String) {
display("Invalid comparison operator '{}'", op)
}
SimpleExpected {
display("Expected a 'feature-name op version' expression")
}
Uncomparable(left: String, right: String) {
display("Don't know how to compare {} to anything, including {}", left, right)
}
}
}
const RE_VAR: &str = r"[A-Za-z0-9_-]+";
const RE_VALUE: &str = r"[A-Za-z0-9.]+";
const RE_OP: &str = r"(?: < | <= | = | >= | > | lt | le | eq | ge | gt )";
#[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(s: &str) -> Result<Self, Self::Err> {
match s {
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_string())),
}
}
}
#[derive(Debug)]
pub enum CalcResult {
Null,
Bool(bool),
Version(fver::Version),
}
pub trait Calculable: fmt::Debug {
fn get_value(
&self,
features: &HashMap<String, String>,
) -> Result<CalcResult, Box<dyn error::Error>>;
}
#[derive(Debug)]
struct BoolOp {
op: BoolOpKind,
left: Box<dyn Calculable>,
right: Box<dyn Calculable>,
}
impl BoolOp {
fn new(op: BoolOpKind, left: Box<dyn Calculable>, right: Box<dyn Calculable>) -> Self {
Self { op, left, right }
}
fn boxed(op: BoolOpKind, left: Box<dyn Calculable>, right: Box<dyn Calculable>) -> Box<Self> {
Box::new(Self::new(op, left, right))
}
}
impl Calculable for BoolOp {
fn get_value(
&self,
features: &HashMap<String, String>,
) -> Result<CalcResult, Box<dyn error::Error>> {
let left = self.left.get_value(features)?;
let right = self.right.get_value(features)?;
match left {
CalcResult::Version(vleft) => match right {
CalcResult::Version(vright) => {
let ncomp = vleft.cmp(&vright);
match self.op {
BoolOpKind::LessThan => Ok(CalcResult::Bool(ncomp == cmp::Ordering::Less)),
BoolOpKind::LessThanOrEqual => {
Ok(CalcResult::Bool(ncomp != cmp::Ordering::Greater))
}
BoolOpKind::Equal => Ok(CalcResult::Bool(ncomp == cmp::Ordering::Equal)),
BoolOpKind::GreaterThanOrEqual => {
Ok(CalcResult::Bool(ncomp != cmp::Ordering::Less))
}
BoolOpKind::GreaterThan => {
Ok(CalcResult::Bool(ncomp == cmp::Ordering::Greater))
}
}
}
other => Err(Box::new(ParseError::CannotCompare(
format!("{:?}", vleft),
format!("{:?}", other),
))),
},
other => Err(Box::new(ParseError::Uncomparable(
format!("{:?}", other),
format!("{:?}", right),
))),
}
}
}
#[derive(Debug)]
struct FeatureOp {
name: String,
}
impl FeatureOp {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
fn boxed(name: &str) -> Box<Self> {
Box::new(Self::new(name))
}
}
impl Calculable for FeatureOp {
fn get_value(
&self,
features: &HashMap<String, String>,
) -> Result<CalcResult, Box<dyn error::Error>> {
match features.get(&self.name) {
Some(value) => Ok(CalcResult::Version(value.parse()?)),
None => Ok(CalcResult::Null),
}
}
}
#[derive(Debug)]
struct VersionOp {
value: String,
}
impl VersionOp {
fn new(value: &str) -> Self {
Self {
value: value.to_string(),
}
}
fn boxed(value: &str) -> Box<Self> {
Box::new(Self::new(value))
}
}
impl Calculable for VersionOp {
fn get_value(
&self,
_features: &HashMap<String, String>,
) -> Result<CalcResult, Box<dyn error::Error>> {
Ok(CalcResult::Version(self.value.parse()?))
}
}
pub fn parse_simple(expr: &str) -> Result<Box<dyn Calculable>, Box<dyn error::Error>> {
let re_simple = regex::Regex::new(&format!(
r"(?x) ^ (?P<var> {} ) \s* (?P<op> {} ) \s* (?P<value> {} ) $",
RE_VAR, RE_OP, RE_VALUE
))
.unwrap();
match re_simple.captures(expr) {
Some(caps) => {
let feature = &caps["var"];
let op_name = &caps["op"];
let value = &caps["value"];
Ok(BoolOp::boxed(
op_name.parse()?,
FeatureOp::boxed(feature),
VersionOp::boxed(value),
))
}
None => Err(Box::new(ParseError::SimpleExpected)),
}
}