refactor(org): store content in separate Vec
This commit is contained in:
parent
50f6b9f52a
commit
3e82172dfe
|
@ -17,19 +17,18 @@ travis-ci = { repository = "PoiScript/orgize" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["serde"]
|
default = ["serde"]
|
||||||
extra-serde-info = ["serde"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytecount = "0.5.1"
|
bytecount = "0.5.1"
|
||||||
chrono = { version = "0.4.7", optional = true }
|
chrono = { version = "0.4.7", optional = true }
|
||||||
indextree = "3.2.0"
|
indextree = "3.3.0"
|
||||||
jetscii = "0.4.4"
|
jetscii = "0.4.4"
|
||||||
memchr = "2.2.0"
|
memchr = "2.2.1"
|
||||||
serde = { version = "1.0.94", optional = true, features = ["derive"] }
|
serde = { version = "1.0.97", optional = true, features = ["derive"] }
|
||||||
nom = "5.0.0"
|
nom = "5.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = "1.3.0"
|
lazy_static = "1.3.0"
|
||||||
pretty_assertions = "0.6.1"
|
pretty_assertions = "0.6.1"
|
||||||
serde_json = "1.0.39"
|
serde_json = "1.0.40"
|
||||||
slugify = "0.1.0"
|
slugify = "0.1.0"
|
||||||
|
|
|
@ -190,8 +190,6 @@ By now, orgize provides three features:
|
||||||
|
|
||||||
+ `serde`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
+ `serde`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
||||||
|
|
||||||
+ `extra-serde-info`: includes the position information while serializing, disabled by default.
|
|
||||||
|
|
||||||
+ `chrono`: adds the ability to convert `Datetime` into `chrono` struct, disabled by default.
|
+ `chrono`: adds the ability to convert `Datetime` into `chrono` struct, disabled by default.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -9,13 +9,11 @@ pub struct Block<'a> {
|
||||||
pub name: &'a str,
|
pub name: &'a str,
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub args: Option<&'a str>,
|
pub args: Option<&'a str>,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
pub contents: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Block<'_> {
|
impl Block<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(text: &str) -> Option<(&str, Element<'_>)> {
|
pub(crate) fn parse(text: &str) -> Option<(&str, Element<'_>, &str)> {
|
||||||
debug_assert!(text.starts_with("#+"));
|
debug_assert!(text.starts_with("#+"));
|
||||||
|
|
||||||
if text.len() <= 8 || text[2..8].to_uppercase() != "BEGIN_" {
|
if text.len() <= 8 || text[2..8].to_uppercase() != "BEGIN_" {
|
||||||
|
@ -40,11 +38,8 @@ impl Block<'_> {
|
||||||
if text[pos..i].trim().eq_ignore_ascii_case(&end) {
|
if text[pos..i].trim().eq_ignore_ascii_case(&end) {
|
||||||
return Some((
|
return Some((
|
||||||
&text[i + 1..],
|
&text[i + 1..],
|
||||||
Element::Block(Block {
|
Element::Block(Block { name, args }),
|
||||||
name,
|
&text[off..pos],
|
||||||
args,
|
|
||||||
contents: &text[off..pos],
|
|
||||||
}),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,14 +47,7 @@ impl Block<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if text[pos..].trim().eq_ignore_ascii_case(&end) {
|
if text[pos..].trim().eq_ignore_ascii_case(&end) {
|
||||||
Some((
|
Some(("", Element::Block(Block { name, args }), &text[off..pos]))
|
||||||
"",
|
|
||||||
Element::Block(Block {
|
|
||||||
name,
|
|
||||||
args,
|
|
||||||
contents: &text[off..pos],
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -75,8 +63,8 @@ fn parse() {
|
||||||
Element::Block(Block {
|
Element::Block(Block {
|
||||||
name: "SRC",
|
name: "SRC",
|
||||||
args: None,
|
args: None,
|
||||||
contents: ""
|
|
||||||
}),
|
}),
|
||||||
|
""
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -86,8 +74,8 @@ fn parse() {
|
||||||
Element::Block(Block {
|
Element::Block(Block {
|
||||||
name: "SRC",
|
name: "SRC",
|
||||||
args: Some("javascript"),
|
args: Some("javascript"),
|
||||||
contents: "console.log('Hello World!');\n"
|
|
||||||
}),
|
}),
|
||||||
|
"console.log('Hello World!');\n"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
// TODO: more testing
|
// TODO: more testing
|
||||||
|
|
|
@ -7,13 +7,11 @@ use crate::elements::Element;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Drawer<'a> {
|
pub struct Drawer<'a> {
|
||||||
pub name: &'a str,
|
pub name: &'a str,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
pub contents: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drawer<'_> {
|
impl Drawer<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(text: &str) -> Option<(&str, Element<'_>)> {
|
pub(crate) fn parse(text: &str) -> Option<(&str, Element<'_>, &str)> {
|
||||||
debug_assert!(text.starts_with(':'));
|
debug_assert!(text.starts_with(':'));
|
||||||
|
|
||||||
let mut lines = memchr_iter(b'\n', text.as_bytes());
|
let mut lines = memchr_iter(b'\n', text.as_bytes());
|
||||||
|
@ -36,8 +34,8 @@ impl Drawer<'_> {
|
||||||
&text[i + 1..],
|
&text[i + 1..],
|
||||||
Element::Drawer(Drawer {
|
Element::Drawer(Drawer {
|
||||||
name: &name[0..name.len() - 1],
|
name: &name[0..name.len() - 1],
|
||||||
contents: &text[off..pos],
|
|
||||||
}),
|
}),
|
||||||
|
&text[off..pos],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
pos = i + 1;
|
pos = i + 1;
|
||||||
|
@ -48,8 +46,8 @@ impl Drawer<'_> {
|
||||||
"",
|
"",
|
||||||
Element::Drawer(Drawer {
|
Element::Drawer(Drawer {
|
||||||
name: &name[0..name.len() - 1],
|
name: &name[0..name.len() - 1],
|
||||||
contents: &text[off..pos],
|
|
||||||
}),
|
}),
|
||||||
|
&text[off..pos],
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -63,10 +61,8 @@ fn parse() {
|
||||||
Drawer::parse(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
|
Drawer::parse(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
|
||||||
Some((
|
Some((
|
||||||
"",
|
"",
|
||||||
Element::Drawer(Drawer {
|
Element::Drawer(Drawer { name: "PROPERTIES" }),
|
||||||
name: "PROPERTIES",
|
" :CUSTOM_ID: id\n"
|
||||||
contents: " :CUSTOM_ID: id\n"
|
|
||||||
})
|
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,11 @@ pub struct DynBlock<'a> {
|
||||||
pub block_name: &'a str,
|
pub block_name: &'a str,
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub arguments: Option<&'a str>,
|
pub arguments: Option<&'a str>,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
pub contents: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DynBlock<'_> {
|
impl DynBlock<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
// return (dyn_block, contents-begin, contents-end, end)
|
pub(crate) fn parse(text: &str) -> Option<(&str, Element<'_>, &str)> {
|
||||||
pub(crate) fn parse(text: &str) -> Option<(&str, Element<'_>)> {
|
|
||||||
debug_assert!(text.starts_with("#+"));
|
debug_assert!(text.starts_with("#+"));
|
||||||
|
|
||||||
if text.len() <= "#+BEGIN: ".len() || !text[2..9].eq_ignore_ascii_case("BEGIN: ") {
|
if text.len() <= "#+BEGIN: ".len() || !text[2..9].eq_ignore_ascii_case("BEGIN: ") {
|
||||||
|
@ -50,8 +47,8 @@ impl DynBlock<'_> {
|
||||||
Element::DynBlock(DynBlock {
|
Element::DynBlock(DynBlock {
|
||||||
block_name: name,
|
block_name: name,
|
||||||
arguments: para,
|
arguments: para,
|
||||||
contents: &text[off..pos],
|
|
||||||
}),
|
}),
|
||||||
|
&text[off..pos],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,8 +61,8 @@ impl DynBlock<'_> {
|
||||||
Element::DynBlock(DynBlock {
|
Element::DynBlock(DynBlock {
|
||||||
block_name: name,
|
block_name: name,
|
||||||
arguments: para,
|
arguments: para,
|
||||||
contents: &text[off..pos],
|
|
||||||
}),
|
}),
|
||||||
|
&text[off..pos],
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -83,8 +80,8 @@ fn parse() {
|
||||||
Element::DynBlock(DynBlock {
|
Element::DynBlock(DynBlock {
|
||||||
block_name: "clocktable",
|
block_name: "clocktable",
|
||||||
arguments: Some(":scope file"),
|
arguments: Some(":scope file"),
|
||||||
contents: "CONTENTS\n"
|
}),
|
||||||
},)
|
"CONTENTS\n"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,32 @@
|
||||||
use memchr::memchr;
|
use memchr::memchr;
|
||||||
|
use nom::{
|
||||||
|
bytes::complete::{tag, take_while1},
|
||||||
|
IResult,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FnDef<'a> {
|
pub struct FnDef<'a> {
|
||||||
pub label: &'a str,
|
pub label: &'a str,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
}
|
||||||
pub contents: &'a str,
|
|
||||||
|
fn parse_label(input: &str) -> IResult<&str, &str> {
|
||||||
|
let (input, _) = tag("[fn:")(input)?;
|
||||||
|
let (input, label) =
|
||||||
|
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_')(input)?;
|
||||||
|
let (input, _) = tag("]")(input)?;
|
||||||
|
Ok((input, label))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FnDef<'_> {
|
impl FnDef<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(text: &str) -> Option<(&str, FnDef<'_>)> {
|
pub(crate) fn parse(text: &str) -> Option<(&str, FnDef<'_>, &str)> {
|
||||||
if text.starts_with("[fn:") {
|
let (tail, label) = parse_label(text).ok()?;
|
||||||
let (label, off) = memchr(b']', text.as_bytes())
|
|
||||||
.filter(|&i| {
|
|
||||||
i != 4
|
|
||||||
&& text.as_bytes()["[fn:".len()..i]
|
|
||||||
.iter()
|
|
||||||
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
|
||||||
})
|
|
||||||
.map(|i| (&text["[fn:".len()..i], i + 1))?;
|
|
||||||
|
|
||||||
let end = memchr(b'\n', text.as_bytes()).unwrap_or_else(|| text.len());
|
let end = memchr(b'\n', tail.as_bytes()).unwrap_or_else(|| tail.len());
|
||||||
|
|
||||||
Some((
|
Some((&tail[end..], FnDef { label }, &tail[0..end]))
|
||||||
&text[end..],
|
|
||||||
FnDef {
|
|
||||||
label,
|
|
||||||
contents: &text[off..end],
|
|
||||||
},
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,43 +34,19 @@ impl FnDef<'_> {
|
||||||
fn parse() {
|
fn parse() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FnDef::parse("[fn:1] https://orgmode.org"),
|
FnDef::parse("[fn:1] https://orgmode.org"),
|
||||||
Some((
|
Some(("", FnDef { label: "1" }, " https://orgmode.org"))
|
||||||
"",
|
|
||||||
FnDef {
|
|
||||||
label: "1",
|
|
||||||
contents: " https://orgmode.org"
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FnDef::parse("[fn:word_1] https://orgmode.org"),
|
FnDef::parse("[fn:word_1] https://orgmode.org"),
|
||||||
Some((
|
Some(("", FnDef { label: "word_1" }, " https://orgmode.org"))
|
||||||
"",
|
|
||||||
FnDef {
|
|
||||||
label: "word_1",
|
|
||||||
contents: " https://orgmode.org"
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FnDef::parse("[fn:WORD-1] https://orgmode.org"),
|
FnDef::parse("[fn:WORD-1] https://orgmode.org"),
|
||||||
Some((
|
Some(("", FnDef { label: "WORD-1" }, " https://orgmode.org"))
|
||||||
"",
|
|
||||||
FnDef {
|
|
||||||
label: "WORD-1",
|
|
||||||
contents: " https://orgmode.org"
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FnDef::parse("[fn:WORD]"),
|
FnDef::parse("[fn:WORD]"),
|
||||||
Some((
|
Some(("", FnDef { label: "WORD" }, ""))
|
||||||
"",
|
|
||||||
FnDef {
|
|
||||||
label: "WORD",
|
|
||||||
contents: ""
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
assert_eq!(FnDef::parse("[fn:] https://orgmode.org"), None);
|
assert_eq!(FnDef::parse("[fn:] https://orgmode.org"), None);
|
||||||
assert_eq!(FnDef::parse("[fn:wor d] https://orgmode.org"), None);
|
assert_eq!(FnDef::parse("[fn:wor d] https://orgmode.org"), None);
|
||||||
|
|
|
@ -22,12 +22,13 @@ pub struct Headline<'a> {
|
||||||
/// headline keyword
|
/// headline keyword
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub keyword: Option<&'a str>,
|
pub keyword: Option<&'a str>,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
pub contents: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Headline<'_> {
|
impl Headline<'_> {
|
||||||
pub(crate) fn parse<'a>(text: &'a str, config: &ParseConfig<'_>) -> (&'a str, Headline<'a>) {
|
pub(crate) fn parse<'a>(
|
||||||
|
text: &'a str,
|
||||||
|
config: &ParseConfig<'_>,
|
||||||
|
) -> (&'a str, Headline<'a>, &'a str) {
|
||||||
let level = memchr2(b'\n', b' ', text.as_bytes()).unwrap_or_else(|| text.len());
|
let level = memchr2(b'\n', b' ', text.as_bytes()).unwrap_or_else(|| text.len());
|
||||||
|
|
||||||
debug_assert!(level > 0);
|
debug_assert!(level > 0);
|
||||||
|
@ -55,8 +56,8 @@ impl Headline<'_> {
|
||||||
priority: None,
|
priority: None,
|
||||||
title: "",
|
title: "",
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
contents: &text[off..end],
|
|
||||||
},
|
},
|
||||||
|
&text[off..end],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,8 +109,8 @@ impl Headline<'_> {
|
||||||
priority,
|
priority,
|
||||||
title,
|
title,
|
||||||
tags: tags.split(':').filter(|s| !s.is_empty()).collect(),
|
tags: tags.split(':').filter(|s| !s.is_empty()).collect(),
|
||||||
contents: &text[off..end],
|
|
||||||
},
|
},
|
||||||
|
&text[off..end],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,8 +166,8 @@ fn parse() {
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
title: "COMMENT Title",
|
title: "COMMENT Title",
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
contents: ""
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -179,8 +180,8 @@ fn parse() {
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
title: "ToDO [#A] COMMENT Title",
|
title: "ToDO [#A] COMMENT Title",
|
||||||
keyword: None,
|
keyword: None,
|
||||||
contents: ""
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -193,8 +194,8 @@ fn parse() {
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
title: "T0DO [#A] COMMENT Title",
|
title: "T0DO [#A] COMMENT Title",
|
||||||
keyword: None,
|
keyword: None,
|
||||||
contents: ""
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -207,8 +208,8 @@ fn parse() {
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
title: "[#1] COMMENT Title",
|
title: "[#1] COMMENT Title",
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
contents: "",
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -221,8 +222,8 @@ fn parse() {
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
title: "[#a] COMMENT Title",
|
title: "[#a] COMMENT Title",
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
contents: "",
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -235,8 +236,8 @@ fn parse() {
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
title: "COMMENT Title :tag:a2%",
|
title: "COMMENT Title :tag:a2%",
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
contents: ""
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -249,8 +250,8 @@ fn parse() {
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
title: "COMMENT Title tag:a2%:",
|
title: "COMMENT Title tag:a2%:",
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
contents: ""
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -263,8 +264,8 @@ fn parse() {
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
title: "COMMENT Title tag:a2%:",
|
title: "COMMENT Title tag:a2%:",
|
||||||
keyword: None,
|
keyword: None,
|
||||||
contents: ""
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -287,8 +288,8 @@ fn parse_todo_keywords() {
|
||||||
keyword: None,
|
keyword: None,
|
||||||
title: "DONE [#A] COMMENT Title",
|
title: "DONE [#A] COMMENT Title",
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
contents: ""
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -307,8 +308,8 @@ fn parse_todo_keywords() {
|
||||||
keyword: Some("TASK"),
|
keyword: Some("TASK"),
|
||||||
title: "COMMENT Title",
|
title: "COMMENT Title",
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
contents: ""
|
|
||||||
},
|
},
|
||||||
|
""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,14 @@ use std::iter::once;
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct List<'a> {
|
pub struct List {
|
||||||
pub indent: usize,
|
pub indent: usize,
|
||||||
pub ordered: bool,
|
pub ordered: bool,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
pub contents: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl List<'_> {
|
impl List {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(text: &str) -> Option<(&str, List<'_>)> {
|
pub(crate) fn parse(text: &str) -> Option<(&str, List, &str)> {
|
||||||
let (indent, tail) = text
|
let (indent, tail) = text
|
||||||
.find(|c| c != ' ')
|
.find(|c| c != ' ')
|
||||||
.map(|off| (off, &text[off..]))
|
.map(|off| (off, &text[off..]))
|
||||||
|
@ -33,14 +31,7 @@ impl List<'_> {
|
||||||
if line_indent < indent
|
if line_indent < indent
|
||||||
|| (line_indent == indent && is_item(&line[line_indent..]).is_none())
|
|| (line_indent == indent && is_item(&line[line_indent..]).is_none())
|
||||||
{
|
{
|
||||||
Some((
|
Some((&text[pos..], List { indent, ordered }, &text[0..pos]))
|
||||||
&text[pos..],
|
|
||||||
List {
|
|
||||||
indent,
|
|
||||||
ordered,
|
|
||||||
contents: &text[0..pos],
|
|
||||||
},
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
pos = i;
|
pos = i;
|
||||||
continue;
|
continue;
|
||||||
|
@ -52,48 +43,20 @@ impl List<'_> {
|
||||||
if line_indent < indent
|
if line_indent < indent
|
||||||
|| (line_indent == indent && is_item(&line[line_indent..]).is_none())
|
|| (line_indent == indent && is_item(&line[line_indent..]).is_none())
|
||||||
{
|
{
|
||||||
Some((
|
Some((&text[pos..], List { indent, ordered }, &text[0..pos]))
|
||||||
&text[pos..],
|
|
||||||
List {
|
|
||||||
indent,
|
|
||||||
ordered,
|
|
||||||
contents: &text[0..pos],
|
|
||||||
},
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
pos = next_i;
|
pos = next_i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Some((
|
Some((&text[next_i..], List { indent, ordered }, &text[0..pos]))
|
||||||
&text[next_i..],
|
|
||||||
List {
|
|
||||||
indent,
|
|
||||||
ordered,
|
|
||||||
contents: &text[0..pos],
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Some((
|
Some((&text[i..], List { indent, ordered }, &text[0..pos]))
|
||||||
&text[i..],
|
|
||||||
List {
|
|
||||||
indent,
|
|
||||||
ordered,
|
|
||||||
contents: &text[0..pos],
|
|
||||||
},
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((
|
Some((&text[pos..], List { indent, ordered }, &text[0..pos]))
|
||||||
&text[pos..],
|
|
||||||
List {
|
|
||||||
indent,
|
|
||||||
ordered,
|
|
||||||
contents: &text[0..pos],
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,12 +65,11 @@ impl List<'_> {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ListItem<'a> {
|
pub struct ListItem<'a> {
|
||||||
pub bullet: &'a str,
|
pub bullet: &'a str,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
pub contents: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListItem<'_> {
|
impl ListItem<'_> {
|
||||||
pub(crate) fn parse(text: &str, indent: usize) -> (&str, ListItem<'_>) {
|
#[inline]
|
||||||
|
pub(crate) fn parse(text: &str, indent: usize) -> (&str, ListItem<'_>, &str) {
|
||||||
debug_assert!(&text[0..indent].trim().is_empty());
|
debug_assert!(&text[0..indent].trim().is_empty());
|
||||||
let off = &text[indent..].find(' ').unwrap() + 1 + indent;
|
let off = &text[indent..].find(' ').unwrap() + 1 + indent;
|
||||||
|
|
||||||
|
@ -125,8 +87,8 @@ impl ListItem<'_> {
|
||||||
&text[pos..],
|
&text[pos..],
|
||||||
ListItem {
|
ListItem {
|
||||||
bullet: &text[indent..off],
|
bullet: &text[indent..off],
|
||||||
contents: &text[off..pos],
|
|
||||||
},
|
},
|
||||||
|
&text[off..pos],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,8 +99,8 @@ impl ListItem<'_> {
|
||||||
"",
|
"",
|
||||||
ListItem {
|
ListItem {
|
||||||
bullet: &text[indent..off],
|
bullet: &text[indent..off],
|
||||||
contents: &text[off..],
|
|
||||||
},
|
},
|
||||||
|
&text[off..],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,8 +159,8 @@ fn list_parse() {
|
||||||
List {
|
List {
|
||||||
indent: 0,
|
indent: 0,
|
||||||
ordered: false,
|
ordered: false,
|
||||||
contents: "+ item1\n+ item2"
|
|
||||||
},
|
},
|
||||||
|
"+ item1\n+ item2"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -208,8 +170,8 @@ fn list_parse() {
|
||||||
List {
|
List {
|
||||||
indent: 0,
|
indent: 0,
|
||||||
ordered: false,
|
ordered: false,
|
||||||
contents: "* item1\n \n* item2"
|
|
||||||
},
|
},
|
||||||
|
"* item1\n \n* item2"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -219,8 +181,8 @@ fn list_parse() {
|
||||||
List {
|
List {
|
||||||
indent: 0,
|
indent: 0,
|
||||||
ordered: false,
|
ordered: false,
|
||||||
contents: "* item1\n"
|
|
||||||
},
|
},
|
||||||
|
"* item1\n"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -230,8 +192,8 @@ fn list_parse() {
|
||||||
List {
|
List {
|
||||||
indent: 0,
|
indent: 0,
|
||||||
ordered: false,
|
ordered: false,
|
||||||
contents: "* item1\n"
|
|
||||||
},
|
},
|
||||||
|
"* item1\n"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -241,8 +203,8 @@ fn list_parse() {
|
||||||
List {
|
List {
|
||||||
indent: 0,
|
indent: 0,
|
||||||
ordered: false,
|
ordered: false,
|
||||||
contents: "+ item1\n + item2\n"
|
|
||||||
},
|
},
|
||||||
|
"+ item1\n + item2\n"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -252,8 +214,8 @@ fn list_parse() {
|
||||||
List {
|
List {
|
||||||
indent: 0,
|
indent: 0,
|
||||||
ordered: false,
|
ordered: false,
|
||||||
contents: "+ item1\n \n + item2\n \n+ item 3"
|
|
||||||
},
|
},
|
||||||
|
"+ item1\n \n + item2\n \n+ item 3"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -263,8 +225,8 @@ fn list_parse() {
|
||||||
List {
|
List {
|
||||||
indent: 2,
|
indent: 2,
|
||||||
ordered: false,
|
ordered: false,
|
||||||
contents: " + item1\n \n + item2"
|
|
||||||
},
|
},
|
||||||
|
" + item1\n \n + item2"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -274,8 +236,8 @@ fn list_parse() {
|
||||||
List {
|
List {
|
||||||
indent: 0,
|
indent: 0,
|
||||||
ordered: false,
|
ordered: false,
|
||||||
contents: "+ 1\n\n - 2\n\n - 3\n\n+ 4"
|
|
||||||
},
|
},
|
||||||
|
"+ 1\n\n - 2\n\n - 3\n\n+ 4"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ fn parse() {
|
||||||
Element::Macros(Macros {
|
Element::Macros(Macros {
|
||||||
name: "poem",
|
name: "poem",
|
||||||
arguments: Some("red,blue")
|
arguments: Some("red,blue")
|
||||||
},)
|
})
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -54,7 +54,7 @@ fn parse() {
|
||||||
Element::Macros(Macros {
|
Element::Macros(Macros {
|
||||||
name: "poem",
|
name: "poem",
|
||||||
arguments: Some(")")
|
arguments: Some(")")
|
||||||
},)
|
})
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -64,7 +64,7 @@ fn parse() {
|
||||||
Element::Macros(Macros {
|
Element::Macros(Macros {
|
||||||
name: "author",
|
name: "author",
|
||||||
arguments: None
|
arguments: None
|
||||||
},)
|
})
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert!(Macros::parse("{{{0uthor}}}").is_err());
|
assert!(Macros::parse("{{{0uthor}}}").is_err());
|
||||||
|
|
|
@ -56,23 +56,16 @@ pub use self::{
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(tag = "type"))]
|
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
|
||||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
|
||||||
pub enum Element<'a> {
|
pub enum Element<'a> {
|
||||||
Block(Block<'a>),
|
Block(Block<'a>),
|
||||||
BabelCall(BabelCall<'a>),
|
BabelCall(BabelCall<'a>),
|
||||||
Section {
|
Section,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
contents: &'a str,
|
|
||||||
},
|
|
||||||
Clock(Clock<'a>),
|
Clock(Clock<'a>),
|
||||||
Cookie(Cookie<'a>),
|
Cookie(Cookie<'a>),
|
||||||
RadioTarget(RadioTarget<'a>),
|
RadioTarget(RadioTarget),
|
||||||
Drawer(Drawer<'a>),
|
Drawer(Drawer<'a>),
|
||||||
Document {
|
Document,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
contents: &'a str,
|
|
||||||
},
|
|
||||||
DynBlock(DynBlock<'a>),
|
DynBlock(DynBlock<'a>),
|
||||||
FnDef(FnDef<'a>),
|
FnDef(FnDef<'a>),
|
||||||
FnRef(FnRef<'a>),
|
FnRef(FnRef<'a>),
|
||||||
|
@ -81,49 +74,24 @@ pub enum Element<'a> {
|
||||||
InlineSrc(InlineSrc<'a>),
|
InlineSrc(InlineSrc<'a>),
|
||||||
Keyword(Keyword<'a>),
|
Keyword(Keyword<'a>),
|
||||||
Link(Link<'a>),
|
Link(Link<'a>),
|
||||||
List(List<'a>),
|
List(List),
|
||||||
ListItem(ListItem<'a>),
|
ListItem(ListItem<'a>),
|
||||||
Macros(Macros<'a>),
|
Macros(Macros<'a>),
|
||||||
Planning(Planning<'a>),
|
Planning(Planning<'a>),
|
||||||
Snippet(Snippet<'a>),
|
Snippet(Snippet<'a>),
|
||||||
Text {
|
Text { value: &'a str },
|
||||||
value: &'a str,
|
Paragraph,
|
||||||
},
|
|
||||||
Paragraph {
|
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
contents: &'a str,
|
|
||||||
},
|
|
||||||
Rule,
|
Rule,
|
||||||
Timestamp(Timestamp<'a>),
|
Timestamp(Timestamp<'a>),
|
||||||
Target(Target<'a>),
|
Target(Target<'a>),
|
||||||
Bold {
|
Bold,
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
Strike,
|
||||||
contents: &'a str,
|
Italic,
|
||||||
},
|
Underline,
|
||||||
Strike {
|
Verbatim { value: &'a str },
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
Code { value: &'a str },
|
||||||
contents: &'a str,
|
Comment { value: &'a str },
|
||||||
},
|
FixedWidth { value: &'a str },
|
||||||
Italic {
|
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
contents: &'a str,
|
|
||||||
},
|
|
||||||
Underline {
|
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
contents: &'a str,
|
|
||||||
},
|
|
||||||
Verbatim {
|
|
||||||
value: &'a str,
|
|
||||||
},
|
|
||||||
Code {
|
|
||||||
value: &'a str,
|
|
||||||
},
|
|
||||||
Comment {
|
|
||||||
value: &'a str,
|
|
||||||
},
|
|
||||||
FixedWidth {
|
|
||||||
value: &'a str,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_from {
|
macro_rules! impl_from {
|
||||||
|
@ -140,7 +108,6 @@ impl_from!(Block);
|
||||||
impl_from!(BabelCall);
|
impl_from!(BabelCall);
|
||||||
impl_from!(Clock);
|
impl_from!(Clock);
|
||||||
impl_from!(Cookie);
|
impl_from!(Cookie);
|
||||||
impl_from!(RadioTarget);
|
|
||||||
impl_from!(Drawer);
|
impl_from!(Drawer);
|
||||||
impl_from!(DynBlock);
|
impl_from!(DynBlock);
|
||||||
impl_from!(FnDef);
|
impl_from!(FnDef);
|
||||||
|
@ -150,7 +117,6 @@ impl_from!(InlineCall);
|
||||||
impl_from!(InlineSrc);
|
impl_from!(InlineSrc);
|
||||||
impl_from!(Keyword);
|
impl_from!(Keyword);
|
||||||
impl_from!(Link);
|
impl_from!(Link);
|
||||||
impl_from!(List);
|
|
||||||
impl_from!(ListItem);
|
impl_from!(ListItem);
|
||||||
impl_from!(Macros);
|
impl_from!(Macros);
|
||||||
impl_from!(Planning);
|
impl_from!(Planning);
|
||||||
|
|
|
@ -10,14 +10,11 @@ use crate::elements::Element;
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RadioTarget<'a> {
|
pub struct RadioTarget;
|
||||||
#[cfg_attr(all(feature = "serde", not(feature = "extra-serde-info")), serde(skip))]
|
|
||||||
contents: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RadioTarget<'_> {
|
impl RadioTarget {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, (Element, &str)> {
|
||||||
let (input, _) = tag("<<<")(input)?;
|
let (input, _) = tag("<<<")(input)?;
|
||||||
let (input, contents) = verify(
|
let (input, contents) = verify(
|
||||||
take_while(|c: char| c != '<' && c != '\n' && c != '>'),
|
take_while(|c: char| c != '<' && c != '\n' && c != '>'),
|
||||||
|
@ -25,7 +22,7 @@ impl RadioTarget<'_> {
|
||||||
)(input)?;
|
)(input)?;
|
||||||
let (input, _) = tag(">>>")(input)?;
|
let (input, _) = tag(">>>")(input)?;
|
||||||
|
|
||||||
Ok((input, Element::RadioTarget(RadioTarget { contents })))
|
Ok((input, (Element::RadioTarget(RadioTarget), contents)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,16 +30,11 @@ impl RadioTarget<'_> {
|
||||||
fn parse() {
|
fn parse() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
RadioTarget::parse("<<<target>>>"),
|
RadioTarget::parse("<<<target>>>"),
|
||||||
Ok(("", Element::RadioTarget(RadioTarget { contents: "target" })))
|
Ok(("", (Element::RadioTarget(RadioTarget), "target")))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
RadioTarget::parse("<<<tar get>>>"),
|
RadioTarget::parse("<<<tar get>>>"),
|
||||||
Ok((
|
Ok(("", (Element::RadioTarget(RadioTarget), "tar get")))
|
||||||
"",
|
|
||||||
Element::RadioTarget(RadioTarget {
|
|
||||||
contents: "tar get"
|
|
||||||
},)
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
assert!(RadioTarget::parse("<<<target >>>").is_err());
|
assert!(RadioTarget::parse("<<<target >>>").is_err());
|
||||||
assert!(RadioTarget::parse("<<< target>>>").is_err());
|
assert!(RadioTarget::parse("<<< target>>>").is_err());
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
pub mod html;
|
pub mod html;
|
||||||
|
pub mod org;
|
||||||
|
|
||||||
pub use html::{DefaultHtmlHandler, HtmlHandler};
|
pub use html::{DefaultHtmlHandler, HtmlHandler};
|
||||||
|
pub use org::{DefaultOrgHandler, OrgHandler};
|
||||||
|
|
|
@ -201,8 +201,6 @@
|
||||||
//!
|
//!
|
||||||
//! + `serde`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
//! + `serde`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
||||||
//!
|
//!
|
||||||
//! + `extra-serde-info`: includes the position information while serializing, disabled by default.
|
|
||||||
//!
|
|
||||||
//! + `chrono`: adds the ability to convert `Datetime` into `chrono` struct, disabled by default.
|
//! + `chrono`: adds the ability to convert `Datetime` into `chrono` struct, disabled by default.
|
||||||
//!
|
//!
|
||||||
//! # License
|
//! # License
|
||||||
|
|
483
src/org.rs
483
src/org.rs
|
@ -5,7 +5,7 @@ use std::io::{Error, Write};
|
||||||
|
|
||||||
use crate::config::ParseConfig;
|
use crate::config::ParseConfig;
|
||||||
use crate::elements::*;
|
use crate::elements::*;
|
||||||
use crate::export::{DefaultHtmlHandler, HtmlHandler};
|
use crate::export::*;
|
||||||
use crate::iter::Iter;
|
use crate::iter::Iter;
|
||||||
|
|
||||||
pub struct Org<'a> {
|
pub struct Org<'a> {
|
||||||
|
@ -13,19 +13,104 @@ pub struct Org<'a> {
|
||||||
pub(crate) document: NodeId,
|
pub(crate) document: NodeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Container<'a> {
|
||||||
|
// List
|
||||||
|
List {
|
||||||
|
content: &'a str,
|
||||||
|
node: NodeId,
|
||||||
|
indent: usize,
|
||||||
|
},
|
||||||
|
// Block, List Item
|
||||||
|
Block {
|
||||||
|
content: &'a str,
|
||||||
|
node: NodeId,
|
||||||
|
},
|
||||||
|
// Pargraph, Inline Markup
|
||||||
|
Inline {
|
||||||
|
content: &'a str,
|
||||||
|
node: NodeId,
|
||||||
|
},
|
||||||
|
// Headline, Document
|
||||||
|
Headline {
|
||||||
|
content: &'a str,
|
||||||
|
node: NodeId,
|
||||||
|
},
|
||||||
|
// Section
|
||||||
|
Section {
|
||||||
|
content: &'a str,
|
||||||
|
node: NodeId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Org<'a> {
|
impl<'a> Org<'a> {
|
||||||
pub fn parse(text: &'a str) -> Self {
|
pub fn parse(text: &'a str) -> Self {
|
||||||
Org::parse_with_config(text, ParseConfig::default())
|
Org::parse_with_config(text, ParseConfig::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_with_config(text: &'a str, config: ParseConfig<'_>) -> Self {
|
pub fn parse_with_config(content: &'a str, config: ParseConfig<'_>) -> Self {
|
||||||
let mut arena = Arena::new();
|
let mut arena = Arena::new();
|
||||||
let document = arena.new_node(Element::Document { contents: text });
|
let document = arena.new_node(Element::Document);
|
||||||
|
|
||||||
let mut org = Org { arena, document };
|
let mut containers = vec![Container::Headline {
|
||||||
org.parse_internal(config);
|
content,
|
||||||
|
node: document,
|
||||||
|
}];
|
||||||
|
|
||||||
org
|
while let Some(container) = containers.pop() {
|
||||||
|
match container {
|
||||||
|
Container::Headline {
|
||||||
|
mut content,
|
||||||
|
node: parent,
|
||||||
|
} => {
|
||||||
|
if !content.is_empty() {
|
||||||
|
let off = Headline::find_level(content, std::usize::MAX);
|
||||||
|
if off != 0 {
|
||||||
|
let node = arena.new_node(Element::Section);
|
||||||
|
parent.append(node, &mut arena).unwrap();
|
||||||
|
containers.push(Container::Section {
|
||||||
|
content: &content[0..off],
|
||||||
|
node,
|
||||||
|
});
|
||||||
|
content = &content[off..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while !content.is_empty() {
|
||||||
|
let (tail, headline, headline_content) = Headline::parse(content, &config);
|
||||||
|
let headline = Element::Headline(headline);
|
||||||
|
let node = arena.new_node(headline);
|
||||||
|
parent.append(node, &mut arena).unwrap();
|
||||||
|
containers.push(Container::Headline {
|
||||||
|
content: headline_content,
|
||||||
|
node,
|
||||||
|
});
|
||||||
|
content = tail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Container::Section { content, node } => {
|
||||||
|
// TODO
|
||||||
|
if let Some((tail, _planning)) = Planning::parse(content) {
|
||||||
|
parse_elements_children(&mut arena, tail, node, &mut containers);
|
||||||
|
} else {
|
||||||
|
parse_elements_children(&mut arena, content, node, &mut containers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Container::Block { content, node } => {
|
||||||
|
parse_elements_children(&mut arena, content, node, &mut containers);
|
||||||
|
}
|
||||||
|
Container::Inline { content, node } => {
|
||||||
|
parse_objects_children(&mut arena, content, node, &mut containers);
|
||||||
|
}
|
||||||
|
Container::List {
|
||||||
|
content,
|
||||||
|
node,
|
||||||
|
indent,
|
||||||
|
} => {
|
||||||
|
parse_list_items(&mut arena, content, indent, node, &mut containers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Org { arena, document }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&'a self) -> Iter<'a> {
|
pub fn iter(&'a self) -> Iter<'a> {
|
||||||
|
@ -49,96 +134,25 @@ impl<'a> Org<'a> {
|
||||||
|
|
||||||
for event in self.iter() {
|
for event in self.iter() {
|
||||||
match event {
|
match event {
|
||||||
Start(e) => handler.start(&mut writer, e)?,
|
Start(element) => handler.start(&mut writer, element)?,
|
||||||
End(e) => handler.end(&mut writer, e)?,
|
End(element) => handler.end(&mut writer, element)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_internal(&mut self, config: ParseConfig<'_>) {
|
|
||||||
let mut node = self.document;
|
|
||||||
loop {
|
|
||||||
match self.arena[node].data {
|
|
||||||
Element::Document { mut contents }
|
|
||||||
| Element::Headline(Headline { mut contents, .. }) => {
|
|
||||||
if !contents.is_empty() {
|
|
||||||
let off = Headline::find_level(contents, std::usize::MAX);
|
|
||||||
if off != 0 {
|
|
||||||
let section = Element::Section {
|
|
||||||
contents: &contents[0..off],
|
|
||||||
};
|
|
||||||
let new_node = self.arena.new_node(section);
|
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
contents = &contents[off..];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while !contents.is_empty() {
|
|
||||||
let (tail, headline) = Headline::parse(contents, &config);
|
|
||||||
let headline = Element::Headline(headline);
|
|
||||||
let new_node = self.arena.new_node(headline);
|
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
contents = tail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Section { contents } => {
|
|
||||||
// TODO
|
|
||||||
if let Some((tail, _planning)) = Planning::parse(contents) {
|
|
||||||
self.parse_elements_children(tail, node);
|
|
||||||
} else {
|
|
||||||
self.parse_elements_children(contents, node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Block(Block { contents, .. })
|
|
||||||
| Element::ListItem(ListItem { contents, .. }) => {
|
|
||||||
self.parse_elements_children(contents, node);
|
|
||||||
}
|
|
||||||
Element::Paragraph { contents }
|
|
||||||
| Element::Bold { contents }
|
|
||||||
| Element::Underline { contents }
|
|
||||||
| Element::Italic { contents }
|
|
||||||
| Element::Strike { contents } => {
|
|
||||||
self.parse_objects_children(contents, node);
|
|
||||||
}
|
|
||||||
Element::List(List {
|
|
||||||
contents, indent, ..
|
|
||||||
}) => {
|
|
||||||
self.parse_list_items(contents, indent, node);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(next_node) = self.next_node(node) {
|
fn parse_elements_children<'a>(
|
||||||
node = next_node;
|
arena: &mut Arena<Element<'a>>,
|
||||||
} else {
|
content: &'a str,
|
||||||
break;
|
parent: NodeId,
|
||||||
}
|
containers: &mut Vec<Container<'a>>,
|
||||||
}
|
) {
|
||||||
}
|
let mut tail = skip_empty_lines(content);
|
||||||
|
|
||||||
fn next_node(&self, mut node: NodeId) -> Option<NodeId> {
|
if let Some((new_tail, element)) = parse_element(content, arena, containers) {
|
||||||
if let Some(child) = self.arena[node].first_child() {
|
parent.append(element, arena).unwrap();
|
||||||
return Some(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Some(sibling) = self.arena[node].next_sibling() {
|
|
||||||
return Some(sibling);
|
|
||||||
} else if let Some(parent) = self.arena[node].parent() {
|
|
||||||
node = parent;
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_elements_children(&mut self, input: &'a str, node: NodeId) {
|
|
||||||
let mut tail = skip_empty_lines(input);
|
|
||||||
|
|
||||||
if let Some((new_tail, element)) = self.parse_element(input) {
|
|
||||||
let new_node = self.arena.new_node(element);
|
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
tail = skip_empty_lines(new_tail);
|
tail = skip_empty_lines(new_tail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,30 +165,25 @@ impl<'a> Org<'a> {
|
||||||
.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) {
|
||||||
tail = skip_empty_lines(&tail[i..]);
|
tail = skip_empty_lines(&tail[i..]);
|
||||||
let new_node = self.arena.new_node(Element::Paragraph {
|
let node = arena.new_node(Element::Paragraph);
|
||||||
contents: if text.as_bytes()[pos - 1] == b'\n' {
|
parent.append(node, arena).unwrap();
|
||||||
&text[0..pos - 1]
|
containers.push(Container::Inline {
|
||||||
} else {
|
content: &text[0..pos].trim_end_matches('\n'),
|
||||||
&text[0..pos]
|
node,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
text = tail;
|
text = tail;
|
||||||
pos = 0;
|
pos = 0;
|
||||||
} else if let Some((new_tail, element)) = self.parse_element(tail) {
|
} else if let Some((new_tail, element)) = parse_element(tail, arena, containers) {
|
||||||
if pos != 0 {
|
if pos != 0 {
|
||||||
let new_node = self.arena.new_node(Element::Paragraph {
|
let node = arena.new_node(Element::Paragraph);
|
||||||
contents: if text.as_bytes()[pos - 1] == b'\n' {
|
parent.append(node, arena).unwrap();
|
||||||
&text[0..pos - 1]
|
containers.push(Container::Inline {
|
||||||
} else {
|
content: &text[0..pos].trim_end_matches('\n'),
|
||||||
&text[0..pos]
|
node,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
pos = 0;
|
pos = 0;
|
||||||
}
|
}
|
||||||
let new_node = self.arena.new_node(element);
|
parent.append(element, arena).unwrap();
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
tail = skip_empty_lines(new_tail);
|
tail = skip_empty_lines(new_tail);
|
||||||
text = tail;
|
text = tail;
|
||||||
} else {
|
} else {
|
||||||
|
@ -184,30 +193,39 @@ impl<'a> Org<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
let new_node = self.arena.new_node(Element::Paragraph {
|
let node = arena.new_node(Element::Paragraph);
|
||||||
contents: if text.as_bytes()[pos - 1] == b'\n' {
|
parent.append(node, arena).unwrap();
|
||||||
&text[0..pos - 1]
|
containers.push(Container::Inline {
|
||||||
} else {
|
content: &text[0..pos].trim_end_matches('\n'),
|
||||||
&text[0..pos]
|
node,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_element(&self, contents: &'a str) -> Option<(&'a str, Element<'a>)> {
|
fn parse_element<'a>(
|
||||||
if let Some((tail, fn_def)) = FnDef::parse(contents) {
|
contents: &'a str,
|
||||||
let fn_def = Element::FnDef(fn_def);
|
arena: &mut Arena<Element<'a>>,
|
||||||
return Some((tail, fn_def));
|
containers: &mut Vec<Container<'a>>,
|
||||||
} else if let Some((tail, list)) = List::parse(contents) {
|
) -> Option<(&'a str, NodeId)> {
|
||||||
let list = Element::List(list);
|
if let Some((tail, fn_def, content)) = FnDef::parse(contents) {
|
||||||
return Some((tail, list));
|
let node = arena.new_node(Element::FnDef(fn_def));
|
||||||
|
containers.push(Container::Block { content, node });
|
||||||
|
return Some((tail, node));
|
||||||
|
} else if let Some((tail, list, content)) = List::parse(contents) {
|
||||||
|
let indent = list.indent;
|
||||||
|
let node = arena.new_node(Element::List(list));
|
||||||
|
containers.push(Container::List {
|
||||||
|
content,
|
||||||
|
node,
|
||||||
|
indent,
|
||||||
|
});
|
||||||
|
return Some((tail, node));
|
||||||
}
|
}
|
||||||
|
|
||||||
let tail = contents.trim_start();
|
let tail = contents.trim_start();
|
||||||
|
|
||||||
if let Some((tail, clock)) = Clock::parse(tail) {
|
if let Some((tail, clock)) = Clock::parse(tail) {
|
||||||
return Some((tail, clock));
|
return Some((tail, arena.new_node(clock)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: LaTeX environment
|
// TODO: LaTeX environment
|
||||||
|
@ -215,67 +233,83 @@ impl<'a> Org<'a> {
|
||||||
|
|
||||||
if tail.starts_with('-') {
|
if tail.starts_with('-') {
|
||||||
if let Ok((tail, rule)) = Rule::parse(tail) {
|
if let Ok((tail, rule)) = Rule::parse(tail) {
|
||||||
return Some((tail, rule));
|
return Some((tail, arena.new_node(rule)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tail.starts_with(':') {
|
if tail.starts_with(':') {
|
||||||
if let Some((tail, drawer)) = Drawer::parse(tail) {
|
if let Some((tail, drawer, content)) = Drawer::parse(tail) {
|
||||||
return Some((tail, drawer));
|
return Some((tail, arena.new_node(drawer)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FixedWidth
|
||||||
if tail == ":" || tail.starts_with(": ") || tail.starts_with(":\n") {
|
if tail == ":" || tail.starts_with(": ") || tail.starts_with(":\n") {
|
||||||
let mut last_end = 1; // ":"
|
let mut last_end = 1; // ":"
|
||||||
for i in memchr_iter(b'\n', contents.as_bytes()) {
|
for i in memchr_iter(b'\n', contents.as_bytes()) {
|
||||||
last_end = i + 1;
|
last_end = i + 1;
|
||||||
let line = &contents[last_end..];
|
let line = &contents[last_end..];
|
||||||
if !(line == ":" || line.starts_with(": ") || line.starts_with(":\n")) {
|
if !(line == ":" || line.starts_with(": ") || line.starts_with(":\n")) {
|
||||||
let fixed_width = Element::FixedWidth {
|
let fixed_width = arena.new_node(Element::FixedWidth {
|
||||||
value: &contents[0..i + 1],
|
value: &contents[0..i + 1],
|
||||||
};
|
});
|
||||||
return Some((&contents[i + 1..], fixed_width));
|
return Some((&contents[i + 1..], fixed_width));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let fixed_width = Element::FixedWidth {
|
let fixed_width = arena.new_node(Element::FixedWidth {
|
||||||
value: &contents[0..last_end],
|
value: &contents[0..last_end],
|
||||||
};
|
});
|
||||||
return Some((&contents[last_end..], fixed_width));
|
return Some((&contents[last_end..], fixed_width));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comment
|
||||||
if tail == "#" || tail.starts_with("# ") || tail.starts_with("#\n") {
|
if tail == "#" || tail.starts_with("# ") || tail.starts_with("#\n") {
|
||||||
let mut last_end = 1; // "#"
|
let mut last_end = 1; // "#"
|
||||||
for i in memchr_iter(b'\n', contents.as_bytes()) {
|
for i in memchr_iter(b'\n', contents.as_bytes()) {
|
||||||
last_end = i + 1;
|
last_end = i + 1;
|
||||||
let line = &contents[last_end..];
|
let line = &contents[last_end..];
|
||||||
if !(line == "#" || line.starts_with("# ") || line.starts_with("#\n")) {
|
if !(line == "#" || line.starts_with("# ") || line.starts_with("#\n")) {
|
||||||
let fixed_width = Element::Comment {
|
let comment = arena.new_node(Element::Comment {
|
||||||
value: &contents[0..i + 1],
|
value: &contents[0..i + 1],
|
||||||
};
|
});
|
||||||
return Some((&contents[i + 1..], fixed_width));
|
return Some((&contents[i + 1..], comment));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let fixed_width = Element::Comment {
|
let comment = arena.new_node(Element::Comment {
|
||||||
value: &contents[0..last_end],
|
value: &contents[0..last_end],
|
||||||
};
|
});
|
||||||
return Some((&contents[last_end..], fixed_width));
|
return Some((&contents[last_end..], comment));
|
||||||
}
|
}
|
||||||
|
|
||||||
if tail.starts_with("#+") {
|
if tail.starts_with("#+") {
|
||||||
Block::parse(tail)
|
if let Some((tail, block, content)) = Block::parse(tail) {
|
||||||
.or_else(|| DynBlock::parse(tail))
|
let node = arena.new_node(block);
|
||||||
.or_else(|| Keyword::parse(tail).ok())
|
containers.push(Container::Block { content, node });
|
||||||
|
Some((tail, node))
|
||||||
|
} else if let Some((tail, dyn_block, content)) = DynBlock::parse(tail) {
|
||||||
|
let node = arena.new_node(dyn_block);
|
||||||
|
containers.push(Container::Block { content, node });
|
||||||
|
Some((tail, node))
|
||||||
|
} else {
|
||||||
|
Keyword::parse(tail)
|
||||||
|
.ok()
|
||||||
|
.map(|(tail, kw)| (tail, arena.new_node(kw)))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_objects_children(&mut self, contents: &'a str, node: NodeId) {
|
fn parse_objects_children<'a>(
|
||||||
let mut tail = contents;
|
arena: &mut Arena<Element<'a>>,
|
||||||
|
content: &'a str,
|
||||||
|
parent: NodeId,
|
||||||
|
containers: &mut Vec<Container<'a>>,
|
||||||
|
) {
|
||||||
|
let mut tail = content;
|
||||||
|
|
||||||
if let Some((new_tail, obj)) = self.parse_object(tail) {
|
if let Some((new_tail, obj)) = parse_object(tail, arena, containers) {
|
||||||
let new_node = self.arena.new_node(obj);
|
parent.append(obj, arena).unwrap();
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,101 +321,108 @@ impl<'a> Org<'a> {
|
||||||
while let Some(off) = bs.find(tail.as_bytes()) {
|
while let Some(off) = bs.find(tail.as_bytes()) {
|
||||||
match tail.as_bytes()[off] {
|
match tail.as_bytes()[off] {
|
||||||
b'{' => {
|
b'{' => {
|
||||||
if let Some((new_tail, obj)) = self.parse_object(&tail[off..]) {
|
if let Some((new_tail, obj)) = parse_object(&tail[off..], arena, containers) {
|
||||||
if pos != 0 {
|
if pos != 0 {
|
||||||
let new_node = self.arena.new_node(Element::Text {
|
let node = arena.new_node(Element::Text {
|
||||||
value: &text[0..pos + off],
|
value: &text[0..pos + off],
|
||||||
});
|
});
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
pos = 0;
|
pos = 0;
|
||||||
}
|
}
|
||||||
let new_node = self.arena.new_node(obj);
|
parent.append(obj, arena).unwrap();
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
text = new_tail;
|
text = new_tail;
|
||||||
} else if let Some((new_tail, obj)) = self.parse_object(&tail[off + 1..]) {
|
continue;
|
||||||
let new_node = self.arena.new_node(Element::Text {
|
} else if let Some((new_tail, obj)) =
|
||||||
|
parse_object(&tail[off + 1..], arena, containers)
|
||||||
|
{
|
||||||
|
let node = arena.new_node(Element::Text {
|
||||||
value: &text[0..pos + off + 1],
|
value: &text[0..pos + off + 1],
|
||||||
});
|
});
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
pos = 0;
|
pos = 0;
|
||||||
let new_node = self.arena.new_node(obj);
|
parent.append(obj, arena).unwrap();
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
text = new_tail;
|
text = new_tail;
|
||||||
} else {
|
continue;
|
||||||
tail = &tail[off + 1..];
|
|
||||||
pos += off + 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b' ' | b'(' | b'\'' | b'"' | b'\n' => {
|
b' ' | b'(' | b'\'' | b'"' | b'\n' => {
|
||||||
if let Some((new_tail, obj)) = self.parse_object(&tail[off + 1..]) {
|
if let Some((new_tail, obj)) = parse_object(&tail[off + 1..], arena, containers) {
|
||||||
let new_node = self.arena.new_node(Element::Text {
|
let node = arena.new_node(Element::Text {
|
||||||
value: &text[0..pos + off + 1],
|
value: &text[0..pos + off + 1],
|
||||||
});
|
});
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
pos = 0;
|
pos = 0;
|
||||||
let new_node = self.arena.new_node(obj);
|
parent.append(obj, arena).unwrap();
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
text = new_tail;
|
text = new_tail;
|
||||||
} else {
|
continue;
|
||||||
tail = &tail[off + 1..];
|
|
||||||
pos += off + 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some((new_tail, obj)) = self.parse_object(&tail[off..]) {
|
if let Some((new_tail, obj)) = parse_object(&tail[off..], arena, containers) {
|
||||||
if pos != 0 {
|
if pos != 0 {
|
||||||
let new_node = self.arena.new_node(Element::Text {
|
let node = arena.new_node(Element::Text {
|
||||||
value: &text[0..pos + off],
|
value: &text[0..pos + off],
|
||||||
});
|
});
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
pos = 0;
|
pos = 0;
|
||||||
}
|
}
|
||||||
let new_node = self.arena.new_node(obj);
|
parent.append(obj, arena).unwrap();
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
text = new_tail;
|
text = new_tail;
|
||||||
} else {
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
tail = &tail[off + 1..];
|
tail = &tail[off + 1..];
|
||||||
pos += off + 1;
|
pos += off + 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
let new_node = self.arena.new_node(Element::Text { value: text });
|
let node = arena.new_node(Element::Text { value: text });
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_object(&self, contents: &'a str) -> Option<(&'a str, Element<'a>)> {
|
fn parse_object<'a>(
|
||||||
|
contents: &'a str,
|
||||||
|
arena: &mut Arena<Element<'a>>,
|
||||||
|
containers: &mut Vec<Container<'a>>,
|
||||||
|
) -> Option<(&'a str, NodeId)> {
|
||||||
if contents.len() < 3 {
|
if contents.len() < 3 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes = contents.as_bytes();
|
let bytes = contents.as_bytes();
|
||||||
match bytes[0] {
|
match bytes[0] {
|
||||||
b'@' => Snippet::parse(contents).ok(),
|
b'@' => Snippet::parse(contents)
|
||||||
b'{' => Macros::parse(contents).ok(),
|
.ok()
|
||||||
|
.map(|(tail, element)| (tail, arena.new_node(element))),
|
||||||
|
b'{' => Macros::parse(contents)
|
||||||
|
.ok()
|
||||||
|
.map(|(tail, element)| (tail, arena.new_node(element))),
|
||||||
b'<' => RadioTarget::parse(contents)
|
b'<' => RadioTarget::parse(contents)
|
||||||
|
.map(|(tail, (radio, content))| (tail, radio))
|
||||||
.or_else(|_| Target::parse(contents))
|
.or_else(|_| Target::parse(contents))
|
||||||
.or_else(|_| {
|
.or_else(|_| {
|
||||||
Timestamp::parse_active(contents)
|
Timestamp::parse_active(contents).map(|(tail, timestamp)| (tail, timestamp.into()))
|
||||||
.map(|(tail, timestamp)| (tail, timestamp.into()))
|
|
||||||
})
|
})
|
||||||
.or_else(|_| {
|
.or_else(|_| {
|
||||||
Timestamp::parse_diary(contents)
|
Timestamp::parse_diary(contents).map(|(tail, timestamp)| (tail, timestamp.into()))
|
||||||
.map(|(tail, timestamp)| (tail, timestamp.into()))
|
|
||||||
})
|
})
|
||||||
.ok(),
|
.ok()
|
||||||
|
.map(|(tail, element)| (tail, arena.new_node(element))),
|
||||||
b'[' => {
|
b'[' => {
|
||||||
if contents[1..].starts_with("fn:") {
|
if contents[1..].starts_with("fn:") {
|
||||||
FnRef::parse(contents).map(|(tail, fn_ref)| (tail, fn_ref.into()))
|
FnRef::parse(contents)
|
||||||
|
.map(|(tail, fn_ref)| (tail, fn_ref.into()))
|
||||||
|
.map(|(tail, element)| (tail, arena.new_node(element)))
|
||||||
} else if bytes[1] == b'[' {
|
} else if bytes[1] == b'[' {
|
||||||
Link::parse(contents).ok()
|
Link::parse(contents)
|
||||||
|
.ok()
|
||||||
|
.map(|(tail, element)| (tail, arena.new_node(element)))
|
||||||
} else {
|
} else {
|
||||||
Cookie::parse(contents)
|
Cookie::parse(contents)
|
||||||
.map(|(tail, cookie)| (tail, cookie.into()))
|
.map(|(tail, cookie)| (tail, cookie.into()))
|
||||||
|
@ -390,37 +431,75 @@ impl<'a> Org<'a> {
|
||||||
.map(|(tail, timestamp)| (tail, timestamp.into()))
|
.map(|(tail, timestamp)| (tail, timestamp.into()))
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
|
.map(|(tail, element)| (tail, arena.new_node(element)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b'*' => {
|
||||||
|
if let Some((tail, content)) = parse_emphasis(contents, b'*') {
|
||||||
|
let node = arena.new_node(Element::Bold);
|
||||||
|
containers.push(Container::Inline { content, node });
|
||||||
|
Some((tail, node))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b'+' => {
|
||||||
|
if let Some((tail, content)) = parse_emphasis(contents, b'+') {
|
||||||
|
let node = arena.new_node(Element::Strike);
|
||||||
|
containers.push(Container::Inline { content, node });
|
||||||
|
Some((tail, node))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b'/' => {
|
||||||
|
if let Some((tail, content)) = parse_emphasis(contents, b'/') {
|
||||||
|
let node = arena.new_node(Element::Italic);
|
||||||
|
containers.push(Container::Inline { content, node });
|
||||||
|
Some((tail, node))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b'_' => {
|
||||||
|
if let Some((tail, content)) = parse_emphasis(contents, b'_') {
|
||||||
|
let node = arena.new_node(Element::Underline);
|
||||||
|
containers.push(Container::Inline { content, node });
|
||||||
|
Some((tail, node))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b'*' => parse_emphasis(contents, b'*')
|
|
||||||
.map(|(tail, contents)| (tail, Element::Bold { contents })),
|
|
||||||
b'+' => parse_emphasis(contents, b'+')
|
|
||||||
.map(|(tail, contents)| (tail, Element::Strike { contents })),
|
|
||||||
b'/' => parse_emphasis(contents, b'/')
|
|
||||||
.map(|(tail, contents)| (tail, Element::Italic { contents })),
|
|
||||||
b'_' => parse_emphasis(contents, b'_')
|
|
||||||
.map(|(tail, contents)| (tail, Element::Underline { contents })),
|
|
||||||
b'=' => parse_emphasis(contents, b'=')
|
b'=' => parse_emphasis(contents, b'=')
|
||||||
.map(|(tail, value)| (tail, Element::Verbatim { value })),
|
.map(|(tail, value)| (tail, arena.new_node(Element::Verbatim { value }))),
|
||||||
b'~' => {
|
b'~' => parse_emphasis(contents, b'~')
|
||||||
parse_emphasis(contents, b'~').map(|(tail, value)| (tail, Element::Code { value }))
|
.map(|(tail, value)| (tail, arena.new_node(Element::Code { value }))),
|
||||||
}
|
b's' => InlineSrc::parse(contents)
|
||||||
b's' if contents.starts_with("src_") => InlineSrc::parse(contents).ok(),
|
.ok()
|
||||||
b'c' if contents.starts_with("call_") => InlineCall::parse(contents).ok(),
|
.map(|(tail, element)| (tail, arena.new_node(element))),
|
||||||
|
b'c' => InlineCall::parse(contents)
|
||||||
|
.ok()
|
||||||
|
.map(|(tail, element)| (tail, arena.new_node(element))),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_list_items(&mut self, mut contents: &'a str, indent: usize, node: NodeId) {
|
fn parse_list_items<'a>(
|
||||||
|
arena: &mut Arena<Element<'a>>,
|
||||||
|
mut contents: &'a str,
|
||||||
|
indent: usize,
|
||||||
|
parent: NodeId,
|
||||||
|
containers: &mut Vec<Container<'a>>,
|
||||||
|
) {
|
||||||
while !contents.is_empty() {
|
while !contents.is_empty() {
|
||||||
let (tail, list_item) = ListItem::parse(contents, indent);
|
let (tail, list_item, content) = ListItem::parse(contents, indent);
|
||||||
let list_item = Element::ListItem(list_item);
|
let list_item = Element::ListItem(list_item);
|
||||||
let new_node = self.arena.new_node(list_item);
|
let node = arena.new_node(list_item);
|
||||||
node.append(new_node, &mut self.arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
|
containers.push(Container::Block { content, node });
|
||||||
contents = tail;
|
contents = tail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn skip_empty_lines(contents: &str) -> &str {
|
fn skip_empty_lines(contents: &str) -> &str {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
Loading…
Reference in a new issue