use nom::{
AsChar, IResult, Parser,
bytes::complete::{is_not, tag, take_till, take_until},
character::complete::{alpha1, char},
sequence::{delimited, preceded},
};
pub fn parse_between_double_quotes(input: &str) -> IResult<&str, &str> {
delimited(char('"'), is_not("\""), char('"')).parse(input)
}
pub fn parse_not_double_quote(input: &str) -> IResult<&str, &str> {
is_not("\"")(input)
}
pub fn parse_not_alphanumeric(input: &str) -> IResult<&str, &str> {
take_till(|a| (a as u8).is_alphanum())(input)
}
pub fn parse_till_end_of_line(input: &str) -> IResult<&str, &str> {
take_till(|a| (a as u8).is_newline())(input)
}
#[allow(unused)]
pub fn parse_double_quoted_key_value(line: &str) -> IResult<&str, (&str, &str)> {
let (line, key) = preceded(parse_not_double_quote, parse_between_double_quotes).parse(line)?;
let (line, value) =
preceded(parse_not_double_quote, parse_between_double_quotes).parse(line)?;
Ok((line, (key, value)))
}
pub fn parse_until_key_json<'a>(file_content: &'a str, key: &'a str) -> IResult<&'a str, String> {
let quoted_key = format!("\"{key}\"");
let (file_content, _) = take_until(quoted_key.as_str()).parse(file_content)?;
Ok((file_content, quoted_key))
}
pub fn parse_until_key_yml<'a>(file_content: &'a str, key: &'a str) -> IResult<&'a str, String> {
let key_with_colon = format!("{key}:");
let (file_content, _) = take_until(key_with_colon.as_str()).parse(file_content)?;
Ok((file_content, key_with_colon))
}
pub fn parse_until_key_cfg<'a>(file_content: &'a str, key: &'a str) -> IResult<&'a str, String> {
let key_with_equals = format!("{key}=");
let (file_content, _) = take_until(key_with_equals.as_str()).parse(file_content)?;
Ok((file_content, key_with_equals))
}
pub fn parse_value_json<'a>(file_content: &'a str, key: &'a str) -> IResult<&'a str, String> {
let (file_content, matched_key) = parse_until_key_json(file_content, key)?;
let (file_content, _) = tag(matched_key.as_str()).parse(file_content)?;
let (file_content, value) =
preceded(parse_not_double_quote, parse_between_double_quotes).parse(file_content)?;
Ok((file_content, value.to_owned()))
}
pub fn parse_value_json_unquoted<'a>(
file_content: &'a str,
key: &'a str,
) -> IResult<&'a str, String> {
let (file_content, matched_key) = parse_until_key_json(file_content, key)?;
let (file_content, _) = tag(matched_key.as_str()).parse(file_content)?;
let (file_content, value) = preceded(parse_not_alphanumeric, alpha1).parse(file_content)?;
Ok((file_content, value.to_owned()))
}
pub fn parse_value_yml<'a>(file_content: &'a str, key: &'a str) -> IResult<&'a str, String> {
let (file_content, matched_key) = parse_until_key_yml(file_content, key)?;
let (file_content, _) = tag(matched_key.as_str()).parse(file_content)?;
let (file_content, value) =
preceded(parse_not_alphanumeric, parse_till_end_of_line).parse(file_content)?;
Ok((file_content, value.to_owned()))
}
pub fn parse_value_cfg<'a>(file_content: &'a str, key: &'a str) -> IResult<&'a str, String> {
let (file_content, matched_key) = parse_until_key_cfg(file_content, key)?;
let (file_content, _) = tag(matched_key.as_str()).parse(file_content)?;
let (file_content, value) = parse_till_end_of_line(file_content)?;
Ok((file_content, value.to_owned()))
}
#[cfg(test)]
mod tests {
use test_case::test_case;
use super::*;
#[test_case("\t\"key\": \"value\"", "key", "value", true)]
#[test_case("\"key\": \"value\"", "key", "value", false)]
#[test_case("\"key\": false", "key", "false", false)]
#[test_case("\n{\tkey: \"value\"}", "key", "value", false)]
#[test_case("{\n\t\"emn\": \"abc\"}", "emn", "abc", true)]
#[test_case("\t\"key\": \"new\nline\"", "key", "new\nline", true)]
#[test_case("\tkey=value", "key", "value", false)]
#[test_case("{\"key1\": \"val1\", \"key2\": \"val2\"\n}", "key1", "val1", true)]
#[test_case("{\"key1\": \"val2\", \"key2\": \"val2\"\n}", "key1", "val1", false)]
#[test_case("{\"key1\": \"val1\", \"key2\": \"val2\"\n}", "key2", "val1", false)]
fn test_parse_double_quoted_key_value(line: &str, key: &str, value: &str, should_pass: bool) {
if let Ok((_, (parsed_key, parsed_value))) = parse_double_quoted_key_value(line) {
assert_eq!(key == parsed_key && value == parsed_value, should_pass);
} else {
assert!(!should_pass);
}
}
#[test_case("\"key\": value", "key", true)]
#[test_case("\n\t\"aio\": value", "aio", true)]
#[test_case("{\n\"wrong_key\": value1\n\"key\": value\n}", "key", true)]
#[test_case("{\"wrong_key\": value1\n\"abc\": value2\n}", "abc", true)]
#[test_case("{\"key\": value1}", "abc", false)]
#[test_case("{key: value1\n\"abc\": value2\n}", "key", false)]
fn test_parse_until_key_json(file_content: &str, key: &str, should_pass: bool) {
assert_eq!(parse_until_key_json(file_content, key).is_ok(), should_pass);
}
#[test_case("key: value", "key", true)]
#[test_case("lmn: value", "lmn", true)]
#[test_case("{\nwrong_key: value1\nkey: value\n}", "key", true)]
#[test_case("key1: value1\nkey: value2\n", "key", true)]
#[test_case("{\n\twrong_key: value1\n\tabc: value2\n}", "abc", true)]
#[test_case("\"key\": value", "key", false)]
#[test_case("{\n\"wrong_key\": value1\n\"key\": value\n}", "key", false)]
fn test_parse_until_key_yml(file_content: &str, key: &str, should_pass: bool) {
assert_eq!(parse_until_key_yml(file_content, key).is_ok(), should_pass);
}
#[test_case("key=value", "key", true)]
#[test_case("\n\taio=value", "aio", true)]
#[test_case("\nwrong_key=value1\nkey=value\n", "key", true)]
#[test_case("wrong_key=value1\nabc=value2\n", "abc", true)]
#[test_case("key=value1", "abc", false)]
#[test_case("key: value1\n\"key\"=value2\n", "key", false)]
fn test_parse_until_key_cfg(file_content: &str, key: &str, should_pass: bool) {
assert_eq!(parse_until_key_cfg(file_content, key).is_ok(), should_pass);
}
#[test_case("{\"key\": \"value\"}", "key", "value", true)]
#[test_case("{\"key\": \"value\"}", "key", "wrong_value", false)]
#[test_case(
"{\"wrong_key1\": false, \"wrong_key2\": \"value1\"\n\"key\": \"value2\"\n}",
"key",
"value2",
true
)]
#[test_case("{key: \"value\"}", "key", "value", false)]
#[test_case(
"{\"wrong_key1\": false, \"wrong_key2\": \"value1\"\n\"key\": value2\n}",
"key",
"value2",
false
)]
fn test_parse_value_json(file_content: &str, key: &str, value: &str, should_pass: bool) {
if let Ok((_, parsed_value)) = parse_value_json(file_content, key) {
assert_eq!(value == parsed_value, should_pass);
} else {
assert!(!should_pass);
}
}
#[test_case("\"key\": true", "key", "true", true)]
#[test_case("\"key\": true", "key", "false", false)]
#[test_case("{key: \"true\"}", "key", "true", false)]
#[test_case("key=value", "key", "value", false)]
#[test_case(
"{\"wrong_key1\": false, \"wrong_key2\": \"value1\"\n\"key\": true\n}",
"key",
"true",
true
)]
#[test_case(
"{\"wrong_key1\": false, \"wrong_key2\": \"value1\"\n\"key\": \"value2\"\n}",
"key",
"value2",
false
)]
fn test_parse_value_json_unquoted(
file_content: &str,
key: &str,
value: &str,
should_pass: bool,
) {
if let Ok((_, parsed_value)) = parse_value_json_unquoted(file_content, key) {
assert_eq!(value == parsed_value, should_pass);
} else {
assert!(!should_pass);
}
}
#[test_case("data:\n\tkey: value", "key", "value", true)]
#[test_case("data:\n\t\"key\": value", "key", "value", false)]
#[test_case("key=value", "key", "value", false)]
#[test_case(
"data:\n\twrong_key1: false\n\twrong_key2: value1\n\tkey: value2\n",
"key",
"value2",
true
)]
#[test_case(
"data:\n\twrong_key1: false\n\twrong_key2: value1\n\tkey: abc\n",
"key",
"value2",
false
)]
fn test_parse_value_yml(file_content: &str, key: &str, value: &str, should_pass: bool) {
if let Ok((_, parsed_value)) = parse_value_yml(file_content, key) {
assert_eq!(value == parsed_value, should_pass);
} else {
assert!(!should_pass);
}
}
#[test_case("\nkey=value", "key", "value", true)]
#[test_case("key=value1\n", "key", "value", false)]
#[test_case("key=value with space\n", "key", "value with space", true)]
#[test_case("key=no new\nline\n", "key", "no new", true)]
#[test_case("key=\nvalue", "key", "value", false)]
#[test_case("\"key\": \"value\"", "key", "value", false)]
#[test_case(
"wrong_key1=false\nwrong_key2=value1\nabc=value2\n",
"abc",
"value2",
true
)]
#[test_case(
"data:\n\twrong_key1=false\n\twrong_key2=value1\n\tkey=abc\n",
"key",
"value2",
false
)]
fn test_parse_value_cfg(file_content: &str, key: &str, value: &str, should_pass: bool) {
if let Ok((_, parsed_value)) = parse_value_cfg(file_content, key) {
assert_eq!(value == parsed_value, should_pass);
} else {
assert!(!should_pass);
}
}
}