diff --git a/src/elements/block.rs b/src/elements/block.rs index 9cf5df0..8963a2e 100644 --- a/src/elements/block.rs +++ b/src/elements/block.rs @@ -1,19 +1,15 @@ use memchr::{memchr, memchr_iter}; -use crate::elements::Element; - #[cfg_attr(test, derive(PartialEq))] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug)] pub struct Block<'a> { pub name: &'a str, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub args: Option<&'a str>, } impl Block<'_> { #[inline] - pub(crate) fn parse(text: &str) -> Option<(&str, Element<'_>, &str)> { + pub(crate) fn parse(text: &str) -> Option<(&str, Block<'_>, &str)> { debug_assert!(text.starts_with("#+")); if text.len() <= 8 || text[2..8].to_uppercase() != "BEGIN_" { @@ -36,18 +32,14 @@ impl Block<'_> { for i in lines { if text[pos..i].trim().eq_ignore_ascii_case(&end) { - return Some(( - &text[i + 1..], - Element::Block(Block { name, args }), - &text[off..pos], - )); + return Some((&text[i + 1..], Block { name, args }, &text[off..pos])); } pos = i + 1; } if text[pos..].trim().eq_ignore_ascii_case(&end) { - Some(("", Element::Block(Block { name, args }), &text[off..pos])) + Some(("", Block { name, args }, &text[off..pos])) } else { None } @@ -60,10 +52,10 @@ fn parse() { Block::parse("#+BEGIN_SRC\n#+END_SRC"), Some(( "", - Element::Block(Block { + Block { name: "SRC", args: None, - }), + }, "" )) ); @@ -71,12 +63,74 @@ fn parse() { Block::parse("#+BEGIN_SRC javascript \nconsole.log('Hello World!');\n#+END_SRC\n"), Some(( "", - Element::Block(Block { + Block { name: "SRC", args: Some("javascript"), - }), + }, "console.log('Hello World!');\n" )) ); // TODO: more testing } + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct SpecialBlock<'a> { + pub parameters: Option<&'a str>, + pub name: &'a str, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct QuoteBlock<'a> { + pub parameters: Option<&'a str>, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct CenterBlock<'a> { + pub parameters: Option<&'a str>, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct VerseBlock<'a> { + pub parameters: Option<&'a str>, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct CommentBlock<'a> { + pub data: Option<&'a str>, + pub contents: &'a str, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct ExampleBlock<'a> { + pub data: Option<&'a str>, + pub contents: &'a str, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct ExportBlock<'a> { + pub data: &'a str, + pub contents: &'a str, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct SourceBlock<'a> { + pub contents: &'a str, + pub language: &'a str, + pub arguments: &'a str, +} diff --git a/src/elements/cookie.rs b/src/elements/cookie.rs index 1de7e73..be03e9f 100644 --- a/src/elements/cookie.rs +++ b/src/elements/cookie.rs @@ -4,7 +4,7 @@ use memchr::{memchr, memchr2}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug)] pub struct Cookie<'a> { - value: &'a str, + pub value: &'a str, } impl Cookie<'_> { diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 2dfe523..14d636f 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -23,9 +23,13 @@ mod timestamp; mod title; pub(crate) use emphasis::parse as parse_emphasis; +pub(crate) use block::Block; pub use self::{ - block::Block, + block::{ + CenterBlock, CommentBlock, ExampleBlock, ExportBlock, QuoteBlock, SourceBlock, + SpecialBlock, VerseBlock, + }, clock::Clock, cookie::Cookie, drawer::Drawer, @@ -53,7 +57,14 @@ pub use self::{ #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))] pub enum Element<'a> { - Block(Block<'a>), + SpecialBlock(SpecialBlock<'a>), + QuoteBlock(QuoteBlock<'a>), + CenterBlock(CenterBlock<'a>), + VerseBlock(VerseBlock<'a>), + CommentBlock(CommentBlock<'a>), + ExampleBlock(ExampleBlock<'a>), + ExportBlock(ExportBlock<'a>), + SourceBlock(SourceBlock<'a>), BabelCall(BabelCall<'a>), Section, Clock(Clock<'a>), @@ -93,7 +104,10 @@ pub enum Element<'a> { impl Element<'_> { pub fn is_container(&self) -> bool { match self { - Element::Block(_) + Element::SpecialBlock(_) + | Element::QuoteBlock(_) + | Element::CenterBlock(_) + | Element::VerseBlock(_) | Element::Bold | Element::Document | Element::DynBlock(_) @@ -112,30 +126,50 @@ impl Element<'_> { } macro_rules! impl_from { - ($ident:ident) => { - impl<'a> From<$ident<'a>> for Element<'a> { - fn from(ele: $ident<'a>) -> Element<'a> { - Element::$ident(ele) + ($($ele0:ident),*; $($ele1:ident),*) => { + $( + impl<'a> From<$ele0<'a>> for Element<'a> { + fn from(ele: $ele0<'a>) -> Element<'a> { + Element::$ele0(ele) + } } - } + )* + $( + impl<'a> From<$ele1> for Element<'a> { + fn from(ele: $ele1) -> Element<'a> { + Element::$ele1(ele) + } + } + )* }; } -impl_from!(Block); -impl_from!(BabelCall); -impl_from!(Clock); -impl_from!(Cookie); -impl_from!(Drawer); -impl_from!(DynBlock); -impl_from!(FnDef); -impl_from!(FnRef); -impl_from!(InlineCall); -impl_from!(InlineSrc); -impl_from!(Keyword); -impl_from!(Link); -impl_from!(ListItem); -impl_from!(Macros); -impl_from!(Planning); -impl_from!(Snippet); -impl_from!(Timestamp); -impl_from!(Target); +impl_from!( + BabelCall, + CenterBlock, + Clock, + CommentBlock, + Cookie, + Drawer, + DynBlock, + ExampleBlock, + ExportBlock, + FnDef, + FnRef, + InlineCall, + InlineSrc, + Keyword, + Link, + ListItem, + Macros, + Planning, + QuoteBlock, + Snippet, + SourceBlock, + SpecialBlock, + Target, + Timestamp, + VerseBlock; + RadioTarget, + List +); diff --git a/src/export/html.rs b/src/export/html.rs index 6ac4b12..1128510 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -34,7 +34,10 @@ pub trait HtmlHandler> { match element { // container elements - Block(_block) => write!(w, "
")?, + SpecialBlock(_) => (), + QuoteBlock(_) => write!(w, "
")?, + CenterBlock(_) => write!(w, "
")?, + VerseBlock(_) => write!(w, "

")?, Bold => write!(w, "")?, Document => write!(w, "

")?, DynBlock(_dyn_block) => (), @@ -53,11 +56,37 @@ pub trait HtmlHandler> { Strike => write!(w, "")?, Underline => write!(w, "")?, // non-container elements - BabelCall(_babel_call) => (), - InlineSrc(inline_src) => write!(w, "{}", Escape(inline_src.body))?, + CommentBlock(_) => (), + ExampleBlock(block) => { + write!(w, "
{}
", Escape(block.contents))? + } + ExportBlock(block) => { + if block.data.eq_ignore_ascii_case("HTML") { + write!(w, "{}", block.contents)? + } + } + SourceBlock(block) => { + if block.language.is_empty() { + write!(w, "
{}
", Escape(block.contents))?; + } else { + write!( + w, + "
{}
", + block.language, + Escape(block.contents) + )?; + } + } + BabelCall(_) => (), + InlineSrc(inline_src) => write!( + w, + "{}", + inline_src.lang, + Escape(inline_src.body) + )?, Code { value } => write!(w, "{}", Escape(value))?, FnRef(_fn_ref) => (), - InlineCall(_inline_call) => (), + InlineCall(_) => (), Link(link) => write!( w, "{}", @@ -74,16 +103,85 @@ pub trait HtmlHandler> { } Target(_target) => (), Text { value } => write!(w, "{}", Escape(value))?, - Timestamp(_timestamp) => (), + Timestamp(timestamp) => { + use crate::elements::{Date, Time, Timestamp::*}; + + write!( + &mut w, + "" + )?; + + fn write_datetime( + mut w: W, + start: &str, + date: &Date, + time: &Option")?; + } Verbatim { value } => write!(&mut w, "{}", Escape(value))?, FnDef(_fn_def) => (), Clock(_clock) => (), - Comment { value } => write!(w, "", Escape(value))?, - FixedWidth { value } => write!(w, "
{}
", Escape(value))?, + Comment { .. } => (), + FixedWidth { value } => write!(w, "
{}
", Escape(value))?, Keyword(_keyword) => (), Drawer(_drawer) => (), Rule => write!(w, "
")?, - Cookie(_cookie) => (), + Cookie(cookie) => write!(w, "{}", cookie.value)?, Title(title) => write!(w, "", if title.level <= 6 { title.level } else { 6 })?, } @@ -94,7 +192,10 @@ pub trait HtmlHandler> { match element { // container elements - Block(_block) => write!(w, "
")?, + SpecialBlock(_) => (), + QuoteBlock(_) => write!(w, "
")?, + CenterBlock(_) => write!(w, "
")?, + VerseBlock(_) => write!(w, "

")?, Bold => write!(w, "
")?, Document => write!(w, "")?, DynBlock(_dyn_block) => (), diff --git a/src/export/org.rs b/src/export/org.rs index 9e3e082..c750576 100644 --- a/src/export/org.rs +++ b/src/export/org.rs @@ -7,13 +7,10 @@ pub trait OrgHandler> { match element { // container elements - Block(block) => { - write!(&mut w, "#+BEGIN_{}", block.name)?; - if let Some(parameters) = block.args { - write!(&mut w, " {}", parameters)?; - } - writeln!(&mut w)?; - } + SpecialBlock(block) => writeln!(w, "#+BEGIN_{}", block.name)?, + QuoteBlock(_) => write!(w, "#+BEGIN_QUOTE")?, + CenterBlock(_) => write!(w, "#+BEGIN_CENTER")?, + VerseBlock(_) => write!(w, "#+BEGIN_VERSE")?, Bold => write!(w, "*")?, Document => (), DynBlock(dyn_block) => { @@ -33,6 +30,22 @@ pub trait OrgHandler> { Underline => write!(w, "_")?, Drawer(drawer) => writeln!(w, ":{}:", drawer.name)?, // non-container elements + CommentBlock(block) => { + writeln!(w, "#+BEGIN_COMMENT\n{}\n#+END_COMMENT", block.contents)? + } + ExampleBlock(block) => { + writeln!(w, "#+BEGIN_EXAMPLE\n{}\n#+END_EXAMPLE", block.contents)? + } + ExportBlock(block) => writeln!( + w, + "#+BEGIN_EXPORT {}\n{}\n#+END_EXPORT", + block.data, block.contents + )?, + SourceBlock(block) => writeln!( + w, + "#+BEGIN_SRC {}\n{}\n#+END_SRC", + block.language, block.contents + )?, BabelCall(_babel_call) => (), InlineSrc(inline_src) => { write!(&mut w, "src_{}", inline_src.lang)?; @@ -78,20 +91,23 @@ pub trait OrgHandler> { Timestamp(timestamp) => { use crate::elements::{Date, Time, Timestamp::*}; - fn write_date(mut w: W, date: &Date) -> Result<(), Error> { + fn write_datetime( + mut w: W, + start: &str, + date: &Date, + time: &Option