feat(element): rename Headline with Title struct
This commit is contained in:
parent
e835103ac6
commit
53d8f9dc90
|
@ -24,8 +24,8 @@ chrono = { version = "0.4.7", optional = true }
|
||||||
indextree = "3.3.0"
|
indextree = "3.3.0"
|
||||||
jetscii = "0.4.4"
|
jetscii = "0.4.4"
|
||||||
memchr = "2.2.1"
|
memchr = "2.2.1"
|
||||||
serde = { version = "1.0.97", optional = true, features = ["derive"] }
|
|
||||||
nom = "5.0.0"
|
nom = "5.0.0"
|
||||||
|
serde = { version = "1.0.97", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = "1.3.0"
|
lazy_static = "1.3.0"
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::io::{Error as IOError, Write};
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
use orgize::export::{html::Escape, DefaultHtmlHandler, HtmlHandler};
|
use orgize::export::{DefaultHtmlHandler, HtmlHandler};
|
||||||
use orgize::{Element, Org};
|
use orgize::{Element, Org};
|
||||||
use slugify::slugify;
|
use slugify::slugify;
|
||||||
|
|
||||||
|
@ -34,16 +34,15 @@ struct MyHtmlHandler(DefaultHtmlHandler);
|
||||||
impl HtmlHandler<MyError> for MyHtmlHandler {
|
impl HtmlHandler<MyError> for MyHtmlHandler {
|
||||||
fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
|
fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
|
||||||
match element {
|
match element {
|
||||||
Element::Headline(headline) => {
|
Element::Title(title) => {
|
||||||
if headline.level > 6 {
|
if title.level > 6 {
|
||||||
return Err(MyError::Heading);
|
return Err(MyError::Heading);
|
||||||
} else {
|
} else {
|
||||||
write!(
|
write!(
|
||||||
w,
|
w,
|
||||||
"<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>",
|
"<h{0}><a id=\"{1}\" href=\"#{1}\">",
|
||||||
headline.level,
|
title.level,
|
||||||
slugify!(headline.title),
|
slugify!(title.raw),
|
||||||
Escape(headline.title),
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +51,16 @@ impl HtmlHandler<MyError> for MyHtmlHandler {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn end<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
|
||||||
|
match element {
|
||||||
|
Element::Title(title) => {
|
||||||
|
write!(w, "</a></h{}>", title.level,)?;
|
||||||
|
}
|
||||||
|
_ => self.0.end(w, element)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), MyError> {
|
fn main() -> Result<(), MyError> {
|
||||||
|
|
|
@ -67,7 +67,8 @@ fn parse() {
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert!(InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>").is_err(),);
|
|
||||||
assert!(InlineSrc::parse("src_[:exports code]{<tag>text</tag>}").is_err(),);
|
assert!(InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>").is_err());
|
||||||
// assert_eq!(parse("src_xml[:exports code]"), None);
|
assert!(InlineSrc::parse("src_[:exports code]{<tag>text</tag>}").is_err());
|
||||||
|
assert!(InlineSrc::parse("src_xml[:exports code]").is_err());
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ mod dyn_block;
|
||||||
mod emphasis;
|
mod emphasis;
|
||||||
mod fn_def;
|
mod fn_def;
|
||||||
mod fn_ref;
|
mod fn_ref;
|
||||||
mod headline;
|
|
||||||
mod inline_call;
|
mod inline_call;
|
||||||
mod inline_src;
|
mod inline_src;
|
||||||
mod keyword;
|
mod keyword;
|
||||||
|
@ -21,6 +20,7 @@ mod rule;
|
||||||
mod snippet;
|
mod snippet;
|
||||||
mod target;
|
mod target;
|
||||||
mod timestamp;
|
mod timestamp;
|
||||||
|
mod title;
|
||||||
|
|
||||||
pub(crate) use emphasis::parse as parse_emphasis;
|
pub(crate) use emphasis::parse as parse_emphasis;
|
||||||
|
|
||||||
|
@ -32,7 +32,6 @@ pub use self::{
|
||||||
dyn_block::DynBlock,
|
dyn_block::DynBlock,
|
||||||
fn_def::FnDef,
|
fn_def::FnDef,
|
||||||
fn_ref::FnRef,
|
fn_ref::FnRef,
|
||||||
headline::Headline,
|
|
||||||
inline_call::InlineCall,
|
inline_call::InlineCall,
|
||||||
inline_src::InlineSrc,
|
inline_src::InlineSrc,
|
||||||
keyword::{BabelCall, Keyword},
|
keyword::{BabelCall, Keyword},
|
||||||
|
@ -45,14 +44,10 @@ pub use self::{
|
||||||
snippet::Snippet,
|
snippet::Snippet,
|
||||||
target::Target,
|
target::Target,
|
||||||
timestamp::{Date, Time, Timestamp},
|
timestamp::{Date, Time, Timestamp},
|
||||||
|
title::Title,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Org-mode element enum
|
/// Org-mode element enum
|
||||||
///
|
|
||||||
/// Generally, each variant contains a element struct and
|
|
||||||
/// a set of properties which indicate the position of the
|
|
||||||
/// element in the original string.
|
|
||||||
///
|
|
||||||
#[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))]
|
||||||
|
@ -69,7 +64,7 @@ pub enum Element<'a> {
|
||||||
DynBlock(DynBlock<'a>),
|
DynBlock(DynBlock<'a>),
|
||||||
FnDef(FnDef<'a>),
|
FnDef(FnDef<'a>),
|
||||||
FnRef(FnRef<'a>),
|
FnRef(FnRef<'a>),
|
||||||
Headline(Headline<'a>),
|
Headline,
|
||||||
InlineCall(InlineCall<'a>),
|
InlineCall(InlineCall<'a>),
|
||||||
InlineSrc(InlineSrc<'a>),
|
InlineSrc(InlineSrc<'a>),
|
||||||
Keyword(Keyword<'a>),
|
Keyword(Keyword<'a>),
|
||||||
|
@ -92,6 +87,28 @@ pub enum Element<'a> {
|
||||||
Code { value: &'a str },
|
Code { value: &'a str },
|
||||||
Comment { value: &'a str },
|
Comment { value: &'a str },
|
||||||
FixedWidth { value: &'a str },
|
FixedWidth { value: &'a str },
|
||||||
|
Title(Title<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element<'_> {
|
||||||
|
pub fn is_container(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Element::Block(_)
|
||||||
|
| Element::Bold
|
||||||
|
| Element::Document
|
||||||
|
| Element::DynBlock(_)
|
||||||
|
| Element::Headline
|
||||||
|
| Element::Italic
|
||||||
|
| Element::List(_)
|
||||||
|
| Element::ListItem(_)
|
||||||
|
| Element::Paragraph
|
||||||
|
| Element::Section
|
||||||
|
| Element::Strike
|
||||||
|
| Element::Underline
|
||||||
|
| Element::Title(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_from {
|
macro_rules! impl_from {
|
||||||
|
@ -112,7 +129,6 @@ impl_from!(Drawer);
|
||||||
impl_from!(DynBlock);
|
impl_from!(DynBlock);
|
||||||
impl_from!(FnDef);
|
impl_from!(FnDef);
|
||||||
impl_from!(FnRef);
|
impl_from!(FnRef);
|
||||||
impl_from!(Headline);
|
|
||||||
impl_from!(InlineCall);
|
impl_from!(InlineCall);
|
||||||
impl_from!(InlineSrc);
|
impl_from!(InlineSrc);
|
||||||
impl_from!(Keyword);
|
impl_from!(Keyword);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
//! Headline
|
//! Headline Title
|
||||||
|
|
||||||
use jetscii::ByteSubstring;
|
|
||||||
use memchr::{memchr, memchr2, memrchr};
|
use memchr::{memchr, memchr2, memrchr};
|
||||||
|
|
||||||
use crate::config::ParseConfig;
|
use crate::config::ParseConfig;
|
||||||
|
@ -8,7 +7,7 @@ use crate::config::ParseConfig;
|
||||||
#[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 Headline<'a> {
|
pub struct Title<'a> {
|
||||||
/// headline level, number of stars
|
/// headline level, number of stars
|
||||||
pub level: usize,
|
pub level: usize,
|
||||||
/// priority cookie
|
/// priority cookie
|
||||||
|
@ -17,47 +16,35 @@ pub struct Headline<'a> {
|
||||||
/// headline tags, including the sparated colons
|
/// headline tags, including the sparated colons
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
|
||||||
pub tags: Vec<&'a str>,
|
pub tags: Vec<&'a str>,
|
||||||
/// headline title
|
|
||||||
pub title: &'a str,
|
|
||||||
/// 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>,
|
||||||
|
pub raw: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Headline<'_> {
|
impl Title<'_> {
|
||||||
pub(crate) fn parse<'a>(
|
#[inline]
|
||||||
text: &'a str,
|
pub(crate) fn parse<'a>(text: &'a str, config: &ParseConfig) -> (&'a str, Title<'a>, &'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);
|
||||||
debug_assert!(text.as_bytes()[0..level].iter().all(|&c| c == b'*'));
|
debug_assert!(text.as_bytes()[0..level].iter().all(|&c| c == b'*'));
|
||||||
|
|
||||||
let (off, end) = memchr(b'\n', text.as_bytes())
|
let off = memchr(b'\n', text.as_bytes())
|
||||||
.map(|i| {
|
.map(|i| i + 1)
|
||||||
(
|
.unwrap_or_else(|| text.len());
|
||||||
i + 1,
|
|
||||||
if i + 1 == text.len() {
|
|
||||||
i + 1
|
|
||||||
} else {
|
|
||||||
Headline::find_level(&text[i + 1..], level) + i + 1
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| (text.len(), text.len()));
|
|
||||||
|
|
||||||
if level == off {
|
if level == off {
|
||||||
return (
|
return (
|
||||||
&text[end..],
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level,
|
level,
|
||||||
keyword: None,
|
keyword: None,
|
||||||
priority: None,
|
priority: None,
|
||||||
title: "",
|
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
|
raw: "",
|
||||||
},
|
},
|
||||||
&text[off..end],
|
"",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,47 +90,18 @@ impl Headline<'_> {
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
&text[end..],
|
&text[off..],
|
||||||
Headline {
|
Title {
|
||||||
level,
|
level,
|
||||||
keyword,
|
keyword,
|
||||||
priority,
|
priority,
|
||||||
title,
|
|
||||||
tags: tags.split(':').filter(|s| !s.is_empty()).collect(),
|
tags: tags.split(':').filter(|s| !s.is_empty()).collect(),
|
||||||
|
raw: title,
|
||||||
},
|
},
|
||||||
&text[off..end],
|
title,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn find_level(text: &str, level: usize) -> usize {
|
|
||||||
let bytes = text.as_bytes();
|
|
||||||
if bytes[0] == b'*' {
|
|
||||||
if let Some(stars) = memchr2(b'\n', b' ', bytes) {
|
|
||||||
if stars <= level && bytes[0..stars].iter().all(|&c| c == b'*') {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut pos = 0;
|
|
||||||
while let Some(off) = ByteSubstring::new(b"\n*").find(&bytes[pos..]) {
|
|
||||||
pos += off + 1;
|
|
||||||
if let Some(stars) = memchr2(b'\n', b' ', &bytes[pos..]) {
|
|
||||||
if stars > 0 && stars <= level && bytes[pos..pos + stars].iter().all(|&c| c == b'*')
|
|
||||||
{
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
text.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// checks if this headline is "commented"
|
|
||||||
pub fn is_commented(&self) -> bool {
|
|
||||||
self.title.starts_with("COMMENT ")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// checks if this headline is "archived"
|
/// checks if this headline is "archived"
|
||||||
pub fn is_archived(&self) -> bool {
|
pub fn is_archived(&self) -> bool {
|
||||||
self.tags.contains(&"ARCHIVE")
|
self.tags.contains(&"ARCHIVE")
|
||||||
|
@ -158,123 +116,120 @@ lazy_static::lazy_static! {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse() {
|
fn parse() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &CONFIG),
|
Title::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &CONFIG),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: Some('A'),
|
priority: Some('A'),
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
title: "COMMENT Title",
|
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
|
raw: "COMMENT Title"
|
||||||
},
|
},
|
||||||
""
|
"COMMENT Title"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse("**** ToDO [#A] COMMENT Title :tag:a2%:", &CONFIG),
|
Title::parse("**** ToDO [#A] COMMENT Title :tag:a2%:", &CONFIG),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: None,
|
priority: None,
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
title: "ToDO [#A] COMMENT Title",
|
|
||||||
keyword: None,
|
keyword: None,
|
||||||
|
raw: "ToDO [#A] COMMENT Title"
|
||||||
},
|
},
|
||||||
""
|
"ToDO [#A] COMMENT Title"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse("**** T0DO [#A] COMMENT Title :tag:a2%:", &CONFIG),
|
Title::parse("**** T0DO [#A] COMMENT Title :tag:a2%:", &CONFIG),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: None,
|
priority: None,
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
title: "T0DO [#A] COMMENT Title",
|
|
||||||
keyword: None,
|
keyword: None,
|
||||||
|
raw: "T0DO [#A] COMMENT Title"
|
||||||
},
|
},
|
||||||
""
|
"T0DO [#A] COMMENT Title"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse("**** DONE [#1] COMMENT Title :tag:a2%:", &CONFIG),
|
Title::parse("**** DONE [#1] COMMENT Title :tag:a2%:", &CONFIG),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: None,
|
priority: None,
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
title: "[#1] COMMENT Title",
|
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
|
raw: "[#1] COMMENT Title"
|
||||||
},
|
},
|
||||||
""
|
"[#1] COMMENT Title"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse("**** DONE [#a] COMMENT Title :tag:a2%:", &CONFIG),
|
Title::parse("**** DONE [#a] COMMENT Title :tag:a2%:", &CONFIG),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: None,
|
priority: None,
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
title: "[#a] COMMENT Title",
|
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
|
raw: "[#a] COMMENT Title"
|
||||||
},
|
},
|
||||||
""
|
"[#a] COMMENT Title"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse("**** DONE [#A] COMMENT Title :tag:a2%", &CONFIG),
|
Title::parse("**** DONE [#A] COMMENT Title :tag:a2%", &CONFIG),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: Some('A'),
|
priority: Some('A'),
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
title: "COMMENT Title :tag:a2%",
|
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
|
raw: "COMMENT Title :tag:a2%"
|
||||||
},
|
},
|
||||||
""
|
"COMMENT Title :tag:a2%"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse("**** DONE [#A] COMMENT Title tag:a2%:", &CONFIG),
|
Title::parse("**** DONE [#A] COMMENT Title tag:a2%:", &CONFIG),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: Some('A'),
|
priority: Some('A'),
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
title: "COMMENT Title tag:a2%:",
|
|
||||||
keyword: Some("DONE"),
|
keyword: Some("DONE"),
|
||||||
|
raw: "COMMENT Title tag:a2%:"
|
||||||
},
|
},
|
||||||
""
|
"COMMENT Title tag:a2%:"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse("**** COMMENT Title tag:a2%:", &CONFIG),
|
Title::parse("**** COMMENT Title tag:a2%:", &CONFIG),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: None,
|
priority: None,
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
title: "COMMENT Title tag:a2%:",
|
|
||||||
keyword: None,
|
keyword: None,
|
||||||
|
raw: "COMMENT Title tag:a2%:"
|
||||||
},
|
},
|
||||||
""
|
"COMMENT Title tag:a2%:"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_todo_keywords() {
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse(
|
Title::parse(
|
||||||
"**** DONE [#A] COMMENT Title :tag:a2%:",
|
"**** DONE [#A] COMMENT Title :tag:a2%:",
|
||||||
&ParseConfig {
|
&ParseConfig {
|
||||||
done_keywords: vec![],
|
done_keywords: vec![],
|
||||||
|
@ -283,18 +238,18 @@ fn parse_todo_keywords() {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: None,
|
priority: None,
|
||||||
keyword: None,
|
keyword: None,
|
||||||
title: "DONE [#A] COMMENT Title",
|
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
|
raw: "DONE [#A] COMMENT Title"
|
||||||
},
|
},
|
||||||
""
|
"DONE [#A] COMMENT Title"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse(
|
Title::parse(
|
||||||
"**** TASK [#A] COMMENT Title :tag:a2%:",
|
"**** TASK [#A] COMMENT Title :tag:a2%:",
|
||||||
&ParseConfig {
|
&ParseConfig {
|
||||||
todo_keywords: vec!["TASK".to_string()],
|
todo_keywords: vec!["TASK".to_string()],
|
||||||
|
@ -303,53 +258,39 @@ fn parse_todo_keywords() {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"",
|
"",
|
||||||
Headline {
|
Title {
|
||||||
level: 4,
|
level: 4,
|
||||||
priority: Some('A'),
|
priority: Some('A'),
|
||||||
keyword: Some("TASK"),
|
keyword: Some("TASK"),
|
||||||
title: "COMMENT Title",
|
|
||||||
tags: vec!["tag", "a2%"],
|
tags: vec!["tag", "a2%"],
|
||||||
|
raw: "COMMENT Title"
|
||||||
},
|
},
|
||||||
""
|
"COMMENT Title"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
fn is_commented() {
|
// fn is_commented() {
|
||||||
assert!(Headline::parse("* COMMENT Title", &CONFIG).1.is_commented());
|
// assert!(Title::parse("* COMMENT Title", &CONFIG)
|
||||||
assert!(!Headline::parse("* Title", &CONFIG).1.is_commented());
|
// .1
|
||||||
assert!(!Headline::parse("* C0MMENT Title", &CONFIG).1.is_commented());
|
// .is_commented());
|
||||||
assert!(!Headline::parse("* comment Title", &CONFIG).1.is_commented());
|
// assert!(!Title::parse("* Title", &CONFIG).1.is_commented());
|
||||||
}
|
// assert!(!Title::parse("* C0MMENT Title", &CONFIG)
|
||||||
|
// .1
|
||||||
|
// .is_commented());
|
||||||
|
// assert!(!Title::parse("* comment Title", &CONFIG)
|
||||||
|
// .1
|
||||||
|
// .is_commented());
|
||||||
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn is_archived() {
|
fn is_archived() {
|
||||||
assert!(Headline::parse("* Title :ARCHIVE:", &CONFIG)
|
assert!(Title::parse("* Title :ARCHIVE:", &CONFIG).1.is_archived());
|
||||||
.1
|
assert!(Title::parse("* Title :t:ARCHIVE:", &CONFIG).1.is_archived());
|
||||||
.is_archived());
|
assert!(Title::parse("* Title :ARCHIVE:t:", &CONFIG).1.is_archived());
|
||||||
assert!(Headline::parse("* Title :t:ARCHIVE:", &CONFIG)
|
assert!(!Title::parse("* Title", &CONFIG).1.is_archived());
|
||||||
.1
|
assert!(!Title::parse("* Title :ARCHIVED:", &CONFIG).1.is_archived());
|
||||||
.is_archived());
|
assert!(!Title::parse("* Title :ARCHIVES:", &CONFIG).1.is_archived());
|
||||||
assert!(Headline::parse("* Title :ARCHIVE:t:", &CONFIG)
|
assert!(!Title::parse("* Title :archive:", &CONFIG).1.is_archived());
|
||||||
.1
|
|
||||||
.is_archived());
|
|
||||||
assert!(!Headline::parse("* Title", &CONFIG).1.is_commented());
|
|
||||||
assert!(!Headline::parse("* Title :ARCHIVED:", &CONFIG)
|
|
||||||
.1
|
|
||||||
.is_archived());
|
|
||||||
assert!(!Headline::parse("* Title :ARCHIVES:", &CONFIG)
|
|
||||||
.1
|
|
||||||
.is_archived());
|
|
||||||
assert!(!Headline::parse("* Title :archive:", &CONFIG)
|
|
||||||
.1
|
|
||||||
.is_archived());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn find_level() {
|
|
||||||
assert_eq!(
|
|
||||||
Headline::find_level("\n** Title\n* Title\n** Title\n", 1),
|
|
||||||
"\n** Title\n".len()
|
|
||||||
);
|
|
||||||
}
|
}
|
|
@ -35,17 +35,10 @@ pub trait HtmlHandler<E: From<Error>> {
|
||||||
match element {
|
match element {
|
||||||
// container elements
|
// container elements
|
||||||
Block(_block) => write!(w, "<div>")?,
|
Block(_block) => write!(w, "<div>")?,
|
||||||
Bold { .. } => write!(w, "<b>")?,
|
Bold => write!(w, "<b>")?,
|
||||||
Document { .. } => write!(w, "<main>")?,
|
Document => write!(w, "<main>")?,
|
||||||
DynBlock(_dyn_block) => (),
|
DynBlock(_dyn_block) => (),
|
||||||
Headline(headline) => {
|
Headline => (),
|
||||||
let level = if headline.level <= 6 {
|
|
||||||
headline.level
|
|
||||||
} else {
|
|
||||||
6
|
|
||||||
};
|
|
||||||
write!(w, "<h{0}>{1}</h{0}>", level, Escape(headline.title))?;
|
|
||||||
}
|
|
||||||
List(list) => {
|
List(list) => {
|
||||||
if list.ordered {
|
if list.ordered {
|
||||||
write!(w, "<ol>")?;
|
write!(w, "<ol>")?;
|
||||||
|
@ -53,12 +46,12 @@ pub trait HtmlHandler<E: From<Error>> {
|
||||||
write!(w, "<ul>")?;
|
write!(w, "<ul>")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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>")?,
|
||||||
// non-container elements
|
// non-container elements
|
||||||
BabelCall(_babel_call) => (),
|
BabelCall(_babel_call) => (),
|
||||||
InlineSrc(inline_src) => write!(w, "<code>{}</code>", Escape(inline_src.body))?,
|
InlineSrc(inline_src) => write!(w, "<code>{}</code>", Escape(inline_src.body))?,
|
||||||
|
@ -91,6 +84,7 @@ pub trait HtmlHandler<E: From<Error>> {
|
||||||
Drawer(_drawer) => (),
|
Drawer(_drawer) => (),
|
||||||
Rule => write!(w, "<hr>")?,
|
Rule => write!(w, "<hr>")?,
|
||||||
Cookie(_cookie) => (),
|
Cookie(_cookie) => (),
|
||||||
|
Title(title) => write!(w, "<h{}>", if title.level <= 6 { title.level } else { 6 })?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -101,10 +95,10 @@ pub trait HtmlHandler<E: From<Error>> {
|
||||||
match element {
|
match element {
|
||||||
// container elements
|
// container elements
|
||||||
Block(_block) => write!(w, "</div>")?,
|
Block(_block) => write!(w, "</div>")?,
|
||||||
Bold { .. } => write!(w, "</b>")?,
|
Bold => write!(w, "</b>")?,
|
||||||
Document { .. } => write!(w, "</main>")?,
|
Document => write!(w, "</main>")?,
|
||||||
DynBlock(_dyn_block) => (),
|
DynBlock(_dyn_block) => (),
|
||||||
Headline(_headline) => (),
|
Headline => (),
|
||||||
List(list) => {
|
List(list) => {
|
||||||
if list.ordered {
|
if list.ordered {
|
||||||
write!(w, "</ol>")?;
|
write!(w, "</ol>")?;
|
||||||
|
@ -112,14 +106,15 @@ pub trait HtmlHandler<E: From<Error>> {
|
||||||
write!(w, "</ul>")?;
|
write!(w, "</ul>")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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>")?,
|
||||||
|
Title(title) => write!(w, "</h{}>", if title.level <= 6 { title.level } else { 6 })?,
|
||||||
// non-container elements
|
// non-container elements
|
||||||
_ => (),
|
_ => debug_assert!(!element.is_container()),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
pub mod html;
|
pub mod html;
|
||||||
pub mod org;
|
// pub mod org;
|
||||||
|
|
||||||
pub use html::{DefaultHtmlHandler, HtmlHandler};
|
pub use html::{DefaultHtmlHandler, HtmlHandler};
|
||||||
pub use org::{DefaultOrgHandler, OrgHandler};
|
// pub use org::{DefaultOrgHandler, OrgHandler};
|
||||||
|
|
21
src/lib.rs
21
src/lib.rs
|
@ -117,16 +117,15 @@
|
||||||
//! impl HtmlHandler<MyError> for MyHtmlHandler {
|
//! impl HtmlHandler<MyError> for MyHtmlHandler {
|
||||||
//! fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
|
//! fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
|
||||||
//! match element {
|
//! match element {
|
||||||
//! Element::Headline(headline) => {
|
//! Element::Title(title) => {
|
||||||
//! if headline.level > 6 {
|
//! if title.level > 6 {
|
||||||
//! return Err(MyError::Heading);
|
//! return Err(MyError::Heading);
|
||||||
//! } else {
|
//! } else {
|
||||||
//! write!(
|
//! write!(
|
||||||
//! w,
|
//! w,
|
||||||
//! "<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>",
|
//! "<h{0}><a id=\"{1}\" href=\"#{1}\">",
|
||||||
//! headline.level,
|
//! title.level,
|
||||||
//! slugify!(headline.title),
|
//! slugify!(title.raw),
|
||||||
//! Escape(headline.title),
|
|
||||||
//! )?;
|
//! )?;
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
|
@ -135,6 +134,16 @@
|
||||||
//! }
|
//! }
|
||||||
//! Ok(())
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn end<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
|
||||||
|
//! match element {
|
||||||
|
//! Element::Title(title) => {
|
||||||
|
//! write!(w, "</a></h{}>", title.level,)?;
|
||||||
|
//! }
|
||||||
|
//! _ => self.0.end(w, element)?,
|
||||||
|
//! }
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<(), MyError> {
|
//! fn main() -> Result<(), MyError> {
|
||||||
|
|
158
src/org.rs
158
src/org.rs
|
@ -1,12 +1,12 @@
|
||||||
use indextree::{Arena, NodeId};
|
use indextree::{Arena, NodeId};
|
||||||
use jetscii::bytes;
|
use jetscii::bytes;
|
||||||
use memchr::{memchr, memchr_iter};
|
use memchr::{memchr, memchr2, memchr_iter};
|
||||||
use std::io::{Error, Write};
|
use std::io::{Error, Write};
|
||||||
|
|
||||||
use crate::config::ParseConfig;
|
use crate::config::ParseConfig;
|
||||||
use crate::elements::*;
|
use crate::elements::*;
|
||||||
use crate::export::*;
|
use crate::export::*;
|
||||||
use crate::iter::Iter;
|
use crate::iter::{Event, Iter};
|
||||||
|
|
||||||
pub struct Org<'a> {
|
pub struct Org<'a> {
|
||||||
pub(crate) arena: Arena<Element<'a>>,
|
pub(crate) arena: Arena<Element<'a>>,
|
||||||
|
@ -30,13 +30,13 @@ enum Container<'a> {
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
node: NodeId,
|
node: NodeId,
|
||||||
},
|
},
|
||||||
// Headline, Document
|
// Headline
|
||||||
Headline {
|
Headline {
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
node: NodeId,
|
node: NodeId,
|
||||||
},
|
},
|
||||||
// Section
|
// Document
|
||||||
Section {
|
Document {
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
node: NodeId,
|
node: NodeId,
|
||||||
},
|
},
|
||||||
|
@ -51,54 +51,61 @@ impl<'a> Org<'a> {
|
||||||
let mut arena = Arena::new();
|
let mut arena = Arena::new();
|
||||||
let document = arena.new_node(Element::Document);
|
let document = arena.new_node(Element::Document);
|
||||||
|
|
||||||
let mut containers = vec![Container::Headline {
|
let mut containers = vec![Container::Document {
|
||||||
content,
|
content,
|
||||||
node: document,
|
node: document,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
while let Some(container) = containers.pop() {
|
while let Some(container) = containers.pop() {
|
||||||
match container {
|
match container {
|
||||||
Container::Headline {
|
Container::Document {
|
||||||
mut content,
|
content,
|
||||||
node: parent,
|
node: parent,
|
||||||
} => {
|
} => {
|
||||||
if !content.is_empty() {
|
let mut tail = skip_empty_lines(content);
|
||||||
let off = Headline::find_level(content, std::usize::MAX);
|
if let Some((new_tail, content)) = parse_section(tail) {
|
||||||
if off != 0 {
|
|
||||||
let node = arena.new_node(Element::Section);
|
let node = arena.new_node(Element::Section);
|
||||||
parent.append(node, &mut arena).unwrap();
|
parent.append(node, &mut arena).unwrap();
|
||||||
containers.push(Container::Section {
|
containers.push(Container::Block { content, node });
|
||||||
content: &content[0..off],
|
tail = new_tail;
|
||||||
node,
|
|
||||||
});
|
|
||||||
content = &content[off..];
|
|
||||||
}
|
}
|
||||||
}
|
while !tail.is_empty() {
|
||||||
while !content.is_empty() {
|
let (new_tail, content) = parse_headline(tail);
|
||||||
let (tail, headline, headline_content) = Headline::parse(content, &config);
|
let node = arena.new_node(Element::Headline);
|
||||||
let headline = Element::Headline(headline);
|
|
||||||
let node = arena.new_node(headline);
|
|
||||||
parent.append(node, &mut arena).unwrap();
|
parent.append(node, &mut arena).unwrap();
|
||||||
containers.push(Container::Headline {
|
containers.push(Container::Headline { content, node });
|
||||||
content: headline_content,
|
tail = new_tail;
|
||||||
node,
|
|
||||||
});
|
|
||||||
content = tail;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Container::Section { content, node } => {
|
Container::Headline {
|
||||||
// TODO
|
content,
|
||||||
if let Some((tail, _planning)) = Planning::parse(content) {
|
node: parent,
|
||||||
parse_elements_children(&mut arena, tail, node, &mut containers);
|
} => {
|
||||||
} else {
|
let mut tail = content;
|
||||||
parse_elements_children(&mut arena, content, node, &mut containers);
|
let (new_tail, title, content) = Title::parse(tail, config);
|
||||||
|
let node = arena.new_node(Element::Title(title));
|
||||||
|
parent.append(node, &mut arena).unwrap();
|
||||||
|
containers.push(Container::Inline { content, node });
|
||||||
|
tail = skip_empty_lines(new_tail);
|
||||||
|
if let Some((new_tail, content)) = parse_section(tail) {
|
||||||
|
let node = arena.new_node(Element::Section);
|
||||||
|
parent.append(node, &mut arena).unwrap();
|
||||||
|
containers.push(Container::Block { content, node });
|
||||||
|
tail = new_tail;
|
||||||
|
}
|
||||||
|
while !tail.is_empty() {
|
||||||
|
let (new_tail, content) = parse_headline(tail);
|
||||||
|
let node = arena.new_node(Element::Headline);
|
||||||
|
parent.append(node, &mut arena).unwrap();
|
||||||
|
containers.push(Container::Headline { content, node });
|
||||||
|
tail = new_tail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Container::Block { content, node } => {
|
Container::Block { content, node } => {
|
||||||
parse_elements_children(&mut arena, content, node, &mut containers);
|
parse_blocks(&mut arena, content, node, &mut containers);
|
||||||
}
|
}
|
||||||
Container::Inline { content, node } => {
|
Container::Inline { content, node } => {
|
||||||
parse_objects_children(&mut arena, content, node, &mut containers);
|
parse_inlines(&mut arena, content, node, &mut containers);
|
||||||
}
|
}
|
||||||
Container::List {
|
Container::List {
|
||||||
content,
|
content,
|
||||||
|
@ -130,12 +137,10 @@ impl<'a> Org<'a> {
|
||||||
E: From<Error>,
|
E: From<Error>,
|
||||||
H: HtmlHandler<E>,
|
H: HtmlHandler<E>,
|
||||||
{
|
{
|
||||||
use crate::iter::Event::*;
|
|
||||||
|
|
||||||
for event in self.iter() {
|
for event in self.iter() {
|
||||||
match event {
|
match event {
|
||||||
Start(element) => handler.start(&mut writer, element)?,
|
Event::Start(element) => handler.start(&mut writer, element)?,
|
||||||
End(element) => handler.end(&mut writer, element)?,
|
Event::End(element) => handler.end(&mut writer, element)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +148,49 @@ impl<'a> Org<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_elements_children<'a>(
|
fn is_headline(text: &str) -> Option<usize> {
|
||||||
|
if let Some(off) = memchr2(b'\n', b' ', text.as_bytes()) {
|
||||||
|
if off > 0 && text[0..off].as_bytes().iter().all(|&c| c == b'*') {
|
||||||
|
Some(off)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else if text.len() > 0 && text.as_bytes().iter().all(|&c| c == b'*') {
|
||||||
|
Some(text.len())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_section(text: &str) -> Option<(&str, &str)> {
|
||||||
|
if text.is_empty() || is_headline(text).is_some() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in memchr_iter(b'\n', text.as_bytes()) {
|
||||||
|
if is_headline(&text[i + 1..]).is_some() {
|
||||||
|
return Some((&text[i + 1..], &text[0..i + 1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(("", text))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_headline(text: &str) -> (&str, &str) {
|
||||||
|
let level = is_headline(text).unwrap();
|
||||||
|
|
||||||
|
for i in memchr_iter(b'\n', text.as_bytes()) {
|
||||||
|
if let Some(l) = is_headline(&text[i + 1..]) {
|
||||||
|
if l <= level {
|
||||||
|
return (&text[i + 1..], &text[0..i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_blocks<'a>(
|
||||||
arena: &mut Arena<Element<'a>>,
|
arena: &mut Arena<Element<'a>>,
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
parent: NodeId,
|
parent: NodeId,
|
||||||
|
@ -151,7 +198,7 @@ fn parse_elements_children<'a>(
|
||||||
) {
|
) {
|
||||||
let mut tail = skip_empty_lines(content);
|
let mut tail = skip_empty_lines(content);
|
||||||
|
|
||||||
if let Some((new_tail, element)) = parse_element(content, arena, containers) {
|
if let Some((new_tail, element)) = parse_block(content, arena, containers) {
|
||||||
parent.append(element, arena).unwrap();
|
parent.append(element, arena).unwrap();
|
||||||
tail = skip_empty_lines(new_tail);
|
tail = skip_empty_lines(new_tail);
|
||||||
}
|
}
|
||||||
|
@ -173,7 +220,7 @@ fn parse_elements_children<'a>(
|
||||||
});
|
});
|
||||||
text = tail;
|
text = tail;
|
||||||
pos = 0;
|
pos = 0;
|
||||||
} else if let Some((new_tail, element)) = parse_element(tail, arena, containers) {
|
} else if let Some((new_tail, element)) = parse_block(tail, arena, containers) {
|
||||||
if pos != 0 {
|
if pos != 0 {
|
||||||
let node = arena.new_node(Element::Paragraph);
|
let node = arena.new_node(Element::Paragraph);
|
||||||
parent.append(node, arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
|
@ -202,7 +249,7 @@ fn parse_elements_children<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_element<'a>(
|
fn parse_block<'a>(
|
||||||
contents: &'a str,
|
contents: &'a str,
|
||||||
arena: &mut Arena<Element<'a>>,
|
arena: &mut Arena<Element<'a>>,
|
||||||
containers: &mut Vec<Container<'a>>,
|
containers: &mut Vec<Container<'a>>,
|
||||||
|
@ -300,7 +347,7 @@ fn parse_element<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_objects_children<'a>(
|
fn parse_inlines<'a>(
|
||||||
arena: &mut Arena<Element<'a>>,
|
arena: &mut Arena<Element<'a>>,
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
parent: NodeId,
|
parent: NodeId,
|
||||||
|
@ -308,8 +355,8 @@ fn parse_objects_children<'a>(
|
||||||
) {
|
) {
|
||||||
let mut tail = content;
|
let mut tail = content;
|
||||||
|
|
||||||
if let Some((new_tail, obj)) = parse_object(tail, arena, containers) {
|
if let Some((new_tail, element)) = parse_inline(tail, arena, containers) {
|
||||||
parent.append(obj, arena).unwrap();
|
parent.append(element, arena).unwrap();
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,7 +368,7 @@ fn parse_objects_children<'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)) = parse_object(&tail[off..], arena, containers) {
|
if let Some((new_tail, element)) = parse_inline(&tail[off..], arena, containers) {
|
||||||
if pos != 0 {
|
if pos != 0 {
|
||||||
let node = arena.new_node(Element::Text {
|
let node = arena.new_node(Element::Text {
|
||||||
value: &text[0..pos + off],
|
value: &text[0..pos + off],
|
||||||
|
@ -329,39 +376,40 @@ fn parse_objects_children<'a>(
|
||||||
parent.append(node, arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
pos = 0;
|
pos = 0;
|
||||||
}
|
}
|
||||||
parent.append(obj, arena).unwrap();
|
parent.append(element, arena).unwrap();
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
text = new_tail;
|
text = new_tail;
|
||||||
continue;
|
continue;
|
||||||
} else if let Some((new_tail, obj)) =
|
} else if let Some((new_tail, element)) =
|
||||||
parse_object(&tail[off + 1..], arena, containers)
|
parse_inline(&tail[off + 1..], arena, containers)
|
||||||
{
|
{
|
||||||
let node = arena.new_node(Element::Text {
|
let node = arena.new_node(Element::Text {
|
||||||
value: &text[0..pos + off + 1],
|
value: &text[0..pos + off + 1],
|
||||||
});
|
});
|
||||||
parent.append(node, arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
pos = 0;
|
pos = 0;
|
||||||
parent.append(obj, arena).unwrap();
|
parent.append(element, arena).unwrap();
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
text = new_tail;
|
text = new_tail;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b' ' | b'(' | b'\'' | b'"' | b'\n' => {
|
b' ' | b'(' | b'\'' | b'"' | b'\n' => {
|
||||||
if let Some((new_tail, obj)) = parse_object(&tail[off + 1..], arena, containers) {
|
if let Some((new_tail, element)) = parse_inline(&tail[off + 1..], arena, containers)
|
||||||
|
{
|
||||||
let node = arena.new_node(Element::Text {
|
let node = arena.new_node(Element::Text {
|
||||||
value: &text[0..pos + off + 1],
|
value: &text[0..pos + off + 1],
|
||||||
});
|
});
|
||||||
parent.append(node, arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
pos = 0;
|
pos = 0;
|
||||||
parent.append(obj, arena).unwrap();
|
parent.append(element, arena).unwrap();
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
text = new_tail;
|
text = new_tail;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some((new_tail, obj)) = parse_object(&tail[off..], arena, containers) {
|
if let Some((new_tail, element)) = parse_inline(&tail[off..], arena, containers) {
|
||||||
if pos != 0 {
|
if pos != 0 {
|
||||||
let node = arena.new_node(Element::Text {
|
let node = arena.new_node(Element::Text {
|
||||||
value: &text[0..pos + off],
|
value: &text[0..pos + off],
|
||||||
|
@ -369,7 +417,7 @@ fn parse_objects_children<'a>(
|
||||||
parent.append(node, arena).unwrap();
|
parent.append(node, arena).unwrap();
|
||||||
pos = 0;
|
pos = 0;
|
||||||
}
|
}
|
||||||
parent.append(obj, arena).unwrap();
|
parent.append(element, arena).unwrap();
|
||||||
tail = new_tail;
|
tail = new_tail;
|
||||||
text = new_tail;
|
text = new_tail;
|
||||||
continue;
|
continue;
|
||||||
|
@ -386,7 +434,7 @@ fn parse_objects_children<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_object<'a>(
|
fn parse_inline<'a>(
|
||||||
contents: &'a str,
|
contents: &'a str,
|
||||||
arena: &mut Arena<Element<'a>>,
|
arena: &mut Arena<Element<'a>>,
|
||||||
containers: &mut Vec<Container<'a>>,
|
containers: &mut Vec<Container<'a>>,
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
use orgize::Org;
|
use orgize::Org;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use std::io::Result;
|
|
||||||
|
|
||||||
macro_rules! test_suite {
|
macro_rules! test_suite {
|
||||||
($name:ident, $content:expr, $expected:expr) => {
|
($name:ident, $content:expr, $expected:expr) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() -> Result<()> {
|
fn $name() {
|
||||||
let mut writer = Vec::new();
|
let mut writer = Vec::new();
|
||||||
let org = Org::parse($content);
|
let org = Org::parse($content);
|
||||||
org.html(&mut writer).unwrap();
|
org.html(&mut writer).unwrap();
|
||||||
let string = String::from_utf8(writer).unwrap();
|
let string = String::from_utf8(writer).unwrap();
|
||||||
assert_eq!(string, $expected);
|
assert_eq!(string, $expected);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue