From 1ed561b9f9e12179fd7f1bc6c318d36fd9b4ebae Mon Sep 17 00:00:00 2001
From: Noah Hellman
Date: Fri, 17 Mar 2023 18:48:17 +0100
Subject: [PATCH 1/8] html: avoid peek of next event
Try to make rendering of each event independent.
The only case where we need to peek is when a backref link should be
added to the last paragraph within a footnote.
Before, when exiting a paragraph, we would peek and add the link before
emitting the close tag if the next event is a the footnote end.
Now, the paragraph end event skips emitting a paragraph close tag if it
is within a footnote. The next event will then always close the
paragraph, and if it is a footnote end, it will add the backref link
before closing.
---
src/html.rs | 45 +++++++++++++++++++++++----------------------
1 file changed, 23 insertions(+), 22 deletions(-)
diff --git a/src/html.rs b/src/html.rs
index 1b825f87..c0f78649 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -73,8 +73,8 @@ struct Writer<'s, I: Iterator- >, W> {
list_tightness: Vec,
encountered_footnote: bool,
footnote_number: Option,
- footnote_backlink_written: bool,
first_line: bool,
+ close_para: bool,
}
impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
@@ -87,13 +87,22 @@ impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
list_tightness: Vec::new(),
encountered_footnote: false,
footnote_number: None,
- footnote_backlink_written: false,
first_line: true,
+ close_para: false,
}
}
fn write(&mut self) -> std::fmt::Result {
while let Some(e) = self.events.next() {
+ let close_para = self.close_para;
+ if close_para {
+ self.close_para = false;
+ if !matches!(&e, Event::End(Container::Footnote { .. })) {
+ // no need to add href before para close
+ self.out.write_str("
")?;
+ }
+ }
+
match e {
Event::Start(c, attrs) => {
if c.is_block() && !self.first_line {
@@ -143,7 +152,6 @@ impl<'s, I: Iterator- >, W: std::fmt::Write> Writer<'s, I, W> {
.write_str("\n
\n\n")?;
}
write!(self.out, "- ", number)?;
- self.footnote_backlink_written = false;
continue;
}
Container::Table => self.out.write_str("
>, W: std::fmt::Write> Writer<'s, I, W> {
Container::DescriptionList => self.out.write_str("")?,
Container::DescriptionDetails => self.out.write_str("")?,
Container::Footnote { number, .. } => {
- if !self.footnote_backlink_written {
- write!(
- self.out,
- "\n↩︎︎
",
- number,
- )?;
+ if !close_para {
+ // create a new paragraph
+ self.out.write_str("\n")?;
}
+ write!(
+ self.out,
+ r##"↩︎︎
"##,
+ number,
+ )?;
self.out.write_str("\n")?;
self.footnote_number = None;
}
@@ -347,20 +357,11 @@ impl<'s, I: Iterator- >, W: std::fmt::Write> Writer<'s, I, W> {
if matches!(self.list_tightness.last(), Some(true)) {
continue;
}
- if let Some(num) = self.footnote_number {
- if matches!(
- self.events.peek(),
- Some(Event::End(Container::Footnote { .. }))
- ) {
- write!(
- self.out,
- r##"↩︎︎"##,
- num
- )?;
- self.footnote_backlink_written = true;
- }
+ if self.footnote_number.is_none() {
+ self.out.write_str("")?;
+ } else {
+ self.close_para = true;
}
- self.out.write_str("")?;
}
Container::Heading { level, .. } => write!(self.out, "", level)?,
Container::TableCell { head: false, .. } => self.out.write_str("")?,
From 524d9595baf0b2a04ff0822c8fb3037d167fbb8a Mon Sep 17 00:00:00 2001
From: Noah Hellman
Date: Fri, 17 Mar 2023 19:05:56 +0100
Subject: [PATCH 2/8] html: rm FilteredEvents
no longer useful as no peeking is needed, use simple early exit instead
---
src/html.rs | 24 ++++++------------------
1 file changed, 6 insertions(+), 18 deletions(-)
diff --git a/src/html.rs b/src/html.rs
index c0f78649..51e21d55 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -49,24 +49,8 @@ enum Raw {
Other,
}
-struct FilteredEvents {
- events: I,
-}
-
-impl<'s, I: Iterator
- >> Iterator for FilteredEvents {
- type Item = Event<'s>;
-
- fn next(&mut self) -> Option {
- let mut ev = self.events.next();
- while matches!(ev, Some(Event::Blankline | Event::Escape)) {
- ev = self.events.next();
- }
- ev
- }
-}
-
struct Writer<'s, I: Iterator
- >, W> {
- events: std::iter::Peekable>,
+ events: I,
out: W,
raw: Raw,
img_alt_text: usize,
@@ -80,7 +64,7 @@ struct Writer<'s, I: Iterator
- >, W> {
impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
fn new(events: I, out: W) -> Self {
Self {
- events: FilteredEvents { events }.peekable(),
+ events,
out,
raw: Raw::None,
img_alt_text: 0,
@@ -94,6 +78,10 @@ impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
fn write(&mut self) -> std::fmt::Result {
while let Some(e) = self.events.next() {
+ if matches!(&e, Event::Blankline | Event::Escape) {
+ continue;
+ }
+
let close_para = self.close_para;
if close_para {
self.close_para = false;
From 2a65ce2b983694dc8914913296ce7670ab63ec63 Mon Sep 17 00:00:00 2001
From: Noah Hellman
Date: Fri, 17 Mar 2023 19:13:00 +0100
Subject: [PATCH 3/8] html: rm events/out from Writer
separate input/output from rendering state
---
src/html.rs | 320 +++++++++++++++++++++++++++-------------------------
1 file changed, 164 insertions(+), 156 deletions(-)
diff --git a/src/html.rs b/src/html.rs
index 51e21d55..f78173cb 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -39,7 +39,7 @@ impl Render for Renderer {
events: I,
out: W,
) -> std::fmt::Result {
- Writer::new(events, out).write()
+ Writer::default().write(events, out)
}
}
@@ -49,9 +49,7 @@ enum Raw {
Other,
}
-struct Writer<'s, I: Iterator
- >, W> {
- events: I,
- out: W,
+struct Writer {
raw: Raw,
img_alt_text: usize,
list_tightness: Vec,
@@ -61,11 +59,9 @@ struct Writer<'s, I: Iterator
- >, W> {
close_para: bool,
}
-impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
- fn new(events: I, out: W) -> Self {
+impl Default for Writer {
+ fn default() -> Self {
Self {
- events,
- out,
raw: Raw::None,
img_alt_text: 0,
list_tightness: Vec::new(),
@@ -75,9 +71,15 @@ impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
close_para: false,
}
}
+}
- fn write(&mut self) -> std::fmt::Result {
- while let Some(e) = self.events.next() {
+impl Writer {
+ fn write<'s>(
+ &mut self,
+ events: impl Iterator
- >,
+ mut out: impl std::fmt::Write,
+ ) -> std::fmt::Result {
+ for e in events {
if matches!(&e, Event::Blankline | Event::Escape) {
continue;
}
@@ -87,32 +89,30 @@ impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
self.close_para = false;
if !matches!(&e, Event::End(Container::Footnote { .. })) {
// no need to add href before para close
- self.out.write_str("")?;
+ out.write_str("")?;
}
}
match e {
Event::Start(c, attrs) => {
if c.is_block() && !self.first_line {
- self.out.write_char('\n')?;
+ out.write_char('\n')?;
}
if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
continue;
}
match &c {
- Container::Blockquote => self.out.write_str("
out.write_str(" {
self.list_tightness.push(*tight);
match kind {
- ListKind::Unordered | ListKind::Task => {
- self.out.write_str(" out.write_str(" {
- self.out.write_str(" 1 {
- write!(self.out, r#" start="{}""#, start)?;
+ write!(out, r#" start="{}""#, start)?;
}
if let Some(ty) = match numbering {
Decimal => None,
@@ -121,65 +121,64 @@ impl<'s, I: Iterator- >, W: std::fmt::Write> Writer<'s, I, W> {
RomanLower => Some('i'),
RomanUpper => Some('I'),
} {
- write!(self.out, r#" type="{}""#, ty)?;
+ write!(out, r#" type="{}""#, ty)?;
}
}
}
}
Container::ListItem | Container::TaskListItem { .. } => {
- self.out.write_str("
- self.out.write_str("
self.out.write_str("- out.write_str("
out.write_str("- {
assert!(self.footnote_number.is_none());
self.footnote_number = Some((*number).try_into().unwrap());
if !self.encountered_footnote {
self.encountered_footnote = true;
- self.out
- .write_str("\n
\n\n")?;
+ out.write_str("\n
\n\n")?;
}
- write!(self.out, "- ", number)?;
+ write!(out, "
- ", number)?;
continue;
}
- Container::Table => self.out.write_str("
self.out.write_str(" self.out.write_str(" self.out.write_str(" out.write_str("
out.write_str(" out.write_str(" out.write_str(" {
if matches!(self.list_tightness.last(), Some(true)) {
continue;
}
- self.out.write_str("
write!(self.out, " self.out.write_str(" self.out.write_str(" | self.out.write_str(" self.out.write_str("- self.out.write_str("
self.out.write_str(" write!(out, " out.write_str(" out.write_str(" | out.write_str(" out.write_str("- out.write_str("
out.write_str(" {
if matches!(ty, LinkType::Span(SpanLinkType::Unresolved)) {
- self.out.write_str(" {
self.img_alt_text += 1;
if self.img_alt_text == 1 {
- self.out.write_str(" self.out.write_str(" out.write_str(" {
self.raw = if format == &"html" {
Raw::Html
@@ -188,19 +187,19 @@ impl<'s, I: Iterator- >, W: std::fmt::Write> Writer<'s, I, W> {
};
continue;
}
- Container::Subscript => self.out.write_str(" self.out.write_str(" self.out.write_str(" self.out.write_str("
self.out.write_str(" self.out.write_str(" self.out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(">, W: std::fmt::Write> Writer<'s, I, W> {
| Container::Section { id } = &c
{
if !attrs.iter().any(|(a, _)| a == "id") {
- self.out.write_str(r#" id=""#)?;
- self.write_attr(id)?;
- self.out.write_char('"')?;
+ out.write_str(r#" id=""#)?;
+ write_attr(id, &mut out)?;
+ out.write_char('"')?;
}
}
@@ -229,7 +228,7 @@ impl<'s, I: Iterator- >, W: std::fmt::Write> Writer<'s, I, W> {
| Container::TaskListItem { .. }
)
{
- self.out.write_str(r#" class=""#)?;
+ out.write_str(r#" class=""#)?;
let mut first_written = false;
if let Some(cls) = match c {
Container::List {
@@ -243,7 +242,7 @@ impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
_ => None,
} {
first_written = true;
- self.out.write_str(cls)?;
+ out.write_str(cls)?;
}
for cls in attrs
.iter()
@@ -251,19 +250,20 @@ impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
.map(|(_, cls)| cls)
{
if first_written {
- self.out.write_char(' ')?;
+ out.write_char(' ')?;
}
first_written = true;
- cls.parts().try_for_each(|part| self.write_attr(part))?;
+ cls.parts()
+ .try_for_each(|part| write_attr(part, &mut out))?;
}
// div class goes after classes from attrs
if let Container::Div { class: Some(cls) } = c {
if first_written {
- self.out.write_char(' ')?;
+ out.write_char(' ')?;
}
- self.out.write_str(cls)?;
+ out.write_str(cls)?;
}
- self.out.write_char('"')?;
+ out.write_char('"')?;
}
match c {
@@ -276,102 +276,101 @@ impl<'s, I: Iterator
- >, W: std::fmt::Write> Writer<'s, I, W> {
Alignment::Center => "center",
Alignment::Right => "right",
};
- write!(self.out, r#" style="text-align: {};">"#, a)?;
+ write!(out, r#" style="text-align: {};">"#, a)?;
}
Container::CodeBlock { lang } => {
if let Some(l) = lang {
- self.out.write_str(r#">
"#)?;
+ out.write_str(r#">"#)?;
} else {
- self.out.write_str(">")?;
+ out.write_str(">")?;
}
}
Container::Image(..) => {
if self.img_alt_text == 1 {
- self.out.write_str(r#" alt=""#)?;
+ out.write_str(r#" alt=""#)?;
}
}
Container::Math { display } => {
- self.out
- .write_str(if display { r#">\["# } else { r#">\("# })?;
+ out.write_str(if display { r#">\["# } else { r#">\("# })?;
}
- _ => self.out.write_char('>')?,
+ _ => out.write_char('>')?,
}
}
Event::End(c) => {
if c.is_block_container() && !matches!(c, Container::Footnote { .. }) {
- self.out.write_char('\n')?;
+ out.write_char('\n')?;
}
if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
continue;
}
match c {
- Container::Blockquote => self.out.write_str("")?,
+ Container::Blockquote => out.write_str("")?,
Container::List {
kind: ListKind::Unordered | ListKind::Task,
..
} => {
self.list_tightness.pop();
- self.out.write_str("")?;
+ out.write_str("")?;
}
Container::List {
kind: ListKind::Ordered { .. },
..
- } => self.out.write_str("")?,
+ } => out.write_str("")?,
Container::ListItem | Container::TaskListItem { .. } => {
- self.out.write_str("")?;
+ out.write_str("")?;
}
- Container::DescriptionList => self.out.write_str("")?,
- Container::DescriptionDetails => self.out.write_str("")?,
+ Container::DescriptionList => out.write_str("")?,
+ Container::DescriptionDetails => out.write_str("")?,
Container::Footnote { number, .. } => {
if !close_para {
// create a new paragraph
- self.out.write_str("\n")?;
+ out.write_str("\n ")?;
}
write!(
- self.out,
+ out,
r##"↩︎︎ "##,
number,
)?;
- self.out.write_str("\n")?;
+ out.write_str("\n")?;
self.footnote_number = None;
}
- Container::Table => self.out.write_str(" | |
")?,
- Container::TableRow { .. } => self.out.write_str("
")?,
- Container::Section { .. } => self.out.write_str("")?,
- Container::Div { .. } => self.out.write_str("")?,
+ Container::Table => out.write_str("
")?,
+ Container::TableRow { .. } => out.write_str("")?,
+ Container::Section { .. } => out.write_str("
")?,
+ Container::Div { .. } => out.write_str("")?,
Container::Paragraph => {
if matches!(self.list_tightness.last(), Some(true)) {
continue;
}
if self.footnote_number.is_none() {
- self.out.write_str("")?;
+ out.write_str("")?;
} else {
self.close_para = true;
}
}
- Container::Heading { level, .. } => write!(self.out, "", level)?,
- Container::TableCell { head: false, .. } => self.out.write_str("")?,
- Container::TableCell { head: true, .. } => self.out.write_str("")?,
- Container::Caption => self.out.write_str("")?,
- Container::DescriptionTerm => self.out.write_str("")?,
- Container::CodeBlock { .. } => self.out.write_str("")?,
- Container::Span => self.out.write_str("")?,
- Container::Link(..) => self.out.write_str("")?,
+ Container::Heading { level, .. } => write!(out, "", level)?,
+ Container::TableCell { head: false, .. } => out.write_str("")?,
+ Container::TableCell { head: true, .. } => out.write_str("")?,
+ Container::Caption => out.write_str("")?,
+ Container::DescriptionTerm => out.write_str("")?,
+ Container::CodeBlock { .. } => out.write_str("")?,
+ Container::Span => out.write_str("")?,
+ Container::Link(..) => out.write_str("")?,
Container::Image(src, ..) => {
if self.img_alt_text == 1 {
if !src.is_empty() {
- self.out.write_str(r#"" src=""#)?;
- self.write_attr(&src)?;
+ out.write_str(r#"" src=""#)?;
+ write_attr(&src, &mut out)?;
}
- self.out.write_str(r#"">"#)?;
+ out.write_str(r#"">"#)?;
}
self.img_alt_text -= 1;
}
- Container::Verbatim => self.out.write_str("")?,
+ Container::Verbatim => out.write_str("")?,
Container::Math { display } => {
- self.out.write_str(if display {
+ out.write_str(if display {
r#"\]"#
} else {
r#"\)"#
@@ -380,88 +379,97 @@ impl<'s, I: Iterator- >, W: std::fmt::Write> Writer<'s, I, W> {
Container::RawBlock { .. } | Container::RawInline { .. } => {
self.raw = Raw::None;
}
- Container::Subscript => self.out.write_str("")?,
- Container::Superscript => self.out.write_str("")?,
- Container::Insert => self.out.write_str("")?,
- Container::Delete => self.out.write_str("")?,
- Container::Strong => self.out.write_str("")?,
- Container::Emphasis => self.out.write_str("")?,
- Container::Mark => self.out.write_str("")?,
+ Container::Subscript => out.write_str("")?,
+ Container::Superscript => out.write_str("")?,
+ Container::Insert => out.write_str("")?,
+ Container::Delete => out.write_str("")?,
+ Container::Strong => out.write_str("")?,
+ Container::Emphasis => out.write_str("")?,
+ Container::Mark => out.write_str("")?,
}
}
Event::Str(s) => match self.raw {
- Raw::None if self.img_alt_text > 0 => self.write_attr(&s)?,
- Raw::None => self.write_text(&s)?,
- Raw::Html => self.out.write_str(&s)?,
+ Raw::None if self.img_alt_text > 0 => write_attr(&s, &mut out)?,
+ Raw::None => write_text(&s, &mut out)?,
+ Raw::Html => out.write_str(&s)?,
Raw::Other => {}
},
Event::FootnoteReference(_tag, number) => {
if self.img_alt_text == 0 {
write!(
- self.out,
+ out,
r##"{}"##,
number, number, number
)?;
}
}
- Event::Symbol(sym) => write!(self.out, ":{}:", sym)?,
- Event::LeftSingleQuote => self.out.write_str("‘")?,
- Event::RightSingleQuote => self.out.write_str("’")?,
- Event::LeftDoubleQuote => self.out.write_str("“")?,
- Event::RightDoubleQuote => self.out.write_str("”")?,
- Event::Ellipsis => self.out.write_str("…")?,
- Event::EnDash => self.out.write_str("–")?,
- Event::EmDash => self.out.write_str("—")?,
- Event::NonBreakingSpace => self.out.write_str(" ")?,
- Event::Hardbreak => self.out.write_str("
\n")?,
- Event::Softbreak => self.out.write_char('\n')?,
+ Event::Symbol(sym) => write!(out, ":{}:", sym)?,
+ Event::LeftSingleQuote => out.write_str("‘")?,
+ Event::RightSingleQuote => out.write_str("’")?,
+ Event::LeftDoubleQuote => out.write_str("“")?,
+ Event::RightDoubleQuote => out.write_str("”")?,
+ Event::Ellipsis => out.write_str("…")?,
+ Event::EnDash => out.write_str("–")?,
+ Event::EmDash => out.write_str("—")?,
+ Event::NonBreakingSpace => out.write_str(" ")?,
+ Event::Hardbreak => out.write_str("
\n")?,
+ Event::Softbreak => out.write_char('\n')?,
Event::Escape | Event::Blankline => unreachable!("filtered out"),
Event::ThematicBreak(attrs) => {
- self.out.write_str("\n
")?;
+ out.write_str(">")?;
}
}
self.first_line = false;
}
if self.encountered_footnote {
- self.out.write_str("\n
\n")?;
+ out.write_str("\n
\n")?;
}
- self.out.write_char('\n')?;
+ out.write_char('\n')?;
Ok(())
}
+}
- fn write_escape(&mut self, mut s: &str, escape_quotes: bool) -> std::fmt::Result {
- let mut ent = "";
- while let Some(i) = s.find(|c| {
- match c {
- '<' => Some("<"),
- '>' => Some(">"),
- '&' => Some("&"),
- '"' if escape_quotes => Some("""),
- _ => None,
- }
- .map_or(false, |s| {
- ent = s;
- true
- })
- }) {
- self.out.write_str(&s[..i])?;
- self.out.write_str(ent)?;
- s = &s[i + 1..];
- }
- self.out.write_str(s)
- }
+fn write_text(s: &str, out: W) -> std::fmt::Result
+where
+ W: std::fmt::Write,
+{
+ write_escape(s, false, out)
+}
- fn write_text(&mut self, s: &str) -> std::fmt::Result {
- self.write_escape(s, false)
- }
+fn write_attr(s: &str, out: W) -> std::fmt::Result
+where
+ W: std::fmt::Write,
+{
+ write_escape(s, true, out)
+}
- fn write_attr(&mut self, s: &str) -> std::fmt::Result {
- self.write_escape(s, true)
+fn write_escape(mut s: &str, escape_quotes: bool, mut out: W) -> std::fmt::Result
+where
+ W: std::fmt::Write,
+{
+ let mut ent = "";
+ while let Some(i) = s.find(|c| {
+ match c {
+ '<' => Some("<"),
+ '>' => Some(">"),
+ '&' => Some("&"),
+ '"' if escape_quotes => Some("""),
+ _ => None,
+ }
+ .map_or(false, |s| {
+ ent = s;
+ true
+ })
+ }) {
+ out.write_str(&s[..i])?;
+ out.write_str(ent)?;
+ s = &s[i + 1..];
}
+ out.write_str(s)
}
From 3cd1d6ded3fbd79b02a0caae76b40e4ac83563db Mon Sep 17 00:00:00 2001
From: Noah Hellman
Date: Sun, 19 Mar 2023 18:35:41 +0100
Subject: [PATCH 4/8] html: extract Writer::render_{event, epilogue}
---
src/html.rs | 641 +++++++++++++++++++++++++++-------------------------
1 file changed, 328 insertions(+), 313 deletions(-)
diff --git a/src/html.rs b/src/html.rs
index f78173cb..0e29c457 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -74,363 +74,378 @@ impl Default for Writer {
}
impl Writer {
- fn write<'s>(
- &mut self,
- events: impl Iterator- >,
- mut out: impl std::fmt::Write,
- ) -> std::fmt::Result {
- for e in events {
- if matches!(&e, Event::Blankline | Event::Escape) {
- continue;
- }
+ fn write<'s, I, W>(&mut self, mut events: I, mut out: W) -> std::fmt::Result
+ where
+ I: Iterator
- >,
+ W: std::fmt::Write,
+ {
+ events.try_for_each(|e| self.render_event(&e, &mut out))?;
+ self.render_epilogue(&mut out)
+ }
- let close_para = self.close_para;
- if close_para {
- self.close_para = false;
- if !matches!(&e, Event::End(Container::Footnote { .. })) {
- // no need to add href before para close
- out.write_str("")?;
- }
+ fn render_event<'s, W>(&mut self, e: &Event<'s>, mut out: W) -> std::fmt::Result
+ where
+ W: std::fmt::Write,
+ {
+ if matches!(&e, Event::Blankline | Event::Escape) {
+ return Ok(());
+ }
+
+ let close_para = self.close_para;
+ if close_para {
+ self.close_para = false;
+ if !matches!(&e, Event::End(Container::Footnote { .. })) {
+ // no need to add href before para close
+ out.write_str("")?;
}
+ }
- match e {
- Event::Start(c, attrs) => {
- if c.is_block() && !self.first_line {
- out.write_char('\n')?;
- }
- if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
- continue;
- }
- match &c {
- Container::Blockquote => out.write_str("
{
- self.list_tightness.push(*tight);
- match kind {
- ListKind::Unordered | ListKind::Task => out.write_str(" {
- out.write_str(" 1 {
- write!(out, r#" start="{}""#, start)?;
- }
- if let Some(ty) = match numbering {
- Decimal => None,
- AlphaLower => Some('a'),
- AlphaUpper => Some('A'),
- RomanLower => Some('i'),
- RomanUpper => Some('I'),
- } {
- write!(out, r#" type="{}""#, ty)?;
- }
+ match e {
+ Event::Start(c, attrs) => {
+ if c.is_block() && !self.first_line {
+ out.write_char('\n')?;
+ }
+ if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
+ return Ok(());
+ }
+ match &c {
+ Container::Blockquote => out.write_str(" {
+ self.list_tightness.push(*tight);
+ match kind {
+ ListKind::Unordered | ListKind::Task => out.write_str(" {
+ out.write_str(" 1 {
+ write!(out, r#" start="{}""#, start)?;
+ }
+ if let Some(ty) = match numbering {
+ Decimal => None,
+ AlphaLower => Some('a'),
+ AlphaUpper => Some('A'),
+ RomanLower => Some('i'),
+ RomanUpper => Some('I'),
+ } {
+ write!(out, r#" type="{}""#, ty)?;
}
}
}
- Container::ListItem | Container::TaskListItem { .. } => {
- out.write_str("- out.write_str("
out.write_str("- {
- assert!(self.footnote_number.is_none());
- self.footnote_number = Some((*number).try_into().unwrap());
- if !self.encountered_footnote {
- self.encountered_footnote = true;
- out.write_str("\n
\n\n")?;
- }
- write!(out, "- ", number)?;
- continue;
- }
- Container::Table => out.write_str("
out.write_str(" out.write_str(" out.write_str(" {
- if matches!(self.list_tightness.last(), Some(true)) {
- continue;
- }
- out.write_str("
{
+ out.write_str("
- out.write_str("
out.write_str("- {
+ assert!(self.footnote_number.is_none());
+ self.footnote_number = Some((*number).try_into().unwrap());
+ if !self.encountered_footnote {
+ self.encountered_footnote = true;
+ out.write_str("\n
\n\n")?;
}
- Container::Heading { level, .. } => write!(out, " out.write_str(" out.write_str(" | out.write_str(" out.write_str("- out.write_str("
out.write_str(" {
- if matches!(ty, LinkType::Span(SpanLinkType::Unresolved)) {
- out.write_str("", number)?;
+ return Ok(());
+ }
+ Container::Table => out.write_str(" out.write_str(" out.write_str(" out.write_str(" {
+ if matches!(self.list_tightness.last(), Some(true)) {
+ return Ok(());
}
- Container::Image(..) => {
- self.img_alt_text += 1;
- if self.img_alt_text == 1 {
- out.write_str(" write!(out, " out.write_str(" out.write_str(" | out.write_str(" out.write_str("- out.write_str("
out.write_str(" {
+ if matches!(ty, LinkType::Span(SpanLinkType::Unresolved)) {
+ out.write_str(" out.write_str(" {
- self.raw = if format == &"html" {
- Raw::Html
- } else {
- Raw::Other
- };
- continue;
+ }
+ Container::Image(..) => {
+ self.img_alt_text += 1;
+ if self.img_alt_text == 1 {
+ out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" {
+ self.raw = if format == &"html" {
+ Raw::Html
+ } else {
+ Raw::Other
+ };
+ return Ok(());
}
+ Container::Subscript => out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" Some("task-list"),
- Container::TaskListItem { checked: false } => Some("unchecked"),
- Container::TaskListItem { checked: true } => Some("checked"),
- Container::Math { display: false } => Some("math inline"),
- Container::Math { display: true } => Some("math display"),
- _ => None,
- } {
- first_written = true;
- out.write_str(cls)?;
- }
- for cls in attrs
- .iter()
- .filter(|(a, _)| a == &"class")
- .map(|(_, cls)| cls)
- {
- if first_written {
- out.write_char(' ')?;
}
- first_written = true;
- cls.parts()
- .try_for_each(|part| write_attr(part, &mut out))?;
+ | Container::TaskListItem { .. }
+ )
+ {
+ out.write_str(r#" class=""#)?;
+ let mut first_written = false;
+ if let Some(cls) = match c {
+ Container::List {
+ kind: ListKind::Task,
+ ..
+ } => Some("task-list"),
+ Container::TaskListItem { checked: false } => Some("unchecked"),
+ Container::TaskListItem { checked: true } => Some("checked"),
+ Container::Math { display: false } => Some("math inline"),
+ Container::Math { display: true } => Some("math display"),
+ _ => None,
+ } {
+ first_written = true;
+ out.write_str(cls)?;
+ }
+ for cls in attrs
+ .iter()
+ .filter(|(a, _)| a == &"class")
+ .map(|(_, cls)| cls)
+ {
+ if first_written {
+ out.write_char(' ')?;
}
- // div class goes after classes from attrs
- if let Container::Div { class: Some(cls) } = c {
- if first_written {
- out.write_char(' ')?;
- }
- out.write_str(cls)?;
+ first_written = true;
+ cls.parts()
+ .try_for_each(|part| write_attr(part, &mut out))?;
+ }
+ // div class goes after classes from attrs
+ if let Container::Div { class: Some(cls) } = c {
+ if first_written {
+ out.write_char(' ')?;
}
- out.write_char('"')?;
+ out.write_str(cls)?;
}
+ out.write_char('"')?;
+ }
- match c {
- Container::TableCell { alignment, .. }
- if !matches!(alignment, Alignment::Unspecified) =>
- {
- let a = match alignment {
- Alignment::Unspecified => unreachable!(),
- Alignment::Left => "left",
- Alignment::Center => "center",
- Alignment::Right => "right",
- };
- write!(out, r#" style="text-align: {};">"#, a)?;
- }
- Container::CodeBlock { lang } => {
- if let Some(l) = lang {
- out.write_str(r#">"#)?;
- } else {
- out.write_str(">")?;
- }
- }
- Container::Image(..) => {
- if self.img_alt_text == 1 {
- out.write_str(r#" alt=""#)?;
- }
+ match c {
+ Container::TableCell { alignment, .. }
+ if !matches!(alignment, Alignment::Unspecified) =>
+ {
+ let a = match alignment {
+ Alignment::Unspecified => unreachable!(),
+ Alignment::Left => "left",
+ Alignment::Center => "center",
+ Alignment::Right => "right",
+ };
+ write!(out, r#" style="text-align: {};">"#, a)?;
+ }
+ Container::CodeBlock { lang } => {
+ if let Some(l) = lang {
+ out.write_str(r#">"#)?;
+ } else {
+ out.write_str(">")?;
}
- Container::Math { display } => {
- out.write_str(if display { r#">\["# } else { r#">\("# })?;
+ }
+ Container::Image(..) => {
+ if self.img_alt_text == 1 {
+ out.write_str(r#" alt=""#)?;
}
- _ => out.write_char('>')?,
}
+ Container::Math { display } => {
+ out.write_str(if *display { r#">\["# } else { r#">\("# })?;
+ }
+ _ => out.write_char('>')?,
+ }
+ }
+ Event::End(c) => {
+ if c.is_block_container() && !matches!(c, Container::Footnote { .. }) {
+ out.write_char('\n')?;
}
- Event::End(c) => {
- if c.is_block_container() && !matches!(c, Container::Footnote { .. }) {
- out.write_char('\n')?;
+ if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
+ return Ok(());
+ }
+ match c {
+ Container::Blockquote => out.write_str("")?,
+ Container::List {
+ kind: ListKind::Unordered | ListKind::Task,
+ ..
+ } => {
+ self.list_tightness.pop();
+ out.write_str("")?;
}
- if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
- continue;
+ Container::List {
+ kind: ListKind::Ordered { .. },
+ ..
+ } => out.write_str("")?,
+ Container::ListItem | Container::TaskListItem { .. } => {
+ out.write_str("")?;
}
- match c {
- Container::Blockquote => out.write_str("")?,
- Container::List {
- kind: ListKind::Unordered | ListKind::Task,
- ..
- } => {
- self.list_tightness.pop();
- out.write_str("")?;
+ Container::DescriptionList => out.write_str("")?,
+ Container::DescriptionDetails => out.write_str("")?,
+ Container::Footnote { number, .. } => {
+ if !close_para {
+ // create a new paragraph
+ out.write_str("\n")?;
}
- Container::List {
- kind: ListKind::Ordered { .. },
- ..
- } => out.write_str("")?,
- Container::ListItem | Container::TaskListItem { .. } => {
- out.write_str("")?;
- }
- Container::DescriptionList => out.write_str("")?,
- Container::DescriptionDetails => out.write_str("")?,
- Container::Footnote { number, .. } => {
- if !close_para {
- // create a new paragraph
- out.write_str("\n ")?;
- }
- write!(
- out,
- r##"↩︎︎ "##,
- number,
- )?;
- out.write_str("\n")?;
- self.footnote_number = None;
+ write!(
+ out,
+ r##"↩︎︎"##,
+ number,
+ )?;
+ out.write_str("\n")?;
+ self.footnote_number = None;
+ }
+ Container::Table => out.write_str(" | ")?,
+ Container::TableRow { .. } => out.write_str(" |
")?,
+ Container::Section { .. } => out.write_str("")?,
+ Container::Div { .. } => out.write_str("")?,
+ Container::Paragraph => {
+ if matches!(self.list_tightness.last(), Some(true)) {
+ return Ok(());
}
- Container::Table => out.write_str("
")?,
- Container::TableRow { .. } => out.write_str("")?,
- Container::Section { .. } => out.write_str("
")?,
- Container::Div { .. } => out.write_str("")?,
- Container::Paragraph => {
- if matches!(self.list_tightness.last(), Some(true)) {
- continue;
- }
- if self.footnote_number.is_none() {
- out.write_str("")?;
- } else {
- self.close_para = true;
- }
+ if self.footnote_number.is_none() {
+ out.write_str("")?;
+ } else {
+ self.close_para = true;
}
- Container::Heading { level, .. } => write!(out, "", level)?,
- Container::TableCell { head: false, .. } => out.write_str("")?,
- Container::TableCell { head: true, .. } => out.write_str("")?,
- Container::Caption => out.write_str("")?,
- Container::DescriptionTerm => out.write_str("")?,
- Container::CodeBlock { .. } => out.write_str("")?,
- Container::Span => out.write_str("")?,
- Container::Link(..) => out.write_str("")?,
- Container::Image(src, ..) => {
- if self.img_alt_text == 1 {
- if !src.is_empty() {
- out.write_str(r#"" src=""#)?;
- write_attr(&src, &mut out)?;
- }
- out.write_str(r#"">"#)?;
+ }
+ Container::Heading { level, .. } => write!(out, "", level)?,
+ Container::TableCell { head: false, .. } => out.write_str("")?,
+ Container::TableCell { head: true, .. } => out.write_str("")?,
+ Container::Caption => out.write_str("")?,
+ Container::DescriptionTerm => out.write_str("")?,
+ Container::CodeBlock { .. } => out.write_str("")?,
+ Container::Span => out.write_str("")?,
+ Container::Link(..) => out.write_str("")?,
+ Container::Image(src, ..) => {
+ if self.img_alt_text == 1 {
+ if !src.is_empty() {
+ out.write_str(r#"" src=""#)?;
+ write_attr(src, &mut out)?;
}
- self.img_alt_text -= 1;
+ out.write_str(r#"">"#)?;
}
- Container::Verbatim => out.write_str("")?,
- Container::Math { display } => {
- out.write_str(if display {
- r#"\]"#
- } else {
- r#"\)"#
- })?;
- }
- Container::RawBlock { .. } | Container::RawInline { .. } => {
- self.raw = Raw::None;
- }
- Container::Subscript => out.write_str("")?,
- Container::Superscript => out.write_str("")?,
- Container::Insert => out.write_str("")?,
- Container::Delete => out.write_str("")?,
- Container::Strong => out.write_str("")?,
- Container::Emphasis => out.write_str("")?,
- Container::Mark => out.write_str("")?,
+ self.img_alt_text -= 1;
}
- }
- Event::Str(s) => match self.raw {
- Raw::None if self.img_alt_text > 0 => write_attr(&s, &mut out)?,
- Raw::None => write_text(&s, &mut out)?,
- Raw::Html => out.write_str(&s)?,
- Raw::Other => {}
- },
- Event::FootnoteReference(_tag, number) => {
- if self.img_alt_text == 0 {
- write!(
- out,
- r##"{}"##,
- number, number, number
- )?;
+ Container::Verbatim => out.write_str("")?,
+ Container::Math { display } => {
+ out.write_str(if *display {
+ r#"\]"#
+ } else {
+ r#"\)"#
+ })?;
}
- }
- Event::Symbol(sym) => write!(out, ":{}:", sym)?,
- Event::LeftSingleQuote => out.write_str("‘")?,
- Event::RightSingleQuote => out.write_str("’")?,
- Event::LeftDoubleQuote => out.write_str("“")?,
- Event::RightDoubleQuote => out.write_str("”")?,
- Event::Ellipsis => out.write_str("…")?,
- Event::EnDash => out.write_str("–")?,
- Event::EmDash => out.write_str("—")?,
- Event::NonBreakingSpace => out.write_str(" ")?,
- Event::Hardbreak => out.write_str("
\n")?,
- Event::Softbreak => out.write_char('\n')?,
- Event::Escape | Event::Blankline => unreachable!("filtered out"),
- Event::ThematicBreak(attrs) => {
- out.write_str("\n
{
+ self.raw = Raw::None;
}
- out.write_str(">")?;
+ Container::Subscript => out.write_str("")?,
+ Container::Superscript => out.write_str("")?,
+ Container::Insert => out.write_str("")?,
+ Container::Delete => out.write_str("")?,
+ Container::Strong => out.write_str("")?,
+ Container::Emphasis => out.write_str("")?,
+ Container::Mark => out.write_str("")?,
+ }
+ }
+ Event::Str(s) => match self.raw {
+ Raw::None if self.img_alt_text > 0 => write_attr(s, &mut out)?,
+ Raw::None => write_text(s, &mut out)?,
+ Raw::Html => out.write_str(s)?,
+ Raw::Other => {}
+ },
+ Event::FootnoteReference(_tag, number) => {
+ if self.img_alt_text == 0 {
+ write!(
+ out,
+ r##"{}"##,
+ number, number, number
+ )?;
+ }
+ }
+ Event::Symbol(sym) => write!(out, ":{}:", sym)?,
+ Event::LeftSingleQuote => out.write_str("‘")?,
+ Event::RightSingleQuote => out.write_str("’")?,
+ Event::LeftDoubleQuote => out.write_str("“")?,
+ Event::RightDoubleQuote => out.write_str("”")?,
+ Event::Ellipsis => out.write_str("…")?,
+ Event::EnDash => out.write_str("–")?,
+ Event::EmDash => out.write_str("—")?,
+ Event::NonBreakingSpace => out.write_str(" ")?,
+ Event::Hardbreak => out.write_str("
\n")?,
+ Event::Softbreak => out.write_char('\n')?,
+ Event::Escape | Event::Blankline => unreachable!("filtered out"),
+ Event::ThematicBreak(attrs) => {
+ out.write_str("\n
")?;
}
- self.first_line = false;
}
+ self.first_line = false;
+
+ Ok(())
+ }
+
+ fn render_epilogue(&mut self, mut out: W) -> std::fmt::Result
+ where
+ W: std::fmt::Write,
+ {
if self.encountered_footnote {
out.write_str("\n
\n")?;
}
out.write_char('\n')?;
+
Ok(())
}
}
From ccdaf2b9e7cb502eec396388326e118152a45934 Mon Sep 17 00:00:00 2001
From: Noah Hellman
Date: Sun, 19 Mar 2023 13:54:44 +0100
Subject: [PATCH 5/8] mv push/write examples from html to Render trait
They apply more to the Render trait now than the implementation in the
html module
---
src/html.rs | 22 ----------------------
src/lib.rs | 23 +++++++++++++++++++++++
2 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/src/html.rs b/src/html.rs
index 0e29c457..0014ef3f 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -1,26 +1,4 @@
//! An HTML renderer that takes an iterator of [`Event`]s and emits HTML.
-//!
-//! The HTML can be written to either a [`std::fmt::Write`] or a [`std::io::Write`] object.
-//!
-//! # Examples
-//!
-//! Push to a [`String`] (implements [`std::fmt::Write`]):
-//!
-//! ```
-//! # use jotdown::Render;
-//! # let events = std::iter::empty();
-//! let mut html = String::new();
-//! jotdown::html::Renderer.push(events, &mut html);
-//! ```
-//!
-//! Write to standard output with buffering ([`std::io::Stdout`] implements [`std::io::Write`]):
-//!
-//! ```
-//! # use jotdown::Render;
-//! # let events = std::iter::empty();
-//! let mut out = std::io::BufWriter::new(std::io::stdout());
-//! jotdown::html::Renderer.write(events, &mut out).unwrap();
-//! ```
use crate::Alignment;
use crate::Container;
diff --git a/src/lib.rs b/src/lib.rs
index 2f0d2be8..eb41f121 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -67,6 +67,29 @@ pub use attr::{AttributeValue, AttributeValueParts, Attributes};
type CowStr<'s> = std::borrow::Cow<'s, str>;
+/// A trait for rendering [`Event`]s to an output format.
+///
+/// The output can be written to either a [`std::fmt::Write`] or a [`std::io::Write`] object.
+///
+/// # Examples
+///
+/// Push to a [`String`] (implements [`std::fmt::Write`]):
+///
+/// ```
+/// # use jotdown::Render;
+/// # let events = std::iter::empty();
+/// let mut output = String::new();
+/// jotdown::html::Renderer.push(events, &mut output);
+/// ```
+///
+/// Write to standard output with buffering ([`std::io::Stdout`] implements [`std::io::Write`]):
+///
+/// ```
+/// # use jotdown::Render;
+/// # let events = std::iter::empty();
+/// let mut out = std::io::BufWriter::new(std::io::stdout());
+/// jotdown::html::Renderer.write(events, &mut out).unwrap();
+/// ```
pub trait Render {
/// Push [`Event`]s to a unicode-accepting buffer or stream.
fn push<'s, I: Iterator- >, W: fmt::Write>(
From f940d5c23c20e60983cd9f6ac0e79c165248affa Mon Sep 17 00:00:00 2001
From: Noah Hellman
Date: Sun, 19 Mar 2023 18:44:58 +0100
Subject: [PATCH 6/8] lib: add Render::render_{event, prologue, epilogue}
derive push/write automatically from these
---
bench/criterion/main.rs | 6 ++-
bench/iai/main.rs | 2 +-
examples/jotdown_wasm/src/lib.rs | 4 +-
src/html.rs | 27 ++-----------
src/lib.rs | 68 +++++++++++++++++++++++++-------
src/main.rs | 6 +--
tests/afl/src/lib.rs | 4 +-
tests/lib.rs | 4 +-
8 files changed, 73 insertions(+), 48 deletions(-)
diff --git a/bench/criterion/main.rs b/bench/criterion/main.rs
index 5ff477f0..28c2a4fa 100644
--- a/bench/criterion/main.rs
+++ b/bench/criterion/main.rs
@@ -51,7 +51,9 @@ fn gen_html(c: &mut criterion::Criterion) {
|| jotdown::Parser::new(input).collect::>(),
|p| {
let mut s = String::new();
- jotdown::html::Renderer.push(p.into_iter(), &mut s).unwrap();
+ jotdown::html::Renderer::default()
+ .push(p.into_iter(), &mut s)
+ .unwrap();
s
},
criterion::BatchSize::SmallInput,
@@ -72,7 +74,7 @@ fn gen_full(c: &mut criterion::Criterion) {
|b, &input| {
b.iter_with_large_drop(|| {
let mut s = String::new();
- jotdown::html::Renderer
+ jotdown::html::Renderer::default()
.push(jotdown::Parser::new(input), &mut s)
.unwrap();
s
diff --git a/bench/iai/main.rs b/bench/iai/main.rs
index e606d5f3..d948bb68 100644
--- a/bench/iai/main.rs
+++ b/bench/iai/main.rs
@@ -12,7 +12,7 @@ fn block_inline() -> Option> {
fn full() -> String {
let mut s = String::new();
- jotdown::html::Renderer
+ jotdown::html::Renderer::default()
.push(jotdown::Parser::new(bench_input::ALL), &mut s)
.unwrap();
s
diff --git a/examples/jotdown_wasm/src/lib.rs b/examples/jotdown_wasm/src/lib.rs
index 3ab7fb01..5250cf1c 100644
--- a/examples/jotdown_wasm/src/lib.rs
+++ b/examples/jotdown_wasm/src/lib.rs
@@ -7,6 +7,8 @@ use jotdown::Render;
pub fn jotdown_render(djot: &str) -> String {
let events = jotdown::Parser::new(djot);
let mut html = String::new();
- jotdown::html::Renderer.push(events, &mut html).unwrap();
+ jotdown::html::Renderer::default()
+ .push(events, &mut html)
+ .unwrap();
html
}
diff --git a/src/html.rs b/src/html.rs
index 0014ef3f..bd105c88 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -9,25 +9,13 @@ use crate::OrderedListNumbering::*;
use crate::Render;
use crate::SpanLinkType;
-pub struct Renderer;
-
-impl Render for Renderer {
- fn push<'s, I: Iterator
- >, W: std::fmt::Write>(
- &self,
- events: I,
- out: W,
- ) -> std::fmt::Result {
- Writer::default().write(events, out)
- }
-}
-
enum Raw {
None,
Html,
Other,
}
-struct Writer {
+pub struct Renderer {
raw: Raw,
img_alt_text: usize,
list_tightness: Vec,
@@ -37,7 +25,7 @@ struct Writer {
close_para: bool,
}
-impl Default for Writer {
+impl Default for Renderer {
fn default() -> Self {
Self {
raw: Raw::None,
@@ -51,16 +39,7 @@ impl Default for Writer {
}
}
-impl Writer {
- fn write<'s, I, W>(&mut self, mut events: I, mut out: W) -> std::fmt::Result
- where
- I: Iterator
- >,
- W: std::fmt::Write,
- {
- events.try_for_each(|e| self.render_event(&e, &mut out))?;
- self.render_epilogue(&mut out)
- }
-
+impl Render for Renderer {
fn render_event<'s, W>(&mut self, e: &Event<'s>, mut out: W) -> std::fmt::Result
where
W: std::fmt::Write,
diff --git a/src/lib.rs b/src/lib.rs
index eb41f121..d8aa6f57 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,7 +20,7 @@
//! let djot_input = "hello *world*!";
//! let events = jotdown::Parser::new(djot_input);
//! let mut html = String::new();
-//! jotdown::html::Renderer.push(events, &mut html);
+//! jotdown::html::Renderer::default().push(events, &mut html);
//! assert_eq!(html, "
hello world!
\n");
//! # }
//! ```
@@ -41,7 +41,7 @@
//! e => e,
//! });
//! let mut html = String::new();
-//! jotdown::html::Renderer.push(events, &mut html);
+//! jotdown::html::Renderer::default().push(events, &mut html);
//! assert_eq!(html, "a link
\n");
//! # }
//! ```
@@ -71,6 +71,11 @@ type CowStr<'s> = std::borrow::Cow<'s, str>;
///
/// The output can be written to either a [`std::fmt::Write`] or a [`std::io::Write`] object.
///
+/// An implementor needs to at least implement the [`Render::render_event`] function that renders a
+/// single event to the output. If anything needs to be rendered at the beginning or end of the
+/// output, the [`Render::render_prologue`] and [`Render::render_epilogue`] can be implemented as
+/// well.
+///
/// # Examples
///
/// Push to a [`String`] (implements [`std::fmt::Write`]):
@@ -79,7 +84,8 @@ type CowStr<'s> = std::borrow::Cow<'s, str>;
/// # use jotdown::Render;
/// # let events = std::iter::empty();
/// let mut output = String::new();
-/// jotdown::html::Renderer.push(events, &mut output);
+/// let mut renderer = jotdown::html::Renderer::default();
+/// renderer.push(events, &mut output);
/// ```
///
/// Write to standard output with buffering ([`std::io::Stdout`] implements [`std::io::Write`]):
@@ -88,25 +94,57 @@ type CowStr<'s> = std::borrow::Cow<'s, str>;
/// # use jotdown::Render;
/// # let events = std::iter::empty();
/// let mut out = std::io::BufWriter::new(std::io::stdout());
-/// jotdown::html::Renderer.write(events, &mut out).unwrap();
+/// let mut renderer = jotdown::html::Renderer::default();
+/// renderer.write(events, &mut out).unwrap();
/// ```
pub trait Render {
- /// Push [`Event`]s to a unicode-accepting buffer or stream.
- fn push<'s, I: Iterator- >, W: fmt::Write>(
- &self,
- events: I,
- out: W,
- ) -> fmt::Result;
+ /// Render a single event.
+ fn render_event<'s, W>(&mut self, e: &Event<'s>, out: W) -> std::fmt::Result
+ where
+ W: std::fmt::Write;
+
+ /// Render something before any events have been provided.
+ ///
+ /// This does nothing by default, but an implementation may choose to prepend data at the
+ /// beginning of the output if needed.
+ fn render_prologue(&mut self, _out: W) -> std::fmt::Result
+ where
+ W: std::fmt::Write,
+ {
+ Ok(())
+ }
+
+ /// Render something after all events have been provided.
+ ///
+ /// This does nothing by default, but an implementation may choose to append extra data at the
+ /// end of the output if needed.
+ fn render_epilogue(&mut self, _out: W) -> std::fmt::Result
+ where
+ W: std::fmt::Write,
+ {
+ Ok(())
+ }
+
+ /// Push owned [`Event`]s to a unicode-accepting buffer or stream.
+ fn push<'s, I, W>(&mut self, mut events: I, mut out: W) -> fmt::Result
+ where
+ I: Iterator
- >,
+ W: fmt::Write,
+ {
+ self.render_prologue(&mut out)?;
+ events.try_for_each(|e| self.render_event(&e, &mut out))?;
+ self.render_epilogue(&mut out)
+ }
/// Write [`Event`]s to a byte sink, encoded as UTF-8.
///
/// NOTE: This performs many small writes, so IO writes should be buffered with e.g.
/// [`std::io::BufWriter`].
- fn write<'s, I: Iterator
- >, W: io::Write>(
- &self,
- events: I,
- out: W,
- ) -> io::Result<()> {
+ fn write<'s, I, W>(&mut self, events: I, out: W) -> io::Result<()>
+ where
+ I: Iterator
- >,
+ W: io::Write,
+ {
struct Adapter {
inner: T,
error: io::Result<()>,
diff --git a/src/main.rs b/src/main.rs
index b9ea08c3..e73c081c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -68,11 +68,11 @@ fn run() -> Result<(), std::io::Error> {
};
let parser = jotdown::Parser::new(&content);
- let html = jotdown::html::Renderer;
+ let mut renderer = jotdown::html::Renderer::default();
match app.output {
- Some(path) => html.write(parser, File::create(path)?)?,
- None => html.write(parser, BufWriter::new(std::io::stdout()))?,
+ Some(path) => renderer.write(parser, File::create(path)?)?,
+ None => renderer.write(parser, BufWriter::new(std::io::stdout()))?,
}
Ok(())
diff --git a/tests/afl/src/lib.rs b/tests/afl/src/lib.rs
index 530a6aec..adbca14f 100644
--- a/tests/afl/src/lib.rs
+++ b/tests/afl/src/lib.rs
@@ -19,7 +19,9 @@ pub fn html(data: &[u8]) {
if !s.contains("=html") {
let p = jotdown::Parser::new(s);
let mut html = "\n".to_string();
- jotdown::html::Renderer.push(p, &mut html).unwrap();
+ jotdown::html::Renderer::default()
+ .push(p, &mut html)
+ .unwrap();
validate_html(&html);
}
}
diff --git a/tests/lib.rs b/tests/lib.rs
index 984b6102..4fd36afd 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -14,7 +14,9 @@ macro_rules! suite_test {
let expected = $expected;
let p = jotdown::Parser::new(src);
let mut actual = String::new();
- jotdown::html::Renderer.push(p, &mut actual).unwrap();
+ jotdown::html::Renderer::default()
+ .push(p, &mut actual)
+ .unwrap();
assert_eq!(
actual.trim(),
expected.trim(),
From f186ca96fd0a9b5c13b9510deefe2ee2e0bc0d23 Mon Sep 17 00:00:00 2001
From: Noah Hellman
Date: Fri, 17 Mar 2023 19:25:24 +0100
Subject: [PATCH 7/8] lib: add Render::{push, write}_borrowed
allow rendering iterators with borrowed events
resolves #24
---
src/lib.rs | 97 ++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 73 insertions(+), 24 deletions(-)
diff --git a/src/lib.rs b/src/lib.rs
index d8aa6f57..21999ec9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -71,6 +71,9 @@ type CowStr<'s> = std::borrow::Cow<'s, str>;
///
/// The output can be written to either a [`std::fmt::Write`] or a [`std::io::Write`] object.
///
+/// If ownership of the [`Event`]s cannot be given to the renderer, use [`Render::push_borrowed`]
+/// or [`Render::write_borrowed`].
+///
/// An implementor needs to at least implement the [`Render::render_event`] function that renders a
/// single event to the output. If anything needs to be rendered at the beginning or end of the
/// output, the [`Render::render_prologue`] and [`Render::render_epilogue`] can be implemented as
@@ -136,7 +139,7 @@ pub trait Render {
self.render_epilogue(&mut out)
}
- /// Write [`Event`]s to a byte sink, encoded as UTF-8.
+ /// Write owned [`Event`]s to a byte sink, encoded as UTF-8.
///
/// NOTE: This performs many small writes, so IO writes should be buffered with e.g.
/// [`std::io::BufWriter`].
@@ -145,35 +148,81 @@ pub trait Render {
I: Iterator
- >,
W: io::Write,
{
- struct Adapter {
- inner: T,
- error: io::Result<()>,
- }
+ let mut out = WriteAdapter {
+ inner: out,
+ error: Ok(()),
+ };
- impl fmt::Write for Adapter {
- fn write_str(&mut self, s: &str) -> fmt::Result {
- match self.inner.write_all(s.as_bytes()) {
- Ok(()) => Ok(()),
- Err(e) => {
- self.error = Err(e);
- Err(fmt::Error)
- }
- }
- }
- }
+ self.push(events, &mut out).map_err(|_| match out.error {
+ Err(e) => e,
+ _ => io::Error::new(io::ErrorKind::Other, "formatter error"),
+ })
+ }
- let mut out = Adapter {
+ /// Push borrowed [`Event`]s to a unicode-accepting buffer or stream.
+ ///
+ /// # Examples
+ ///
+ /// Render a borrowed slice of [`Event`]s.
+ /// ```
+ /// # use jotdown::Render;
+ /// # let events: &[jotdown::Event] = &[];
+ /// let mut output = String::new();
+ /// let mut renderer = jotdown::html::Renderer::default();
+ /// renderer.push_borrowed(events.iter(), &mut output);
+ /// ```
+ fn push_borrowed<'s, E, I, W>(&mut self, mut events: I, mut out: W) -> fmt::Result
+ where
+ E: AsRef>,
+ I: Iterator
- ,
+ W: fmt::Write,
+ {
+ self.render_prologue(&mut out)?;
+ events.try_for_each(|e| self.render_event(e.as_ref(), &mut out))?;
+ self.render_epilogue(&mut out)
+ }
+
+ /// Write borrowed [`Event`]s to a byte sink, encoded as UTF-8.
+ ///
+ /// NOTE: This performs many small writes, so IO writes should be buffered with e.g.
+ /// [`std::io::BufWriter`].
+ fn write_borrowed<'s, E, I, W>(&mut self, events: I, out: W) -> io::Result<()>
+ where
+ E: AsRef>,
+ I: Iterator
- ,
+ W: io::Write,
+ {
+ let mut out = WriteAdapter {
inner: out,
error: Ok(()),
};
- match self.push(events, &mut out) {
- Ok(()) => Ok(()),
- Err(_) => match out.error {
- Err(_) => out.error,
- _ => Err(io::Error::new(io::ErrorKind::Other, "formatter error")),
- },
- }
+ self.push_borrowed(events, &mut out)
+ .map_err(|_| match out.error {
+ Err(e) => e,
+ _ => io::Error::new(io::ErrorKind::Other, "formatter error"),
+ })
+ }
+}
+
+struct WriteAdapter {
+ inner: T,
+ error: io::Result<()>,
+}
+
+impl fmt::Write for WriteAdapter {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ self.inner.write_all(s.as_bytes()).map_err(|e| {
+ self.error = Err(e);
+ fmt::Error
+ })
+ }
+}
+
+// XXX why is this not a blanket implementation?
+impl<'s> AsRef> for &Event<'s> {
+ fn as_ref(&self) -> &Event<'s> {
+ self
}
}
From d28542079d852f5f487bbfa01cec497123ab2030 Mon Sep 17 00:00:00 2001
From: Noah Hellman
Date: Tue, 21 Mar 2023 22:31:49 +0100
Subject: [PATCH 8/8] bench-crit: add html_borrow, html_clone
allow comparing between rendering owned, borrowed or cloned events
---
bench/criterion/main.rs | 56 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
diff --git a/bench/criterion/main.rs b/bench/criterion/main.rs
index 28c2a4fa..42835a53 100644
--- a/bench/criterion/main.rs
+++ b/bench/criterion/main.rs
@@ -64,6 +64,60 @@ fn gen_html(c: &mut criterion::Criterion) {
}
criterion_group!(html, gen_html);
+fn gen_html_borrow(c: &mut criterion::Criterion) {
+ let mut group = c.benchmark_group("html_borrow");
+ for (name, input) in bench_input::INPUTS {
+ group.throughput(criterion::Throughput::Elements(
+ jotdown::Parser::new(input).count() as u64,
+ ));
+ group.bench_with_input(
+ criterion::BenchmarkId::from_parameter(name),
+ input,
+ |b, &input| {
+ b.iter_batched(
+ || jotdown::Parser::new(input).collect::>(),
+ |p| {
+ let mut s = String::new();
+ jotdown::html::Renderer::default()
+ .push_borrowed(p.as_slice().iter(), &mut s)
+ .unwrap();
+ s
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ },
+ );
+ }
+}
+criterion_group!(html_borrow, gen_html_borrow);
+
+fn gen_html_clone(c: &mut criterion::Criterion) {
+ let mut group = c.benchmark_group("html_clone");
+ for (name, input) in bench_input::INPUTS {
+ group.throughput(criterion::Throughput::Elements(
+ jotdown::Parser::new(input).count() as u64,
+ ));
+ group.bench_with_input(
+ criterion::BenchmarkId::from_parameter(name),
+ input,
+ |b, &input| {
+ b.iter_batched(
+ || jotdown::Parser::new(input).collect::>(),
+ |p| {
+ let mut s = String::new();
+ jotdown::html::Renderer::default()
+ .push(p.iter().cloned(), &mut s)
+ .unwrap();
+ s
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ },
+ );
+ }
+}
+criterion_group!(html_clone, gen_html_clone);
+
fn gen_full(c: &mut criterion::Criterion) {
let mut group = c.benchmark_group("full");
for (name, input) in bench_input::INPUTS {
@@ -85,4 +139,4 @@ fn gen_full(c: &mut criterion::Criterion) {
}
criterion_group!(full, gen_full);
-criterion_main!(block, inline, html, full);
+criterion_main!(block, inline, html, html_borrow, html_clone, full);