Close #12
This commit is contained in:
Noah Hellman 2023-02-11 10:59:26 +01:00
commit 77f4a9114a
5 changed files with 80 additions and 45 deletions

View file

@ -1,10 +1,12 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use jotdown::Render;
#[must_use] #[must_use]
#[wasm_bindgen] #[wasm_bindgen]
pub fn jotdown_render(djot: &str) -> String { pub fn jotdown_render(djot: &str) -> String {
let events = jotdown::Parser::new(djot); let events = jotdown::Parser::new(djot);
let mut html = String::new(); let mut html = String::new();
jotdown::html::push(events, &mut html); jotdown::html::Renderer.push(events, &mut html).unwrap();
html html
} }

View file

@ -7,17 +7,19 @@
//! Push to a [`String`] (implements [`std::fmt::Write`]): //! Push to a [`String`] (implements [`std::fmt::Write`]):
//! //!
//! ``` //! ```
//! # use jotdown::Render;
//! # let events = std::iter::empty(); //! # let events = std::iter::empty();
//! let mut html = String::new(); //! 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`]): //! Write to standard output with buffering ([`std::io::Stdout`] implements [`std::io::Write`]):
//! //!
//! ``` //! ```
//! # use jotdown::Render;
//! # let events = std::iter::empty(); //! # let events = std::iter::empty();
//! let mut out = std::io::BufWriter::new(std::io::stdout()); //! 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; use crate::Alignment;
@ -25,45 +27,18 @@ use crate::Container;
use crate::Event; use crate::Event;
use crate::ListKind; use crate::ListKind;
use crate::OrderedListNumbering::*; use crate::OrderedListNumbering::*;
use crate::Render;
/// Generate HTML and push it to a unicode-accepting buffer or stream. pub struct Renderer;
pub fn push<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write>(events: I, out: W) {
Writer::new(events, out).write().unwrap();
}
/// Generate HTML and write it to a byte sink, encoded as UTF-8. impl Render for Renderer {
/// fn push<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write>(
/// NOTE: This performs many small writes, so IO writes should be buffered with e.g. &self,
/// [`std::io::BufWriter`].
pub fn write<'s, I: Iterator<Item = Event<'s>>, W: std::io::Write>(
events: I, events: I,
mut out: W, out: W,
) -> std::io::Result<()> { ) -> std::fmt::Result {
struct Adapter<'a, T: ?Sized + 'a> { Writer::new(events, out).write()
inner: &'a mut T,
error: std::io::Result<()>,
} }
impl<T: std::io::Write + ?Sized> 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 { enum Raw {

View file

@ -16,10 +16,11 @@
//! ``` //! ```
//! # #[cfg(feature = "html")] //! # #[cfg(feature = "html")]
//! # { //! # {
//! use jotdown::Render;
//! let djot_input = "hello *world*!"; //! let djot_input = "hello *world*!";
//! let events = jotdown::Parser::new(djot_input); //! let events = jotdown::Parser::new(djot_input);
//! let mut html = String::new(); //! let mut html = String::new();
//! jotdown::html::push(events, &mut html); //! jotdown::html::Renderer.push(events, &mut html);
//! assert_eq!(html, "<p>hello <strong>world</strong>!</p>\n"); //! assert_eq!(html, "<p>hello <strong>world</strong>!</p>\n");
//! # } //! # }
//! ``` //! ```
@ -31,6 +32,7 @@
//! # { //! # {
//! # use jotdown::Event; //! # use jotdown::Event;
//! # use jotdown::Container::Link; //! # use jotdown::Container::Link;
//! # use jotdown::Render;
//! let events = //! let events =
//! jotdown::Parser::new("a [link](https://example.com)").map(|e| match e { //! jotdown::Parser::new("a [link](https://example.com)").map(|e| match e {
//! Event::Start(Link(dst, ty), attrs) => { //! Event::Start(Link(dst, ty), attrs) => {
@ -39,12 +41,14 @@
//! e => e, //! e => e,
//! }); //! });
//! let mut html = String::new(); //! let mut html = String::new();
//! jotdown::html::push(events, &mut html); //! jotdown::html::Renderer.push(events, &mut html);
//! assert_eq!(html, "<p>a <a href=\"https://example.net\">link</a></p>\n"); //! assert_eq!(html, "<p>a <a href=\"https://example.net\">link</a></p>\n");
//! # } //! # }
//! ``` //! ```
use std::fmt::Write; use std::fmt;
use std::fmt::Write as FmtWrite;
use std::io;
#[cfg(feature = "html")] #[cfg(feature = "html")]
pub mod html; pub mod html;
@ -63,6 +67,55 @@ pub use attr::Attributes;
type CowStr<'s> = std::borrow::Cow<'s, str>; 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<Item = Event<'s>>, 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<Item = Event<'s>>, W: io::Write>(
&self,
events: I,
out: W,
) -> io::Result<()> {
struct Adapter<T: io::Write> {
inner: T,
error: io::Result<()>,
}
impl<T: io::Write> fmt::Write for Adapter<T> {
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 event.
/// ///
/// A Djot document is represented by a sequence of events. An element may consist of one or /// A Djot document is represented by a sequence of events. An element may consist of one or

View file

@ -4,6 +4,8 @@ use std::io::BufWriter;
use std::io::Read; use std::io::Read;
use std::process::exit; use std::process::exit;
use jotdown::Render;
#[derive(Default)] #[derive(Default)]
struct App { struct App {
input: Option<OsString>, input: Option<OsString>,
@ -66,10 +68,11 @@ fn run() -> Result<(), std::io::Error> {
}; };
let parser = jotdown::Parser::new(&content); let parser = jotdown::Parser::new(&content);
let html = jotdown::html::Renderer;
match app.output { match app.output {
Some(path) => jotdown::html::write(parser, File::create(path)?)?, Some(path) => html.write(parser, File::create(path)?)?,
None => jotdown::html::write(parser, BufWriter::new(std::io::stdout()))?, None => html.write(parser, BufWriter::new(std::io::stdout()))?,
} }
Ok(()) Ok(())

View file

@ -1,11 +1,13 @@
use afl::fuzz; use afl::fuzz;
use jotdown::Render;
fn main() { fn main() {
fuzz!(|data: &[u8]| { fuzz!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) { if let Ok(s) = std::str::from_utf8(data) {
let p = jotdown::Parser::new(s); let p = jotdown::Parser::new(s);
let mut output = String::new(); let mut output = String::new();
jotdown::html::push(p, &mut output); jotdown::html::Renderer.push(p, &mut output).unwrap();
} }
}); });
} }