diff --git a/bench/criterion/main.rs b/bench/criterion/main.rs
index c2e41b9a..cde7c5d5 100644
--- a/bench/criterion/main.rs
+++ b/bench/criterion/main.rs
@@ -1,5 +1,6 @@
use criterion::criterion_group;
use criterion::criterion_main;
+use jotdown::RenderExt;
fn gen_block(c: &mut criterion::Criterion) {
let mut group = c.benchmark_group("block");
@@ -47,7 +48,7 @@ fn gen_html(c: &mut criterion::Criterion) {
|b, &input| {
b.iter_batched(
|| jotdown::Parser::new(input).collect::>(),
- |p| jotdown::html::render_to_string(p.into_iter()),
+ |p| jotdown::html::Renderer::default().render_events(p.into_iter()),
criterion::BatchSize::SmallInput,
);
},
@@ -68,7 +69,7 @@ fn gen_html_clone(c: &mut criterion::Criterion) {
|b, &input| {
b.iter_batched(
|| jotdown::Parser::new(input).collect::>(),
- |p| jotdown::html::render_to_string(p.iter().cloned()),
+ |p| jotdown::html::Renderer::default().render_events(p.into_iter()),
criterion::BatchSize::SmallInput,
);
},
@@ -85,9 +86,7 @@ fn gen_full(c: &mut criterion::Criterion) {
criterion::BenchmarkId::from_parameter(name),
input,
|b, &input| {
- b.iter_with_large_drop(|| {
- jotdown::html::render_to_string(jotdown::Parser::new(input))
- });
+ b.iter_with_large_drop(|| jotdown::html::render_to_string(input));
},
);
}
diff --git a/examples/jotdown_wasm/src/lib.rs b/examples/jotdown_wasm/src/lib.rs
index 45298c24..1018d726 100644
--- a/examples/jotdown_wasm/src/lib.rs
+++ b/examples/jotdown_wasm/src/lib.rs
@@ -11,7 +11,7 @@ pub fn jotdown_version() -> String {
#[must_use]
#[wasm_bindgen]
pub fn jotdown_render(djot: &str) -> String {
- jotdown::html::render_to_string(jotdown::Parser::new(djot))
+ jotdown::html::render_to_string(djot)
}
#[must_use]
diff --git a/src/html.rs b/src/html.rs
index 35039c45..84a9c3e2 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -13,22 +13,20 @@ use crate::SpanLinkType;
/// Render events into a string.
///
-/// This is a convenience function for using [`Renderer::push`] with fewer imports and without an
-/// intermediate variable.
+/// This is a convenience function for rendering documents whole without any customizations.
///
/// # Examples
///
/// ```
-/// let events = jotdown::Parser::new("hello");
-/// assert_eq!(jotdown::html::render_to_string(events), "hello
\n");
+/// assert_eq!(jotdown::html::render_to_string("hello"), "hello
\n");
/// ```
-pub fn render_to_string<'s, I>(events: I) -> String
-where
- I: Iterator- >,
-{
- let mut s = String::new();
- Renderer::default().push(events, &mut s).unwrap();
- s
+pub fn render_to_string(doc: &str) -> String {
+ use crate::RenderExt;
+ let mut r = Renderer::default();
+
+ r.render_document(doc).unwrap();
+
+ r.into_inner()
}
#[derive(Clone)]
@@ -46,11 +44,9 @@ pub struct Indentation {
/// # use jotdown::*;
/// # use jotdown::html::*;
/// let src = "> a\n";
- /// let events = Parser::new(src);
///
- /// let mut html = String::new();
/// let renderer = Renderer::indented(Indentation::default());
- /// renderer.push(events.clone(), &mut html).unwrap();
+ /// let html = renderer.render_to_string(src);
/// assert_eq!(
/// html,
/// concat!(
@@ -66,14 +62,12 @@ pub struct Indentation {
/// ```
/// # use jotdown::*;
/// # use jotdown::html::*;
- /// # let src = "> a\n";
- /// # let events = Parser::new(src);
- /// # let mut html = String::new();
+ /// let src = "> a\n";
/// let renderer = Renderer::indented(Indentation {
/// string: " ".to_string(),
/// ..Indentation::default()
/// });
- /// renderer.push(events.clone(), &mut html).unwrap();
+ /// let html = renderer.render_to_string(src);
/// assert_eq!(
/// html,
/// concat!(
@@ -94,11 +88,9 @@ pub struct Indentation {
/// # use jotdown::*;
/// # use jotdown::html::*;
/// let src = "> a\n";
- /// let events = Parser::new(src);
///
- /// let mut html = String::new();
/// let renderer = Renderer::indented(Indentation::default());
- /// renderer.push(events.clone(), &mut html).unwrap();
+ /// let html = renderer.render_to_string(src);
/// assert_eq!(
/// html,
/// concat!(
@@ -114,14 +106,12 @@ pub struct Indentation {
/// ```
/// # use jotdown::*;
/// # use jotdown::html::*;
- /// # let src = "> a\n";
- /// # let events = Parser::new(src);
- /// # let mut html = String::new();
+ /// let src = "> a\n";
/// let renderer = Renderer::indented(Indentation {
/// initial_level: 2,
/// ..Indentation::default()
/// });
- /// renderer.push(events.clone(), &mut html).unwrap();
+ /// let html = renderer.render_to_string(src);
/// assert_eq!(
/// html,
/// concat!(
@@ -147,12 +137,21 @@ impl Default for Indentation {
///
/// By default, block elements are placed on separate lines. To configure the formatting of the
/// output, see the [`Renderer::minified`] and [`Renderer::indented`] constructors.
-#[derive(Clone)]
-pub struct Renderer {
+pub struct Renderer<'s, W> {
indent: Option
,
+ writer: Writer<'s>,
+ output: W,
}
-impl Renderer {
+impl<'s> Renderer<'s, String> {
+ fn new(indent: Option) -> Self {
+ Self {
+ writer: Writer::new(&indent),
+ indent,
+ output: String::new(),
+ }
+ }
+
/// Create a renderer that emits no whitespace between elements.
///
/// # Examples
@@ -167,16 +166,15 @@ impl Renderer {
/// "\n",
/// " - c\n",
/// );
- /// let mut actual = String::new();
/// let renderer = Renderer::minified();
- /// renderer.push(Parser::new(src), &mut actual).unwrap();
+ /// let actual = renderer.render_to_string(src);
/// let expected =
/// "";
/// assert_eq!(actual, expected);
/// ```
#[must_use]
pub fn minified() -> Self {
- Self { indent: None }
+ Self::new(None)
}
/// Create a renderer that indents lines based on their block element depth.
@@ -195,9 +193,8 @@ impl Renderer {
/// "\n",
/// " - c\n",
/// );
- /// let mut actual = String::new();
/// let renderer = Renderer::indented(Indentation::default());
- /// renderer.push(Parser::new(src), &mut actual).unwrap();
+ /// let actual = renderer.render_to_string(src);
/// let expected = concat!(
/// "\n",
/// "\t\n",
@@ -217,13 +214,30 @@ impl Renderer {
/// ```
#[must_use]
pub fn indented(indent: Indentation) -> Self {
- Self {
- indent: Some(indent),
- }
+ Self::new(Some(indent))
+ }
+
+ pub fn render_to_string(self, input: &str) -> String {
+ use super::RenderExt;
+ let mut s = self.with_fmt_writer(String::new());
+ s.render_document(input).expect("Can't fail");
+
+ s.into_inner()
+ }
+
+ pub fn render_events_to_string(self, events: I) -> String
+ where
+ I: Iterator- >,
+ {
+ use super::RenderExt;
+ let mut s = self.with_fmt_writer(String::new());
+ s.render_events(events).expect("Can't fail");
+
+ s.into_inner()
}
}
-impl Default for Renderer {
+impl<'s> Default for Renderer<'s, String> {
/// Place block elements on separate lines.
///
/// This is the default behavior and matches the reference implementation.
@@ -240,9 +254,8 @@ impl Default for Renderer {
/// "\n",
/// " - c\n",
/// );
- /// let mut actual = String::new();
/// let renderer = Renderer::default();
- /// renderer.push(Parser::new(src), &mut actual).unwrap();
+ /// let actual = renderer.render_to_string(src);
/// let expected = concat!(
/// "
\n",
/// "\n",
@@ -261,24 +274,129 @@ impl Default for Renderer {
/// assert_eq!(actual, expected);
/// ```
fn default() -> Self {
+ Renderer::new(Some(Indentation {
+ string: String::new(),
+ initial_level: 0,
+ }))
+ }
+}
+
+impl<'s, W> Renderer<'s, W> {
+ pub fn into_inner(self) -> W {
+ self.output
+ }
+}
+
+pub struct WriteAdapter(T);
+
+struct WriteAdapterInner {
+ inner: T,
+ error: std::io::Result<()>,
+}
+
+impl WriteAdapterInner {
+ fn new(output: T) -> Self {
Self {
- indent: Some(Indentation {
- string: String::new(),
- initial_level: 0,
- }),
+ inner: output,
+ error: Ok(()),
}
}
}
+impl std::fmt::Write for WriteAdapterInner {
+ fn write_str(&mut self, s: &str) -> std::fmt::Result {
+ self.inner.write_all(s.as_bytes()).map_err(|e| {
+ self.error = Err(e);
+ std::fmt::Error
+ })
+ }
+}
-impl Render for Renderer {
- fn push<'s, I, W>(&self, mut events: I, mut out: W) -> std::fmt::Result
+impl<'s, W> Renderer<'s, W> {
+ pub fn with_io_writer(
+ self,
+ output: NewWriter,
+ ) -> Renderer<'s, WriteAdapter>
where
- I: Iterator- >,
- W: std::fmt::Write,
+ NewWriter: std::io::Write,
{
- let mut w = Writer::new(&self.indent);
- events.try_for_each(|e| w.render_event(e, &mut out))?;
- w.render_epilogue(&mut out)
+ let Renderer {
+ indent,
+ writer,
+ output: _,
+ } = self;
+
+ let output = WriteAdapter(output);
+ Renderer {
+ indent,
+ writer,
+ output,
+ }
+ }
+
+ pub fn with_fmt_writer
(self, output: NewWriter) -> Renderer<'s, NewWriter>
+ where
+ NewWriter: std::fmt::Write,
+ {
+ let Renderer {
+ indent,
+ writer,
+ output: _,
+ } = self;
+
+ Renderer {
+ indent,
+ writer,
+ output,
+ }
+ }
+}
+
+impl<'s, W> Render<'s> for Renderer<'s, W>
+where
+ W: std::fmt::Write,
+{
+ type Error = std::fmt::Error;
+
+ fn begin(&mut self) -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ /// Called iteratively with every single event emitted by parsing djot document
+ fn emit(&mut self, event: Event<'s>) -> Result<(), Self::Error> {
+ self.writer
+ .render_event(event, &mut self.output, &self.indent)
+ }
+
+ /// Called at the end of rendering a djot document
+ fn finish(&mut self) -> Result<(), Self::Error> {
+ self.writer.render_epilogue(&mut self.output, &self.indent)
+ }
+}
+
+impl<'s, W> Render<'s> for Renderer<'s, WriteAdapter>
+where
+ W: std::io::Write,
+{
+ type Error = std::io::Error;
+
+ fn begin(&mut self) -> Result<(), Self::Error> {
+ Ok(())
+ }
+
+ /// Called iteratively with every single event emitted by parsing djot document
+ fn emit(&mut self, event: Event<'s>) -> Result<(), Self::Error> {
+ let mut adapter = WriteAdapterInner::new(&mut self.output.0);
+ self.writer
+ .render_event(event, &mut adapter, &self.indent)
+ .map_err(|_| adapter.error.expect_err("Inner adapter always sets error"))
+ }
+
+ /// Called at the end of rendering a djot document
+ fn finish(&mut self) -> Result<(), Self::Error> {
+ let mut adapter = WriteAdapterInner::new(&mut self.output.0);
+ self.writer
+ .render_epilogue(&mut adapter, &self.indent)
+ .map_err(|_| adapter.error.expect_err("Inner adapter always sets error"))
}
}
@@ -294,8 +412,7 @@ impl Default for Raw {
}
}
-struct Writer<'s, 'f> {
- indent: &'f Option,
+struct Writer<'s> {
depth: usize,
raw: Raw,
img_alt_text: usize,
@@ -305,15 +422,14 @@ struct Writer<'s, 'f> {
footnotes: Footnotes<'s>,
}
-impl<'s, 'f> Writer<'s, 'f> {
- fn new(indent: &'f Option) -> Self {
+impl<'s> Writer<'s> {
+ fn new(indent: &Option) -> Self {
let depth = if let Some(indent) = indent {
indent.initial_level
} else {
0
};
Self {
- indent,
depth,
raw: Raw::default(),
img_alt_text: 0,
@@ -324,11 +440,16 @@ impl<'s, 'f> Writer<'s, 'f> {
}
}
- fn block(&mut self, mut out: W, depth_change: isize) -> std::fmt::Result
+ fn block(
+ &mut self,
+ mut out: W,
+ indent: &Option,
+ depth_change: isize,
+ ) -> std::fmt::Result
where
W: std::fmt::Write,
{
- if self.indent.is_none() {
+ if indent.is_none() {
return Ok(());
}
@@ -340,7 +461,7 @@ impl<'s, 'f> Writer<'s, 'f> {
if depth_change < 0 {
self.depth = next_depth;
}
- self.indent(&mut out)?;
+ self.indent(&mut out, indent)?;
if depth_change > 0 {
self.depth = next_depth;
}
@@ -348,11 +469,11 @@ impl<'s, 'f> Writer<'s, 'f> {
Ok(())
}
- fn indent(&self, mut out: W) -> std::fmt::Result
+ fn indent(&self, mut out: W, indent: &Option) -> std::fmt::Result
where
W: std::fmt::Write,
{
- if let Some(indent) = self.indent {
+ if let Some(indent) = indent {
if !indent.string.is_empty() {
for _ in 0..self.depth {
out.write_str(&indent.string)?;
@@ -362,7 +483,12 @@ impl<'s, 'f> Writer<'s, 'f> {
Ok(())
}
- fn render_event(&mut self, e: Event<'s>, mut out: W) -> std::fmt::Result
+ fn render_event(
+ &mut self,
+ e: Event<'s>,
+ mut out: W,
+ indent: &Option,
+ ) -> std::fmt::Result
where
W: std::fmt::Write,
{
@@ -395,7 +521,7 @@ impl<'s, 'f> Writer<'s, 'f> {
match e {
Event::Start(c, attrs) => {
if c.is_block() {
- self.block(&mut out, c.is_block_container().into())?;
+ self.block(&mut out, indent, c.is_block_container().into())?;
}
if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
return Ok(());
@@ -562,7 +688,7 @@ impl<'s, 'f> Writer<'s, 'f> {
}
Container::TaskListItem { checked } => {
out.write_char('>')?;
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
if checked {
out.write_str(r#" "#)?;
} else {
@@ -574,7 +700,7 @@ impl<'s, 'f> Writer<'s, 'f> {
}
Event::End(c) => {
if c.is_block_container() {
- self.block(&mut out, -1)?;
+ self.block(&mut out, indent, -1)?;
}
if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
return Ok(());
@@ -670,15 +796,15 @@ impl<'s, 'f> Writer<'s, 'f> {
Event::NonBreakingSpace => out.write_str(" ")?,
Event::Hardbreak => {
out.write_str(" ")?;
- self.block(out, 0)?;
+ self.block(out, indent, 0)?;
}
Event::Softbreak => {
out.write_char('\n')?;
- self.indent(&mut out)?;
+ self.indent(&mut out, indent)?;
}
Event::Escape | Event::Blankline | Event::Attributes(..) => {}
Event::ThematicBreak(attrs) => {
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
out.write_str(" Writer<'s, 'f> {
Ok(())
}
- fn render_epilogue(&mut self, mut out: W) -> std::fmt::Result
+ fn render_epilogue(&mut self, mut out: W, indent: &Option) -> std::fmt::Result
where
W: std::fmt::Write,
{
if self.footnotes.reference_encountered() {
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
out.write_str("")?;
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
out.write_str(" ")?;
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
out.write_str("")?;
while let Some((number, events)) = self.footnotes.next() {
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
write!(out, "", number)?;
let mut unclosed_para = false;
@@ -718,13 +844,13 @@ impl<'s, 'f> Writer<'s, 'f> {
// not a footnote, so no need to add href before para close
out.write_str("
")?;
}
- self.render_event(e.clone(), &mut out)?;
+ self.render_event(e.clone(), &mut out, indent)?;
unclosed_para = matches!(e, Event::End(Container::Paragraph { .. }))
&& !matches!(self.list_tightness.last(), Some(true));
}
if !unclosed_para {
// create a new paragraph
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
out.write_str("")?;
}
write!(
@@ -733,17 +859,17 @@ impl<'s, 'f> Writer<'s, 'f> {
number,
)?;
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
out.write_str("")?;
}
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
out.write_str("")?;
- self.block(&mut out, 0)?;
+ self.block(&mut out, indent, 0)?;
out.write_str("")?;
}
- if self.indent.is_some() {
+ if indent.is_some() {
out.write_char('\n')?;
}
diff --git a/src/lib.rs b/src/lib.rs
index 71374f38..ecd0c9a5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,7 +17,7 @@
//! # {
//! let djot_input = "hello *world*!";
//! let events = jotdown::Parser::new(djot_input);
-//! let html = jotdown::html::render_to_string(events);
+//! let html = jotdown::html::Renderer::default().render_events_to_string(events);
//! assert_eq!(html, "
hello world !
\n");
//! # }
//! ```
@@ -36,7 +36,7 @@
//! }
//! e => e,
//! });
-//! let html = jotdown::html::render_to_string(events);
+//! let html = jotdown::html::Renderer::default().render_events_to_string(events);
//! assert_eq!(html, "a link
\n");
//! # }
//! ```
@@ -57,85 +57,45 @@ pub use attr::ParseAttributesError;
type CowStr<'s> = std::borrow::Cow<'s, str>;
-/// A trait for rendering [`Event`]s to an output format.
+/// An implementation of rendering a djot document
///
-/// 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`]):
-///
-/// ```
-/// # #[cfg(feature = "html")]
-/// # {
-/// # use jotdown::Render;
-/// # let events = std::iter::empty();
-/// let mut output = String::new();
-/// let renderer = jotdown::html::Renderer::default();
-/// renderer.push(events, &mut output);
-/// # }
-/// ```
-///
-/// Write to standard output with buffering ([`std::io::Stdout`] implements [`std::io::Write`]):
-///
-/// ```
-/// # #[cfg(feature = "html")]
-/// # {
-/// # use jotdown::Render;
-/// # let events = std::iter::empty();
-/// let mut out = std::io::BufWriter::new(std::io::stdout());
-/// let renderer = jotdown::html::Renderer::default();
-/// renderer.write(events, &mut out).unwrap();
-/// # }
-/// ```
-pub trait Render {
- /// Push owned [`Event`]s to a unicode-accepting buffer or stream.
- fn push<'s, I, W>(&self, events: I, out: W) -> std::fmt::Result
- where
- I: Iterator- >,
- W: std::fmt::Write;
+/// This interface will be called by the djot parser with elements
+/// parsed by it.
+pub trait Render<'s> {
+ type Error;
- /// 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`].
- fn write<'s, I, W>(&self, events: I, out: W) -> std::io::Result<()>
+ /// Called at the start of rendering a djot document
+ fn begin(&mut self) -> Result<(), Self::Error>;
+
+ /// Called iteratively with every single event emitted by parsing djot document
+ fn emit(&mut self, event: Event<'s>) -> Result<(), Self::Error>;
+
+ /// Called at the end of rendering a djot document
+ fn finish(&mut self) -> Result<(), Self::Error>;
+}
+
+pub trait RenderExt<'s>: Render<'s> {
+ /// Parse and render the whole document with `renderer`
+ fn render_document(&mut self, src: &'s str) -> Result<(), Self::Error> {
+ self.render_events(Parser::new(src))
+ }
+
+ /// Render document as a list of events already parsed by the [`Parser`]
+ fn render_events
(&mut self, events: I) -> Result<(), Self::Error>
where
I: Iterator- >,
- W: std::io::Write,
{
- struct WriteAdapter
{
- inner: T,
- error: std::io::Result<()>,
- }
+ self.begin()?;
- impl std::fmt::Write for WriteAdapter {
- fn write_str(&mut self, s: &str) -> std::fmt::Result {
- self.inner.write_all(s.as_bytes()).map_err(|e| {
- self.error = Err(e);
- std::fmt::Error
- })
- }
+ for event in events {
+ self.emit(event)?;
}
- let mut out = WriteAdapter {
- inner: out,
- error: Ok(()),
- };
-
- self.push(events, &mut out).map_err(|_| match out.error {
- Err(e) => e,
- _ => std::io::Error::new(std::io::ErrorKind::Other, "formatter error"),
- })
+ self.finish()
}
}
-// XXX why is this not a blanket implementation?
-impl<'s> AsRef> for &Event<'s> {
- fn as_ref(&self) -> &Event<'s> {
- self
- }
-}
+impl<'s, R> RenderExt<'s> for R where R: Render<'s> {}
/// A Djot event.
///
@@ -175,7 +135,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "word
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Start(Container<'s>, Attributes<'s>),
/// End of a container.
@@ -202,7 +162,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "str
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Str(CowStr<'s>),
/// A footnote reference.
@@ -234,7 +194,7 @@ pub enum Event<'s> {
/// "\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
FootnoteReference(CowStr<'s>),
/// A symbol, by default rendered literally but may be treated specially.
@@ -255,7 +215,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "a :sym:
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Symbol(CowStr<'s>),
/// Left single quotation mark.
@@ -277,7 +237,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "‘quote’
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
LeftSingleQuote,
/// Right single quotation mark.
@@ -299,7 +259,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "’Tis Socrates’
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
RightSingleQuote,
/// Left single quotation mark.
@@ -322,7 +282,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "“Hello,” he said
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
LeftDoubleQuote,
/// Right double quotation mark.
@@ -345,7 +305,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "yes…
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Ellipsis,
/// An en dash.
@@ -367,7 +327,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "57–33
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
EnDash,
/// An em dash.
@@ -389,7 +349,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "oxen—and
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
EmDash,
/// A space that must not break a line.
@@ -412,7 +372,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "no break
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
NonBreakingSpace,
/// A newline that may or may not break a line in the output.
@@ -440,7 +400,7 @@ pub enum Event<'s> {
/// "soft\n",
/// "break
\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Softbreak,
/// A newline that must break a line in the output.
@@ -469,7 +429,7 @@ pub enum Event<'s> {
/// "hard \n",
/// "break
\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Hardbreak,
/// An escape character, not visible in output.
@@ -492,7 +452,7 @@ pub enum Event<'s> {
/// ],
/// );
/// let html = "*a*
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Escape,
/// A blank line, not visible in output.
@@ -523,7 +483,7 @@ pub enum Event<'s> {
/// "para0
\n",
/// "para1
\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Blankline,
/// A thematic break, typically a horizontal rule.
@@ -567,7 +527,7 @@ pub enum Event<'s> {
/// "para1
\n",
/// " \n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
ThematicBreak(Attributes<'s>),
/// Dangling attributes not attached to anything.
@@ -613,7 +573,7 @@ pub enum Event<'s> {
/// "\n",
/// "inline
\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Attributes(Attributes<'s>),
}
@@ -656,7 +616,7 @@ pub enum Container<'s> {
/// "b\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Blockquote,
/// A list.
@@ -708,7 +668,7 @@ pub enum Container<'s> {
/// "\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
List { kind: ListKind, tight: bool },
/// An item of a list
@@ -747,7 +707,7 @@ pub enum Container<'s> {
/// "\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
ListItem,
/// An item of a task list, either checked or unchecked.
@@ -790,7 +750,7 @@ pub enum Container<'s> {
/// "\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
TaskListItem { checked: bool },
/// A description list.
@@ -845,7 +805,7 @@ pub enum Container<'s> {
/// "\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
DescriptionList,
/// Details describing a term within a description list.
@@ -891,7 +851,7 @@ pub enum Container<'s> {
/// "\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Footnote { label: CowStr<'s> },
/// A table element.
@@ -983,7 +943,7 @@ pub enum Container<'s> {
/// "\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Table,
/// A row element of a table.
@@ -1052,7 +1012,7 @@ pub enum Container<'s> {
/// "\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Section { id: CowStr<'s> },
/// A block-level divider element.
@@ -1085,7 +1045,7 @@ pub enum Container<'s> {
/// "this is a note
\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Div { class: CowStr<'s> },
/// A paragraph.
@@ -1127,7 +1087,7 @@ pub enum Container<'s> {
/// "heading \n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Heading {
level: u16,
@@ -1182,7 +1142,7 @@ pub enum Container<'s> {
/// "\n",
/// "\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Caption,
/// A term within a description list.
@@ -1207,7 +1167,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
LinkDefinition { label: CowStr<'s> },
/// A block with raw markup for a specific output format.
@@ -1234,7 +1194,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "x \n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
RawBlock { format: CowStr<'s> },
/// A block with code in a specific language.
@@ -1264,7 +1224,7 @@ pub enum Container<'s> {
/// "<tag>x</tag>\n",
/// " \n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
CodeBlock { language: CowStr<'s> },
/// An inline divider element.
@@ -1304,7 +1264,7 @@ pub enum Container<'s> {
/// "word \n",
/// "two words
\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Span,
/// An inline link, the first field is either a destination URL or an unresolved tag.
@@ -1356,7 +1316,7 @@ pub enum Container<'s> {
/// "https://example.com \n",
/// "me@example.com
\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
///
/// Anchor text and the URL can be specified inline:
@@ -1385,7 +1345,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "anchor
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
///
/// Alternatively, the URL can be retrieved from a link definition using hard brackets, if it
@@ -1443,7 +1403,7 @@ pub enum Container<'s> {
/// "a \n",
/// "b
\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Link(CowStr<'s>, LinkType),
/// An inline image, the first field is either a destination URL or an unresolved tag.
@@ -1472,7 +1432,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Image(CowStr<'s>, SpanLinkType),
/// An inline verbatim string.
@@ -1495,7 +1455,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "inline verbatim
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Verbatim,
/// An inline or display math element.
@@ -1536,7 +1496,7 @@ pub enum Container<'s> {
/// "inline \\(a\\cdot{}b\\) or\n",
/// "display \\[\\frac{a}{b}\\]
\n",
/// );
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Math { display: bool },
/// Inline raw markup for a specific output format.
@@ -1560,7 +1520,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "a
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
RawInline { format: CowStr<'s> },
/// A subscripted element.
@@ -1582,7 +1542,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "SUB
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Subscript,
/// A superscripted element.
@@ -1604,7 +1564,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "SUP
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Superscript,
/// An inserted inline element.
@@ -1626,7 +1586,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "INS
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Insert,
/// A deleted inline element.
@@ -1648,7 +1608,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "DEL
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Delete,
/// An inline element emphasized with a bold typeface.
@@ -1670,7 +1630,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "STRONG
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Strong,
/// An emphasized inline element.
@@ -1692,7 +1652,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "EM
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Emphasis,
/// A highlighted inline element.
@@ -1714,7 +1674,7 @@ pub enum Container<'s> {
/// ],
/// );
/// let html = "MARK
\n";
- /// assert_eq!(&html::render_to_string(events.into_iter()), html);
+ /// assert_eq!(&html::Renderer::default().render_events_to_string(events.into_iter()), html);
/// ```
Mark,
}
diff --git a/src/main.rs b/src/main.rs
index 3d382ff7..056dde18 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,5 @@
+use jotdown::RenderExt as _;
+
#[derive(Default)]
struct App {
input: Option,
@@ -79,7 +81,6 @@ fn parse_args() -> App {
}
fn run() -> Result<(), std::io::Error> {
- use jotdown::Render;
use std::io::Read;
let app = parse_args();
@@ -93,7 +94,6 @@ fn run() -> Result<(), std::io::Error> {
}
};
- let parser = jotdown::Parser::new(&content);
let renderer = if app.minified {
jotdown::html::Renderer::minified()
} else {
@@ -104,8 +104,12 @@ fn run() -> Result<(), std::io::Error> {
};
match app.output {
- Some(path) => renderer.write(parser, std::fs::File::create(path)?)?,
- None => renderer.write(parser, std::io::BufWriter::new(std::io::stdout()))?,
+ Some(path) => renderer
+ .with_io_writer(&mut std::fs::File::create(path)?)
+ .render_document(&content)?,
+ None => renderer
+ .with_io_writer(std::io::BufWriter::new(std::io::stdout()))
+ .render_document(&content)?,
}
Ok(())
diff --git a/tests/afl/src/lib.rs b/tests/afl/src/lib.rs
index 591f46c5..03218ac0 100644
--- a/tests/afl/src/lib.rs
+++ b/tests/afl/src/lib.rs
@@ -1,9 +1,8 @@
-use jotdown::Render;
-
use html5ever::tendril;
use html5ever::tendril::TendrilSink;
use html5ever::tokenizer;
use html5ever::tree_builder;
+use jotdown::RenderExt;
/// Perform sanity checks on events.
pub fn parse(data: &[u8]) {
@@ -63,7 +62,8 @@ pub fn html(data: &[u8]) {
let p = jotdown::Parser::new(s);
let mut html = "\n".to_string();
jotdown::html::Renderer::default()
- .push(p, &mut html)
+ .with_fmt_writer(&mut html)
+ .render_events(p)
.unwrap();
validate_html(&html);
}
diff --git a/tests/filters.rs b/tests/filters.rs
new file mode 100644
index 00000000..003afd3a
--- /dev/null
+++ b/tests/filters.rs
@@ -0,0 +1,46 @@
+use jotdown::{Container, Event, Render, RenderExt as _};
+use std::borrow::Cow;
+
+struct RickrollRenderer(R);
+
+impl<'s, R> Render<'s> for RickrollRenderer
+where
+ R: Render<'s>,
+{
+ type Error = R::Error;
+
+ fn begin(&mut self) -> Result<(), Self::Error> {
+ self.0.begin()
+ }
+
+ fn emit(&mut self, event: Event<'s>) -> Result<(), Self::Error> {
+ match event {
+ Event::Start(Container::Link(_link, t), attrs) => self.0.emit(Event::Start(
+ Container::Link(
+ Cow::Owned("https://www.youtube.com/watch?v=E4WlUXrJgy4".to_owned()),
+ t,
+ ),
+ attrs,
+ )),
+ _ => self.0.emit(event),
+ }
+ }
+
+ fn finish(&mut self) -> Result<(), Self::Error> {
+ self.0.finish()
+ }
+}
+
+#[test]
+fn rickroll_me() {
+ use jotdown::RenderOutputExt;
+ let src = "[interesting link](https://example.com)";
+ let out = RickrollRenderer(jotdown::html::Renderer::minified())
+ .render_into_document(src)
+ .unwrap();
+
+ assert_eq!(
+ r.0.into_inner(),
+ "interesting link
"
+ );
+}
diff --git a/tests/html-ref/lib.rs b/tests/html-ref/lib.rs
index fabada5f..3feae2c8 100644
--- a/tests/html-ref/lib.rs
+++ b/tests/html-ref/lib.rs
@@ -4,14 +4,9 @@ mod r#ref;
#[macro_export]
macro_rules! compare {
($src:expr, $expected:expr) => {
- use jotdown::Render;
let src = $src;
let expected = std::fs::read_to_string($expected).expect("read failed");
- let p = jotdown::Parser::new(src);
- let mut actual = String::new();
- jotdown::html::Renderer::default()
- .push(p, &mut actual)
- .unwrap();
+ let actual = jotdown::html::Renderer::default().render_to_string(src);
assert_eq!(actual, expected, "\n{}", {
use std::io::Write;
let mut child = std::process::Command::new("diff")
diff --git a/tests/html-ut/lib.rs b/tests/html-ut/lib.rs
index d616f539..3cd981fa 100644
--- a/tests/html-ut/lib.rs
+++ b/tests/html-ut/lib.rs
@@ -6,8 +6,7 @@ macro_rules! compare {
($src:expr, $expected:expr) => {
let src = $src;
let expected = $expected;
- let p = jotdown::Parser::new(src);
- let actual = jotdown::html::render_to_string(p);
+ let actual = jotdown::html::render_to_string(src);
assert_eq!(
actual.trim(),
expected.trim(),
diff --git a/tests/html.rs b/tests/html.rs
index e808fb2e..8d886a3b 100644
--- a/tests/html.rs
+++ b/tests/html.rs
@@ -1,15 +1,14 @@
use jotdown::html::Indentation;
-use jotdown::Render;
+use jotdown::RenderExt;
macro_rules! test_html {
($src:expr, $expected:expr $(,$indent:expr)? $(,)?) => {
#[allow(unused)]
let mut renderer = jotdown::html::Renderer::minified();
$(renderer = jotdown::html::Renderer::indented($indent);)?
- let mut actual = String::new();
renderer
- .push(jotdown::Parser::new($src), &mut actual)
- .unwrap();
+ .render_document($src).expect("Can't fail");
+ let actual = renderer.into_inner();
assert_eq!(actual, $expected);
};
($src:expr, $expected:expr, $(,)?) => {
diff --git a/tests/lib.rs b/tests/lib.rs
index 3f06414a..e86ca523 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -1,3 +1,4 @@
mod attr;
+mod filters;
mod html;
mod parse_events;