feat: pre_blank and post_blank

This commit is contained in:
PoiScript 2019-10-28 13:33:18 +08:00
parent 1a0240a747
commit 948b1be2db
19 changed files with 678 additions and 211 deletions

View file

@ -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
) )
)) ))
); );

View file

@ -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
View 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,
}
}
}

View file

@ -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,
}, },
"" ""
) )

View file

@ -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"
) )

View 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
}
))
);
}

View file

@ -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,
}, },
"" ""
) )

View file

@ -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)))
); );
} }

View file

@ -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
); );

View file

@ -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());
} }

View file

@ -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()
} }

View file

@ -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"
) )

View file

@ -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>")?,
@ -337,17 +339,17 @@ mod syntect_handler {
write!(w, "<pre class=\"example\">{}</pre>", block.contents)?; write!(w, "<pre class=\"example\">{}</pre>", block.contents)?;
} else { } else {
write!( write!(
w, w,
"<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,

View file

@ -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, &timestamp)?; write_timestamp(&mut w, &timestamp)?;
} }
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,28 +193,48 @@ 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 {
@ -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, ">")?;

View file

@ -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!(),
}) })
} }

View file

@ -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,

View file

@ -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));
}

View file

@ -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
View 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);
}