actiondb 0.7.0

A safe and efficient unstructured text (log) parsing library.
Documentation
use std::collections::BTreeSet;
use std::iter::FromIterator;
use std::hash::{SipHasher, Hash, Hasher};

use parsers::{Parser, ObjectSafeHash, ParseResult, ParserBase, HasLengthConstraint};

#[derive(Clone, Debug, Hash)]
pub struct SetParser {
    base: ParserBase,
    character_set: BTreeSet<u8>,
    min_length: Option<usize>,
    max_length: Option<usize>,
}

impl SetParser {
    pub fn with_name(name: String, set: &str) -> SetParser {
        SetParser {
            base: ParserBase::with_name(name),
            character_set: SetParser::create_set_from_str(set),
            min_length: None,
            max_length: None,
        }
    }

    pub fn new(set: &str) -> SetParser {
        SetParser {
            base: ParserBase::new(),
            character_set: SetParser::create_set_from_str(set),
            min_length: None,
            max_length: None,
        }
    }

    pub fn from_str(name: &str, set: &str) -> SetParser {
        SetParser::with_name(name.to_owned(), set)
    }

    pub fn set_character_set(&mut self, set: &str) {
        self.character_set = SetParser::create_set_from_str(set);
    }

    fn create_set_from_str(set: &str) -> BTreeSet<u8> {
        let vset: Vec<u8> = set.bytes().collect();
        BTreeSet::from_iter(vset)
    }

    fn calculate_match_length(&self, value: &str) -> usize {
        let mut match_len = 0;

        for c in value.bytes() {
            if self.character_set.contains(&c) {
                match_len += 1;
            } else {
                break;
            }
        }

        match_len
    }
}

impl HasLengthConstraint for SetParser {
    fn min_length(&self) -> Option<usize> {
        self.min_length
    }
    fn set_min_length(&mut self, length: Option<usize>) {
        self.min_length = length;
    }
    fn max_length(&self) -> Option<usize> {
        self.max_length
    }
    fn set_max_length(&mut self, length: Option<usize>) {
        self.max_length = length;
    }
}

impl Parser for SetParser {
    fn parse<'a, 'b>(&'a self, value: &'b str) -> Option<ParseResult<'a, 'b>> {
        let match_len = self.calculate_match_length(value);

        if self.is_match_length_ok(match_len) {
            Some(ParseResult::new(self, &value[..match_len]))
        } else {
            None
        }
    }

    fn name(&self) -> Option<&str> {
        self.base.name()
    }

    fn set_name(&mut self, name: Option<String>) {
        self.base.set_name(name);
    }

    fn boxed_clone(&self) -> Box<Parser> {
        Box::new(self.clone())
    }
}

impl ObjectSafeHash for SetParser {
    fn hash_os(&self) -> u64 {
        let mut hasher = SipHasher::new();
        "parser:set".hash(&mut hasher);
        self.hash(&mut hasher);
        hasher.finish()
    }
}

#[cfg(test)]
mod test {
    use parsers::{Parser, SetParser, HasLengthConstraint};

    #[test]
    fn test_given_empty_string_when_parsed_it_wont_match() {
        let p = SetParser::from_str("test", "");
        assert_eq!(p.parse("almafa").is_none(), true);
    }

    #[test]
    fn test_given_not_matching_string_when_parsed_it_wont_match() {
        let p = SetParser::from_str("test", "123");
        assert_eq!(p.parse("almafa").is_none(), true);
    }

    #[test]
    fn test_given_matching_string_when_parsed_it_matches() {
        let p = SetParser::from_str("name", "0123");
        let res = p.parse("11230almafa").unwrap();
        assert_eq!(res.parser().name(), Some("name"));
        assert_eq!(res.value(), "11230");
    }

    #[test]
    fn test_given_minimum_match_length_when_a_match_is_shorter_it_doesnt_count_as_a_match() {
        let mut p = SetParser::from_str("test", "0123");
        p.set_min_length(Some(7));
        let res = p.parse("11230almafa");
        assert_eq!(res.is_none(), true);
    }

    #[test]
    fn test_given_maximum_match_length_when_a_match_is_longer_it_doesnt_count_as_a_match() {
        let mut p = SetParser::from_str("name", "0123");
        p.set_max_length(Some(3));
        assert_eq!(p.parse("11230almafa").is_none(), true);
    }

    #[test]
    fn test_given_minimum_and_maximum_match_length_when_a_proper_length_match_occures_it_counts_as_a_match
        () {
        let mut p = SetParser::from_str("testname", "0123");
        p.set_min_length(Some(3));
        p.set_max_length(Some(7));
        let res = p.parse("11230almafa").unwrap();
        assert_eq!(res.parser().name(), Some("testname"));
        assert_eq!(res.value(), "11230");
    }

    use parsers::ObjectSafeHash;

    #[test]
    fn test_given_set_parser_and_when_differently_parametrized_instances_are_hashed_then_the_hashes_are_different
        () {
        let p1 = SetParser::from_str("test", "0123");
        let p2 = SetParser::from_str("test", "01234");
        assert_eq!(p1.hash_os() == p2.hash_os(), false);
    }
}