use crate::types::{Jqesque, JqesqueError, Operation, PathToken, Separator};
use nom::{
branch::alt,
bytes::complete::{escaped_transform, is_not, take_while1},
character::complete::{char, digit1, none_of, one_of},
combinator::{all_consuming, map, map_res, opt},
multi::{many1, separated_list1},
sequence::delimited,
IResult, Parser,
};
use nom_language::error::VerboseError;
use serde_json::Value;
type Res<T, U> = IResult<T, U, VerboseError<T>>;
pub fn parse_input(input: &str, separator: Separator) -> Result<Jqesque, JqesqueError> {
let sep_char = separator.as_char();
let res = all_consuming(|i| jqesque(i, sep_char)).parse(input);
match res {
Ok((_, jqesque)) => Ok(jqesque),
Err(err) => Err(JqesqueError::NomError(format!("{}", err))),
}
}
fn jqesque(input: &str, separator: char) -> Res<&str, Jqesque> {
let (input, operation) = opt(operation_prefix).parse(input)?;
let operation = operation.unwrap_or(Operation::Auto);
let (input, (tokens, value)) = assignment(input, separator, &operation)?;
Ok((
input,
Jqesque {
operation,
tokens,
value,
},
))
}
fn operation_prefix(input: &str) -> Res<&str, Operation> {
let (input, op_char) = one_of(Operation::operators())(input)?;
let operation =
Operation::from_operator(op_char).expect("operator should be valid since we used one_of");
Ok((input, operation))
}
fn assignment<'a>(
input: &'a str,
separator: char,
operation: &Operation,
) -> Res<&'a str, (Vec<PathToken>, Option<Value>)> {
let (input, tokens) = path(input, separator)?;
let (input, value_opt) = match operation {
Operation::Remove => (input, None),
_ => {
let (input, _) = char('=')(input)?;
let (input, _) = opt(char(' ')).parse(input)?;
let (input, value) = json_value(input)?;
(input, Some(value))
}
};
Ok((input, (tokens, value_opt)))
}
fn path(input: &str, separator: char) -> Res<&str, Vec<PathToken>> {
let (input, token_vecs) =
separated_list1(char(separator), alt((array_access, key_segment))).parse(input)?;
let tokens = token_vecs.into_iter().flatten().collect();
Ok((input, tokens))
}
fn key_segment(input: &str) -> Res<&str, Vec<PathToken>> {
map(alt((quoted_string, valid_identifier)), |s: String| {
vec![PathToken::Key(s)]
})
.parse(input)
}
fn array_access(input: &str) -> Res<&str, Vec<PathToken>> {
let (input, key_opt) = opt(alt((quoted_string, valid_identifier))).parse(input)?;
let (input, indices) = many1(delimited(
char('['),
map_res(digit1, |s: &str| s.parse::<usize>()),
char(']'),
))
.parse(input)?;
let mut tokens = Vec::new();
if let Some(key) = key_opt {
tokens.push(PathToken::Key(key));
}
for index in indices {
tokens.push(PathToken::Index(index));
}
Ok((input, tokens))
}
fn valid_identifier(input: &str) -> Res<&str, String> {
map(
take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '-'),
|s: &str| s.to_string(),
)
.parse(input)
}
fn quoted_string(input: &str) -> Res<&str, String> {
delimited(
char('"'),
escaped_transform(none_of("\\\""), '\\', one_of("\\\"nrt")),
char('"'),
)
.parse(input)
}
fn json_value(input: &str) -> Res<&str, Value> {
map(is_not(""), |s: &str| {
serde_json::from_str(s).unwrap_or(Value::String(s.to_string()))
})
.parse(input)
}