Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change write_to_html to allow fmt::Write #870

Merged
merged 3 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
66 changes: 50 additions & 16 deletions pulldown-cmark-escape/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
//! Utility functions for HTML escaping. Only useful when building your own
//! HTML renderer.

use std::fmt::{Arguments, Write as FmtWrite};
use std::io::{self, ErrorKind, Write};
use std::fmt::{self, Arguments};
use std::io::{self, Write};
use std::str::from_utf8;

#[rustfmt::skip]
Expand All @@ -46,20 +46,23 @@ static SINGLE_QUOTE_ESCAPE: &str = "'";
/// `W: StrWrite`. Since we need the latter a lot, we choose to wrap
/// `Write` types.
#[derive(Debug)]
pub struct WriteWrapper<W>(pub W);
pub struct IoWriter<W>(pub W);

/// Trait that allows writing string slices. This is basically an extension
/// of `std::io::Write` in order to include `String`.
pub trait StrWrite {
fn write_str(&mut self, s: &str) -> io::Result<()>;
type Error;

fn write_fmt(&mut self, args: Arguments) -> io::Result<()>;
fn write_str(&mut self, s: &str) -> Result<(), Self::Error>;
fn write_fmt(&mut self, args: Arguments) -> Result<(), Self::Error>;
}

impl<W> StrWrite for WriteWrapper<W>
impl<W> StrWrite for IoWriter<W>
where
W: Write,
{
type Error = io::Error;

#[inline]
fn write_str(&mut self, s: &str) -> io::Result<()> {
self.0.write_all(s.as_bytes())
Expand All @@ -71,37 +74,64 @@ where
}
}

/// This wrapper exists because we can't have both a blanket implementation
/// for all types implementing `io::Write` and types of the form `&mut W` where
/// `W: StrWrite`. Since we need the latter a lot, we choose to wrap
/// `Write` types.
#[derive(Debug)]
pub struct FmtWriter<W>(pub W);

impl<W> StrWrite for FmtWriter<W>
where
W: fmt::Write,
{
type Error = fmt::Error;

#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0.write_str(s)
}

#[inline]
fn write_fmt(&mut self, args: Arguments) -> fmt::Result {
self.0.write_fmt(args)
}
}

impl StrWrite for String {
type Error = fmt::Error;

#[inline]
fn write_str(&mut self, s: &str) -> io::Result<()> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}

#[inline]
fn write_fmt(&mut self, args: Arguments) -> io::Result<()> {
// FIXME: translate fmt error to io error?
FmtWrite::write_fmt(self, args).map_err(|_| ErrorKind::Other.into())
fn write_fmt(&mut self, args: Arguments) -> fmt::Result {
fmt::Write::write_fmt(self, args)
}
}

impl<W> StrWrite for &'_ mut W
where
W: StrWrite,
{
type Error = W::Error;

#[inline]
fn write_str(&mut self, s: &str) -> io::Result<()> {
fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
(**self).write_str(s)
}

#[inline]
fn write_fmt(&mut self, args: Arguments) -> io::Result<()> {
fn write_fmt(&mut self, args: Arguments) -> Result<(), Self::Error> {
(**self).write_fmt(args)
}
}

/// Writes an href to the buffer, escaping href unsafe bytes.
pub fn escape_href<W>(mut w: W, s: &str) -> io::Result<()>
pub fn escape_href<W>(mut w: W, s: &str) -> Result<(), W::Error>
where
W: StrWrite,
{
Expand Down Expand Up @@ -171,7 +201,7 @@ static HTML_ESCAPES: [&str; 6] = ["", "&amp;", "&lt;", "&gt;", "&quot;", "&#39;"
/// // This is not okay.
/// //let not_ok = format!("<a title={value}>test</a>");
/// ````
pub fn escape_html<W: StrWrite>(w: W, s: &str) -> io::Result<()> {
pub fn escape_html<W: StrWrite>(w: W, s: &str) -> Result<(), W::Error> {
#[cfg(all(target_arch = "x86_64", feature = "simd"))]
{
simd::escape_html(w, s, &HTML_ESCAPE_TABLE)
Expand Down Expand Up @@ -200,7 +230,7 @@ pub fn escape_html<W: StrWrite>(w: W, s: &str) -> io::Result<()> {
/// It should always be correct, but will produce larger output.
///
/// </div>
pub fn escape_html_body_text<W: StrWrite>(w: W, s: &str) -> io::Result<()> {
pub fn escape_html_body_text<W: StrWrite>(w: W, s: &str) -> Result<(), W::Error> {
#[cfg(all(target_arch = "x86_64", feature = "simd"))]
{
simd::escape_html(w, s, &HTML_BODY_TEXT_ESCAPE_TABLE)
Expand All @@ -211,7 +241,11 @@ pub fn escape_html_body_text<W: StrWrite>(w: W, s: &str) -> io::Result<()> {
}
}

fn escape_html_scalar<W: StrWrite>(mut w: W, s: &str, table: &'static [u8; 256]) -> io::Result<()> {
fn escape_html_scalar<W: StrWrite>(
mut w: W,
s: &str,
table: &'static [u8; 256],
) -> Result<(), W::Error> {
let bytes = s.as_bytes();
let mut mark = 0;
let mut i = 0;
Expand Down
2 changes: 1 addition & 1 deletion pulldown-cmark/examples/event-filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ fn main() {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
handle.write_all(b"\nHTML output:\n").unwrap();
html::write_html(&mut handle, parser).unwrap();
html::write_html_io(&mut handle, parser).unwrap();
}
4 changes: 2 additions & 2 deletions pulldown-cmark/examples/footnote-rewrite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fn main() {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
handle.write_all(b"\nHTML output:\n").unwrap();
html::write_html(&mut handle, parser).unwrap();
html::write_html_io(&mut handle, parser).unwrap();

// To make the footnotes look right, we need to sort them by their appearance order, not by
// the in-tree order of their actual definitions. Unused items are omitted entirely.
Expand Down Expand Up @@ -88,7 +88,7 @@ fn main() {
handle
.write_all(b"<hr><ol class=\"footnotes-list\">\n")
.unwrap();
html::write_html(
html::write_html_io(
&mut handle,
footnotes.into_iter().flat_map(|fl| {
// To write backrefs, the name needs kept until the end of the footnote definition.
Expand Down
63 changes: 49 additions & 14 deletions pulldown-cmark/src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@
//! HTML renderer that takes an iterator of events as input.

use std::collections::HashMap;
use std::io::{self, Write};

use crate::strings::CowStr;
use crate::Event::*;
use crate::{Alignment, BlockQuoteKind, CodeBlockKind, Event, LinkType, Tag, TagEnd};
use pulldown_cmark_escape::{
escape_href, escape_html, escape_html_body_text, StrWrite, WriteWrapper,
escape_href, escape_html, escape_html_body_text, FmtWriter, IoWriter, StrWrite,
};

enum TableState {
Expand Down Expand Up @@ -73,14 +72,15 @@ where
}

/// Writes a new line.
fn write_newline(&mut self) -> io::Result<()> {
#[inline]
fn write_newline(&mut self) -> Result<(), W::Error> {
self.end_newline = true;
self.writer.write_str("\n")
}

/// Writes a buffer, and tracks whether or not a newline was written.
#[inline]
fn write(&mut self, s: &str) -> io::Result<()> {
fn write(&mut self, s: &str) -> Result<(), W::Error> {
self.writer.write_str(s)?;

if !s.is_empty() {
Expand All @@ -89,7 +89,7 @@ where
Ok(())
}

fn run(mut self) -> io::Result<()> {
fn run(mut self) -> Result<(), W::Error> {
while let Some(event) = self.iter.next() {
match event {
Start(tag) => {
Expand Down Expand Up @@ -156,7 +156,7 @@ where
}

/// Writes the start of an HTML tag.
fn start_tag(&mut self, tag: Tag<'a>) -> io::Result<()> {
fn start_tag(&mut self, tag: Tag<'a>) -> Result<(), W::Error> {
match tag {
Tag::HtmlBlock => Ok(()),
Tag::Paragraph => {
Expand Down Expand Up @@ -368,7 +368,7 @@ where
}
}

fn end_tag(&mut self, tag: TagEnd) -> io::Result<()> {
fn end_tag(&mut self, tag: TagEnd) -> Result<(), W::Error> {
match tag {
TagEnd::HtmlBlock => {}
TagEnd::Paragraph => {
Expand Down Expand Up @@ -439,7 +439,7 @@ where
}

// run raw text, consuming end tag
fn raw_text(&mut self) -> io::Result<()> {
fn raw_text(&mut self) -> Result<(), W::Error> {
let mut nest = 0;
while let Some(event) = self.iter.next() {
match event {
Expand Down Expand Up @@ -514,11 +514,11 @@ pub fn push_html<'a, I>(s: &mut String, iter: I)
where
I: Iterator<Item = Event<'a>>,
{
HtmlWriter::new(iter, s).run().unwrap();
write_html_fmt(s, iter).unwrap()
}

/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
/// write it out to a writable stream.
/// write it out to an I/O stream.
///
/// **Note**: using this function with an unbuffered writer like a file or socket
/// will result in poor performance. Wrap these in a
Expand All @@ -541,7 +541,7 @@ where
/// let mut bytes = Vec::new();
/// let parser = Parser::new(markdown_str);
///
/// html::write_html(Cursor::new(&mut bytes), parser);
/// html::write_html_io(Cursor::new(&mut bytes), parser);
///
/// assert_eq!(&String::from_utf8_lossy(&bytes)[..], r#"<h1>hello</h1>
/// <ul>
Expand All @@ -550,10 +550,45 @@ where
/// </ul>
/// "#);
/// ```
pub fn write_html<'a, I, W>(writer: W, iter: I) -> io::Result<()>
pub fn write_html_io<'a, I, W>(writer: W, iter: I) -> std::io::Result<()>
where
I: Iterator<Item = Event<'a>>,
W: std::io::Write,
{
HtmlWriter::new(iter, IoWriter(writer)).run()
}

/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and
/// write it into Unicode-accepting buffer or stream.
///
/// # Examples
///
/// ```
/// use pulldown_cmark::{html, Parser};
///
/// let markdown_str = r#"
/// hello
/// =====
///
/// * alpha
/// * beta
/// "#;
/// let mut buf = String::new();
/// let parser = Parser::new(markdown_str);
///
/// html::write_html_fmt(&mut buf, parser);
///
/// assert_eq!(buf, r#"<h1>hello</h1>
/// <ul>
/// <li>alpha</li>
/// <li>beta</li>
/// </ul>
/// "#);
/// ```
pub fn write_html_fmt<'a, I, W>(writer: W, iter: I) -> std::fmt::Result
where
I: Iterator<Item = Event<'a>>,
W: Write,
W: std::fmt::Write,
{
HtmlWriter::new(iter, WriteWrapper(writer)).run()
HtmlWriter::new(iter, FmtWriter(writer)).run()
}
2 changes: 1 addition & 1 deletion pulldown-cmark/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,5 @@ pub fn pulldown_cmark(input: &str, opts: Options, broken_links: &mut Vec<BrokenL
);
let stdio = io::stdout();
let buffer = std::io::BufWriter::with_capacity(1024 * 1024, stdio.lock());
let _ = html::write_html(buffer, &mut p);
let _ = html::write_html_io(buffer, &mut p);
}