refactor: Refactor filter parsing to use comma separation and JSON values
Co-authored-by: aider (openai/andrew/openrouter/sonoma-sky-alpha) <aider@aider.chat>
This commit is contained in:
@@ -1,41 +1,30 @@
|
|||||||
WHITESPACE = _{ " " | "\t" | "\n" | "\r" }
|
WHITESPACE = _{ " " | "\t" | "\n" | "\r" }
|
||||||
|
|
||||||
//! This Pest grammar defines the syntax for filter chains used in the Keep application.
|
filters = { filter ~ ("," ~ filters)? }
|
||||||
|
filter = { filter_name ~ ("(" ~ options ~ ")")? }
|
||||||
|
filter_name = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
|
||||||
|
|
||||||
// Main entry point for parsing multiple filters separated by pipes
|
options = { option ~ ("," ~ options)? }
|
||||||
filters = { SOI ~ filter ~ (pipe ~ filter)* ~ EOI }
|
option = { (option_name ~ "=")? ~ option_value }
|
||||||
|
option_name = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
|
||||||
|
|
||||||
// A single filter with optional options
|
option_value = {
|
||||||
filter = { filter_name ~ options? }
|
JSON_NUMBER |
|
||||||
|
JSON_STRING |
|
||||||
|
JSON_BOOLEAN
|
||||||
|
}
|
||||||
|
|
||||||
// Filter name (alphanumeric with underscores)
|
JSON_NUMBER = @{
|
||||||
filter_name = @{ [a-zA-Z_][a-zA-Z0-9_]* }
|
("-")? ~
|
||||||
|
("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) ~
|
||||||
|
("." ~ ASCII_DIGIT*)? ~
|
||||||
|
(("e" | "E") ~ ("+" | "-")? ~ ASCII_DIGIT+)?
|
||||||
|
}
|
||||||
|
|
||||||
// Options block with parentheses
|
JSON_STRING = ${
|
||||||
options = { "(" ~ WO ~ option* ~ WO ~ ")" }
|
"\"" ~
|
||||||
|
(("\\" ~ ANY) | (!("\"" | "\\") ~ ANY))* ~
|
||||||
|
"\""
|
||||||
|
}
|
||||||
|
|
||||||
// Single option: either named (name=value) or unnamed (just value)
|
JSON_BOOLEAN = ${ "true" | "false" }
|
||||||
option = { WO ~ (option_name ~ WO ~ "=" ~ WO ~ option_value | option_value) }
|
|
||||||
|
|
||||||
// Option name (alphanumeric with underscores)
|
|
||||||
option_name = @{ [a-zA-Z_][a-zA-Z0-9_]* }
|
|
||||||
|
|
||||||
// Option value: number, boolean, or quoted string
|
|
||||||
option_value = { number | boolean | string }
|
|
||||||
|
|
||||||
// Simple number (integer or float)
|
|
||||||
number = @{ ("-")? ~ [0-9]+ ~ ('.' ~ [0-9]+)? }
|
|
||||||
|
|
||||||
// Boolean true or false
|
|
||||||
boolean = { "true" | "false" }
|
|
||||||
|
|
||||||
// Quoted string (double or single quotes)
|
|
||||||
string = { (dquote ~ (!dquote ~ [^"])* ~ dquote) | (squote ~ (!squote ~ [^'])* ~ squote) }
|
|
||||||
dquote = { '"' }
|
|
||||||
squote = { '\'' }
|
|
||||||
|
|
||||||
// Pipe separator
|
|
||||||
pipe = { "|" }
|
|
||||||
|
|
||||||
// Optional whitespace
|
|
||||||
WO = { (WHITESPACE)* }
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use pest::Parser;
|
use pest::Parser;
|
||||||
use pest_derive::Parser;
|
use pest_derive::Parser;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[grammar = "filter.pest"]
|
#[grammar = "filter.pest"]
|
||||||
@@ -66,31 +67,7 @@ pub fn parse_filter_string(input: &str) -> Result<Vec<Filter>, Box<dyn std::erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_option_value(input: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
fn parse_option_value(input: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
||||||
// Try to parse as number
|
serde_json::from_str(input).map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
|
||||||
if let Ok(num) = input.parse::<i64>() {
|
|
||||||
return Ok(serde_json::Value::Number(num.into()));
|
|
||||||
}
|
|
||||||
if let Ok(num) = input.parse::<f64>() {
|
|
||||||
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::<bool>() {
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
@@ -128,7 +105,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_multiple_filters() {
|
fn test_parse_multiple_filters() {
|
||||||
let result = parse_filter_string(r#"head_lines(10)|grep(pattern="error")"#).unwrap();
|
let result = parse_filter_string(r#"head_lines(10),grep(pattern="error")"#).unwrap();
|
||||||
assert_eq!(result.len(), 2);
|
assert_eq!(result.len(), 2);
|
||||||
assert_eq!(result[0].name, "head_lines");
|
assert_eq!(result[0].name, "head_lines");
|
||||||
assert_eq!(result[0].options.len(), 1);
|
assert_eq!(result[0].options.len(), 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user