use super::pest::{Deb822Parser, Rule};
use pest::{Parser, error::Error as PestError, iterators::Pair};
#[derive(Clone, Debug, Default, PartialEq)]
pub struct RawParagraph {
pub fields: Vec<RawField>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RawField {
pub key: String,
pub value: String,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
Parse((String, pest::error::InputLocation)),
Malformed,
}
crate::errors::error_enum!(Error);
impl From<PestError<Rule>> for Error {
fn from(err: PestError<Rule>) -> Self {
Error::Parse((err.variant.message().into(), err.location))
}
}
impl TryFrom<Pair<'_, Rule>> for RawField {
type Error = Error;
fn try_from(token: Pair<'_, Rule>) -> Result<Self, Error> {
let mut key: Option<String> = None;
let mut value = String::new();
for part in token.into_inner() {
match part.as_rule() {
Rule::field_name => {
key = Some(part.as_str().to_owned());
}
Rule::field_value => {
value.push_str(&format!("{}\n", part.as_str()));
}
_ => continue,
};
}
let Some(key) = key else {
return Err(Error::Malformed);
};
Ok(RawField {
key,
value: value.trim_start_matches(' ').trim_end().to_owned(),
})
}
}
impl TryFrom<Pair<'_, Rule>> for RawParagraph {
type Error = Error;
fn try_from(token: Pair<'_, Rule>) -> Result<Self, Error> {
let mut ret = Self { fields: vec![] };
for token in token.into_inner() {
match token.as_rule() {
Rule::comment => {}
Rule::field => {
ret.fields.push(token.try_into()?);
}
_ => {}
}
}
Ok(ret)
}
}
impl RawParagraph {
pub fn parse(paragraph: &str) -> Result<Self, Error> {
let tokens = Deb822Parser::parse(Rule::single_paragraph, paragraph)?;
let Some(token) = tokens.into_iter().next() else {
unreachable!();
};
for token in token.into_inner() {
#[allow(clippy::single_match)]
match token.as_rule() {
Rule::paragraph => return token.try_into(),
_ => {}
}
}
Ok(Default::default())
}
pub fn iter(&self) -> impl Iterator<Item = &RawField> {
self.fields.iter()
}
pub fn field<'field>(
&'field self,
field_name: &'field str,
) -> impl Iterator<Item = &'field RawField> {
self.fields.iter().filter(move |f| f.key == field_name)
}
}
#[cfg(test)]
mod tests {
use crate::control::RawParagraph;
macro_rules! check_paragraph_parse {
($name:ident, $paragraph:expr, |$para:ident| $block:tt ) => {
#[test]
fn $name() {
let $para = RawParagraph::parse($paragraph).unwrap();
$block;
}
};
}
macro_rules! check_paragraph_parse_fails {
($name:ident, $paragraph:expr) => {
#[test]
fn $name() {
assert!(RawParagraph::parse($paragraph).is_err());
}
};
}
check_paragraph_parse!(
check_parse_comment,
"\
Key: Value
# Comment
Key1: Value1
Key2: Value2
",
|p| {
assert_eq!("Value", p.field("Key").next().unwrap().value);
assert_eq!("Value1", p.field("Key1").next().unwrap().value);
assert_eq!("Value2", p.field("Key2").next().unwrap().value);
}
);
check_paragraph_parse!(
check_parse_comment_end,
"\
Key: Value
Key1: Value1
Key2: Value2
# Comment
",
|p| {
assert_eq!("Value", p.field("Key").next().unwrap().value);
assert_eq!("Value1", p.field("Key1").next().unwrap().value);
assert_eq!("Value2", p.field("Key2").next().unwrap().value);
}
);
check_paragraph_parse!(
check_parse_no_nl,
"\
Key: Value
Key1: Value1
Key2: Value2
",
|p| {
assert_eq!("Value", p.field("Key").next().unwrap().value);
assert_eq!("Value1", p.field("Key1").next().unwrap().value);
assert_eq!("Value2", p.field("Key2").next().unwrap().value);
}
);
check_paragraph_parse!(
check_parse_eof,
"\
Key: Value
Key1: Value1
Key2: Value2",
|p| {
assert_eq!("Value", p.field("Key").next().unwrap().value);
assert_eq!("Value1", p.field("Key1").next().unwrap().value);
assert_eq!("Value2", p.field("Key2").next().unwrap().value);
}
);
check_paragraph_parse!(
check_parse_nlnlnlnl,
"\
Key: Value
Key1: Value1
Key2: Value2
",
|p| {
assert_eq!("Value", p.field("Key").next().unwrap().value);
assert_eq!("Value1", p.field("Key1").next().unwrap().value);
assert_eq!("Value2", p.field("Key2").next().unwrap().value);
}
);
check_paragraph_parse_fails!(
check_fails_invalid_key,
"\
Foo bar: 1
"
);
check_paragraph_parse_fails!(
check_fails_two_paragraphs,
"\
Foo: Bar
Bar: Baz
"
);
check_paragraph_parse!(
check_parse_confusing_sep,
"\
Key:Name: Value?
",
|p| {
assert_eq!("Name: Value?", p.field("Key").next().unwrap().value);
}
);
}