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

View file

@ -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<'_> {

View file

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

View file

@ -34,7 +34,10 @@ pub trait HtmlHandler<E: From<Error>> {
match element {
// 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>")?,
Document => write!(w, "<main>")?,
DynBlock(_dyn_block) => (),
@ -53,11 +56,37 @@ pub trait HtmlHandler<E: From<Error>> {
Strike => write!(w, "<s>")?,
Underline => write!(w, "<u>")?,
// non-container elements
BabelCall(_babel_call) => (),
InlineSrc(inline_src) => write!(w, "<code>{}</code>", Escape(inline_src.body))?,
CommentBlock(_) => (),
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))?,
FnRef(_fn_ref) => (),
InlineCall(_inline_call) => (),
InlineCall(_) => (),
Link(link) => write!(
w,
"<a href=\"{}\">{}</a>",
@ -74,16 +103,85 @@ pub trait HtmlHandler<E: From<Error>> {
}
Target(_target) => (),
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))?,
FnDef(_fn_def) => (),
Clock(_clock) => (),
Comment { value } => write!(w, "<!--\n{}\n-->", Escape(value))?,
FixedWidth { value } => write!(w, "<pre>{}</pre>", Escape(value))?,
Comment { .. } => (),
FixedWidth { value } => write!(w, "<pre class=\"example\">{}</pre>", Escape(value))?,
Keyword(_keyword) => (),
Drawer(_drawer) => (),
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 })?,
}
@ -94,7 +192,10 @@ pub trait HtmlHandler<E: From<Error>> {
match element {
// container elements
Block(_block) => write!(w, "</div>")?,
SpecialBlock(_) => (),
QuoteBlock(_) => write!(w, "</blockquote>")?,
CenterBlock(_) => write!(w, "</div>")?,
VerseBlock(_) => write!(w, "</p>")?,
Bold => write!(w, "</b>")?,
Document => write!(w, "</main>")?,
DynBlock(_dyn_block) => (),

View file

@ -7,13 +7,10 @@ pub trait OrgHandler<E: From<Error>> {
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<E: From<Error>> {
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<E: From<Error>> {
Timestamp(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!(
w,
"{}-{}-{} {}",
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 {
write!(w, " {}:{}", time.hour, time.minute)
} else {
Ok(())
write!(w, " {}:{}", time.hour, time.minute,)?;
}
write!(w, "{}", end)
}
match timestamp {
@ -100,20 +116,14 @@ pub trait OrgHandler<E: From<Error>> {
start_time,
..
} => {
write!(&mut w, "<")?;
write_date(&mut w, start_date)?;
write_time(&mut w, start_time)?;
write!(&mut w, ">")?;
write_datetime(&mut w, "<", start_date, start_time, ">")?;
}
Inactive {
start_date,
start_time,
..
} => {
write!(&mut w, "[")?;
write_date(&mut w, start_date)?;
write_time(&mut w, start_time)?;
write!(&mut w, "]")?;
write_datetime(&mut w, "[", start_date, start_time, "]")?;
}
ActiveRange {
start_date,
@ -122,13 +132,8 @@ pub trait OrgHandler<E: From<Error>> {
end_time,
..
} => {
write!(&mut w, "<")?;
write_date(&mut w, start_date)?;
write_time(&mut w, start_time)?;
write!(&mut w, ">--<")?;
write_date(&mut w, end_date)?;
write_time(&mut w, end_time)?;
write!(&mut w, ">")?;
write_datetime(&mut w, "<", start_date, start_time, ">--")?;
write_datetime(&mut w, "<", end_date, end_time, ">")?;
}
InactiveRange {
start_date,
@ -137,13 +142,8 @@ pub trait OrgHandler<E: From<Error>> {
end_time,
..
} => {
write!(&mut w, "[")?;
write_date(&mut w, start_date)?;
write_time(&mut w, start_time)?;
write!(&mut w, "]--[")?;
write_date(&mut w, end_date)?;
write_time(&mut w, end_time)?;
write!(&mut w, "]")?;
write_datetime(&mut w, "[", start_date, start_time, "]--")?;
write_datetime(&mut w, "[", end_date, end_time, "]")?;
}
Diary(value) => write!(w, "<%%({})>", value)?,
}
@ -184,7 +184,10 @@ pub trait OrgHandler<E: From<Error>> {
match element {
// 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, "*")?,
Document => (),
DynBlock(_dyn_block) => writeln!(w, "#+END:")?,

View file

@ -315,12 +315,12 @@ fn parse_block<'a>(
let mut last_end = 1; // ":"
for i in memchr_iter(b'\n', contents.as_bytes()) {
last_end = i + 1;
let line = &contents[last_end..];
if !(line == ":" || line.starts_with(": ") || line.starts_with(":\n")) {
let tail = contents[last_end..].trim_start();
if !(tail == ":" || tail.starts_with(": ") || tail.starts_with(":\n")) {
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 {
@ -334,12 +334,12 @@ fn parse_block<'a>(
let mut last_end = 1; // "#"
for i in memchr_iter(b'\n', contents.as_bytes()) {
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")) {
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 {
@ -350,9 +350,70 @@ fn parse_block<'a>(
if tail.starts_with("#+") {
if let Some((tail, block, content)) = Block::parse(tail) {
let node = arena.new_node(block);
containers.push(Container::Block { content, node });
Some((tail, node))
match &*block.name.to_uppercase() {
"CENTER" => {
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) {
let node = arena.new_node(dyn_block);
containers.push(Container::Block { content, node });