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

Use std::fmt::Write instead of custom StrWrite trait #493

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
80 changes: 24 additions & 56 deletions src/escape.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;
use std::str::from_utf8;

#[rustfmt::skip]
Expand All @@ -42,67 +42,36 @@ static AMP_ESCAPE: &str = "&";
static SLASH_ESCAPE: &str = "'";

/// This wrapper exists because we can't have both a blanket implementation
/// for all types implementing `Write` and types of the for `&mut W` where
/// `W: StrWrite`. Since we need the latter a lot, we choose to wrap
/// for all types implementing `Write` and types of the form `&mut W` where
/// `W: std::fmt::Write`. Since we need the latter a lot, we choose to wrap
/// `Write` types.
pub struct WriteWrapper<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<()>;

fn write_fmt(&mut self, args: Arguments) -> io::Result<()>;
}

impl<W> StrWrite for WriteWrapper<W>
where
W: Write,
{
#[inline]
fn write_str(&mut self, s: &str) -> io::Result<()> {
self.0.write_all(s.as_bytes())
}

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

impl<'w> StrWrite for String {
#[inline]
fn write_str(&mut self, s: &str) -> io::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())
}
}

impl<W> StrWrite for &'_ mut W
impl<W> fmt::Write for WriteWrapper<W>
where
W: StrWrite,
W: io::Write,
{
#[inline]
fn write_str(&mut self, s: &str) -> io::Result<()> {
(**self).write_str(s)
fn write_str(&mut self, s: &str) -> fmt::Result {
match self.0.write_all(s.as_bytes()) {
Ok(()) => Ok(()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could .map_err here.

Err(_) => Err(fmt::Error),
}
}

#[inline]
fn write_fmt(&mut self, args: Arguments) -> io::Result<()> {
(**self).write_fmt(args)
fn write_fmt(&mut self, args: Arguments) -> fmt::Result {
match self.0.write_fmt(args) {
Ok(()) => Ok(()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

Err(_) => Err(fmt::Error),
}
}
}

/// 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) -> fmt::Result
where
W: StrWrite,
W: fmt::Write,
{
let bytes = s.as_bytes();
let mut mark = 0;
Expand Down Expand Up @@ -152,7 +121,7 @@ static HTML_ESCAPES: [&str; 5] = ["", "&quot;", "&amp;", "&lt;", "&gt;"];

/// Writes the given string to the Write sink, replacing special HTML bytes
/// (<, >, &, ") by escape sequences.
pub fn escape_html<W: StrWrite>(w: W, s: &str) -> io::Result<()> {
pub fn escape_html<W: fmt::Write>(w: W, s: &str) -> fmt::Result {
#[cfg(all(target_arch = "x86_64", feature = "simd"))]
{
simd::escape_html(w, s)
Expand All @@ -163,7 +132,7 @@ pub fn escape_html<W: StrWrite>(w: W, s: &str) -> io::Result<()> {
}
}

fn escape_html_scalar<W: StrWrite>(mut w: W, s: &str) -> io::Result<()> {
fn escape_html_scalar<W: fmt::Write>(mut w: W, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
let mut mark = 0;
let mut i = 0;
Expand All @@ -190,14 +159,13 @@ fn escape_html_scalar<W: StrWrite>(mut w: W, s: &str) -> io::Result<()> {

#[cfg(all(target_arch = "x86_64", feature = "simd"))]
mod simd {
use super::StrWrite;
use std::arch::x86_64::*;
use std::io;
use std::fmt;
use std::mem::size_of;

const VECTOR_SIZE: usize = size_of::<__m128i>();

pub(super) fn escape_html<W: StrWrite>(mut w: W, s: &str) -> io::Result<()> {
pub(super) fn escape_html<W: fmt::Write>(mut w: W, s: &str) -> fmt::Result {
// The SIMD accelerated code uses the PSHUFB instruction, which is part
// of the SSSE3 instruction set. Further, we can only use this code if
// the buffer is at least one VECTOR_SIZE in length to prevent reading
Expand Down Expand Up @@ -277,9 +245,9 @@ mod simd {
bytes: &[u8],
mut offset: usize,
mut callback: F,
) -> io::Result<()>
) -> fmt::Result
where
F: FnMut(usize) -> io::Result<()>,
F: FnMut(usize) -> fmt::Result,
{
// The strategy here is to walk the byte buffer in chunks of VECTOR_SIZE (16)
// bytes at a time starting at the given offset. For each chunk, we compute a
Expand Down
23 changes: 12 additions & 11 deletions src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
//! HTML renderer that takes an iterator of events as input.

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

use crate::escape::{escape_href, escape_html, StrWrite, WriteWrapper};
use crate::escape::{escape_href, escape_html, WriteWrapper};
use crate::strings::CowStr;
use crate::Event::*;
use crate::{Alignment, CodeBlockKind, Event, LinkType, Tag};
Expand Down Expand Up @@ -52,7 +53,7 @@ struct HtmlWriter<'a, I, W> {
impl<'a, I, W> HtmlWriter<'a, I, W>
where
I: Iterator<Item = Event<'a>>,
W: StrWrite,
W: Write,
{
fn new(iter: I, writer: W) -> Self {
Self {
Expand All @@ -67,14 +68,14 @@ where
}

/// Writes a new line.
fn write_newline(&mut self) -> io::Result<()> {
fn write_newline(&mut self) -> fmt::Result {
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) -> fmt::Result {
self.writer.write_str(s)?;

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

fn run(mut self) -> io::Result<()> {
fn run(mut self) -> fmt::Result {
while let Some(event) = self.iter.next() {
match event {
Start(tag) => {
Expand Down Expand Up @@ -138,7 +139,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>) -> fmt::Result {
match tag {
Tag::Paragraph => {
if self.end_newline {
Expand Down Expand Up @@ -287,7 +288,7 @@ where
}
}

fn end_tag(&mut self, tag: Tag) -> io::Result<()> {
fn end_tag(&mut self, tag: Tag) -> fmt::Result {
match tag {
Tag::Paragraph => {
self.write("</p>\n")?;
Expand Down Expand Up @@ -354,7 +355,7 @@ where
}

// run raw text, consuming end tag
fn raw_text(&mut self) -> io::Result<()> {
fn raw_text(&mut self) -> fmt::Result {
let mut nest = 0;
while let Some(event) = self.iter.next() {
match event {
Expand Down Expand Up @@ -452,10 +453,10 @@ where
/// </ul>
/// "#);
/// ```
pub fn write_html<'a, I, W>(writer: W, iter: I) -> io::Result<()>
pub fn write_html<'a, I, W>(writer: W, iter: I) -> fmt::Result
where
I: Iterator<Item = Event<'a>>,
W: Write,
W: io::Write,
{
HtmlWriter::new(iter, WriteWrapper(writer)).run()
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub fn main() -> std::io::Result<()> {
let mut p = Parser::new_ext(&input, opts);
let stdio = io::stdout();
let buffer = std::io::BufWriter::with_capacity(1024 * 1024, stdio.lock());
html::write_html(buffer, &mut p)?;
html::write_html(buffer, &mut p).map_err(|_| io::ErrorKind::Other)?;
// Since the program will now terminate and the memory will be returned
// to the operating system anyway, there is no point in tidely cleaning
// up all the datastructures we have used. We shouldn't do this if we'd
Expand Down