diff --git a/src/filter.pest b/src/filter.pest new file mode 100644 index 0000000..c4d2e94 --- /dev/null +++ b/src/filter.pest @@ -0,0 +1,30 @@ +WHITESPACE = _{ " " | "\t" | "\n" | "\r" } + +filters = { filter ~ ("," ~ filters)? } +filter = { filter_name ~ ("(" ~ options ~ ")")? } +filter_name = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } + +options = { option ~ ("," ~ options)? } +option = { (option_name ~ "=")? ~ option_value } +option_name = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } + +option_value = { + JSON_NUMBER | + JSON_STRING | + JSON_BOOLEAN +} + +JSON_NUMBER = @{ + ("-")? ~ + ("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) ~ + ("." ~ ASCII_DIGIT*)? ~ + (("e" | "E") ~ ("+" | "-")? ~ ASCII_DIGIT+)? +} + +JSON_STRING = ${ + "\"" ~ + (("\\" ~ ANY) | (!("\"" | "\\") ~ ANY))* ~ + "\"" +} + +JSON_BOOLEAN = ${ "true" | "false" } diff --git a/src/filter_parser.rs b/src/filter_parser.rs index e69de29..30b0c0a 100644 --- a/src/filter_parser.rs +++ b/src/filter_parser.rs @@ -0,0 +1,131 @@ +use pest::Parser; +use pest_derive::Parser; +use std::collections::HashMap; + +#[derive(Parser)] +#[grammar = "filter.pest"] +pub struct FilterParser; + +#[derive(Debug)] +pub struct Filter { + pub name: String, + pub options: HashMap, +} + +pub fn parse_filter_string(input: &str) -> Result, Box> { + let mut filters = Vec::new(); + let pairs = FilterParser::parse(Rule::filters, input)?; + + for pair in pairs { + if pair.as_rule() == Rule::filter { + let mut name = String::new(); + let mut options = HashMap::new(); + + for inner_pair in pair.into_inner() { + match inner_pair.as_rule() { + Rule::filter_name => { + name = inner_pair.as_str().to_string(); + } + Rule::options => { + for option_pair in inner_pair.into_inner() { + if option_pair.as_rule() == Rule::option { + let mut option_name = None; + let mut option_value = None; + + for option_inner in option_pair.into_inner() { + match option_inner.as_rule() { + Rule::option_name => { + option_name = Some(option_inner.as_str().to_string()); + } + Rule::option_value => { + option_value = Some(parse_option_value(option_inner.as_str())?); + } + _ => {} + } + } + + if let Some(value) = option_value { + // If no name is provided, use the filter name as the key + let key = option_name.unwrap_or_else(|| name.clone()); + options.insert(key, value); + } + } + } + } + _ => {} + } + } + + filters.push(Filter { name, options }); + } + } + + Ok(filters) +} + +fn parse_option_value(input: &str) -> Result> { + // Try to parse as number + if let Ok(num) = input.parse::() { + return Ok(serde_json::Value::Number(num.into())); + } + if let Ok(num) = input.parse::() { + if let Some(number) = serde_json::Number::from_f64(num) { + return Ok(serde_json::Value::Number(number)); + } + } + + // Try to parse as boolean + if let Ok(boolean) = input.parse::() { + return Ok(serde_json::Value::Bool(boolean)); + } + + // Treat as string (remove quotes if present) + let value = if input.starts_with('"') && input.ends_with('"') { + input[1..input.len()-1].to_string() + } else if input.starts_with('\'') && input.ends_with('\'') { + input[1..input.len()-1].to_string() + } else { + input.to_string() + }; + + Ok(serde_json::Value::String(value)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_simple_filter() { + let result = parse_filter_string("grep").unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].name, "grep"); + assert!(result[0].options.is_empty()); + } + + #[test] + fn test_parse_filter_with_options() { + let result = parse_filter_string("head_lines(10)").unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].name, "head_lines"); + assert_eq!(result[0].options["head_lines"], 10); + } + + #[test] + fn test_parse_filter_with_named_options() { + let result = parse_filter_string("grep(pattern=\"error\")").unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].name, "grep"); + assert_eq!(result[0].options["pattern"], "error"); + } + + #[test] + fn test_parse_multiple_filters() { + let result = parse_filter_string("head_lines(10), grep(pattern=\"error\")").unwrap(); + assert_eq!(result.len(), 2); + assert_eq!(result[0].name, "head_lines"); + assert_eq!(result[0].options["head_lines"], 10); + assert_eq!(result[1].name, "grep"); + assert_eq!(result[1].options["pattern"], "error"); + } +}