feat: pre_blank and post_blank
This commit is contained in:
parent
1a0240a747
commit
948b1be2db
|
@ -5,7 +5,7 @@ use nom::{
|
||||||
sequence::preceded, IResult,
|
sequence::preceded, IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parsers::{line, take_lines_while};
|
use crate::parsers::{blank_lines, line, take_lines_while};
|
||||||
|
|
||||||
/// Special Block Element
|
/// Special Block Element
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -13,9 +13,14 @@ use crate::parsers::{line, take_lines_while};
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
pub struct SpecialBlock<'a> {
|
pub struct SpecialBlock<'a> {
|
||||||
/// Optional block parameters
|
/// Optional block parameters
|
||||||
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub parameters: Option<Cow<'a, str>>,
|
pub parameters: Option<Cow<'a, str>>,
|
||||||
/// Block name
|
/// Block name
|
||||||
pub name: Cow<'a, str>,
|
pub name: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub pre_blank: usize,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpecialBlock<'_> {
|
impl SpecialBlock<'_> {
|
||||||
|
@ -23,6 +28,8 @@ impl SpecialBlock<'_> {
|
||||||
SpecialBlock {
|
SpecialBlock {
|
||||||
name: self.name.into_owned().into(),
|
name: self.name.into_owned().into(),
|
||||||
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
||||||
|
pre_blank: self.pre_blank,
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,13 +40,20 @@ impl SpecialBlock<'_> {
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
pub struct QuoteBlock<'a> {
|
pub struct QuoteBlock<'a> {
|
||||||
/// Optional block parameters
|
/// Optional block parameters
|
||||||
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub parameters: Option<Cow<'a, str>>,
|
pub parameters: Option<Cow<'a, str>>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub pre_blank: usize,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QuoteBlock<'_> {
|
impl QuoteBlock<'_> {
|
||||||
pub fn into_owned(self) -> QuoteBlock<'static> {
|
pub fn into_owned(self) -> QuoteBlock<'static> {
|
||||||
QuoteBlock {
|
QuoteBlock {
|
||||||
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
||||||
|
pre_blank: self.pre_blank,
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,13 +64,20 @@ impl QuoteBlock<'_> {
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
pub struct CenterBlock<'a> {
|
pub struct CenterBlock<'a> {
|
||||||
/// Optional block parameters
|
/// Optional block parameters
|
||||||
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub parameters: Option<Cow<'a, str>>,
|
pub parameters: Option<Cow<'a, str>>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub pre_blank: usize,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CenterBlock<'_> {
|
impl CenterBlock<'_> {
|
||||||
pub fn into_owned(self) -> CenterBlock<'static> {
|
pub fn into_owned(self) -> CenterBlock<'static> {
|
||||||
CenterBlock {
|
CenterBlock {
|
||||||
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
||||||
|
pre_blank: self.pre_blank,
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,13 +88,20 @@ impl CenterBlock<'_> {
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
pub struct VerseBlock<'a> {
|
pub struct VerseBlock<'a> {
|
||||||
/// Optional block parameters
|
/// Optional block parameters
|
||||||
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub parameters: Option<Cow<'a, str>>,
|
pub parameters: Option<Cow<'a, str>>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub pre_blank: usize,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VerseBlock<'_> {
|
impl VerseBlock<'_> {
|
||||||
pub fn into_owned(self) -> VerseBlock<'static> {
|
pub fn into_owned(self) -> VerseBlock<'static> {
|
||||||
VerseBlock {
|
VerseBlock {
|
||||||
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
||||||
|
pre_blank: self.pre_blank,
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,9 +111,12 @@ impl VerseBlock<'_> {
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
pub struct CommentBlock<'a> {
|
pub struct CommentBlock<'a> {
|
||||||
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub data: Option<Cow<'a, str>>,
|
pub data: Option<Cow<'a, str>>,
|
||||||
/// Comment, without block's boundaries
|
/// Comment, without block's boundaries
|
||||||
pub contents: Cow<'a, str>,
|
pub contents: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommentBlock<'_> {
|
impl CommentBlock<'_> {
|
||||||
|
@ -93,6 +124,7 @@ impl CommentBlock<'_> {
|
||||||
CommentBlock {
|
CommentBlock {
|
||||||
data: self.data.map(Into::into).map(Cow::Owned),
|
data: self.data.map(Into::into).map(Cow::Owned),
|
||||||
contents: self.contents.into_owned().into(),
|
contents: self.contents.into_owned().into(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,9 +134,12 @@ impl CommentBlock<'_> {
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
pub struct ExampleBlock<'a> {
|
pub struct ExampleBlock<'a> {
|
||||||
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub data: Option<Cow<'a, str>>,
|
pub data: Option<Cow<'a, str>>,
|
||||||
/// Block contents
|
/// Block contents
|
||||||
pub contents: Cow<'a, str>,
|
pub contents: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExampleBlock<'_> {
|
impl ExampleBlock<'_> {
|
||||||
|
@ -112,6 +147,7 @@ impl ExampleBlock<'_> {
|
||||||
ExampleBlock {
|
ExampleBlock {
|
||||||
data: self.data.map(Into::into).map(Cow::Owned),
|
data: self.data.map(Into::into).map(Cow::Owned),
|
||||||
contents: self.contents.into_owned().into(),
|
contents: self.contents.into_owned().into(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +160,8 @@ pub struct ExportBlock<'a> {
|
||||||
pub data: Cow<'a, str>,
|
pub data: Cow<'a, str>,
|
||||||
/// Block contents
|
/// Block contents
|
||||||
pub contents: Cow<'a, str>,
|
pub contents: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExportBlock<'_> {
|
impl ExportBlock<'_> {
|
||||||
|
@ -131,12 +169,13 @@ impl ExportBlock<'_> {
|
||||||
ExportBlock {
|
ExportBlock {
|
||||||
data: self.data.into_owned().into(),
|
data: self.data.into_owned().into(),
|
||||||
contents: self.contents.into_owned().into(),
|
contents: self.contents.into_owned().into(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Src Block Element
|
/// Src Block Element
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
pub struct SourceBlock<'a> {
|
pub struct SourceBlock<'a> {
|
||||||
|
@ -145,6 +184,8 @@ pub struct SourceBlock<'a> {
|
||||||
/// Language of the code in the block
|
/// Language of the code in the block
|
||||||
pub language: Cow<'a, str>,
|
pub language: Cow<'a, str>,
|
||||||
pub arguments: Cow<'a, str>,
|
pub arguments: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceBlock<'_> {
|
impl SourceBlock<'_> {
|
||||||
|
@ -153,6 +194,7 @@ impl SourceBlock<'_> {
|
||||||
language: self.language.into_owned().into(),
|
language: self.language.into_owned().into(),
|
||||||
arguments: self.arguments.into_owned().into(),
|
arguments: self.arguments.into_owned().into(),
|
||||||
contents: self.contents.into_owned().into(),
|
contents: self.contents.into_owned().into(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,20 +206,21 @@ impl SourceBlock<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parse_block_element(input: &str) -> Option<(&str, (&str, Option<&str>, &str))> {
|
pub fn parse_block_element(input: &str) -> Option<(&str, (&str, Option<&str>, &str, usize))> {
|
||||||
parse_block_element_internal::<()>(input).ok()
|
parse_block_element_internal::<()>(input).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn parse_block_element_internal<'a, E: ParseError<&'a str>>(
|
fn parse_block_element_internal<'a, E: ParseError<&'a str>>(
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
) -> IResult<&str, (&str, Option<&str>, &str), E> {
|
) -> IResult<&str, (&str, Option<&str>, &str, usize), E> {
|
||||||
let (input, name) = preceded(tag_no_case("#+BEGIN_"), alpha1)(input)?;
|
let (input, name) = preceded(tag_no_case("#+BEGIN_"), alpha1)(input)?;
|
||||||
let (input, args) = line(input)?;
|
let (input, args) = line(input)?;
|
||||||
let end_line = format!("#+END_{}", name);
|
let end_line = format!("#+END_{}", name);
|
||||||
let (input, contents) =
|
let (input, contents) =
|
||||||
take_lines_while(|line| !line.trim().eq_ignore_ascii_case(&end_line))(input);
|
take_lines_while(|line| !line.trim().eq_ignore_ascii_case(&end_line))(input);
|
||||||
let (input, _) = line(input)?;
|
let (input, _) = line(input)?;
|
||||||
|
let (input, blank) = blank_lines(input);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
|
@ -189,6 +232,7 @@ fn parse_block_element_internal<'a, E: ParseError<&'a str>>(
|
||||||
Some(args.trim())
|
Some(args.trim())
|
||||||
},
|
},
|
||||||
contents,
|
contents,
|
||||||
|
blank,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -202,20 +246,21 @@ fn parse() {
|
||||||
r#"#+BEGIN_SRC
|
r#"#+BEGIN_SRC
|
||||||
#+END_SRC"#
|
#+END_SRC"#
|
||||||
),
|
),
|
||||||
Ok(("", ("SRC".into(), None, "")))
|
Ok(("", ("SRC".into(), None, "", 0)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_block_element_internal::<VerboseError<&str>>(
|
parse_block_element_internal::<VerboseError<&str>>(
|
||||||
r#"#+begin_src
|
r#"#+begin_src
|
||||||
#+end_src"#
|
#+end_src"#
|
||||||
),
|
),
|
||||||
Ok(("", ("src".into(), None, "")))
|
Ok(("", ("src".into(), None, "", 0)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_block_element_internal::<VerboseError<&str>>(
|
parse_block_element_internal::<VerboseError<&str>>(
|
||||||
r#"#+BEGIN_SRC javascript
|
r#"#+BEGIN_SRC javascript
|
||||||
console.log('Hello World!');
|
console.log('Hello World!');
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
Ok((
|
Ok((
|
||||||
|
@ -223,7 +268,8 @@ console.log('Hello World!');
|
||||||
(
|
(
|
||||||
"SRC".into(),
|
"SRC".into(),
|
||||||
Some("javascript".into()),
|
Some("javascript".into()),
|
||||||
"console.log('Hello World!');\n"
|
"console.log('Hello World!');\n",
|
||||||
|
1
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,7 +11,7 @@ use nom::{
|
||||||
|
|
||||||
use crate::elements::timestamp::{parse_inactive, Datetime, Timestamp};
|
use crate::elements::timestamp::{parse_inactive, Datetime, Timestamp};
|
||||||
|
|
||||||
use crate::parsers::eol;
|
use crate::parsers::{blank_lines, eol};
|
||||||
|
|
||||||
/// Clock Element
|
/// Clock Element
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
@ -31,6 +31,8 @@ pub enum Clock<'a> {
|
||||||
delay: Option<Cow<'a, str>>,
|
delay: Option<Cow<'a, str>>,
|
||||||
/// Clock duration
|
/// Clock duration
|
||||||
duration: Cow<'a, str>,
|
duration: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
post_blank: usize,
|
||||||
},
|
},
|
||||||
/// Running Clock
|
/// Running Clock
|
||||||
Running {
|
Running {
|
||||||
|
@ -40,6 +42,8 @@ pub enum Clock<'a> {
|
||||||
repeater: Option<Cow<'a, str>>,
|
repeater: Option<Cow<'a, str>>,
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
delay: Option<Cow<'a, str>>,
|
delay: Option<Cow<'a, str>>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
post_blank: usize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,21 +60,25 @@ impl Clock<'_> {
|
||||||
repeater,
|
repeater,
|
||||||
delay,
|
delay,
|
||||||
duration,
|
duration,
|
||||||
|
post_blank,
|
||||||
} => Clock::Closed {
|
} => Clock::Closed {
|
||||||
start: start.into_owned(),
|
start: start.into_owned(),
|
||||||
end: end.into_owned(),
|
end: end.into_owned(),
|
||||||
repeater: repeater.map(Into::into).map(Cow::Owned),
|
repeater: repeater.map(Into::into).map(Cow::Owned),
|
||||||
delay: delay.map(Into::into).map(Cow::Owned),
|
delay: delay.map(Into::into).map(Cow::Owned),
|
||||||
duration: duration.into_owned().into(),
|
duration: duration.into_owned().into(),
|
||||||
|
post_blank,
|
||||||
},
|
},
|
||||||
Clock::Running {
|
Clock::Running {
|
||||||
start,
|
start,
|
||||||
repeater,
|
repeater,
|
||||||
delay,
|
delay,
|
||||||
|
post_blank,
|
||||||
} => Clock::Running {
|
} => Clock::Running {
|
||||||
start: start.into_owned(),
|
start: start.into_owned(),
|
||||||
repeater: repeater.map(Into::into).map(Cow::Owned),
|
repeater: repeater.map(Into::into).map(Cow::Owned),
|
||||||
delay: delay.map(Into::into).map(Cow::Owned),
|
delay: delay.map(Into::into).map(Cow::Owned),
|
||||||
|
post_blank,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,6 +126,7 @@ impl Clock<'_> {
|
||||||
start,
|
start,
|
||||||
repeater,
|
repeater,
|
||||||
delay,
|
delay,
|
||||||
|
..
|
||||||
} => Timestamp::Inactive {
|
} => Timestamp::Inactive {
|
||||||
start: start.clone(),
|
start: start.clone(),
|
||||||
repeater: repeater.clone(),
|
repeater: repeater.clone(),
|
||||||
|
@ -144,6 +153,7 @@ fn parse_clock<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, Cloc
|
||||||
let (input, _) = space0(input)?;
|
let (input, _) = space0(input)?;
|
||||||
let (input, duration) = recognize(separated_pair(digit1, char(':'), digit1))(input)?;
|
let (input, duration) = recognize(separated_pair(digit1, char(':'), digit1))(input)?;
|
||||||
let (input, _) = eol(input)?;
|
let (input, _) = eol(input)?;
|
||||||
|
let (input, blank) = blank_lines(input);
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Clock::Closed {
|
Clock::Closed {
|
||||||
|
@ -152,6 +162,7 @@ fn parse_clock<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, Cloc
|
||||||
repeater,
|
repeater,
|
||||||
delay,
|
delay,
|
||||||
duration: duration.into(),
|
duration: duration.into(),
|
||||||
|
post_blank: blank,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -161,12 +172,14 @@ fn parse_clock<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, Cloc
|
||||||
delay,
|
delay,
|
||||||
} => {
|
} => {
|
||||||
let (input, _) = eol(input)?;
|
let (input, _) = eol(input)?;
|
||||||
|
let (input, blank) = blank_lines(input);
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Clock::Running {
|
Clock::Running {
|
||||||
start,
|
start,
|
||||||
repeater,
|
repeater,
|
||||||
delay,
|
delay,
|
||||||
|
post_blank: blank,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -195,12 +208,13 @@ fn parse() {
|
||||||
},
|
},
|
||||||
repeater: None,
|
repeater: None,
|
||||||
delay: None,
|
delay: None,
|
||||||
|
post_blank: 0,
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_clock::<VerboseError<&str>>(
|
parse_clock::<VerboseError<&str>>(
|
||||||
"CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"
|
"CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00\n\n"
|
||||||
),
|
),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
|
@ -224,6 +238,7 @@ fn parse() {
|
||||||
repeater: None,
|
repeater: None,
|
||||||
delay: None,
|
delay: None,
|
||||||
duration: "1:00".into(),
|
duration: "1:00".into(),
|
||||||
|
post_blank: 1,
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
36
src/elements/comment.rs
Normal file
36
src/elements/comment.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::parsers::{blank_lines, take_lines_while};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
|
pub struct Comment<'a> {
|
||||||
|
pub value: Cow<'a, str>,
|
||||||
|
pub post_blank: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Comment<'_> {
|
||||||
|
pub(crate) fn parse(input: &str) -> Option<(&str, Comment<'_>)> {
|
||||||
|
let (input, value) = take_lines_while(|line| line == "#" || line.starts_with("# "))(input);
|
||||||
|
let (input, blank) = blank_lines(input);
|
||||||
|
|
||||||
|
if value.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((
|
||||||
|
input,
|
||||||
|
Comment {
|
||||||
|
value: value.into(),
|
||||||
|
post_blank: blank,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_owned(self) -> Comment<'static> {
|
||||||
|
Comment {
|
||||||
|
value: self.value.into_owned().into(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,15 +7,19 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parsers::{eol, line, take_lines_while};
|
use crate::parsers::{blank_lines, eol, line, take_lines_while};
|
||||||
|
|
||||||
/// Drawer Element
|
/// Drawer Element
|
||||||
|
#[derive(Debug, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Drawer<'a> {
|
pub struct Drawer<'a> {
|
||||||
/// Drawer name
|
/// Drawer name
|
||||||
pub name: Cow<'a, str>,
|
pub name: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub pre_blank: usize,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drawer<'_> {
|
impl Drawer<'_> {
|
||||||
|
@ -26,6 +30,8 @@ impl Drawer<'_> {
|
||||||
pub fn into_owned(self) -> Drawer<'static> {
|
pub fn into_owned(self) -> Drawer<'static> {
|
||||||
Drawer {
|
Drawer {
|
||||||
name: self.name.into_owned().into(),
|
name: self.name.into_owned().into(),
|
||||||
|
pre_blank: self.pre_blank,
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +39,20 @@ impl Drawer<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parse_drawer<'a, E: ParseError<&'a str>>(
|
pub fn parse_drawer<'a, E: ParseError<&'a str>>(
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
|
) -> IResult<&str, (Drawer, &str), E> {
|
||||||
|
let (input, (mut drawer, content)) = parse_drawer_without_blank(input)?;
|
||||||
|
|
||||||
|
let (content, blank) = blank_lines(content);
|
||||||
|
drawer.pre_blank = blank;
|
||||||
|
|
||||||
|
let (input, blank) = blank_lines(input);
|
||||||
|
drawer.post_blank = blank;
|
||||||
|
|
||||||
|
Ok((input, (drawer, content)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_drawer_without_blank<'a, E: ParseError<&'a str>>(
|
||||||
|
input: &'a str,
|
||||||
) -> IResult<&str, (Drawer, &str), E> {
|
) -> IResult<&str, (Drawer, &str), E> {
|
||||||
let (input, name) = delimited(
|
let (input, name) = delimited(
|
||||||
tag(":"),
|
tag(":"),
|
||||||
|
@ -44,7 +64,17 @@ pub fn parse_drawer<'a, E: ParseError<&'a str>>(
|
||||||
take_lines_while(|line| !line.trim().eq_ignore_ascii_case(":END:"))(input);
|
take_lines_while(|line| !line.trim().eq_ignore_ascii_case(":END:"))(input);
|
||||||
let (input, _) = line(input)?;
|
let (input, _) = line(input)?;
|
||||||
|
|
||||||
Ok((input, (Drawer { name: name.into() }, contents)))
|
Ok((
|
||||||
|
input,
|
||||||
|
(
|
||||||
|
Drawer {
|
||||||
|
name: name.into(),
|
||||||
|
pre_blank: 0,
|
||||||
|
post_blank: 0,
|
||||||
|
},
|
||||||
|
contents,
|
||||||
|
),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -52,24 +82,39 @@ fn parse() {
|
||||||
use nom::error::VerboseError;
|
use nom::error::VerboseError;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_drawer::<VerboseError<&str>>(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
|
parse_drawer::<VerboseError<&str>>(
|
||||||
|
r#":PROPERTIES:
|
||||||
|
:CUSTOM_ID: id
|
||||||
|
:END:"#
|
||||||
|
),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
(
|
(
|
||||||
Drawer {
|
Drawer {
|
||||||
name: "PROPERTIES".into()
|
name: "PROPERTIES".into(),
|
||||||
|
pre_blank: 0,
|
||||||
|
post_blank: 0
|
||||||
},
|
},
|
||||||
" :CUSTOM_ID: id\n"
|
" :CUSTOM_ID: id\n"
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_drawer::<VerboseError<&str>>(":PROPERTIES:\n :END:"),
|
parse_drawer::<VerboseError<&str>>(
|
||||||
|
r#":PROPERTIES:
|
||||||
|
|
||||||
|
|
||||||
|
:END:
|
||||||
|
|
||||||
|
"#
|
||||||
|
),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
(
|
(
|
||||||
Drawer {
|
Drawer {
|
||||||
name: "PROPERTIES".into()
|
name: "PROPERTIES".into(),
|
||||||
|
pre_blank: 2,
|
||||||
|
post_blank: 1,
|
||||||
},
|
},
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,18 +7,22 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parsers::{line, take_lines_while};
|
use crate::parsers::{blank_lines, line, take_lines_while};
|
||||||
|
|
||||||
/// Dynamic Block Element
|
/// Dynamic Block Element
|
||||||
|
#[derive(Debug, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DynBlock<'a> {
|
pub struct DynBlock<'a> {
|
||||||
/// Block name
|
/// Block name
|
||||||
pub block_name: Cow<'a, str>,
|
pub block_name: Cow<'a, str>,
|
||||||
/// Block argument
|
/// Block argument
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub arguments: Option<Cow<'a, str>>,
|
pub arguments: Option<Cow<'a, str>>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub pre_blank: usize,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DynBlock<'_> {
|
impl DynBlock<'_> {
|
||||||
|
@ -30,6 +34,8 @@ impl DynBlock<'_> {
|
||||||
DynBlock {
|
DynBlock {
|
||||||
block_name: self.block_name.into_owned().into(),
|
block_name: self.block_name.into_owned().into(),
|
||||||
arguments: self.arguments.map(Into::into).map(Cow::Owned),
|
arguments: self.arguments.map(Into::into).map(Cow::Owned),
|
||||||
|
pre_blank: self.pre_blank,
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +50,9 @@ fn parse_dyn_block<'a, E: ParseError<&'a str>>(
|
||||||
let (input, args) = line(input)?;
|
let (input, args) = line(input)?;
|
||||||
let (input, contents) =
|
let (input, contents) =
|
||||||
take_lines_while(|line| !line.trim().eq_ignore_ascii_case("#+END:"))(input);
|
take_lines_while(|line| !line.trim().eq_ignore_ascii_case("#+END:"))(input);
|
||||||
|
let (contents, pre_blank) = blank_lines(contents);
|
||||||
let (input, _) = line(input)?;
|
let (input, _) = line(input)?;
|
||||||
|
let (input, post_blank) = blank_lines(input);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
|
@ -56,6 +64,8 @@ fn parse_dyn_block<'a, E: ParseError<&'a str>>(
|
||||||
} else {
|
} else {
|
||||||
Some(args.trim().into())
|
Some(args.trim().into())
|
||||||
},
|
},
|
||||||
|
pre_blank,
|
||||||
|
post_blank,
|
||||||
},
|
},
|
||||||
contents,
|
contents,
|
||||||
),
|
),
|
||||||
|
@ -70,8 +80,11 @@ fn parse() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_dyn_block::<VerboseError<&str>>(
|
parse_dyn_block::<VerboseError<&str>>(
|
||||||
r#"#+BEGIN: clocktable :scope file
|
r#"#+BEGIN: clocktable :scope file
|
||||||
|
|
||||||
|
|
||||||
CONTENTS
|
CONTENTS
|
||||||
#+END:
|
#+END:
|
||||||
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
Ok((
|
Ok((
|
||||||
|
@ -80,6 +93,8 @@ CONTENTS
|
||||||
DynBlock {
|
DynBlock {
|
||||||
block_name: "clocktable".into(),
|
block_name: "clocktable".into(),
|
||||||
arguments: Some(":scope file".into()),
|
arguments: Some(":scope file".into()),
|
||||||
|
pre_blank: 2,
|
||||||
|
post_blank: 1,
|
||||||
},
|
},
|
||||||
"CONTENTS\n"
|
"CONTENTS\n"
|
||||||
)
|
)
|
||||||
|
|
63
src/elements/fixed_width.rs
Normal file
63
src/elements/fixed_width.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::parsers::{blank_lines, take_lines_while};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
|
pub struct FixedWidth<'a> {
|
||||||
|
pub value: Cow<'a, str>,
|
||||||
|
pub post_blank: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedWidth<'_> {
|
||||||
|
pub(crate) fn parse(input: &str) -> Option<(&str, FixedWidth<'_>)> {
|
||||||
|
let (input, value) = take_lines_while(|line| line == ":" || line.starts_with(": "))(input);
|
||||||
|
let (input, blank) = blank_lines(input);
|
||||||
|
|
||||||
|
if value.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((
|
||||||
|
input,
|
||||||
|
FixedWidth {
|
||||||
|
value: value.into(),
|
||||||
|
post_blank: blank,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_owned(self) -> FixedWidth<'static> {
|
||||||
|
FixedWidth {
|
||||||
|
value: self.value.into_owned().into(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
assert_eq!(
|
||||||
|
FixedWidth::parse(
|
||||||
|
r#": A
|
||||||
|
:
|
||||||
|
: B
|
||||||
|
: C
|
||||||
|
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
Some((
|
||||||
|
"",
|
||||||
|
FixedWidth {
|
||||||
|
value: r#": A
|
||||||
|
:
|
||||||
|
: B
|
||||||
|
: C
|
||||||
|
"#
|
||||||
|
.into(),
|
||||||
|
post_blank: 1
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
|
@ -7,15 +7,17 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parsers::line;
|
use crate::parsers::{blank_lines, line};
|
||||||
|
|
||||||
/// Footnote Definition Element
|
/// Footnote Definition Element
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct FnDef<'a> {
|
pub struct FnDef<'a> {
|
||||||
/// Footnote label, used for refrence
|
/// Footnote label, used for refrence
|
||||||
pub label: Cow<'a, str>,
|
pub label: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FnDef<'_> {
|
impl FnDef<'_> {
|
||||||
|
@ -26,6 +28,7 @@ impl FnDef<'_> {
|
||||||
pub fn into_owned(self) -> FnDef<'static> {
|
pub fn into_owned(self) -> FnDef<'static> {
|
||||||
FnDef {
|
FnDef {
|
||||||
label: self.label.into_owned().into(),
|
label: self.label.into_owned().into(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,12 +41,14 @@ fn parse_fn_def<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, (Fn
|
||||||
tag("]"),
|
tag("]"),
|
||||||
)(input)?;
|
)(input)?;
|
||||||
let (input, content) = line(input)?;
|
let (input, content) = line(input)?;
|
||||||
|
let (input, blank) = blank_lines(input);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
(
|
(
|
||||||
FnDef {
|
FnDef {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
|
post_blank: blank,
|
||||||
},
|
},
|
||||||
content,
|
content,
|
||||||
),
|
),
|
||||||
|
@ -56,7 +61,16 @@ fn parse() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_fn_def::<VerboseError<&str>>("[fn:1] https://orgmode.org"),
|
parse_fn_def::<VerboseError<&str>>("[fn:1] https://orgmode.org"),
|
||||||
Ok(("", (FnDef { label: "1".into() }, " https://orgmode.org")))
|
Ok((
|
||||||
|
"",
|
||||||
|
(
|
||||||
|
FnDef {
|
||||||
|
label: "1".into(),
|
||||||
|
post_blank: 0
|
||||||
|
},
|
||||||
|
" https://orgmode.org"
|
||||||
|
)
|
||||||
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_fn_def::<VerboseError<&str>>("[fn:word_1] https://orgmode.org"),
|
parse_fn_def::<VerboseError<&str>>("[fn:word_1] https://orgmode.org"),
|
||||||
|
@ -64,7 +78,8 @@ fn parse() {
|
||||||
"",
|
"",
|
||||||
(
|
(
|
||||||
FnDef {
|
FnDef {
|
||||||
label: "word_1".into()
|
label: "word_1".into(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
" https://orgmode.org"
|
" https://orgmode.org"
|
||||||
)
|
)
|
||||||
|
@ -76,7 +91,8 @@ fn parse() {
|
||||||
"",
|
"",
|
||||||
(
|
(
|
||||||
FnDef {
|
FnDef {
|
||||||
label: "WORD-1".into()
|
label: "WORD-1".into(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
" https://orgmode.org"
|
" https://orgmode.org"
|
||||||
)
|
)
|
||||||
|
@ -88,7 +104,8 @@ fn parse() {
|
||||||
"",
|
"",
|
||||||
(
|
(
|
||||||
FnDef {
|
FnDef {
|
||||||
label: "WORD".into()
|
label: "WORD".into(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,7 +8,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parsers::line;
|
use crate::parsers::{blank_lines, line};
|
||||||
|
|
||||||
/// Keyword Elemenet
|
/// Keyword Elemenet
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
@ -21,6 +21,8 @@ pub struct Keyword<'a> {
|
||||||
pub optional: Option<Cow<'a, str>>,
|
pub optional: Option<Cow<'a, str>>,
|
||||||
/// Keyword value
|
/// Keyword value
|
||||||
pub value: Cow<'a, str>,
|
pub value: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyword<'_> {
|
impl Keyword<'_> {
|
||||||
|
@ -29,6 +31,7 @@ impl Keyword<'_> {
|
||||||
key: self.key.into_owned().into(),
|
key: self.key.into_owned().into(),
|
||||||
optional: self.optional.map(Into::into).map(Cow::Owned),
|
optional: self.optional.map(Into::into).map(Cow::Owned),
|
||||||
value: self.value.into_owned().into(),
|
value: self.value.into_owned().into(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,25 +42,28 @@ impl Keyword<'_> {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BabelCall<'a> {
|
pub struct BabelCall<'a> {
|
||||||
pub value: Cow<'a, str>,
|
pub value: Cow<'a, str>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BabelCall<'_> {
|
impl BabelCall<'_> {
|
||||||
pub fn into_owned(self) -> BabelCall<'static> {
|
pub fn into_owned(self) -> BabelCall<'static> {
|
||||||
BabelCall {
|
BabelCall {
|
||||||
value: self.value.into_owned().into(),
|
value: self.value.into_owned().into(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parse_keyword(input: &str) -> Option<(&str, (&str, Option<&str>, &str))> {
|
pub fn parse_keyword(input: &str) -> Option<(&str, (&str, Option<&str>, &str, usize))> {
|
||||||
parse_keyword_internal::<()>(input).ok()
|
parse_keyword_internal::<()>(input).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn parse_keyword_internal<'a, E: ParseError<&'a str>>(
|
fn parse_keyword_internal<'a, E: ParseError<&'a str>>(
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
) -> IResult<&str, (&str, Option<&str>, &str), E> {
|
) -> IResult<&str, (&str, Option<&str>, &str, usize), E> {
|
||||||
let (input, _) = tag("#+")(input)?;
|
let (input, _) = tag("#+")(input)?;
|
||||||
let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
|
let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
|
||||||
let (input, optional) = opt(delimited(
|
let (input, optional) = opt(delimited(
|
||||||
|
@ -67,8 +73,9 @@ fn parse_keyword_internal<'a, E: ParseError<&'a str>>(
|
||||||
))(input)?;
|
))(input)?;
|
||||||
let (input, _) = tag(":")(input)?;
|
let (input, _) = tag(":")(input)?;
|
||||||
let (input, value) = line(input)?;
|
let (input, value) = line(input)?;
|
||||||
|
let (input, blank) = blank_lines(input);
|
||||||
|
|
||||||
Ok((input, (key, optional, value.trim())))
|
Ok((input, (key, optional, value.trim(), blank)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -77,40 +84,40 @@ fn parse() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_keyword_internal::<VerboseError<&str>>("#+KEY:"),
|
parse_keyword_internal::<VerboseError<&str>>("#+KEY:"),
|
||||||
Ok(("", ("KEY", None, "")))
|
Ok(("", ("KEY", None, "", 0)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_keyword_internal::<VerboseError<&str>>("#+KEY: VALUE"),
|
parse_keyword_internal::<VerboseError<&str>>("#+KEY: VALUE"),
|
||||||
Ok(("", ("KEY", None, "VALUE")))
|
Ok(("", ("KEY", None, "VALUE", 0)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_keyword_internal::<VerboseError<&str>>("#+K_E_Y: VALUE"),
|
parse_keyword_internal::<VerboseError<&str>>("#+K_E_Y: VALUE"),
|
||||||
Ok(("", ("K_E_Y", None, "VALUE")))
|
Ok(("", ("K_E_Y", None, "VALUE", 0)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_keyword_internal::<VerboseError<&str>>("#+KEY:VALUE\n"),
|
parse_keyword_internal::<VerboseError<&str>>("#+KEY:VALUE\n"),
|
||||||
Ok(("", ("KEY", None, "VALUE")))
|
Ok(("", ("KEY", None, "VALUE", 0)))
|
||||||
);
|
);
|
||||||
assert!(parse_keyword_internal::<VerboseError<&str>>("#+KE Y: VALUE").is_err());
|
assert!(parse_keyword_internal::<VerboseError<&str>>("#+KE Y: VALUE").is_err());
|
||||||
assert!(parse_keyword_internal::<VerboseError<&str>>("#+ KEY: VALUE").is_err());
|
assert!(parse_keyword_internal::<VerboseError<&str>>("#+ KEY: VALUE").is_err());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_keyword_internal::<VerboseError<&str>>("#+RESULTS:"),
|
parse_keyword_internal::<VerboseError<&str>>("#+RESULTS:"),
|
||||||
Ok(("", ("RESULTS", None, "")))
|
Ok(("", ("RESULTS", None, "", 0)))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_keyword_internal::<VerboseError<&str>>("#+ATTR_LATEX: :width 5cm\n"),
|
parse_keyword_internal::<VerboseError<&str>>("#+ATTR_LATEX: :width 5cm\n"),
|
||||||
Ok(("", ("ATTR_LATEX", None, ":width 5cm")))
|
Ok(("", ("ATTR_LATEX", None, ":width 5cm", 0)))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_keyword_internal::<VerboseError<&str>>("#+CALL: double(n=4)"),
|
parse_keyword_internal::<VerboseError<&str>>("#+CALL: double(n=4)"),
|
||||||
Ok(("", ("CALL", None, "double(n=4)")))
|
Ok(("", ("CALL", None, "double(n=4)", 0)))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_keyword_internal::<VerboseError<&str>>("#+CAPTION[Short caption]: Longer caption."),
|
parse_keyword_internal::<VerboseError<&str>>("#+CAPTION[Short caption]: Longer caption."),
|
||||||
Ok(("", ("CAPTION", Some("Short caption"), "Longer caption.",)))
|
Ok(("", ("CAPTION", Some("Short caption"), "Longer caption.", 0)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
pub(crate) mod block;
|
pub(crate) mod block;
|
||||||
pub(crate) mod clock;
|
pub(crate) mod clock;
|
||||||
|
pub(crate) mod comment;
|
||||||
pub(crate) mod cookie;
|
pub(crate) mod cookie;
|
||||||
pub(crate) mod drawer;
|
pub(crate) mod drawer;
|
||||||
pub(crate) mod dyn_block;
|
pub(crate) mod dyn_block;
|
||||||
pub(crate) mod emphasis;
|
pub(crate) mod emphasis;
|
||||||
|
pub(crate) mod fixed_width;
|
||||||
pub(crate) mod fn_def;
|
pub(crate) mod fn_def;
|
||||||
pub(crate) mod fn_ref;
|
pub(crate) mod fn_ref;
|
||||||
pub(crate) mod inline_call;
|
pub(crate) mod inline_call;
|
||||||
|
@ -29,9 +31,11 @@ pub use self::{
|
||||||
SpecialBlock, VerseBlock,
|
SpecialBlock, VerseBlock,
|
||||||
},
|
},
|
||||||
clock::Clock,
|
clock::Clock,
|
||||||
|
comment::Comment,
|
||||||
cookie::Cookie,
|
cookie::Cookie,
|
||||||
drawer::Drawer,
|
drawer::Drawer,
|
||||||
dyn_block::DynBlock,
|
dyn_block::DynBlock,
|
||||||
|
fixed_width::FixedWidth,
|
||||||
fn_def::FnDef,
|
fn_def::FnDef,
|
||||||
fn_ref::FnRef,
|
fn_ref::FnRef,
|
||||||
inline_call::InlineCall,
|
inline_call::InlineCall,
|
||||||
|
@ -41,6 +45,7 @@ pub use self::{
|
||||||
list::{List, ListItem},
|
list::{List, ListItem},
|
||||||
macros::Macros,
|
macros::Macros,
|
||||||
planning::Planning,
|
planning::Planning,
|
||||||
|
rule::Rule,
|
||||||
snippet::Snippet,
|
snippet::Snippet,
|
||||||
table::{Table, TableRow},
|
table::{Table, TableRow},
|
||||||
target::Target,
|
target::Target,
|
||||||
|
@ -52,7 +57,6 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
/// Element Enum
|
/// Element Enum
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[cfg_attr(feature = "ser", serde(tag = "type", rename_all = "kebab-case"))]
|
#[cfg_attr(feature = "ser", serde(tag = "type", rename_all = "kebab-case"))]
|
||||||
pub enum Element<'a> {
|
pub enum Element<'a> {
|
||||||
|
@ -70,7 +74,7 @@ pub enum Element<'a> {
|
||||||
Cookie(Cookie<'a>),
|
Cookie(Cookie<'a>),
|
||||||
RadioTarget,
|
RadioTarget,
|
||||||
Drawer(Drawer<'a>),
|
Drawer(Drawer<'a>),
|
||||||
Document,
|
Document { pre_blank: usize },
|
||||||
DynBlock(DynBlock<'a>),
|
DynBlock(DynBlock<'a>),
|
||||||
FnDef(FnDef<'a>),
|
FnDef(FnDef<'a>),
|
||||||
FnRef(FnRef<'a>),
|
FnRef(FnRef<'a>),
|
||||||
|
@ -84,8 +88,8 @@ pub enum Element<'a> {
|
||||||
Macros(Macros<'a>),
|
Macros(Macros<'a>),
|
||||||
Snippet(Snippet<'a>),
|
Snippet(Snippet<'a>),
|
||||||
Text { value: Cow<'a, str> },
|
Text { value: Cow<'a, str> },
|
||||||
Paragraph,
|
Paragraph { post_blank: usize },
|
||||||
Rule,
|
Rule(Rule),
|
||||||
Timestamp(Timestamp<'a>),
|
Timestamp(Timestamp<'a>),
|
||||||
Target(Target<'a>),
|
Target(Target<'a>),
|
||||||
Bold,
|
Bold,
|
||||||
|
@ -94,8 +98,8 @@ pub enum Element<'a> {
|
||||||
Underline,
|
Underline,
|
||||||
Verbatim { value: Cow<'a, str> },
|
Verbatim { value: Cow<'a, str> },
|
||||||
Code { value: Cow<'a, str> },
|
Code { value: Cow<'a, str> },
|
||||||
Comment { value: Cow<'a, str> },
|
Comment(Comment<'a>),
|
||||||
FixedWidth { value: Cow<'a, str> },
|
FixedWidth(FixedWidth<'a>),
|
||||||
Title(Title<'a>),
|
Title(Title<'a>),
|
||||||
Table(Table<'a>),
|
Table(Table<'a>),
|
||||||
TableRow(TableRow),
|
TableRow(TableRow),
|
||||||
|
@ -112,13 +116,13 @@ impl Element<'_> {
|
||||||
| CenterBlock(_)
|
| CenterBlock(_)
|
||||||
| VerseBlock(_)
|
| VerseBlock(_)
|
||||||
| Bold
|
| Bold
|
||||||
| Document
|
| Document { .. }
|
||||||
| DynBlock(_)
|
| DynBlock(_)
|
||||||
| Headline { .. }
|
| Headline { .. }
|
||||||
| Italic
|
| Italic
|
||||||
| List(_)
|
| List(_)
|
||||||
| ListItem(_)
|
| ListItem(_)
|
||||||
| Paragraph
|
| Paragraph { .. }
|
||||||
| Section
|
| Section
|
||||||
| Strike
|
| Strike
|
||||||
| Underline
|
| Underline
|
||||||
|
@ -148,7 +152,7 @@ impl Element<'_> {
|
||||||
Cookie(e) => Cookie(e.into_owned()),
|
Cookie(e) => Cookie(e.into_owned()),
|
||||||
RadioTarget => RadioTarget,
|
RadioTarget => RadioTarget,
|
||||||
Drawer(e) => Drawer(e.into_owned()),
|
Drawer(e) => Drawer(e.into_owned()),
|
||||||
Document => Document,
|
Document { pre_blank } => Document { pre_blank },
|
||||||
DynBlock(e) => DynBlock(e.into_owned()),
|
DynBlock(e) => DynBlock(e.into_owned()),
|
||||||
FnDef(e) => FnDef(e.into_owned()),
|
FnDef(e) => FnDef(e.into_owned()),
|
||||||
FnRef(e) => FnRef(e.into_owned()),
|
FnRef(e) => FnRef(e.into_owned()),
|
||||||
|
@ -164,8 +168,8 @@ impl Element<'_> {
|
||||||
Text { value } => Text {
|
Text { value } => Text {
|
||||||
value: value.into_owned().into(),
|
value: value.into_owned().into(),
|
||||||
},
|
},
|
||||||
Paragraph => Paragraph,
|
Paragraph { post_blank } => Paragraph { post_blank },
|
||||||
Rule => Rule,
|
Rule(e) => Rule(e),
|
||||||
Timestamp(e) => Timestamp(e.into_owned()),
|
Timestamp(e) => Timestamp(e.into_owned()),
|
||||||
Target(e) => Target(e.into_owned()),
|
Target(e) => Target(e.into_owned()),
|
||||||
Bold => Bold,
|
Bold => Bold,
|
||||||
|
@ -178,12 +182,8 @@ impl Element<'_> {
|
||||||
Code { value } => Code {
|
Code { value } => Code {
|
||||||
value: value.into_owned().into(),
|
value: value.into_owned().into(),
|
||||||
},
|
},
|
||||||
Comment { value } => Comment {
|
Comment(e) => Comment(e.into_owned()),
|
||||||
value: value.into_owned().into(),
|
FixedWidth(e) => FixedWidth(e.into_owned()),
|
||||||
},
|
|
||||||
FixedWidth { value } => FixedWidth {
|
|
||||||
value: value.into_owned().into(),
|
|
||||||
},
|
|
||||||
Title(e) => Title(e.into_owned()),
|
Title(e) => Title(e.into_owned()),
|
||||||
Table(e) => Table(e.into_owned()),
|
Table(e) => Table(e.into_owned()),
|
||||||
TableRow(e) => TableRow(e),
|
TableRow(e) => TableRow(e),
|
||||||
|
@ -215,12 +215,14 @@ impl_from!(
|
||||||
BabelCall,
|
BabelCall,
|
||||||
CenterBlock,
|
CenterBlock,
|
||||||
Clock,
|
Clock,
|
||||||
|
Comment,
|
||||||
CommentBlock,
|
CommentBlock,
|
||||||
Cookie,
|
Cookie,
|
||||||
Drawer,
|
Drawer,
|
||||||
DynBlock,
|
DynBlock,
|
||||||
ExampleBlock,
|
ExampleBlock,
|
||||||
ExportBlock,
|
ExportBlock,
|
||||||
|
FixedWidth,
|
||||||
FnDef,
|
FnDef,
|
||||||
FnRef,
|
FnRef,
|
||||||
InlineCall,
|
InlineCall,
|
||||||
|
@ -233,11 +235,12 @@ impl_from!(
|
||||||
Snippet,
|
Snippet,
|
||||||
SourceBlock,
|
SourceBlock,
|
||||||
SpecialBlock,
|
SpecialBlock,
|
||||||
|
Table,
|
||||||
Target,
|
Target,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
Table,
|
|
||||||
Title,
|
Title,
|
||||||
VerseBlock;
|
VerseBlock;
|
||||||
List,
|
List,
|
||||||
|
Rule,
|
||||||
TableRow
|
TableRow
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
use std::usize;
|
|
||||||
|
|
||||||
use nom::{bytes::complete::take_while_m_n, error::ParseError, IResult};
|
use nom::{bytes::complete::take_while_m_n, error::ParseError, IResult};
|
||||||
|
|
||||||
use crate::parsers::eol;
|
use crate::parsers::{blank_lines, eol};
|
||||||
|
|
||||||
pub fn parse_rule(input: &str) -> Option<&str> {
|
#[derive(Debug, Default)]
|
||||||
parse_rule_internal::<()>(input)
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
.ok()
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
.map(|(input, _)| input)
|
pub struct Rule {
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_rule_internal<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, (), E> {
|
impl Rule {
|
||||||
let (input, _) = take_while_m_n(5, usize::MAX, |c| c == '-')(input)?;
|
pub(crate) fn parse(input: &str) -> Option<(&str, Rule)> {
|
||||||
|
parse_rule::<()>(input).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_rule<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, Rule, E> {
|
||||||
|
let (input, _) = take_while_m_n(5, usize::max_value(), |c| c == '-')(input)?;
|
||||||
let (input, _) = eol(input)?;
|
let (input, _) = eol(input)?;
|
||||||
Ok((input, ()))
|
let (input, blank) = blank_lines(input);
|
||||||
|
Ok((input, Rule { post_blank: blank }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -21,29 +27,29 @@ fn parse() {
|
||||||
use nom::error::VerboseError;
|
use nom::error::VerboseError;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_rule_internal::<VerboseError<&str>>("-----"),
|
parse_rule::<VerboseError<&str>>("-----"),
|
||||||
Ok(("", ()))
|
Ok(("", Rule { post_blank: 0 }))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_rule_internal::<VerboseError<&str>>("--------"),
|
parse_rule::<VerboseError<&str>>("--------"),
|
||||||
Ok(("", ()))
|
Ok(("", Rule { post_blank: 0 }))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_rule_internal::<VerboseError<&str>>("-----\n"),
|
parse_rule::<VerboseError<&str>>("-----\n\n\n"),
|
||||||
Ok(("", ()))
|
Ok(("", Rule { post_blank: 2 }))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_rule_internal::<VerboseError<&str>>("----- \n"),
|
parse_rule::<VerboseError<&str>>("----- \n"),
|
||||||
Ok(("", ()))
|
Ok(("", Rule { post_blank: 0 }))
|
||||||
);
|
);
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("").is_err());
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("----").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("----").is_err());
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("----").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("----").is_err());
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("None----").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("None----").is_err());
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("None ----").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("None ----").is_err());
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("None------").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("None------").is_err());
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("----None----").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("----None----").is_err());
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("\t\t----").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("\t\t----").is_err());
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("------None").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("------None").is_err());
|
||||||
assert!(parse_rule_internal::<VerboseError<&str>>("----- None").is_err());
|
assert!(parse_rule::<VerboseError<&str>>("----- None").is_err());
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ impl TableRow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_table_el(input: &str) -> Option<(&str, &str)> {
|
pub fn parse_table_el(input: &str) -> Option<(&str, &str)> {
|
||||||
parse_table_el_internal::<()>(input).ok()
|
parse_table_el_internal::<()>(input).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ use nom::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::ParseConfig,
|
config::ParseConfig,
|
||||||
elements::{drawer::parse_drawer, Planning, Timestamp},
|
elements::{drawer::parse_drawer_without_blank, Planning, Timestamp},
|
||||||
parsers::{line, skip_empty_lines, take_one_word},
|
parsers::{blank_lines, line, skip_empty_lines, take_one_word},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Title Elemenet
|
/// Title Elemenet
|
||||||
|
@ -44,6 +44,8 @@ pub struct Title<'a> {
|
||||||
/// Property drawer associated to this headline
|
/// Property drawer associated to this headline
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "HashMap::is_empty"))]
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "HashMap::is_empty"))]
|
||||||
pub properties: HashMap<Cow<'a, str>, Cow<'a, str>>,
|
pub properties: HashMap<Cow<'a, str>, Cow<'a, str>>,
|
||||||
|
/// Numbers of blank lines
|
||||||
|
pub post_blank: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Title<'_> {
|
impl Title<'_> {
|
||||||
|
@ -102,6 +104,7 @@ impl Title<'_> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (k.into_owned().into(), v.into_owned().into()))
|
.map(|(k, v)| (k.into_owned().into(), v.into_owned().into()))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
post_blank: self.post_blank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,6 +119,7 @@ impl Default for Title<'_> {
|
||||||
raw: Cow::Borrowed(""),
|
raw: Cow::Borrowed(""),
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new(),
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,6 +170,7 @@ fn parse_title<'a, E: ParseError<&'a str>>(
|
||||||
.unwrap_or((input, None));
|
.unwrap_or((input, None));
|
||||||
|
|
||||||
let (input, properties) = opt(parse_properties_drawer)(input)?;
|
let (input, properties) = opt(parse_properties_drawer)(input)?;
|
||||||
|
let (input, blank) = blank_lines(input);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
|
@ -178,6 +183,7 @@ fn parse_title<'a, E: ParseError<&'a str>>(
|
||||||
tags,
|
tags,
|
||||||
raw: raw.into(),
|
raw: raw.into(),
|
||||||
planning,
|
planning,
|
||||||
|
post_blank: blank,
|
||||||
},
|
},
|
||||||
raw,
|
raw,
|
||||||
),
|
),
|
||||||
|
@ -188,7 +194,7 @@ fn parse_title<'a, E: ParseError<&'a str>>(
|
||||||
fn parse_properties_drawer<'a, E: ParseError<&'a str>>(
|
fn parse_properties_drawer<'a, E: ParseError<&'a str>>(
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
) -> IResult<&str, HashMap<Cow<'_, str>, Cow<'_, str>>, E> {
|
) -> IResult<&str, HashMap<Cow<'_, str>, Cow<'_, str>>, E> {
|
||||||
let (input, (drawer, content)) = parse_drawer(input.trim_start())?;
|
let (input, (drawer, content)) = parse_drawer_without_blank(input.trim_start())?;
|
||||||
if drawer.name != "PROPERTIES" {
|
if drawer.name != "PROPERTIES" {
|
||||||
return Err(Err::Error(E::from_error_kind(input, ErrorKind::Tag)));
|
return Err(Err::Error(E::from_error_kind(input, ErrorKind::Tag)));
|
||||||
}
|
}
|
||||||
|
@ -236,7 +242,8 @@ fn parse_title_() {
|
||||||
raw: "COMMENT Title".into(),
|
raw: "COMMENT Title".into(),
|
||||||
tags: vec!["tag".into(), "a2%".into()],
|
tags: vec!["tag".into(), "a2%".into()],
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new()
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
"COMMENT Title"
|
"COMMENT Title"
|
||||||
)
|
)
|
||||||
|
@ -254,7 +261,8 @@ fn parse_title_() {
|
||||||
raw: "ToDO [#A] COMMENT Title".into(),
|
raw: "ToDO [#A] COMMENT Title".into(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new()
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
"ToDO [#A] COMMENT Title"
|
"ToDO [#A] COMMENT Title"
|
||||||
)
|
)
|
||||||
|
@ -272,7 +280,8 @@ fn parse_title_() {
|
||||||
raw: "T0DO [#A] COMMENT Title".into(),
|
raw: "T0DO [#A] COMMENT Title".into(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new()
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
"T0DO [#A] COMMENT Title"
|
"T0DO [#A] COMMENT Title"
|
||||||
)
|
)
|
||||||
|
@ -290,7 +299,8 @@ fn parse_title_() {
|
||||||
raw: "[#1] COMMENT Title".into(),
|
raw: "[#1] COMMENT Title".into(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new()
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
"[#1] COMMENT Title"
|
"[#1] COMMENT Title"
|
||||||
)
|
)
|
||||||
|
@ -308,7 +318,8 @@ fn parse_title_() {
|
||||||
raw: "[#a] COMMENT Title".into(),
|
raw: "[#a] COMMENT Title".into(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new()
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
"[#a] COMMENT Title"
|
"[#a] COMMENT Title"
|
||||||
)
|
)
|
||||||
|
@ -326,7 +337,8 @@ fn parse_title_() {
|
||||||
raw: "Title :tag:a2%".into(),
|
raw: "Title :tag:a2%".into(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new()
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
"Title :tag:a2%"
|
"Title :tag:a2%"
|
||||||
)
|
)
|
||||||
|
@ -344,7 +356,8 @@ fn parse_title_() {
|
||||||
raw: "Title tag:a2%:".into(),
|
raw: "Title tag:a2%:".into(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new()
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
"Title tag:a2%:"
|
"Title tag:a2%:"
|
||||||
)
|
)
|
||||||
|
@ -369,7 +382,8 @@ fn parse_title_() {
|
||||||
raw: "DONE Title".into(),
|
raw: "DONE Title".into(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new()
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
"DONE Title"
|
"DONE Title"
|
||||||
)
|
)
|
||||||
|
@ -393,7 +407,8 @@ fn parse_title_() {
|
||||||
raw: "Title".into(),
|
raw: "Title".into(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
planning: None,
|
planning: None,
|
||||||
properties: HashMap::new()
|
properties: HashMap::new(),
|
||||||
|
post_blank: 0,
|
||||||
},
|
},
|
||||||
"Title"
|
"Title"
|
||||||
)
|
)
|
||||||
|
|
|
@ -60,7 +60,7 @@ pub trait HtmlHandler<E: From<Error>>: Default {
|
||||||
CenterBlock(_) => write!(w, "<div class=\"center\">")?,
|
CenterBlock(_) => write!(w, "<div class=\"center\">")?,
|
||||||
VerseBlock(_) => write!(w, "<p class=\"verse\">")?,
|
VerseBlock(_) => write!(w, "<p class=\"verse\">")?,
|
||||||
Bold => write!(w, "<b>")?,
|
Bold => write!(w, "<b>")?,
|
||||||
Document => write!(w, "<main>")?,
|
Document { .. } => write!(w, "<main>")?,
|
||||||
DynBlock(_dyn_block) => (),
|
DynBlock(_dyn_block) => (),
|
||||||
Headline { .. } => (),
|
Headline { .. } => (),
|
||||||
List(list) => {
|
List(list) => {
|
||||||
|
@ -72,7 +72,7 @@ pub trait HtmlHandler<E: From<Error>>: Default {
|
||||||
}
|
}
|
||||||
Italic => write!(w, "<i>")?,
|
Italic => write!(w, "<i>")?,
|
||||||
ListItem(_) => write!(w, "<li>")?,
|
ListItem(_) => write!(w, "<li>")?,
|
||||||
Paragraph => write!(w, "<p>")?,
|
Paragraph { .. } => write!(w, "<p>")?,
|
||||||
Section => write!(w, "<section>")?,
|
Section => write!(w, "<section>")?,
|
||||||
Strike => write!(w, "<s>")?,
|
Strike => write!(w, "<s>")?,
|
||||||
Underline => write!(w, "<u>")?,
|
Underline => write!(w, "<u>")?,
|
||||||
|
@ -162,13 +162,15 @@ pub trait HtmlHandler<E: From<Error>>: Default {
|
||||||
Verbatim { value } => write!(&mut w, "<code>{}</code>", HtmlEscape(value))?,
|
Verbatim { value } => write!(&mut w, "<code>{}</code>", HtmlEscape(value))?,
|
||||||
FnDef(_fn_def) => (),
|
FnDef(_fn_def) => (),
|
||||||
Clock(_clock) => (),
|
Clock(_clock) => (),
|
||||||
Comment { .. } => (),
|
Comment(_) => (),
|
||||||
FixedWidth { value } => {
|
FixedWidth(fixed_width) => write!(
|
||||||
write!(w, "<pre class=\"example\">{}</pre>", HtmlEscape(value))?
|
w,
|
||||||
}
|
"<pre class=\"example\">{}</pre>",
|
||||||
|
HtmlEscape(&fixed_width.value)
|
||||||
|
)?,
|
||||||
Keyword(_keyword) => (),
|
Keyword(_keyword) => (),
|
||||||
Drawer(_drawer) => (),
|
Drawer(_drawer) => (),
|
||||||
Rule => write!(w, "<hr>")?,
|
Rule(_) => write!(w, "<hr>")?,
|
||||||
Cookie(cookie) => write!(w, "<code>{}</code>", cookie.value)?,
|
Cookie(cookie) => write!(w, "<code>{}</code>", cookie.value)?,
|
||||||
Title(title) => write!(w, "<h{}>", if title.level <= 6 { title.level } else { 6 })?,
|
Title(title) => write!(w, "<h{}>", if title.level <= 6 { title.level } else { 6 })?,
|
||||||
Table(_) => (),
|
Table(_) => (),
|
||||||
|
@ -188,7 +190,7 @@ pub trait HtmlHandler<E: From<Error>>: Default {
|
||||||
CenterBlock(_) => write!(w, "</div>")?,
|
CenterBlock(_) => write!(w, "</div>")?,
|
||||||
VerseBlock(_) => write!(w, "</p>")?,
|
VerseBlock(_) => write!(w, "</p>")?,
|
||||||
Bold => write!(w, "</b>")?,
|
Bold => write!(w, "</b>")?,
|
||||||
Document => write!(w, "</main>")?,
|
Document { .. } => write!(w, "</main>")?,
|
||||||
DynBlock(_dyn_block) => (),
|
DynBlock(_dyn_block) => (),
|
||||||
Headline { .. } => (),
|
Headline { .. } => (),
|
||||||
List(list) => {
|
List(list) => {
|
||||||
|
@ -200,7 +202,7 @@ pub trait HtmlHandler<E: From<Error>>: Default {
|
||||||
}
|
}
|
||||||
Italic => write!(w, "</i>")?,
|
Italic => write!(w, "</i>")?,
|
||||||
ListItem(_) => write!(w, "</li>")?,
|
ListItem(_) => write!(w, "</li>")?,
|
||||||
Paragraph => write!(w, "</p>")?,
|
Paragraph { .. } => write!(w, "</p>")?,
|
||||||
Section => write!(w, "</section>")?,
|
Section => write!(w, "</section>")?,
|
||||||
Strike => write!(w, "</s>")?,
|
Strike => write!(w, "</s>")?,
|
||||||
Underline => write!(w, "</u>")?,
|
Underline => write!(w, "</u>")?,
|
||||||
|
@ -341,13 +343,13 @@ mod syntect_handler {
|
||||||
"<div class=\"org-src-container\"><pre class=\"src src-{}\">{}</pre></div>",
|
"<div class=\"org-src-container\"><pre class=\"src src-{}\">{}</pre></div>",
|
||||||
block.language,
|
block.language,
|
||||||
self.highlight(Some(&block.language), &block.contents)
|
self.highlight(Some(&block.language), &block.contents)
|
||||||
)?
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Element::FixedWidth { value } => write!(
|
Element::FixedWidth(fixed_width) => write!(
|
||||||
w,
|
w,
|
||||||
"<pre class=\"example\">{}</pre>",
|
"<pre class=\"example\">{}</pre>",
|
||||||
self.highlight(None, value)
|
self.highlight(None, fixed_width.value)
|
||||||
)?,
|
)?,
|
||||||
Element::ExampleBlock(block) => write!(
|
Element::ExampleBlock(block) => write!(
|
||||||
w,
|
w,
|
||||||
|
|
|
@ -9,46 +9,74 @@ pub trait OrgHandler<E: From<Error>>: Default {
|
||||||
|
|
||||||
match element {
|
match element {
|
||||||
// container elements
|
// container elements
|
||||||
SpecialBlock(block) => writeln!(w, "#+BEGIN_{}", block.name)?,
|
SpecialBlock(block) => {
|
||||||
QuoteBlock(_) => write!(w, "#+BEGIN_QUOTE")?,
|
writeln!(w, "#+BEGIN_{}", block.name)?;
|
||||||
CenterBlock(_) => write!(w, "#+BEGIN_CENTER")?,
|
write_blank_lines(&mut w, block.pre_blank)?;
|
||||||
VerseBlock(_) => write!(w, "#+BEGIN_VERSE")?,
|
}
|
||||||
|
QuoteBlock(block) => {
|
||||||
|
writeln!(&mut w, "#+BEGIN_QUOTE")?;
|
||||||
|
write_blank_lines(&mut w, block.pre_blank)?;
|
||||||
|
}
|
||||||
|
CenterBlock(block) => {
|
||||||
|
writeln!(&mut w, "#+BEGIN_CENTER")?;
|
||||||
|
write_blank_lines(&mut w, block.pre_blank)?;
|
||||||
|
}
|
||||||
|
VerseBlock(block) => {
|
||||||
|
writeln!(&mut w, "#+BEGIN_VERSE")?;
|
||||||
|
write_blank_lines(&mut w, block.pre_blank)?;
|
||||||
|
}
|
||||||
Bold => write!(w, "*")?,
|
Bold => write!(w, "*")?,
|
||||||
Document => (),
|
Document { pre_blank } => {
|
||||||
|
write_blank_lines(w, *pre_blank)?;
|
||||||
|
}
|
||||||
DynBlock(dyn_block) => {
|
DynBlock(dyn_block) => {
|
||||||
write!(&mut w, "#+BEGIN: {}", dyn_block.block_name)?;
|
write!(&mut w, "#+BEGIN: {}", dyn_block.block_name)?;
|
||||||
if let Some(parameters) = &dyn_block.arguments {
|
if let Some(parameters) = &dyn_block.arguments {
|
||||||
write!(&mut w, " {}", parameters)?;
|
write!(&mut w, " {}", parameters)?;
|
||||||
}
|
}
|
||||||
writeln!(&mut w)?;
|
write_blank_lines(&mut w, dyn_block.pre_blank + 1)?;
|
||||||
}
|
}
|
||||||
Headline { .. } => (),
|
Headline { .. } => (),
|
||||||
List(_list) => (),
|
List(_list) => (),
|
||||||
Italic => write!(w, "/")?,
|
Italic => write!(w, "/")?,
|
||||||
ListItem(list_item) => write!(w, "{}", list_item.bullet)?,
|
ListItem(list_item) => write!(w, "{}", list_item.bullet)?,
|
||||||
Paragraph => (),
|
Paragraph { .. } => (),
|
||||||
Section => (),
|
Section => (),
|
||||||
Strike => write!(w, "+")?,
|
Strike => write!(w, "+")?,
|
||||||
Underline => write!(w, "_")?,
|
Underline => write!(w, "_")?,
|
||||||
Drawer(drawer) => writeln!(w, ":{}:", drawer.name)?,
|
Drawer(drawer) => {
|
||||||
|
writeln!(&mut w, ":{}:", drawer.name)?;
|
||||||
|
write_blank_lines(&mut w, drawer.pre_blank)?;
|
||||||
|
}
|
||||||
// non-container elements
|
// non-container elements
|
||||||
CommentBlock(block) => {
|
CommentBlock(block) => {
|
||||||
writeln!(w, "#+BEGIN_COMMENT\n{}\n#+END_COMMENT", block.contents)?
|
writeln!(&mut w, "#+BEGIN_COMMENT")?;
|
||||||
|
write!(&mut w, "{}", block.contents)?;
|
||||||
|
writeln!(&mut w, "#+END_COMMENT")?;
|
||||||
|
write_blank_lines(&mut w, block.post_blank)?;
|
||||||
}
|
}
|
||||||
ExampleBlock(block) => {
|
ExampleBlock(block) => {
|
||||||
writeln!(w, "#+BEGIN_EXAMPLE\n{}\n#+END_EXAMPLE", block.contents)?
|
writeln!(&mut w, "#+BEGIN_EXAMPLE")?;
|
||||||
|
write!(&mut w, "{}", block.contents)?;
|
||||||
|
writeln!(&mut w, "#+END_EXAMPLE")?;
|
||||||
|
write_blank_lines(&mut w, block.post_blank)?;
|
||||||
|
}
|
||||||
|
ExportBlock(block) => {
|
||||||
|
writeln!(&mut w, "#+BEGIN_EXPORT {}", block.data)?;
|
||||||
|
write!(&mut w, "{}", block.contents)?;
|
||||||
|
writeln!(&mut w, "#+END_EXPORT")?;
|
||||||
|
write_blank_lines(&mut w, block.post_blank)?;
|
||||||
|
}
|
||||||
|
SourceBlock(block) => {
|
||||||
|
writeln!(&mut w, "#+BEGIN_SRC {}", block.language)?;
|
||||||
|
write!(&mut w, "{}", block.contents)?;
|
||||||
|
writeln!(&mut w, "#+END_SRC")?;
|
||||||
|
write_blank_lines(&mut w, block.post_blank)?;
|
||||||
|
}
|
||||||
|
BabelCall(call) => {
|
||||||
|
writeln!(&mut w, "#+CALL: {}", call.value)?;
|
||||||
|
write_blank_lines(w, call.post_blank)?;
|
||||||
}
|
}
|
||||||
ExportBlock(block) => writeln!(
|
|
||||||
w,
|
|
||||||
"#+BEGIN_EXPORT {}\n{}\n#+END_EXPORT",
|
|
||||||
block.data, block.contents
|
|
||||||
)?,
|
|
||||||
SourceBlock(block) => writeln!(
|
|
||||||
w,
|
|
||||||
"#+BEGIN_SRC {}\n{}\n#+END_SRC",
|
|
||||||
block.language, block.contents
|
|
||||||
)?,
|
|
||||||
BabelCall(_babel_call) => (),
|
|
||||||
InlineSrc(inline_src) => {
|
InlineSrc(inline_src) => {
|
||||||
write!(&mut w, "src_{}", inline_src.lang)?;
|
write!(&mut w, "src_{}", inline_src.lang)?;
|
||||||
if let Some(options) = &inline_src.options {
|
if let Some(options) = &inline_src.options {
|
||||||
|
@ -90,7 +118,9 @@ pub trait OrgHandler<E: From<Error>>: Default {
|
||||||
write_timestamp(&mut w, ×tamp)?;
|
write_timestamp(&mut w, ×tamp)?;
|
||||||
}
|
}
|
||||||
Verbatim { value } => write!(w, "={}=", value)?,
|
Verbatim { value } => write!(w, "={}=", value)?,
|
||||||
FnDef(_fn_def) => (),
|
FnDef(fn_def) => {
|
||||||
|
write_blank_lines(w, fn_def.post_blank)?;
|
||||||
|
}
|
||||||
Clock(clock) => {
|
Clock(clock) => {
|
||||||
use crate::elements::Clock;
|
use crate::elements::Clock;
|
||||||
|
|
||||||
|
@ -101,27 +131,42 @@ pub trait OrgHandler<E: From<Error>>: Default {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
duration,
|
duration,
|
||||||
|
post_blank,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
write_datetime(&mut w, "[", start, "]--")?;
|
write_datetime(&mut w, "[", &start, "]--")?;
|
||||||
write_datetime(&mut w, "[", end, "]")?;
|
write_datetime(&mut w, "[", &end, "]")?;
|
||||||
writeln!(w, " => {}", duration)?;
|
writeln!(&mut w, " => {}", duration)?;
|
||||||
|
write_blank_lines(&mut w, *post_blank)?;
|
||||||
}
|
}
|
||||||
Clock::Running { start, .. } => {
|
Clock::Running {
|
||||||
write_datetime(&mut w, "[", start, "]\n")?;
|
start, post_blank, ..
|
||||||
|
} => {
|
||||||
|
write_datetime(&mut w, "[", &start, "]\n")?;
|
||||||
|
write_blank_lines(&mut w, *post_blank)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Comment { value } => write!(w, "{}", value)?,
|
Comment(comment) => {
|
||||||
FixedWidth { value } => write!(w, "{}", value)?,
|
write!(w, "{}", comment.value)?;
|
||||||
|
write_blank_lines(&mut w, comment.post_blank)?;
|
||||||
|
}
|
||||||
|
FixedWidth(fixed_width) => {
|
||||||
|
write!(&mut w, "{}", fixed_width.value)?;
|
||||||
|
write_blank_lines(&mut w, fixed_width.post_blank)?;
|
||||||
|
}
|
||||||
Keyword(keyword) => {
|
Keyword(keyword) => {
|
||||||
write!(&mut w, "#+{}", keyword.key)?;
|
write!(&mut w, "#+{}", keyword.key)?;
|
||||||
if let Some(optional) = &keyword.optional {
|
if let Some(optional) = &keyword.optional {
|
||||||
write!(&mut w, "[{}]", optional)?;
|
write!(&mut w, "[{}]", optional)?;
|
||||||
}
|
}
|
||||||
writeln!(&mut w, ": {}", keyword.value)?;
|
writeln!(&mut w, ": {}", keyword.value)?;
|
||||||
|
write_blank_lines(&mut w, keyword.post_blank)?;
|
||||||
|
}
|
||||||
|
Rule(rule) => {
|
||||||
|
writeln!(w, "-----")?;
|
||||||
|
write_blank_lines(&mut w, rule.post_blank)?;
|
||||||
}
|
}
|
||||||
Rule => writeln!(w, "-----")?,
|
|
||||||
Cookie(_cookie) => (),
|
Cookie(_cookie) => (),
|
||||||
Title(title) => {
|
Title(title) => {
|
||||||
for _ in 0..title.level {
|
for _ in 0..title.level {
|
||||||
|
@ -148,29 +193,49 @@ pub trait OrgHandler<E: From<Error>>: Default {
|
||||||
|
|
||||||
match element {
|
match element {
|
||||||
// container elements
|
// container elements
|
||||||
SpecialBlock(block) => writeln!(w, "#+END_{}", block.name)?,
|
SpecialBlock(block) => {
|
||||||
QuoteBlock(_) => writeln!(w, "#+END_QUOTE")?,
|
writeln!(&mut w, "#+END_{}", block.name)?;
|
||||||
CenterBlock(_) => writeln!(w, "#+END_CENTER")?,
|
write_blank_lines(&mut w, block.post_blank)?;
|
||||||
VerseBlock(_) => writeln!(w, "#+END_VERSE")?,
|
}
|
||||||
|
QuoteBlock(block) => {
|
||||||
|
writeln!(&mut w, "#+END_QUOTE")?;
|
||||||
|
write_blank_lines(&mut w, block.post_blank)?;
|
||||||
|
}
|
||||||
|
CenterBlock(block) => {
|
||||||
|
writeln!(&mut w, "#+END_CENTER")?;
|
||||||
|
write_blank_lines(&mut w, block.post_blank)?;
|
||||||
|
}
|
||||||
|
VerseBlock(block) => {
|
||||||
|
writeln!(&mut w, "#+END_VERSE")?;
|
||||||
|
write_blank_lines(&mut w, block.post_blank)?;
|
||||||
|
}
|
||||||
Bold => write!(w, "*")?,
|
Bold => write!(w, "*")?,
|
||||||
Document => (),
|
Document { .. } => (),
|
||||||
DynBlock(_dyn_block) => writeln!(w, "#+END:")?,
|
DynBlock(dyn_block) => {
|
||||||
|
writeln!(w, "#+END:")?;
|
||||||
|
write_blank_lines(w, dyn_block.post_blank)?;
|
||||||
|
}
|
||||||
Headline { .. } => (),
|
Headline { .. } => (),
|
||||||
List(_list) => (),
|
List(_list) => (),
|
||||||
Italic => write!(w, "/")?,
|
Italic => write!(w, "/")?,
|
||||||
ListItem(_) => (),
|
ListItem(_) => (),
|
||||||
Paragraph => write!(w, "\n\n")?,
|
Paragraph { post_blank } => {
|
||||||
|
write_blank_lines(w, post_blank + 1)?;
|
||||||
|
}
|
||||||
Section => (),
|
Section => (),
|
||||||
Strike => write!(w, "+")?,
|
Strike => write!(w, "+")?,
|
||||||
Underline => write!(w, "_")?,
|
Underline => write!(w, "_")?,
|
||||||
Drawer(_) => writeln!(w, ":END:")?,
|
Drawer(drawer) => {
|
||||||
|
writeln!(&mut w, ":END:")?;
|
||||||
|
write_blank_lines(&mut w, drawer.post_blank)?;
|
||||||
|
}
|
||||||
Title(title) => {
|
Title(title) => {
|
||||||
if !title.tags.is_empty() {
|
if !title.tags.is_empty() {
|
||||||
write!(&mut w, " :")?;
|
write!(&mut w, " :")?;
|
||||||
}
|
|
||||||
for tag in &title.tags {
|
for tag in &title.tags {
|
||||||
write!(&mut w, "{}:", tag)?;
|
write!(&mut w, "{}:", tag)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
writeln!(&mut w)?;
|
writeln!(&mut w)?;
|
||||||
if let Some(planning) = &title.planning {
|
if let Some(planning) = &title.planning {
|
||||||
if let Some(scheduled) = &planning.scheduled {
|
if let Some(scheduled) = &planning.scheduled {
|
||||||
|
@ -200,6 +265,7 @@ pub trait OrgHandler<E: From<Error>>: Default {
|
||||||
}
|
}
|
||||||
writeln!(&mut w, ":END:")?;
|
writeln!(&mut w, ":END:")?;
|
||||||
}
|
}
|
||||||
|
write_blank_lines(&mut w, title.post_blank)?;
|
||||||
}
|
}
|
||||||
Table(_) => (),
|
Table(_) => (),
|
||||||
TableRow(_) => (),
|
TableRow(_) => (),
|
||||||
|
@ -212,7 +278,14 @@ pub trait OrgHandler<E: From<Error>>: Default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_timestamp<W: Write>(mut w: W, timestamp: &Timestamp) -> std::io::Result<()> {
|
fn write_blank_lines<W: Write>(mut w: W, count: usize) -> Result<(), Error> {
|
||||||
|
for _ in 0..count {
|
||||||
|
writeln!(w)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_timestamp<W: Write>(mut w: W, timestamp: &Timestamp) -> Result<(), Error> {
|
||||||
match timestamp {
|
match timestamp {
|
||||||
Timestamp::Active { start, .. } => {
|
Timestamp::Active { start, .. } => {
|
||||||
write_datetime(w, "<", start, ">")?;
|
write_datetime(w, "<", start, ">")?;
|
||||||
|
|
|
@ -337,7 +337,7 @@ impl Headline {
|
||||||
pub fn new<'a>(ttl: Title<'a>, org: &mut Org<'a>) -> Headline {
|
pub fn new<'a>(ttl: Title<'a>, org: &mut Org<'a>) -> Headline {
|
||||||
let lvl = ttl.level;
|
let lvl = ttl.level;
|
||||||
let hdl_n = org.arena.new_node(Element::Headline { level: ttl.level });
|
let hdl_n = org.arena.new_node(Element::Headline { level: ttl.level });
|
||||||
let ttl_n = org.arena.new_node(Element::Document); // placeholder
|
let ttl_n = org.arena.new_node(Element::Document { pre_blank: 0 }); // placeholder
|
||||||
hdl_n.append(ttl_n, &mut org.arena);
|
hdl_n.append(ttl_n, &mut org.arena);
|
||||||
|
|
||||||
match ttl.raw {
|
match ttl.raw {
|
||||||
|
@ -685,7 +685,7 @@ impl Headline {
|
||||||
pub fn parent(self, org: &Org) -> Option<Headline> {
|
pub fn parent(self, org: &Org) -> Option<Headline> {
|
||||||
org.arena[self.hdl_n].parent().and_then(|n| match org[n] {
|
org.arena[self.hdl_n].parent().and_then(|n| match org[n] {
|
||||||
Element::Headline { level } => Some(Headline::from_node(n, level, org)),
|
Element::Headline { level } => Some(Headline::from_node(n, level, org)),
|
||||||
Element::Document => None,
|
Element::Document { .. } => None,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
10
src/org.rs
10
src/org.rs
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
config::{ParseConfig, DEFAULT_CONFIG},
|
config::{ParseConfig, DEFAULT_CONFIG},
|
||||||
elements::Element,
|
elements::Element,
|
||||||
export::{DefaultHtmlHandler, DefaultOrgHandler, HtmlHandler, OrgHandler},
|
export::{DefaultHtmlHandler, DefaultOrgHandler, HtmlHandler, OrgHandler},
|
||||||
parsers::{parse_container, Container},
|
parsers::{blank_lines, parse_container, Container},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Org<'a> {
|
pub struct Org<'a> {
|
||||||
|
@ -24,8 +24,7 @@ impl<'a> Org<'a> {
|
||||||
/// Create a new empty `Org` struct
|
/// Create a new empty `Org` struct
|
||||||
pub fn new() -> Org<'static> {
|
pub fn new() -> Org<'static> {
|
||||||
let mut arena = Arena::new();
|
let mut arena = Arena::new();
|
||||||
let root = arena.new_node(Element::Document);
|
let root = arena.new_node(Element::Document { pre_blank: 0 });
|
||||||
|
|
||||||
Org { arena, root }
|
Org { arena, root }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +35,10 @@ impl<'a> Org<'a> {
|
||||||
|
|
||||||
/// Create a new Org struct from parsing `text`, using a custom ParseConfig
|
/// Create a new Org struct from parsing `text`, using a custom ParseConfig
|
||||||
pub fn parse_with_config(text: &'a str, config: &ParseConfig) -> Org<'a> {
|
pub fn parse_with_config(text: &'a str, config: &ParseConfig) -> Org<'a> {
|
||||||
let mut org = Org::new();
|
let mut arena = Arena::new();
|
||||||
|
let (text, blank) = blank_lines(text);
|
||||||
|
let root = arena.new_node(Element::Document { pre_blank: blank });
|
||||||
|
let mut org = Org { arena, root };
|
||||||
|
|
||||||
parse_container(
|
parse_container(
|
||||||
&mut org.arena,
|
&mut org.arena,
|
||||||
|
|
156
src/parsers.rs
156
src/parsers.rs
|
@ -12,10 +12,11 @@ use nom::{bytes::complete::take_while1, combinator::verify, error::ParseError, I
|
||||||
use crate::config::ParseConfig;
|
use crate::config::ParseConfig;
|
||||||
use crate::elements::{
|
use crate::elements::{
|
||||||
block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword,
|
block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword,
|
||||||
radio_target::parse_radio_target, rule::parse_rule, table::parse_table_el, BabelCall,
|
radio_target::parse_radio_target, table::parse_table_el, BabelCall, CenterBlock, Clock,
|
||||||
CenterBlock, Clock, CommentBlock, Cookie, Drawer, DynBlock, Element, ExampleBlock, ExportBlock,
|
Comment, CommentBlock, Cookie, Drawer, DynBlock, Element, ExampleBlock, ExportBlock,
|
||||||
FnDef, FnRef, InlineCall, InlineSrc, Keyword, Link, List, ListItem, Macros, QuoteBlock,
|
FixedWidth, FnDef, FnRef, InlineCall, InlineSrc, Keyword, Link, List, ListItem, Macros,
|
||||||
Snippet, SourceBlock, SpecialBlock, Table, TableRow, Target, Timestamp, Title, VerseBlock,
|
QuoteBlock, Rule, Snippet, SourceBlock, SpecialBlock, Table, TableRow, Target, Timestamp,
|
||||||
|
Title, VerseBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait ElementArena<'a> {
|
pub trait ElementArena<'a> {
|
||||||
|
@ -216,23 +217,32 @@ pub fn parse_blocks<'a, T: ElementArena<'a>>(
|
||||||
.map(|i| i + 1)
|
.map(|i| i + 1)
|
||||||
.unwrap_or_else(|| tail.len());
|
.unwrap_or_else(|| tail.len());
|
||||||
if tail.as_bytes()[0..i].iter().all(u8::is_ascii_whitespace) {
|
if tail.as_bytes()[0..i].iter().all(u8::is_ascii_whitespace) {
|
||||||
let node = arena.append_element(Element::Paragraph, parent);
|
let (tail_, blank) = blank_lines(&tail[i..]);
|
||||||
|
debug_assert_ne!(tail, tail_);
|
||||||
|
tail = tail_;
|
||||||
|
|
||||||
|
let node = arena.append_element(
|
||||||
|
Element::Paragraph {
|
||||||
|
// including current line (&tail[0..i])
|
||||||
|
post_blank: blank + 1,
|
||||||
|
},
|
||||||
|
parent,
|
||||||
|
);
|
||||||
|
|
||||||
containers.push(Container::Inline {
|
containers.push(Container::Inline {
|
||||||
content: &text[0..pos].trim_end_matches('\n'),
|
content: &text[0..pos].trim_end(),
|
||||||
node,
|
node,
|
||||||
});
|
});
|
||||||
|
|
||||||
pos = 0;
|
pos = 0;
|
||||||
debug_assert_ne!(tail, skip_empty_lines(&tail[i..]));
|
|
||||||
tail = skip_empty_lines(&tail[i..]);
|
|
||||||
text = tail;
|
text = tail;
|
||||||
} else if let Some(new_tail) = parse_block(tail, arena, parent, containers) {
|
} else if let Some(new_tail) = parse_block(tail, arena, parent, containers) {
|
||||||
if pos != 0 {
|
if pos != 0 {
|
||||||
let node = arena.insert_before_last_child(Element::Paragraph, parent);
|
let node =
|
||||||
|
arena.insert_before_last_child(Element::Paragraph { post_blank: 0 }, parent);
|
||||||
|
|
||||||
containers.push(Container::Inline {
|
containers.push(Container::Inline {
|
||||||
content: &text[0..pos].trim_end_matches('\n'),
|
content: &text[0..pos].trim_end(),
|
||||||
node,
|
node,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -249,10 +259,10 @@ pub fn parse_blocks<'a, T: ElementArena<'a>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
let node = arena.append_element(Element::Paragraph, parent);
|
let node = arena.append_element(Element::Paragraph { post_blank: 0 }, parent);
|
||||||
|
|
||||||
containers.push(Container::Inline {
|
containers.push(Container::Inline {
|
||||||
content: &text[0..pos].trim_end_matches('\n'),
|
content: &text[0..pos].trim_end(),
|
||||||
node,
|
node,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -264,11 +274,14 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
|
||||||
parent: NodeId,
|
parent: NodeId,
|
||||||
containers: &mut Vec<Container<'a>>,
|
containers: &mut Vec<Container<'a>>,
|
||||||
) -> Option<&'a str> {
|
) -> Option<&'a str> {
|
||||||
|
// footnote definitions must be start at column 0
|
||||||
if let Some((tail, (fn_def, content))) = FnDef::parse(contents) {
|
if let Some((tail, (fn_def, content))) = FnDef::parse(contents) {
|
||||||
let node = arena.append_element(fn_def, parent);
|
let node = arena.append_element(fn_def, parent);
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
return Some(tail);
|
return Some(tail);
|
||||||
} else if let Some((tail, list, content)) = List::parse(contents) {
|
}
|
||||||
|
|
||||||
|
if let Some((tail, list, content)) = List::parse(contents) {
|
||||||
let indent = list.indent;
|
let indent = list.indent;
|
||||||
let node = arena.append_element(list, parent);
|
let node = arena.append_element(list, parent);
|
||||||
containers.push(Container::List {
|
containers.push(Container::List {
|
||||||
|
@ -292,8 +305,8 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
b'-' => {
|
b'-' => {
|
||||||
let tail = parse_rule(contents)?;
|
let (tail, rule) = Rule::parse(contents)?;
|
||||||
arena.append_element(Element::Rule, parent);
|
arena.append_element(rule, parent);
|
||||||
Some(tail)
|
Some(tail)
|
||||||
}
|
}
|
||||||
b':' => {
|
b':' => {
|
||||||
|
@ -302,9 +315,8 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
Some(tail)
|
Some(tail)
|
||||||
} else {
|
} else {
|
||||||
let (tail, value) = parse_fixed_width(contents)?;
|
let (tail, fixed_width) = FixedWidth::parse(contents)?;
|
||||||
let value = value.into();
|
arena.append_element(fixed_width, parent);
|
||||||
arena.append_element(Element::FixedWidth { value }, parent);
|
|
||||||
Some(tail)
|
Some(tail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,7 +325,7 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
|
||||||
Some(tail)
|
Some(tail)
|
||||||
}
|
}
|
||||||
b'#' => {
|
b'#' => {
|
||||||
if let Some((tail, (name, args, content))) = parse_block_element(contents) {
|
if let Some((tail, (name, args, content, blank))) = parse_block_element(contents) {
|
||||||
match_block(
|
match_block(
|
||||||
arena,
|
arena,
|
||||||
parent,
|
parent,
|
||||||
|
@ -321,17 +333,19 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
|
||||||
name.into(),
|
name.into(),
|
||||||
args.map(Into::into),
|
args.map(Into::into),
|
||||||
content,
|
content,
|
||||||
|
blank,
|
||||||
);
|
);
|
||||||
Some(tail)
|
Some(tail)
|
||||||
} else if let Some((tail, (dyn_block, content))) = DynBlock::parse(contents) {
|
} else if let Some((tail, (dyn_block, content))) = DynBlock::parse(contents) {
|
||||||
let node = arena.append_element(dyn_block, parent);
|
let node = arena.append_element(dyn_block, parent);
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
Some(tail)
|
Some(tail)
|
||||||
} else if let Some((tail, (key, optional, value))) = parse_keyword(contents) {
|
} else if let Some((tail, (key, optional, value, blank))) = parse_keyword(contents) {
|
||||||
if (&*key).eq_ignore_ascii_case("CALL") {
|
if (&*key).eq_ignore_ascii_case("CALL") {
|
||||||
arena.append_element(
|
arena.append_element(
|
||||||
BabelCall {
|
BabelCall {
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
|
post_blank: blank,
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
);
|
);
|
||||||
|
@ -341,15 +355,15 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
|
||||||
key: key.into(),
|
key: key.into(),
|
||||||
optional: optional.map(Into::into),
|
optional: optional.map(Into::into),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
|
post_blank: blank,
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Some(tail)
|
Some(tail)
|
||||||
} else {
|
} else {
|
||||||
let (tail, value) = parse_comment(contents)?;
|
let (tail, comment) = Comment::parse(contents)?;
|
||||||
let value = value.into();
|
arena.append_element(comment, parent);
|
||||||
arena.append_element(Element::Comment { value }, parent);
|
|
||||||
Some(tail)
|
Some(tail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -364,14 +378,43 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
name: Cow<'a, str>,
|
name: Cow<'a, str>,
|
||||||
parameters: Option<Cow<'a, str>>,
|
parameters: Option<Cow<'a, str>>,
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
|
post_blank: usize,
|
||||||
) {
|
) {
|
||||||
match &*name.to_uppercase() {
|
match &*name.to_uppercase() {
|
||||||
"CENTER" => {
|
"CENTER" => {
|
||||||
let node = arena.append_element(CenterBlock { parameters }, parent);
|
let (content, pre_blank) = blank_lines(content);
|
||||||
|
let node = arena.append_element(
|
||||||
|
CenterBlock {
|
||||||
|
parameters,
|
||||||
|
pre_blank,
|
||||||
|
post_blank,
|
||||||
|
},
|
||||||
|
parent,
|
||||||
|
);
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
}
|
}
|
||||||
"QUOTE" => {
|
"QUOTE" => {
|
||||||
let node = arena.append_element(QuoteBlock { parameters }, parent);
|
let (content, pre_blank) = blank_lines(content);
|
||||||
|
let node = arena.append_element(
|
||||||
|
QuoteBlock {
|
||||||
|
parameters,
|
||||||
|
pre_blank,
|
||||||
|
post_blank,
|
||||||
|
},
|
||||||
|
parent,
|
||||||
|
);
|
||||||
|
containers.push(Container::Block { content, node });
|
||||||
|
}
|
||||||
|
"VERSE" => {
|
||||||
|
let (content, pre_blank) = blank_lines(content);
|
||||||
|
let node = arena.append_element(
|
||||||
|
VerseBlock {
|
||||||
|
parameters,
|
||||||
|
pre_blank,
|
||||||
|
post_blank,
|
||||||
|
},
|
||||||
|
parent,
|
||||||
|
);
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
}
|
}
|
||||||
"COMMENT" => {
|
"COMMENT" => {
|
||||||
|
@ -379,6 +422,7 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
CommentBlock {
|
CommentBlock {
|
||||||
data: parameters,
|
data: parameters,
|
||||||
contents: content.into(),
|
contents: content.into(),
|
||||||
|
post_blank,
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
);
|
);
|
||||||
|
@ -388,6 +432,7 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
ExampleBlock {
|
ExampleBlock {
|
||||||
data: parameters,
|
data: parameters,
|
||||||
contents: content.into(),
|
contents: content.into(),
|
||||||
|
post_blank,
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
);
|
);
|
||||||
|
@ -397,6 +442,7 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
ExportBlock {
|
ExportBlock {
|
||||||
data: parameters.unwrap_or_default(),
|
data: parameters.unwrap_or_default(),
|
||||||
contents: content.into(),
|
contents: content.into(),
|
||||||
|
post_blank,
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
);
|
);
|
||||||
|
@ -416,16 +462,22 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
arguments,
|
arguments,
|
||||||
language,
|
language,
|
||||||
contents: content.into(),
|
contents: content.into(),
|
||||||
|
post_blank,
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"VERSE" => {
|
|
||||||
let node = arena.append_element(VerseBlock { parameters }, parent);
|
|
||||||
containers.push(Container::Block { content, node });
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
let node = arena.append_element(SpecialBlock { parameters, name }, parent);
|
let (content, pre_blank) = blank_lines(content);
|
||||||
|
let node = arena.append_element(
|
||||||
|
SpecialBlock {
|
||||||
|
parameters,
|
||||||
|
name,
|
||||||
|
pre_blank,
|
||||||
|
post_blank,
|
||||||
|
},
|
||||||
|
parent,
|
||||||
|
);
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -727,26 +779,6 @@ pub fn parse_headline_level(input: &str) -> Option<(&str, usize)> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_fixed_width(input: &str) -> Option<(&str, &str)> {
|
|
||||||
let (input, content) = take_lines_while(|line| line == ":" || line.starts_with(": "))(input);
|
|
||||||
|
|
||||||
if !content.is_empty() {
|
|
||||||
Some((input, content))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_comment(input: &str) -> Option<(&str, &str)> {
|
|
||||||
let (input, content) = take_lines_while(|line| line == "#" || line.starts_with("# "))(input);
|
|
||||||
|
|
||||||
if !content.is_empty() {
|
|
||||||
Some((input, content))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take_one_word<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, &str, E> {
|
pub fn take_one_word<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, &str, E> {
|
||||||
take_while1(|c: char| !c.is_ascii_whitespace())(input)
|
take_while1(|c: char| !c.is_ascii_whitespace())(input)
|
||||||
}
|
}
|
||||||
|
@ -760,3 +792,27 @@ pub fn test_skip_empty_lines() {
|
||||||
assert_eq!(skip_empty_lines(" \n \n\nfoo\n"), "foo\n");
|
assert_eq!(skip_empty_lines(" \n \n\nfoo\n"), "foo\n");
|
||||||
assert_eq!(skip_empty_lines(" \n \n\n foo\n"), " foo\n");
|
assert_eq!(skip_empty_lines(" \n \n\n foo\n"), " foo\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn blank_lines(input: &str) -> (&str, usize) {
|
||||||
|
let bytes = input.as_bytes();
|
||||||
|
let mut blank = 0;
|
||||||
|
let mut last_end = 0;
|
||||||
|
for i in memchr_iter(b'\n', bytes) {
|
||||||
|
if bytes[last_end..i].iter().all(u8::is_ascii_whitespace) {
|
||||||
|
blank += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last_end = 1 + i;
|
||||||
|
}
|
||||||
|
(&input[last_end..], blank)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_blank_lines() {
|
||||||
|
assert_eq!(blank_lines("foo"), ("foo", 0));
|
||||||
|
assert_eq!(blank_lines(" foo"), (" foo", 0));
|
||||||
|
assert_eq!(blank_lines(" \t\nfoo\n"), ("foo\n", 1));
|
||||||
|
assert_eq!(blank_lines("\n \r\n\nfoo\n"), ("foo\n", 3));
|
||||||
|
assert_eq!(blank_lines("\r\n \n \r\n foo\n"), (" foo\n", 3));
|
||||||
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ impl Org<'_> {
|
||||||
for node_id in self.root.descendants(&self.arena) {
|
for node_id in self.root.descendants(&self.arena) {
|
||||||
let node = &self.arena[node_id];
|
let node = &self.arena[node_id];
|
||||||
match node.get() {
|
match node.get() {
|
||||||
Element::Document => {
|
Element::Document { .. } => {
|
||||||
let mut children = node_id.children(&self.arena);
|
let mut children = node_id.children(&self.arena);
|
||||||
if let Some(node) = children.next() {
|
if let Some(node) = children.next() {
|
||||||
expect!(
|
expect!(
|
||||||
|
@ -132,7 +132,7 @@ impl Org<'_> {
|
||||||
| Element::Comment { .. }
|
| Element::Comment { .. }
|
||||||
| Element::FixedWidth { .. }
|
| Element::FixedWidth { .. }
|
||||||
| Element::Keyword(_)
|
| Element::Keyword(_)
|
||||||
| Element::Rule
|
| Element::Rule(_)
|
||||||
| Element::Cookie(_)
|
| Element::Cookie(_)
|
||||||
| Element::Table(Table::TableEl { .. })
|
| Element::Table(Table::TableEl { .. })
|
||||||
| Element::TableRow(TableRow::Rule) => {
|
| Element::TableRow(TableRow::Rule) => {
|
||||||
|
|
66
tests/blank.rs
Normal file
66
tests/blank.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use orgize::Org;
|
||||||
|
|
||||||
|
const ORG_STR: &str = r#"
|
||||||
|
|
||||||
|
#+TITLE: org
|
||||||
|
|
||||||
|
#+BEGIN_QUOTE
|
||||||
|
|
||||||
|
CONTENTS
|
||||||
|
|
||||||
|
#+END_QUOTE
|
||||||
|
|
||||||
|
* Headline 1
|
||||||
|
SCHEDULED: <2019-10-28 Mon>
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: headline-1
|
||||||
|
:END:
|
||||||
|
|
||||||
|
:LOGBOOK:
|
||||||
|
|
||||||
|
CLOCK: [2019-10-28 Mon 08:53]
|
||||||
|
|
||||||
|
CLOCK: [2019-10-28 Mon 08:53]--[2019-10-28 Mon 08:53] => 0:00
|
||||||
|
|
||||||
|
:END:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
#+CALL: VALUE
|
||||||
|
|
||||||
|
#
|
||||||
|
# Comment
|
||||||
|
#
|
||||||
|
|
||||||
|
#+BEGIN: NAME PARAMETERS
|
||||||
|
|
||||||
|
CONTENTS
|
||||||
|
|
||||||
|
#+END:
|
||||||
|
|
||||||
|
:
|
||||||
|
: Fixed width
|
||||||
|
:
|
||||||
|
|
||||||
|
#+BEGIN_COMMENT
|
||||||
|
|
||||||
|
COMMENT
|
||||||
|
|
||||||
|
#+END_COMMENT
|
||||||
|
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blank() {
|
||||||
|
let org = Org::parse(ORG_STR);
|
||||||
|
|
||||||
|
let mut writer = Vec::new();
|
||||||
|
org.org(&mut writer).unwrap();
|
||||||
|
|
||||||
|
// eprintln!("{}", serde_json::to_string_pretty(&org).unwrap());
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8(writer).unwrap(), ORG_STR);
|
||||||
|
}
|
Loading…
Reference in a new issue