feat(element): distinguish block by its name

This commit is contained in:
PoiScript 2019-08-04 11:13:48 +08:00
parent 5b9ceebea4
commit da18d87aeb
6 changed files with 352 additions and 99 deletions

View file

@ -1,19 +1,15 @@
use memchr::{memchr, memchr_iter}; use memchr::{memchr, memchr_iter};
use crate::elements::Element;
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Debug)] #[derive(Debug)]
pub struct Block<'a> { pub struct Block<'a> {
pub name: &'a str, pub name: &'a str,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub args: Option<&'a str>, pub args: Option<&'a str>,
} }
impl Block<'_> { impl Block<'_> {
#[inline] #[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("#+")); 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_" {
@ -36,18 +32,14 @@ impl Block<'_> {
for i in lines { for i in lines {
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..], Block { name, args }, &text[off..pos]));
&text[i + 1..],
Element::Block(Block { name, args }),
&text[off..pos],
));
} }
pos = i + 1; pos = i + 1;
} }
if text[pos..].trim().eq_ignore_ascii_case(&end) { 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 { } else {
None None
} }
@ -60,10 +52,10 @@ fn parse() {
Block::parse("#+BEGIN_SRC\n#+END_SRC"), Block::parse("#+BEGIN_SRC\n#+END_SRC"),
Some(( Some((
"", "",
Element::Block(Block { Block {
name: "SRC", name: "SRC",
args: None, args: None,
}), },
"" ""
)) ))
); );
@ -71,12 +63,74 @@ fn parse() {
Block::parse("#+BEGIN_SRC javascript \nconsole.log('Hello World!');\n#+END_SRC\n"), Block::parse("#+BEGIN_SRC javascript \nconsole.log('Hello World!');\n#+END_SRC\n"),
Some(( Some((
"", "",
Element::Block(Block { Block {
name: "SRC", name: "SRC",
args: Some("javascript"), args: Some("javascript"),
}), },
"console.log('Hello World!');\n" "console.log('Hello World!');\n"
)) ))
); );
// TODO: more testing // 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,
}

View file

@ -4,7 +4,7 @@ use memchr::{memchr, memchr2};
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Debug)] #[derive(Debug)]
pub struct Cookie<'a> { pub struct Cookie<'a> {
value: &'a str, pub value: &'a str,
} }
impl Cookie<'_> { impl Cookie<'_> {

View file

@ -23,9 +23,13 @@ mod timestamp;
mod title; mod title;
pub(crate) use emphasis::parse as parse_emphasis; pub(crate) use emphasis::parse as parse_emphasis;
pub(crate) use block::Block;
pub use self::{ pub use self::{
block::Block, block::{
CenterBlock, CommentBlock, ExampleBlock, ExportBlock, QuoteBlock, SourceBlock,
SpecialBlock, VerseBlock,
},
clock::Clock, clock::Clock,
cookie::Cookie, cookie::Cookie,
drawer::Drawer, drawer::Drawer,
@ -53,7 +57,14 @@ pub use self::{
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))] #[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
pub enum Element<'a> { 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>), BabelCall(BabelCall<'a>),
Section, Section,
Clock(Clock<'a>), Clock(Clock<'a>),
@ -93,7 +104,10 @@ pub enum Element<'a> {
impl Element<'_> { impl Element<'_> {
pub fn is_container(&self) -> bool { pub fn is_container(&self) -> bool {
match self { match self {
Element::Block(_) Element::SpecialBlock(_)
| Element::QuoteBlock(_)
| Element::CenterBlock(_)
| Element::VerseBlock(_)
| Element::Bold | Element::Bold
| Element::Document | Element::Document
| Element::DynBlock(_) | Element::DynBlock(_)
@ -112,30 +126,50 @@ impl Element<'_> {
} }
macro_rules! impl_from { macro_rules! impl_from {
($ident:ident) => { ($($ele0:ident),*; $($ele1:ident),*) => {
impl<'a> From<$ident<'a>> for Element<'a> { $(
fn from(ele: $ident<'a>) -> Element<'a> { impl<'a> From<$ele0<'a>> for Element<'a> {
Element::$ident(ele) 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!(
impl_from!(BabelCall); BabelCall,
impl_from!(Clock); CenterBlock,
impl_from!(Cookie); Clock,
impl_from!(Drawer); CommentBlock,
impl_from!(DynBlock); Cookie,
impl_from!(FnDef); Drawer,
impl_from!(FnRef); DynBlock,
impl_from!(InlineCall); ExampleBlock,
impl_from!(InlineSrc); ExportBlock,
impl_from!(Keyword); FnDef,
impl_from!(Link); FnRef,
impl_from!(ListItem); InlineCall,
impl_from!(Macros); InlineSrc,
impl_from!(Planning); Keyword,
impl_from!(Snippet); Link,
impl_from!(Timestamp); ListItem,
impl_from!(Target); Macros,
Planning,
QuoteBlock,
Snippet,
SourceBlock,
SpecialBlock,
Target,
Timestamp,
VerseBlock;
RadioTarget,
List
);

View file

@ -34,7 +34,10 @@ pub trait HtmlHandler<E: From<Error>> {
match element { match element {
// container elements // container elements
Block(_block) => write!(w, "<div>")?, SpecialBlock(_) => (),
QuoteBlock(_) => write!(w, "<blockquote>")?,
CenterBlock(_) => write!(w, "<div class=\"center\">")?,
VerseBlock(_) => write!(w, "<p class=\"verse\">")?,
Bold => write!(w, "<b>")?, Bold => write!(w, "<b>")?,
Document => write!(w, "<main>")?, Document => write!(w, "<main>")?,
DynBlock(_dyn_block) => (), DynBlock(_dyn_block) => (),
@ -53,11 +56,37 @@ pub trait HtmlHandler<E: From<Error>> {
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) => (), CommentBlock(_) => (),
InlineSrc(inline_src) => write!(w, "<code>{}</code>", Escape(inline_src.body))?, ExampleBlock(block) => {
write!(w, "<pre class=\"example\">{}</pre>", 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, "<pre class=\"example\">{}</pre>", Escape(block.contents))?;
} else {
write!(
w,
"<div class=\"org-src-container\"><pre class=\"src src-{}\">{}</pre></div>",
block.language,
Escape(block.contents)
)?;
}
}
BabelCall(_) => (),
InlineSrc(inline_src) => write!(
w,
"<code class=\"src src-{}\">{}</code>",
inline_src.lang,
Escape(inline_src.body)
)?,
Code { value } => write!(w, "<code>{}</code>", Escape(value))?, Code { value } => write!(w, "<code>{}</code>", Escape(value))?,
FnRef(_fn_ref) => (), FnRef(_fn_ref) => (),
InlineCall(_inline_call) => (), InlineCall(_) => (),
Link(link) => write!( Link(link) => write!(
w, w,
"<a href=\"{}\">{}</a>", "<a href=\"{}\">{}</a>",
@ -74,16 +103,85 @@ pub trait HtmlHandler<E: From<Error>> {
} }
Target(_target) => (), Target(_target) => (),
Text { value } => write!(w, "{}", Escape(value))?, Text { value } => write!(w, "{}", Escape(value))?,
Timestamp(_timestamp) => (), Timestamp(timestamp) => {
use crate::elements::{Date, Time, Timestamp::*};
write!(
&mut w,
"<span class=\"timestamp-wrapper\"><span class=\"timestamp\">"
)?;
fn write_datetime<W: Write>(
mut w: W,
start: &str,
date: &Date,
time: &Option<Time>,
end: &str,
) -> Result<(), Error> {
write!(w, "{}", start)?;
write!(
w,
"{}-{}-{} {}",
date.year,
date.month,
date.day,
Escape(date.dayname)
)?;
if let Some(time) = time {
write!(w, " {}:{}", time.hour, time.minute)?;
}
write!(w, "{}", end)
}
match timestamp {
Active {
start_date,
start_time,
..
} => {
write_datetime(&mut w, "&lt;", start_date, start_time, "&gt;")?;
}
Inactive {
start_date,
start_time,
..
} => {
write_datetime(&mut w, "[", start_date, start_time, "]")?;
}
ActiveRange {
start_date,
start_time,
end_date,
end_time,
..
} => {
write_datetime(&mut w, "&lt;", start_date, start_time, "&gt;&#x2013;")?;
write_datetime(&mut w, "&lt;", end_date, end_time, "&gt;")?;
}
InactiveRange {
start_date,
start_time,
end_date,
end_time,
..
} => {
write_datetime(&mut w, "[", start_date, start_time, "]&#x2013;")?;
write_datetime(&mut w, "[", end_date, end_time, "]")?;
}
Diary(value) => write!(&mut w, "&lt;%%({})&gt;", Escape(value))?,
}
write!(&mut w, "</span></span>")?;
}
Verbatim { value } => write!(&mut w, "<code>{}</code>", Escape(value))?, Verbatim { value } => write!(&mut w, "<code>{}</code>", Escape(value))?,
FnDef(_fn_def) => (), FnDef(_fn_def) => (),
Clock(_clock) => (), Clock(_clock) => (),
Comment { value } => write!(w, "<!--\n{}\n-->", Escape(value))?, Comment { .. } => (),
FixedWidth { value } => write!(w, "<pre>{}</pre>", Escape(value))?, FixedWidth { value } => write!(w, "<pre class=\"example\">{}</pre>", Escape(value))?,
Keyword(_keyword) => (), Keyword(_keyword) => (),
Drawer(_drawer) => (), Drawer(_drawer) => (),
Rule => write!(w, "<hr>")?, Rule => write!(w, "<hr>")?,
Cookie(_cookie) => (), Cookie(cookie) => write!(w, "<code>{}</code>", cookie.value)?,
Title(title) => write!(w, "<h{}>", if title.level <= 6 { title.level } else { 6 })?, Title(title) => write!(w, "<h{}>", if title.level <= 6 { title.level } else { 6 })?,
} }
@ -94,7 +192,10 @@ pub trait HtmlHandler<E: From<Error>> {
match element { match element {
// container elements // container elements
Block(_block) => write!(w, "</div>")?, SpecialBlock(_) => (),
QuoteBlock(_) => write!(w, "</blockquote>")?,
CenterBlock(_) => write!(w, "</div>")?,
VerseBlock(_) => write!(w, "</p>")?,
Bold => write!(w, "</b>")?, Bold => write!(w, "</b>")?,
Document => write!(w, "</main>")?, Document => write!(w, "</main>")?,
DynBlock(_dyn_block) => (), DynBlock(_dyn_block) => (),

View file

@ -7,13 +7,10 @@ pub trait OrgHandler<E: From<Error>> {
match element { match element {
// container elements // container elements
Block(block) => { SpecialBlock(block) => writeln!(w, "#+BEGIN_{}", block.name)?,
write!(&mut w, "#+BEGIN_{}", block.name)?; QuoteBlock(_) => write!(w, "#+BEGIN_QUOTE")?,
if let Some(parameters) = block.args { CenterBlock(_) => write!(w, "#+BEGIN_CENTER")?,
write!(&mut w, " {}", parameters)?; VerseBlock(_) => write!(w, "#+BEGIN_VERSE")?,
}
writeln!(&mut w)?;
}
Bold => write!(w, "*")?, Bold => write!(w, "*")?,
Document => (), Document => (),
DynBlock(dyn_block) => { DynBlock(dyn_block) => {
@ -33,6 +30,22 @@ pub trait OrgHandler<E: From<Error>> {
Underline => write!(w, "_")?, Underline => write!(w, "_")?,
Drawer(drawer) => writeln!(w, ":{}:", drawer.name)?, Drawer(drawer) => writeln!(w, ":{}:", drawer.name)?,
// non-container elements // 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) => (), BabelCall(_babel_call) => (),
InlineSrc(inline_src) => { InlineSrc(inline_src) => {
write!(&mut w, "src_{}", inline_src.lang)?; write!(&mut w, "src_{}", inline_src.lang)?;
@ -78,20 +91,23 @@ pub trait OrgHandler<E: From<Error>> {
Timestamp(timestamp) => { Timestamp(timestamp) => {
use crate::elements::{Date, Time, Timestamp::*}; use crate::elements::{Date, Time, Timestamp::*};
fn write_date<W: Write>(mut w: W, date: &Date) -> Result<(), Error> { fn write_datetime<W: Write>(
mut w: W,
start: &str,
date: &Date,
time: &Option<Time>,
end: &str,
) -> Result<(), Error> {
write!(w, "{}", start)?;
write!( write!(
w, w,
"{}-{}-{} {}", "{}-{}-{} {}",
date.year, date.month, date.day, date.dayname date.year, date.month, date.day, date.dayname
) )?;
}
fn write_time<W: Write>(mut w: W, time: &Option<Time>) -> Result<(), Error> {
if let Some(time) = time { if let Some(time) = time {
write!(w, " {}:{}", time.hour, time.minute) write!(w, " {}:{}", time.hour, time.minute,)?;
} else {
Ok(())
} }
write!(w, "{}", end)
} }
match timestamp { match timestamp {
@ -100,20 +116,14 @@ pub trait OrgHandler<E: From<Error>> {
start_time, start_time,
.. ..
} => { } => {
write!(&mut w, "<")?; write_datetime(&mut w, "<", start_date, start_time, ">")?;
write_date(&mut w, start_date)?;
write_time(&mut w, start_time)?;
write!(&mut w, ">")?;
} }
Inactive { Inactive {
start_date, start_date,
start_time, start_time,
.. ..
} => { } => {
write!(&mut w, "[")?; write_datetime(&mut w, "[", start_date, start_time, "]")?;
write_date(&mut w, start_date)?;
write_time(&mut w, start_time)?;
write!(&mut w, "]")?;
} }
ActiveRange { ActiveRange {
start_date, start_date,
@ -122,13 +132,8 @@ pub trait OrgHandler<E: From<Error>> {
end_time, end_time,
.. ..
} => { } => {
write!(&mut w, "<")?; write_datetime(&mut w, "<", start_date, start_time, ">--")?;
write_date(&mut w, start_date)?; write_datetime(&mut w, "<", end_date, end_time, ">")?;
write_time(&mut w, start_time)?;
write!(&mut w, ">--<")?;
write_date(&mut w, end_date)?;
write_time(&mut w, end_time)?;
write!(&mut w, ">")?;
} }
InactiveRange { InactiveRange {
start_date, start_date,
@ -137,13 +142,8 @@ pub trait OrgHandler<E: From<Error>> {
end_time, end_time,
.. ..
} => { } => {
write!(&mut w, "[")?; write_datetime(&mut w, "[", start_date, start_time, "]--")?;
write_date(&mut w, start_date)?; write_datetime(&mut w, "[", end_date, end_time, "]")?;
write_time(&mut w, start_time)?;
write!(&mut w, "]--[")?;
write_date(&mut w, end_date)?;
write_time(&mut w, end_time)?;
write!(&mut w, "]")?;
} }
Diary(value) => write!(w, "<%%({})>", value)?, Diary(value) => write!(w, "<%%({})>", value)?,
} }
@ -184,7 +184,10 @@ pub trait OrgHandler<E: From<Error>> {
match element { match element {
// container elements // container elements
Block(block) => writeln!(w, "#+END_{}", block.name)?, SpecialBlock(block) => writeln!(w, "#+END_{}", block.name)?,
QuoteBlock(_) => writeln!(w, "#+END_QUOTE")?,
CenterBlock(_) => writeln!(w, "#+END_CENTER")?,
VerseBlock(_) => writeln!(w, "#+END_VERSE")?,
Bold => write!(w, "*")?, Bold => write!(w, "*")?,
Document => (), Document => (),
DynBlock(_dyn_block) => writeln!(w, "#+END:")?, DynBlock(_dyn_block) => writeln!(w, "#+END:")?,

View file

@ -315,12 +315,12 @@ fn parse_block<'a>(
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 tail = contents[last_end..].trim_start();
if !(line == ":" || line.starts_with(": ") || line.starts_with(":\n")) { if !(tail == ":" || tail.starts_with(": ") || tail.starts_with(":\n")) {
let fixed_width = arena.new_node(Element::FixedWidth { let fixed_width = arena.new_node(Element::FixedWidth {
value: &contents[0..i + 1], value: &contents[0..last_end],
}); });
return Some((&contents[i + 1..], fixed_width)); return Some((&contents[last_end..], fixed_width));
} }
} }
let fixed_width = arena.new_node(Element::FixedWidth { let fixed_width = arena.new_node(Element::FixedWidth {
@ -334,12 +334,12 @@ fn parse_block<'a>(
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..].trim_start();
if !(line == "#" || line.starts_with("# ") || line.starts_with("#\n")) { if !(line == "#" || line.starts_with("# ") || line.starts_with("#\n")) {
let comment = arena.new_node(Element::Comment { let comment = arena.new_node(Element::Comment {
value: &contents[0..i + 1], value: &contents[0..last_end],
}); });
return Some((&contents[i + 1..], comment)); return Some((&contents[last_end..], comment));
} }
} }
let comment = arena.new_node(Element::Comment { let comment = arena.new_node(Element::Comment {
@ -350,9 +350,70 @@ fn parse_block<'a>(
if tail.starts_with("#+") { if tail.starts_with("#+") {
if let Some((tail, block, content)) = Block::parse(tail) { if let Some((tail, block, content)) = Block::parse(tail) {
let node = arena.new_node(block); match &*block.name.to_uppercase() {
containers.push(Container::Block { content, node }); "CENTER" => {
Some((tail, node)) let node = arena.new_node(Element::CenterBlock(CenterBlock {
parameters: block.args,
}));
containers.push(Container::Block { content, node });
Some((tail, node))
}
"QUOTE" => {
let node = arena.new_node(Element::QuoteBlock(QuoteBlock {
parameters: block.args,
}));
containers.push(Container::Block { content, node });
Some((tail, node))
}
"COMMENT" => {
let node = arena.new_node(Element::CommentBlock(CommentBlock {
data: block.args,
contents: content,
}));
Some((tail, node))
}
"EXAMPLE" => {
let node = arena.new_node(Element::ExampleBlock(ExampleBlock {
data: block.args,
contents: content,
}));
Some((tail, node))
}
"EXPORT" => {
let node = arena.new_node(Element::ExportBlock(ExportBlock {
data: block.args.unwrap_or(""),
contents: content,
}));
Some((tail, node))
}
"SRC" => {
let (language, arguments) = block
.args
.map(|args| args.split_at(args.find(' ').unwrap_or_else(|| args.len())))
.unwrap_or(("", ""));
let node = arena.new_node(Element::SourceBlock(SourceBlock {
arguments,
language,
contents: content,
}));
Some((tail, node))
}
"VERSE" => {
let node = arena.new_node(Element::VerseBlock(VerseBlock {
parameters: block.args,
}));
containers.push(Container::Block { content, node });
Some((tail, node))
}
_ => {
let node = arena.new_node(Element::SpecialBlock(SpecialBlock {
parameters: block.args,
name: block.name,
}));
containers.push(Container::Block { content, node });
Some((tail, node))
}
}
} else if let Some((tail, dyn_block, content)) = DynBlock::parse(tail) { } else if let Some((tail, dyn_block, content)) = DynBlock::parse(tail) {
let node = arena.new_node(dyn_block); let node = arena.new_node(dyn_block);
containers.push(Container::Block { content, node }); containers.push(Container::Block { content, node });