diff --git a/examples/jotdown_wasm/src/lib.rs b/examples/jotdown_wasm/src/lib.rs index b5c16c0..3ab7fb0 100644 --- a/examples/jotdown_wasm/src/lib.rs +++ b/examples/jotdown_wasm/src/lib.rs @@ -1,10 +1,12 @@ use wasm_bindgen::prelude::*; +use jotdown::Render; + #[must_use] #[wasm_bindgen] pub fn jotdown_render(djot: &str) -> String { let events = jotdown::Parser::new(djot); let mut html = String::new(); - jotdown::html::push(events, &mut html); + jotdown::html::Renderer.push(events, &mut html).unwrap(); html } diff --git a/src/html.rs b/src/html.rs index 5a2c82f..a7f2037 100644 --- a/src/html.rs +++ b/src/html.rs @@ -7,17 +7,19 @@ //! Push to a [`String`] (implements [`std::fmt::Write`]): //! //! ``` +//! # use jotdown::Render; //! # let events = std::iter::empty(); //! let mut html = String::new(); -//! jotdown::html::push(events, &mut html); +//! 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::write(events, &mut out).unwrap(); +//! jotdown::html::Renderer.write(events, &mut out).unwrap(); //! ``` use crate::Alignment; @@ -25,45 +27,18 @@ use crate::Container; use crate::Event; use crate::ListKind; use crate::OrderedListNumbering::*; +use crate::Render; -/// Generate HTML and push it to a unicode-accepting buffer or stream. -pub fn push<'s, I: Iterator>, W: std::fmt::Write>(events: I, out: W) { - Writer::new(events, out).write().unwrap(); -} +pub struct Renderer; -/// Generate HTML and write it 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`]. -pub fn write<'s, I: Iterator>, W: std::io::Write>( - events: I, - mut out: W, -) -> std::io::Result<()> { - struct Adapter<'a, T: ?Sized + 'a> { - inner: &'a mut T, - error: std::io::Result<()>, +impl Render for Renderer { + fn push<'s, I: Iterator>, W: std::fmt::Write>( + &self, + events: I, + out: W, + ) -> std::fmt::Result { + Writer::new(events, out).write() } - - impl std::fmt::Write for Adapter<'_, T> { - fn write_str(&mut self, s: &str) -> std::fmt::Result { - match self.inner.write_all(s.as_bytes()) { - Ok(()) => Ok(()), - Err(e) => { - self.error = Err(e); - Err(std::fmt::Error) - } - } - } - } - - let mut output = Adapter { - inner: &mut out, - error: Ok(()), - }; - - Writer::new(events, &mut output) - .write() - .map_err(|_| output.error.unwrap_err()) } enum Raw { diff --git a/src/lib.rs b/src/lib.rs index ce410fe..ca1aa4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,10 +16,11 @@ //! ``` //! # #[cfg(feature = "html")] //! # { +//! use jotdown::Render; //! let djot_input = "hello *world*!"; //! let events = jotdown::Parser::new(djot_input); //! let mut html = String::new(); -//! jotdown::html::push(events, &mut html); +//! jotdown::html::Renderer.push(events, &mut html); //! assert_eq!(html, "

hello world!

\n"); //! # } //! ``` @@ -31,6 +32,7 @@ //! # { //! # use jotdown::Event; //! # use jotdown::Container::Link; +//! # use jotdown::Render; //! let events = //! jotdown::Parser::new("a [link](https://example.com)").map(|e| match e { //! Event::Start(Link(dst, ty), attrs) => { @@ -39,12 +41,14 @@ //! e => e, //! }); //! let mut html = String::new(); -//! jotdown::html::push(events, &mut html); +//! jotdown::html::Renderer.push(events, &mut html); //! assert_eq!(html, "

a link

\n"); //! # } //! ``` -use std::fmt::Write; +use std::fmt; +use std::fmt::Write as FmtWrite; +use std::io; #[cfg(feature = "html")] pub mod html; @@ -63,6 +67,55 @@ pub use attr::Attributes; type CowStr<'s> = std::borrow::Cow<'s, str>; +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; + + /// 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<()> { + struct Adapter { + inner: T, + error: io::Result<()>, + } + + 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) + } + } + } + } + + let mut out = Adapter { + 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")), + }, + } + } +} + /// A Djot event. /// /// A Djot document is represented by a sequence of events. An element may consist of one or diff --git a/src/main.rs b/src/main.rs index 45f3e4f..b9ea08c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ use std::io::BufWriter; use std::io::Read; use std::process::exit; +use jotdown::Render; + #[derive(Default)] struct App { input: Option, @@ -66,10 +68,11 @@ fn run() -> Result<(), std::io::Error> { }; let parser = jotdown::Parser::new(&content); + let html = jotdown::html::Renderer; match app.output { - Some(path) => jotdown::html::write(parser, File::create(path)?)?, - None => jotdown::html::write(parser, BufWriter::new(std::io::stdout()))?, + Some(path) => html.write(parser, File::create(path)?)?, + None => html.write(parser, BufWriter::new(std::io::stdout()))?, } Ok(()) diff --git a/tests/afl/src/gen.rs b/tests/afl/src/gen.rs index 4ef4cc8..6bd2664 100644 --- a/tests/afl/src/gen.rs +++ b/tests/afl/src/gen.rs @@ -1,11 +1,13 @@ use afl::fuzz; +use jotdown::Render; + fn main() { fuzz!(|data: &[u8]| { if let Ok(s) = std::str::from_utf8(data) { let p = jotdown::Parser::new(s); let mut output = String::new(); - jotdown::html::push(p, &mut output); + jotdown::html::Renderer.push(p, &mut output).unwrap(); } }); }