use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take_till},
character::complete::space0,
combinator::opt,
sequence::delimited,
IResult,
};
use crate::elements::Element;
use crate::parse::combinators::{blank_lines_count, line};
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug, Clone)]
pub struct Keyword<'a> {
pub key: Cow<'a, str>,
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
pub optional: Option<Cow<'a, str>>,
pub value: Cow<'a, str>,
pub post_blank: usize,
}
impl Keyword<'_> {
pub fn into_owned(self) -> Keyword<'static> {
Keyword {
key: self.key.into_owned().into(),
optional: self.optional.map(Into::into).map(Cow::Owned),
value: self.value.into_owned().into(),
post_blank: self.post_blank,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
pub struct BabelCall<'a> {
pub value: Cow<'a, str>,
pub post_blank: usize,
}
impl BabelCall<'_> {
pub fn into_owned(self) -> BabelCall<'static> {
BabelCall {
value: self.value.into_owned().into(),
post_blank: self.post_blank,
}
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(crate) struct RawKeyword<'a> {
pub key: &'a str,
pub value: &'a str,
pub optional: Option<&'a str>,
pub post_blank: usize,
}
impl<'a> RawKeyword<'a> {
pub fn parse(input: &str) -> Option<(&str, RawKeyword)> {
parse_internal(input).ok()
}
pub fn into_element(self) -> Element<'a> {
let RawKeyword {
key,
value,
optional,
post_blank,
} = self;
if (&*key).eq_ignore_ascii_case("CALL") {
BabelCall {
value: value.into(),
post_blank,
}
.into()
} else {
Keyword {
key: key.into(),
optional: optional.map(Into::into),
value: value.into(),
post_blank,
}
.into()
}
}
}
fn parse_internal(input: &str) -> IResult<&str, RawKeyword, ()> {
let (input, _) = space0(input)?;
let (input, _) = tag("#+")(input)?;
let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
let (input, optional) = opt(delimited(
tag("["),
take_till(|c| c == ']' || c == '\n'),
tag("]"),
))(input)?;
let (input, _) = tag(":")(input)?;
let (input, value) = line(input)?;
let (input, post_blank) = blank_lines_count(input)?;
Ok((
input,
RawKeyword {
key,
optional,
value: value.trim(),
post_blank,
},
))
}
#[test]
fn parse() {
assert_eq!(
RawKeyword::parse("#+KEY:"),
Some((
"",
RawKeyword {
key: "KEY",
optional: None,
value: "",
post_blank: 0
}
))
);
assert_eq!(
RawKeyword::parse("#+KEY: VALUE"),
Some((
"",
RawKeyword {
key: "KEY",
optional: None,
value: "VALUE",
post_blank: 0
}
))
);
assert_eq!(
RawKeyword::parse("#+K_E_Y: VALUE"),
Some((
"",
RawKeyword {
key: "K_E_Y",
optional: None,
value: "VALUE",
post_blank: 0
}
))
);
assert_eq!(
RawKeyword::parse("#+KEY:VALUE\n"),
Some((
"",
RawKeyword {
key: "KEY",
optional: None,
value: "VALUE",
post_blank: 0
}
))
);
assert!(RawKeyword::parse("#+KE Y: VALUE").is_none());
assert!(RawKeyword::parse("#+ KEY: VALUE").is_none());
assert_eq!(
RawKeyword::parse("#+RESULTS:"),
Some((
"",
RawKeyword {
key: "RESULTS",
optional: None,
value: "",
post_blank: 0
}
))
);
assert_eq!(
RawKeyword::parse("#+ATTR_LATEX: :width 5cm\n"),
Some((
"",
RawKeyword {
key: "ATTR_LATEX",
optional: None,
value: ":width 5cm",
post_blank: 0
}
))
);
assert_eq!(
RawKeyword::parse("#+CALL: double(n=4)"),
Some((
"",
RawKeyword {
key: "CALL",
optional: None,
value: "double(n=4)",
post_blank: 0
}
))
);
assert_eq!(
RawKeyword::parse("#+CAPTION[Short caption]: Longer caption."),
Some((
"",
RawKeyword {
key: "CAPTION",
optional: Some("Short caption"),
value: "Longer caption.",
post_blank: 0
}
))
);
}