230 lines
5.5 KiB
Rust
230 lines
5.5 KiB
Rust
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};
|
|
|
|
/// Keyword Element
|
|
#[cfg_attr(test, derive(PartialEq))]
|
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
#[derive(Debug, Clone)]
|
|
pub struct Keyword<'a> {
|
|
/// Keyword name
|
|
pub key: Cow<'a, str>,
|
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
pub optional: Option<Cow<'a, str>>,
|
|
/// Keyword value
|
|
pub value: Cow<'a, str>,
|
|
/// Numbers of blank lines between keyword line and next non-blank line or
|
|
/// buffer's end
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Babel Call Element
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(test, derive(PartialEq))]
|
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
pub struct BabelCall<'a> {
|
|
/// Babel call value
|
|
pub value: Cow<'a, str>,
|
|
/// Numbers of blank lines between babel call line and next non-blank line
|
|
/// or buffer's end
|
|
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
|
|
}
|
|
))
|
|
);
|
|
}
|