From f51b06d25c263f7b9417a84a56779a1be4b10340 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 29 Sep 2018 16:43:50 +0100 Subject: [PATCH] Add parsing of version and checksum conditions --- src/function.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 8 deletions(-) diff --git a/src/function.rs b/src/function.rs index 4b4b1ec..25b95c1 100644 --- a/src/function.rs +++ b/src/function.rs @@ -1,12 +1,12 @@ -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::str; -use nom::{Context, Err, ErrorKind, IResult}; +use nom::{hex_digit, Context, Err, ErrorKind, IResult}; use regex::Regex; use super::Error; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum ComparisonOperator { Equal, NotEqual, @@ -16,6 +16,23 @@ pub enum ComparisonOperator { GreaterThanOrEqual, } +impl ComparisonOperator { + pub fn parse(input: &str) -> IResult<&str, ComparisonOperator> { + do_parse!( + input, + operator: + alt!( + tag!("==") => { |_| ComparisonOperator::Equal } | + tag!("!=") => { |_| ComparisonOperator::NotEqual } | + tag!("<") => { |_| ComparisonOperator::LessThan } | + tag!(">") => { |_| ComparisonOperator::GreaterThan } | + tag!("<=") => { |_| ComparisonOperator::LessThanOrEqual } | + tag!(">=") => { |_| ComparisonOperator::GreaterThanOrEqual } + ) >> (operator) + ) + } +} + #[derive(Debug)] pub enum Function { FilePath(PathBuf), @@ -31,10 +48,47 @@ pub enum Function { const INVALID_PATH_CHARS: &str = "\":*?<>|\\"; // \ is treated as invalid to distinguish regex strings. const INVALID_REGEX_PATH_CHARS: &str = "\"<>"; +const PARSE_REGEX_ERROR: ErrorKind = ErrorKind::Custom(1); +const PARSE_CRC_ERROR: ErrorKind = ErrorKind::Custom(2); + fn parse_regex(input: &str) -> IResult<&str, Regex> { Regex::new(input) .map(|r| ("", r)) - .map_err(|e| Err::Failure(Context::Code(input, ErrorKind::Custom(1)))) + .map_err(|_| Err::Failure(Context::Code(input, PARSE_REGEX_ERROR))) +} + +fn parse_version_args(input: &str) -> IResult<&str, (PathBuf, &str, ComparisonOperator)> { + do_parse!( + input, + tag!("\"") + >> path: is_not!(INVALID_PATH_CHARS) + >> tag!("\"") + >> ws!(tag!(",")) + >> tag!("\"") + >> version: is_not!("\"") + >> tag!("\"") + >> ws!(tag!(",")) + >> operator: call!(ComparisonOperator::parse) + >> ((PathBuf::from(path), version, operator)) + ) +} + +fn parse_crc(input: &str) -> IResult<&str, u32> { + u32::from_str_radix(input, 16) + .map(|c| ("", c)) + .map_err(|_| Err::Failure(Context::Code(input, PARSE_CRC_ERROR))) +} + +fn parse_checksum_args(input: &str) -> IResult<&str, (PathBuf, u32)> { + do_parse!( + input, + tag!("\"") + >> path: is_not!(INVALID_PATH_CHARS) + >> tag!("\"") + >> ws!(tag!(",")) + >> crc: flat_map!(call!(hex_digit), parse_crc) + >> ((PathBuf::from(path), crc)) + ) } impl Function { @@ -48,7 +102,6 @@ impl Function { } pub fn parse(input: &str) -> IResult<&str, Function> { - // TODO: Handle all variants. do_parse!( input, function: @@ -70,6 +123,14 @@ impl Function { } | delimited!(tag!("many_active(\""), flat_map!(is_not!(INVALID_REGEX_PATH_CHARS), parse_regex), tag!("\"")) => { |r| Function::ManyActive(r) + } | + delimited!(tag!("version("), call!(parse_version_args), tag!(")")) => { + |(path, version, comparator): (PathBuf, &str, ComparisonOperator)| { + Function::Version(path, version.to_string(), comparator) + } + } | + delimited!(tag!("checksum("), call!(parse_checksum_args), tag!(")")) => { + |(path, crc)| Function::Checksum(path, crc) } ) >> (function) ) @@ -80,12 +141,14 @@ impl Function { mod tests { use super::*; + use std::path::Path; + #[test] fn function_parse_should_parse_a_file_path_function() { let result = Function::parse("file(\"Cargo.toml\")").unwrap().1; match result { - Function::FilePath(f) => assert_eq!(PathBuf::from("Cargo.toml"), f), + Function::FilePath(f) => assert_eq!(Path::new("Cargo.toml"), f), _ => panic!("Expected a file path function"), } } @@ -107,7 +170,7 @@ mod tests { let result = Function::parse("active(\"Cargo.toml\")").unwrap().1; match result { - Function::ActivePath(f) => assert_eq!(PathBuf::from("Cargo.toml"), f), + Function::ActivePath(f) => assert_eq!(Path::new("Cargo.toml"), f), _ => panic!("Expected an active path function"), } } @@ -131,7 +194,7 @@ mod tests { match result { Function::Many(r) => { assert_eq!(Regex::new("Cargo.*").unwrap().as_str(), r.as_str()) - } + }, _ => panic!("Expected a many function"), } } @@ -148,6 +211,37 @@ mod tests { } } + #[test] + fn function_parse_should_parse_a_checksum_function() { + let result = Function::parse("checksum(\"Cargo.toml\", DEADBEEF)") + .unwrap() + .1; + + match result { + Function::Checksum(path, crc) => { + assert_eq!(Path::new("Cargo.toml"), path); + assert_eq!(0xDEADBEEF, crc); + } + _ => panic!("Expected a checksum function"), + } + } + + #[test] + fn function_parse_should_parse_a_version_equals_function() { + let result = Function::parse("version(\"Cargo.toml\", \"1.2\", ==)") + .unwrap() + .1; + + match result { + Function::Version(path, version, comparator) => { + assert_eq!(Path::new("Cargo.toml"), path); + assert_eq!("1.2", version); + assert_eq!(ComparisonOperator::Equal, comparator); + } + _ => panic!("Expected a checksum function"), + } + } + #[test] fn function_file_path_eval_should_return_true_if_the_file_exists_relative_to_the_data_path() { let function = Function::FilePath(PathBuf::from("Cargo.toml"));