use std::error::Error as EError;
use std::result::Result as RResult;
use error::*;
pub trait Condition {
type Issue;
fn satisfied_by(&self, issue: &Self::Issue) -> bool;
}
#[derive(Debug, PartialEq, Eq)]
pub enum MatchOp {
Equivalence,
LowerThan,
GreaterThan,
LowerThanOrEqual,
GreaterThanOrEqual,
Contains,
}
pub trait ConditionFactory<C>
where C: Condition + Sized
{
type Error : From<Error> + EError;
fn make_condition(
&self,
name: &str,
neg: bool,
val_op: Option<(MatchOp, &str)>
) -> RResult<C, Self::Error>;
fn parse_condition(
&self,
string: &str,
) -> RResult<C, Self::Error> {
parse_condition(string)
.map_err(From::from)
.and_then(|(name, neg, op_val)| self.make_condition(name, neg, op_val))
}
}
pub fn parse_condition(string: &str) -> Result<(&str, bool, Option<(MatchOp, &str)>)> {
if let Some(pos) = string.find(|ref c| reserved_char(c)) {
if pos == 0 {
let (neg, name) = string.split_at(1);
return if neg == "!" && !name.contains(|ref c| reserved_char(c)) {
Ok((name, true, None))
} else {
Err(Error::from(ErrorKind::ConditionParseError))
}
}
let (name, mut op_val) = string.split_at(pos);
let negated = op_val.starts_with('!');
if negated {
op_val = op_val.split_at(1).1;
}
Ok((name, negated, parse_op_val(op_val)?.into()))
} else {
Ok((string, false, None))
}
}
fn reserved_char(c: &char) -> bool {
['!', '=', '<', '>', '~'].contains(c)
}
fn parse_op_val(string: &str) -> Result<(MatchOp, &str)> {
let mut chars = string.chars();
let (op, pos) = match chars.next() {
Some('=') => (MatchOp::Equivalence, 1),
Some('<') => match chars.next() {
Some('=') => (MatchOp::LowerThanOrEqual, 2),
_ => (MatchOp::LowerThan, 1),
},
Some('>') => match chars.next() {
Some('=') => (MatchOp::GreaterThanOrEqual, 2),
_ => (MatchOp::GreaterThan, 1),
},
Some('~') => (MatchOp::Contains, 1),
_ => return Err(Error::from(ErrorKind::ConditionParseError)),
};
Ok((op, string.split_at(pos).1))
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(string: &str) -> (&str, bool, Option<(MatchOp, &str)>) {
parse_condition(string).expect("Failed to parse condition atom!")
}
#[test]
fn smoke() {
assert_eq!(parse("foo"), ("foo", false, None));
assert_eq!(parse("!foo"), ("foo", true, None));
assert_eq!(parse("foo=bar"), ("foo", false, Some((MatchOp::Equivalence, "bar"))));
assert_eq!(parse("foo<bar"), ("foo", false, Some((MatchOp::LowerThan, "bar"))));
assert_eq!(parse("foo>bar"), ("foo", false, Some((MatchOp::GreaterThan, "bar"))));
assert_eq!(parse("foo<=bar"), ("foo", false, Some((MatchOp::LowerThanOrEqual, "bar"))));
assert_eq!(parse("foo>=bar"), ("foo", false, Some((MatchOp::GreaterThanOrEqual, "bar"))));
assert_eq!(parse("foo!~bar"), ("foo", true, Some((MatchOp::Contains, "bar"))));
assert_eq!(parse("foo!=bar"), ("foo", true, Some((MatchOp::Equivalence, "bar"))));
assert_eq!(parse("foo!<bar"), ("foo", true, Some((MatchOp::LowerThan, "bar"))));
assert_eq!(parse("foo!>bar"), ("foo", true, Some((MatchOp::GreaterThan, "bar"))));
assert_eq!(parse("foo!<=bar"), ("foo", true, Some((MatchOp::LowerThanOrEqual, "bar"))));
assert_eq!(parse("foo!>=bar"), ("foo", true, Some((MatchOp::GreaterThanOrEqual, "bar"))));
assert_eq!(parse("foo!~bar"), ("foo", true, Some((MatchOp::Contains, "bar"))));
}
}