PR #29 Allow rendering borrowed events
Merge branch 'render_ref' closes #29
This commit is contained in:
		
				commit
				
					
						8893281310
					
				
			
		
					 8 changed files with 605 additions and 464 deletions
				
			
		| 
						 | 
				
			
			@ -51,7 +51,9 @@ fn gen_html(c: &mut criterion::Criterion) {
 | 
			
		|||
                    || jotdown::Parser::new(input).collect::<Vec<_>>(),
 | 
			
		||||
                    |p| {
 | 
			
		||||
                        let mut s = String::new();
 | 
			
		||||
                        jotdown::html::Renderer.push(p.into_iter(), &mut s).unwrap();
 | 
			
		||||
                        jotdown::html::Renderer::default()
 | 
			
		||||
                            .push(p.into_iter(), &mut s)
 | 
			
		||||
                            .unwrap();
 | 
			
		||||
                        s
 | 
			
		||||
                    },
 | 
			
		||||
                    criterion::BatchSize::SmallInput,
 | 
			
		||||
| 
						 | 
				
			
			@ -62,6 +64,60 @@ fn gen_html(c: &mut criterion::Criterion) {
 | 
			
		|||
}
 | 
			
		||||
criterion_group!(html, gen_html);
 | 
			
		||||
 | 
			
		||||
fn gen_html_borrow(c: &mut criterion::Criterion) {
 | 
			
		||||
    let mut group = c.benchmark_group("html_borrow");
 | 
			
		||||
    for (name, input) in bench_input::INPUTS {
 | 
			
		||||
        group.throughput(criterion::Throughput::Elements(
 | 
			
		||||
            jotdown::Parser::new(input).count() as u64,
 | 
			
		||||
        ));
 | 
			
		||||
        group.bench_with_input(
 | 
			
		||||
            criterion::BenchmarkId::from_parameter(name),
 | 
			
		||||
            input,
 | 
			
		||||
            |b, &input| {
 | 
			
		||||
                b.iter_batched(
 | 
			
		||||
                    || jotdown::Parser::new(input).collect::<Vec<_>>(),
 | 
			
		||||
                    |p| {
 | 
			
		||||
                        let mut s = String::new();
 | 
			
		||||
                        jotdown::html::Renderer::default()
 | 
			
		||||
                            .push_borrowed(p.as_slice().iter(), &mut s)
 | 
			
		||||
                            .unwrap();
 | 
			
		||||
                        s
 | 
			
		||||
                    },
 | 
			
		||||
                    criterion::BatchSize::SmallInput,
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
criterion_group!(html_borrow, gen_html_borrow);
 | 
			
		||||
 | 
			
		||||
fn gen_html_clone(c: &mut criterion::Criterion) {
 | 
			
		||||
    let mut group = c.benchmark_group("html_clone");
 | 
			
		||||
    for (name, input) in bench_input::INPUTS {
 | 
			
		||||
        group.throughput(criterion::Throughput::Elements(
 | 
			
		||||
            jotdown::Parser::new(input).count() as u64,
 | 
			
		||||
        ));
 | 
			
		||||
        group.bench_with_input(
 | 
			
		||||
            criterion::BenchmarkId::from_parameter(name),
 | 
			
		||||
            input,
 | 
			
		||||
            |b, &input| {
 | 
			
		||||
                b.iter_batched(
 | 
			
		||||
                    || jotdown::Parser::new(input).collect::<Vec<_>>(),
 | 
			
		||||
                    |p| {
 | 
			
		||||
                        let mut s = String::new();
 | 
			
		||||
                        jotdown::html::Renderer::default()
 | 
			
		||||
                            .push(p.iter().cloned(), &mut s)
 | 
			
		||||
                            .unwrap();
 | 
			
		||||
                        s
 | 
			
		||||
                    },
 | 
			
		||||
                    criterion::BatchSize::SmallInput,
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
criterion_group!(html_clone, gen_html_clone);
 | 
			
		||||
 | 
			
		||||
fn gen_full(c: &mut criterion::Criterion) {
 | 
			
		||||
    let mut group = c.benchmark_group("full");
 | 
			
		||||
    for (name, input) in bench_input::INPUTS {
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +128,7 @@ fn gen_full(c: &mut criterion::Criterion) {
 | 
			
		|||
            |b, &input| {
 | 
			
		||||
                b.iter_with_large_drop(|| {
 | 
			
		||||
                    let mut s = String::new();
 | 
			
		||||
                    jotdown::html::Renderer
 | 
			
		||||
                    jotdown::html::Renderer::default()
 | 
			
		||||
                        .push(jotdown::Parser::new(input), &mut s)
 | 
			
		||||
                        .unwrap();
 | 
			
		||||
                    s
 | 
			
		||||
| 
						 | 
				
			
			@ -83,4 +139,4 @@ fn gen_full(c: &mut criterion::Criterion) {
 | 
			
		|||
}
 | 
			
		||||
criterion_group!(full, gen_full);
 | 
			
		||||
 | 
			
		||||
criterion_main!(block, inline, html, full);
 | 
			
		||||
criterion_main!(block, inline, html, html_borrow, html_clone, full);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ fn block_inline() -> Option<jotdown::Event<'static>> {
 | 
			
		|||
 | 
			
		||||
fn full() -> String {
 | 
			
		||||
    let mut s = String::new();
 | 
			
		||||
    jotdown::html::Renderer
 | 
			
		||||
    jotdown::html::Renderer::default()
 | 
			
		||||
        .push(jotdown::Parser::new(bench_input::ALL), &mut s)
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    s
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,8 @@ use jotdown::Render;
 | 
			
		|||
pub fn jotdown_render(djot: &str) -> String {
 | 
			
		||||
    let events = jotdown::Parser::new(djot);
 | 
			
		||||
    let mut html = String::new();
 | 
			
		||||
    jotdown::html::Renderer.push(events, &mut html).unwrap();
 | 
			
		||||
    jotdown::html::Renderer::default()
 | 
			
		||||
        .push(events, &mut html)
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    html
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										405
									
								
								src/html.rs
									
										
									
									
									
								
							
							
						
						
									
										405
									
								
								src/html.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,26 +1,4 @@
 | 
			
		|||
//! An HTML renderer that takes an iterator of [`Event`]s and emits HTML.
 | 
			
		||||
//!
 | 
			
		||||
//! The HTML can be written to either a [`std::fmt::Write`] or a [`std::io::Write`] object.
 | 
			
		||||
//!
 | 
			
		||||
//! # Examples
 | 
			
		||||
//!
 | 
			
		||||
//! Push to a [`String`] (implements [`std::fmt::Write`]):
 | 
			
		||||
//!
 | 
			
		||||
//! ```
 | 
			
		||||
//! # use jotdown::Render;
 | 
			
		||||
//! # let events = std::iter::empty();
 | 
			
		||||
//! let mut html = String::new();
 | 
			
		||||
//! 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::Renderer.write(events, &mut out).unwrap();
 | 
			
		||||
//! ```
 | 
			
		||||
 | 
			
		||||
use crate::Alignment;
 | 
			
		||||
use crate::Container;
 | 
			
		||||
| 
						 | 
				
			
			@ -31,91 +9,74 @@ use crate::OrderedListNumbering::*;
 | 
			
		|||
use crate::Render;
 | 
			
		||||
use crate::SpanLinkType;
 | 
			
		||||
 | 
			
		||||
pub struct Renderer;
 | 
			
		||||
 | 
			
		||||
impl Render for Renderer {
 | 
			
		||||
    fn push<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write>(
 | 
			
		||||
        &self,
 | 
			
		||||
        events: I,
 | 
			
		||||
        out: W,
 | 
			
		||||
    ) -> std::fmt::Result {
 | 
			
		||||
        Writer::new(events, out).write()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum Raw {
 | 
			
		||||
    None,
 | 
			
		||||
    Html,
 | 
			
		||||
    Other,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct FilteredEvents<I> {
 | 
			
		||||
    events: I,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'s, I: Iterator<Item = Event<'s>>> Iterator for FilteredEvents<I> {
 | 
			
		||||
    type Item = Event<'s>;
 | 
			
		||||
 | 
			
		||||
    fn next(&mut self) -> Option<Self::Item> {
 | 
			
		||||
        let mut ev = self.events.next();
 | 
			
		||||
        while matches!(ev, Some(Event::Blankline | Event::Escape)) {
 | 
			
		||||
            ev = self.events.next();
 | 
			
		||||
        }
 | 
			
		||||
        ev
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Writer<'s, I: Iterator<Item = Event<'s>>, W> {
 | 
			
		||||
    events: std::iter::Peekable<FilteredEvents<I>>,
 | 
			
		||||
    out: W,
 | 
			
		||||
pub struct Renderer {
 | 
			
		||||
    raw: Raw,
 | 
			
		||||
    img_alt_text: usize,
 | 
			
		||||
    list_tightness: Vec<bool>,
 | 
			
		||||
    encountered_footnote: bool,
 | 
			
		||||
    footnote_number: Option<std::num::NonZeroUsize>,
 | 
			
		||||
    footnote_backlink_written: bool,
 | 
			
		||||
    first_line: bool,
 | 
			
		||||
    close_para: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		||||
    fn new(events: I, out: W) -> Self {
 | 
			
		||||
impl Default for Renderer {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            events: FilteredEvents { events }.peekable(),
 | 
			
		||||
            out,
 | 
			
		||||
            raw: Raw::None,
 | 
			
		||||
            img_alt_text: 0,
 | 
			
		||||
            list_tightness: Vec::new(),
 | 
			
		||||
            encountered_footnote: false,
 | 
			
		||||
            footnote_number: None,
 | 
			
		||||
            footnote_backlink_written: false,
 | 
			
		||||
            first_line: true,
 | 
			
		||||
            close_para: false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Render for Renderer {
 | 
			
		||||
    fn render_event<'s, W>(&mut self, e: &Event<'s>, mut out: W) -> std::fmt::Result
 | 
			
		||||
    where
 | 
			
		||||
        W: std::fmt::Write,
 | 
			
		||||
    {
 | 
			
		||||
        if matches!(&e, Event::Blankline | Event::Escape) {
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let close_para = self.close_para;
 | 
			
		||||
        if close_para {
 | 
			
		||||
            self.close_para = false;
 | 
			
		||||
            if !matches!(&e, Event::End(Container::Footnote { .. })) {
 | 
			
		||||
                // no need to add href before para close
 | 
			
		||||
                out.write_str("</p>")?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    fn write(&mut self) -> std::fmt::Result {
 | 
			
		||||
        while let Some(e) = self.events.next() {
 | 
			
		||||
        match e {
 | 
			
		||||
            Event::Start(c, attrs) => {
 | 
			
		||||
                if c.is_block() && !self.first_line {
 | 
			
		||||
                        self.out.write_char('\n')?;
 | 
			
		||||
                    out.write_char('\n')?;
 | 
			
		||||
                }
 | 
			
		||||
                if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    return Ok(());
 | 
			
		||||
                }
 | 
			
		||||
                match &c {
 | 
			
		||||
                        Container::Blockquote => self.out.write_str("<blockquote")?,
 | 
			
		||||
                    Container::Blockquote => out.write_str("<blockquote")?,
 | 
			
		||||
                    Container::List { kind, tight } => {
 | 
			
		||||
                        self.list_tightness.push(*tight);
 | 
			
		||||
                        match kind {
 | 
			
		||||
                                ListKind::Unordered | ListKind::Task => {
 | 
			
		||||
                                    self.out.write_str("<ul")?
 | 
			
		||||
                                }
 | 
			
		||||
                            ListKind::Unordered | ListKind::Task => out.write_str("<ul")?,
 | 
			
		||||
                            ListKind::Ordered {
 | 
			
		||||
                                numbering, start, ..
 | 
			
		||||
                            } => {
 | 
			
		||||
                                    self.out.write_str("<ol")?;
 | 
			
		||||
                                out.write_str("<ol")?;
 | 
			
		||||
                                if *start > 1 {
 | 
			
		||||
                                        write!(self.out, r#" start="{}""#, start)?;
 | 
			
		||||
                                    write!(out, r#" start="{}""#, start)?;
 | 
			
		||||
                                }
 | 
			
		||||
                                if let Some(ty) = match numbering {
 | 
			
		||||
                                    Decimal => None,
 | 
			
		||||
| 
						 | 
				
			
			@ -124,87 +85,85 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
                                    RomanLower => Some('i'),
 | 
			
		||||
                                    RomanUpper => Some('I'),
 | 
			
		||||
                                } {
 | 
			
		||||
                                        write!(self.out, r#" type="{}""#, ty)?;
 | 
			
		||||
                                    write!(out, r#" type="{}""#, ty)?;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Container::ListItem | Container::TaskListItem { .. } => {
 | 
			
		||||
                            self.out.write_str("<li")?;
 | 
			
		||||
                        out.write_str("<li")?;
 | 
			
		||||
                    }
 | 
			
		||||
                        Container::DescriptionList => self.out.write_str("<dl")?,
 | 
			
		||||
                        Container::DescriptionDetails => self.out.write_str("<dd")?,
 | 
			
		||||
                    Container::DescriptionList => out.write_str("<dl")?,
 | 
			
		||||
                    Container::DescriptionDetails => out.write_str("<dd")?,
 | 
			
		||||
                    Container::Footnote { number, .. } => {
 | 
			
		||||
                        assert!(self.footnote_number.is_none());
 | 
			
		||||
                        self.footnote_number = Some((*number).try_into().unwrap());
 | 
			
		||||
                        if !self.encountered_footnote {
 | 
			
		||||
                            self.encountered_footnote = true;
 | 
			
		||||
                                self.out
 | 
			
		||||
                                    .write_str("<section role=\"doc-endnotes\">\n<hr>\n<ol>\n")?;
 | 
			
		||||
                            out.write_str("<section role=\"doc-endnotes\">\n<hr>\n<ol>\n")?;
 | 
			
		||||
                        }
 | 
			
		||||
                            write!(self.out, "<li id=\"fn{}\">", number)?;
 | 
			
		||||
                            self.footnote_backlink_written = false;
 | 
			
		||||
                            continue;
 | 
			
		||||
                        write!(out, "<li id=\"fn{}\">", number)?;
 | 
			
		||||
                        return Ok(());
 | 
			
		||||
                    }
 | 
			
		||||
                        Container::Table => self.out.write_str("<table")?,
 | 
			
		||||
                        Container::TableRow { .. } => self.out.write_str("<tr")?,
 | 
			
		||||
                        Container::Section { .. } => self.out.write_str("<section")?,
 | 
			
		||||
                        Container::Div { .. } => self.out.write_str("<div")?,
 | 
			
		||||
                    Container::Table => out.write_str("<table")?,
 | 
			
		||||
                    Container::TableRow { .. } => out.write_str("<tr")?,
 | 
			
		||||
                    Container::Section { .. } => out.write_str("<section")?,
 | 
			
		||||
                    Container::Div { .. } => out.write_str("<div")?,
 | 
			
		||||
                    Container::Paragraph => {
 | 
			
		||||
                        if matches!(self.list_tightness.last(), Some(true)) {
 | 
			
		||||
                                continue;
 | 
			
		||||
                            return Ok(());
 | 
			
		||||
                        }
 | 
			
		||||
                            self.out.write_str("<p")?;
 | 
			
		||||
                        out.write_str("<p")?;
 | 
			
		||||
                    }
 | 
			
		||||
                        Container::Heading { level, .. } => write!(self.out, "<h{}", level)?,
 | 
			
		||||
                        Container::TableCell { head: false, .. } => self.out.write_str("<td")?,
 | 
			
		||||
                        Container::TableCell { head: true, .. } => self.out.write_str("<th")?,
 | 
			
		||||
                        Container::Caption => self.out.write_str("<caption")?,
 | 
			
		||||
                        Container::DescriptionTerm => self.out.write_str("<dt")?,
 | 
			
		||||
                        Container::CodeBlock { .. } => self.out.write_str("<pre")?,
 | 
			
		||||
                        Container::Span | Container::Math { .. } => self.out.write_str("<span")?,
 | 
			
		||||
                    Container::Heading { level, .. } => write!(out, "<h{}", level)?,
 | 
			
		||||
                    Container::TableCell { head: false, .. } => out.write_str("<td")?,
 | 
			
		||||
                    Container::TableCell { head: true, .. } => out.write_str("<th")?,
 | 
			
		||||
                    Container::Caption => out.write_str("<caption")?,
 | 
			
		||||
                    Container::DescriptionTerm => out.write_str("<dt")?,
 | 
			
		||||
                    Container::CodeBlock { .. } => out.write_str("<pre")?,
 | 
			
		||||
                    Container::Span | Container::Math { .. } => out.write_str("<span")?,
 | 
			
		||||
                    Container::Link(dst, ty) => {
 | 
			
		||||
                        if matches!(ty, LinkType::Span(SpanLinkType::Unresolved)) {
 | 
			
		||||
                                self.out.write_str("<a")?;
 | 
			
		||||
                            out.write_str("<a")?;
 | 
			
		||||
                        } else {
 | 
			
		||||
                                self.out.write_str(r#"<a href=""#)?;
 | 
			
		||||
                            out.write_str(r#"<a href=""#)?;
 | 
			
		||||
                            if matches!(ty, LinkType::Email) {
 | 
			
		||||
                                    self.out.write_str("mailto:")?;
 | 
			
		||||
                                out.write_str("mailto:")?;
 | 
			
		||||
                            }
 | 
			
		||||
                                self.write_attr(dst)?;
 | 
			
		||||
                                self.out.write_char('"')?;
 | 
			
		||||
                            write_attr(dst, &mut out)?;
 | 
			
		||||
                            out.write_char('"')?;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Container::Image(..) => {
 | 
			
		||||
                        self.img_alt_text += 1;
 | 
			
		||||
                        if self.img_alt_text == 1 {
 | 
			
		||||
                                self.out.write_str("<img")?;
 | 
			
		||||
                            out.write_str("<img")?;
 | 
			
		||||
                        } else {
 | 
			
		||||
                                continue;
 | 
			
		||||
                            return Ok(());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                        Container::Verbatim => self.out.write_str("<code")?,
 | 
			
		||||
                    Container::Verbatim => out.write_str("<code")?,
 | 
			
		||||
                    Container::RawBlock { format } | Container::RawInline { format } => {
 | 
			
		||||
                        self.raw = if format == &"html" {
 | 
			
		||||
                            Raw::Html
 | 
			
		||||
                        } else {
 | 
			
		||||
                            Raw::Other
 | 
			
		||||
                        };
 | 
			
		||||
                            continue;
 | 
			
		||||
                        return Ok(());
 | 
			
		||||
                    }
 | 
			
		||||
                        Container::Subscript => self.out.write_str("<sub")?,
 | 
			
		||||
                        Container::Superscript => self.out.write_str("<sup")?,
 | 
			
		||||
                        Container::Insert => self.out.write_str("<ins")?,
 | 
			
		||||
                        Container::Delete => self.out.write_str("<del")?,
 | 
			
		||||
                        Container::Strong => self.out.write_str("<strong")?,
 | 
			
		||||
                        Container::Emphasis => self.out.write_str("<em")?,
 | 
			
		||||
                        Container::Mark => self.out.write_str("<mark")?,
 | 
			
		||||
                    Container::Subscript => out.write_str("<sub")?,
 | 
			
		||||
                    Container::Superscript => out.write_str("<sup")?,
 | 
			
		||||
                    Container::Insert => out.write_str("<ins")?,
 | 
			
		||||
                    Container::Delete => out.write_str("<del")?,
 | 
			
		||||
                    Container::Strong => out.write_str("<strong")?,
 | 
			
		||||
                    Container::Emphasis => out.write_str("<em")?,
 | 
			
		||||
                    Container::Mark => out.write_str("<mark")?,
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (a, v) in attrs.iter().filter(|(a, _)| *a != "class") {
 | 
			
		||||
                        write!(self.out, r#" {}=""#, a)?;
 | 
			
		||||
                        v.parts().try_for_each(|part| self.write_attr(part))?;
 | 
			
		||||
                        self.out.write_char('"')?;
 | 
			
		||||
                    write!(out, r#" {}=""#, a)?;
 | 
			
		||||
                    v.parts().try_for_each(|part| write_attr(part, &mut out))?;
 | 
			
		||||
                    out.write_char('"')?;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if let Container::Heading {
 | 
			
		||||
| 
						 | 
				
			
			@ -215,9 +174,9 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
                | Container::Section { id } = &c
 | 
			
		||||
                {
 | 
			
		||||
                    if !attrs.iter().any(|(a, _)| a == "id") {
 | 
			
		||||
                            self.out.write_str(r#" id=""#)?;
 | 
			
		||||
                            self.write_attr(id)?;
 | 
			
		||||
                            self.out.write_char('"')?;
 | 
			
		||||
                        out.write_str(r#" id=""#)?;
 | 
			
		||||
                        write_attr(id, &mut out)?;
 | 
			
		||||
                        out.write_char('"')?;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -233,7 +192,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
                            | Container::TaskListItem { .. }
 | 
			
		||||
                    )
 | 
			
		||||
                {
 | 
			
		||||
                        self.out.write_str(r#" class=""#)?;
 | 
			
		||||
                    out.write_str(r#" class=""#)?;
 | 
			
		||||
                    let mut first_written = false;
 | 
			
		||||
                    if let Some(cls) = match c {
 | 
			
		||||
                        Container::List {
 | 
			
		||||
| 
						 | 
				
			
			@ -247,7 +206,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
                        _ => None,
 | 
			
		||||
                    } {
 | 
			
		||||
                        first_written = true;
 | 
			
		||||
                            self.out.write_str(cls)?;
 | 
			
		||||
                        out.write_str(cls)?;
 | 
			
		||||
                    }
 | 
			
		||||
                    for cls in attrs
 | 
			
		||||
                        .iter()
 | 
			
		||||
| 
						 | 
				
			
			@ -255,19 +214,20 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
                        .map(|(_, cls)| cls)
 | 
			
		||||
                    {
 | 
			
		||||
                        if first_written {
 | 
			
		||||
                                self.out.write_char(' ')?;
 | 
			
		||||
                            out.write_char(' ')?;
 | 
			
		||||
                        }
 | 
			
		||||
                        first_written = true;
 | 
			
		||||
                            cls.parts().try_for_each(|part| self.write_attr(part))?;
 | 
			
		||||
                        cls.parts()
 | 
			
		||||
                            .try_for_each(|part| write_attr(part, &mut out))?;
 | 
			
		||||
                    }
 | 
			
		||||
                    // div class goes after classes from attrs
 | 
			
		||||
                    if let Container::Div { class: Some(cls) } = c {
 | 
			
		||||
                        if first_written {
 | 
			
		||||
                                self.out.write_char(' ')?;
 | 
			
		||||
                            out.write_char(' ')?;
 | 
			
		||||
                        }
 | 
			
		||||
                            self.out.write_str(cls)?;
 | 
			
		||||
                        out.write_str(cls)?;
 | 
			
		||||
                    }
 | 
			
		||||
                        self.out.write_char('"')?;
 | 
			
		||||
                    out.write_char('"')?;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                match c {
 | 
			
		||||
| 
						 | 
				
			
			@ -280,109 +240,101 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
                            Alignment::Center => "center",
 | 
			
		||||
                            Alignment::Right => "right",
 | 
			
		||||
                        };
 | 
			
		||||
                            write!(self.out, r#" style="text-align: {};">"#, a)?;
 | 
			
		||||
                        write!(out, r#" style="text-align: {};">"#, a)?;
 | 
			
		||||
                    }
 | 
			
		||||
                    Container::CodeBlock { lang } => {
 | 
			
		||||
                        if let Some(l) = lang {
 | 
			
		||||
                                self.out.write_str(r#"><code class="language-"#)?;
 | 
			
		||||
                                self.write_attr(l)?;
 | 
			
		||||
                                self.out.write_str(r#"">"#)?;
 | 
			
		||||
                            out.write_str(r#"><code class="language-"#)?;
 | 
			
		||||
                            write_attr(l, &mut out)?;
 | 
			
		||||
                            out.write_str(r#"">"#)?;
 | 
			
		||||
                        } else {
 | 
			
		||||
                                self.out.write_str("><code>")?;
 | 
			
		||||
                            out.write_str("><code>")?;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Container::Image(..) => {
 | 
			
		||||
                        if self.img_alt_text == 1 {
 | 
			
		||||
                                self.out.write_str(r#" alt=""#)?;
 | 
			
		||||
                            out.write_str(r#" alt=""#)?;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Container::Math { display } => {
 | 
			
		||||
                            self.out
 | 
			
		||||
                                .write_str(if display { r#">\["# } else { r#">\("# })?;
 | 
			
		||||
                        out.write_str(if *display { r#">\["# } else { r#">\("# })?;
 | 
			
		||||
                    }
 | 
			
		||||
                        _ => self.out.write_char('>')?,
 | 
			
		||||
                    _ => out.write_char('>')?,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Event::End(c) => {
 | 
			
		||||
                if c.is_block_container() && !matches!(c, Container::Footnote { .. }) {
 | 
			
		||||
                        self.out.write_char('\n')?;
 | 
			
		||||
                    out.write_char('\n')?;
 | 
			
		||||
                }
 | 
			
		||||
                if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    return Ok(());
 | 
			
		||||
                }
 | 
			
		||||
                match c {
 | 
			
		||||
                        Container::Blockquote => self.out.write_str("</blockquote>")?,
 | 
			
		||||
                    Container::Blockquote => out.write_str("</blockquote>")?,
 | 
			
		||||
                    Container::List {
 | 
			
		||||
                        kind: ListKind::Unordered | ListKind::Task,
 | 
			
		||||
                        ..
 | 
			
		||||
                    } => {
 | 
			
		||||
                        self.list_tightness.pop();
 | 
			
		||||
                            self.out.write_str("</ul>")?;
 | 
			
		||||
                        out.write_str("</ul>")?;
 | 
			
		||||
                    }
 | 
			
		||||
                    Container::List {
 | 
			
		||||
                        kind: ListKind::Ordered { .. },
 | 
			
		||||
                        ..
 | 
			
		||||
                        } => self.out.write_str("</ol>")?,
 | 
			
		||||
                    } => out.write_str("</ol>")?,
 | 
			
		||||
                    Container::ListItem | Container::TaskListItem { .. } => {
 | 
			
		||||
                            self.out.write_str("</li>")?;
 | 
			
		||||
                        out.write_str("</li>")?;
 | 
			
		||||
                    }
 | 
			
		||||
                        Container::DescriptionList => self.out.write_str("</dl>")?,
 | 
			
		||||
                        Container::DescriptionDetails => self.out.write_str("</dd>")?,
 | 
			
		||||
                    Container::DescriptionList => out.write_str("</dl>")?,
 | 
			
		||||
                    Container::DescriptionDetails => out.write_str("</dd>")?,
 | 
			
		||||
                    Container::Footnote { number, .. } => {
 | 
			
		||||
                            if !self.footnote_backlink_written {
 | 
			
		||||
                        if !close_para {
 | 
			
		||||
                            // create a new paragraph
 | 
			
		||||
                            out.write_str("\n<p>")?;
 | 
			
		||||
                        }
 | 
			
		||||
                        write!(
 | 
			
		||||
                                    self.out,
 | 
			
		||||
                                    "\n<p><a href=\"#fnref{}\" role=\"doc-backlink\">↩︎︎</a></p>",
 | 
			
		||||
                            out,
 | 
			
		||||
                            r##"<a href="#fnref{}" role="doc-backlink">↩︎︎</a></p>"##,
 | 
			
		||||
                            number,
 | 
			
		||||
                        )?;
 | 
			
		||||
                            }
 | 
			
		||||
                            self.out.write_str("\n</li>")?;
 | 
			
		||||
                        out.write_str("\n</li>")?;
 | 
			
		||||
                        self.footnote_number = None;
 | 
			
		||||
                    }
 | 
			
		||||
                        Container::Table => self.out.write_str("</table>")?,
 | 
			
		||||
                        Container::TableRow { .. } => self.out.write_str("</tr>")?,
 | 
			
		||||
                        Container::Section { .. } => self.out.write_str("</section>")?,
 | 
			
		||||
                        Container::Div { .. } => self.out.write_str("</div>")?,
 | 
			
		||||
                    Container::Table => out.write_str("</table>")?,
 | 
			
		||||
                    Container::TableRow { .. } => out.write_str("</tr>")?,
 | 
			
		||||
                    Container::Section { .. } => out.write_str("</section>")?,
 | 
			
		||||
                    Container::Div { .. } => out.write_str("</div>")?,
 | 
			
		||||
                    Container::Paragraph => {
 | 
			
		||||
                        if matches!(self.list_tightness.last(), Some(true)) {
 | 
			
		||||
                                continue;
 | 
			
		||||
                            return Ok(());
 | 
			
		||||
                        }
 | 
			
		||||
                            if let Some(num) = self.footnote_number {
 | 
			
		||||
                                if matches!(
 | 
			
		||||
                                    self.events.peek(),
 | 
			
		||||
                                    Some(Event::End(Container::Footnote { .. }))
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    write!(
 | 
			
		||||
                                        self.out,
 | 
			
		||||
                                        r##"<a href="#fnref{}" role="doc-backlink">↩︎︎</a>"##,
 | 
			
		||||
                                        num
 | 
			
		||||
                                    )?;
 | 
			
		||||
                                    self.footnote_backlink_written = true;
 | 
			
		||||
                        if self.footnote_number.is_none() {
 | 
			
		||||
                            out.write_str("</p>")?;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            self.close_para = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                            self.out.write_str("</p>")?;
 | 
			
		||||
                        }
 | 
			
		||||
                        Container::Heading { level, .. } => write!(self.out, "</h{}>", level)?,
 | 
			
		||||
                        Container::TableCell { head: false, .. } => self.out.write_str("</td>")?,
 | 
			
		||||
                        Container::TableCell { head: true, .. } => self.out.write_str("</th>")?,
 | 
			
		||||
                        Container::Caption => self.out.write_str("</caption>")?,
 | 
			
		||||
                        Container::DescriptionTerm => self.out.write_str("</dt>")?,
 | 
			
		||||
                        Container::CodeBlock { .. } => self.out.write_str("</code></pre>")?,
 | 
			
		||||
                        Container::Span => self.out.write_str("</span>")?,
 | 
			
		||||
                        Container::Link(..) => self.out.write_str("</a>")?,
 | 
			
		||||
                    Container::Heading { level, .. } => write!(out, "</h{}>", level)?,
 | 
			
		||||
                    Container::TableCell { head: false, .. } => out.write_str("</td>")?,
 | 
			
		||||
                    Container::TableCell { head: true, .. } => out.write_str("</th>")?,
 | 
			
		||||
                    Container::Caption => out.write_str("</caption>")?,
 | 
			
		||||
                    Container::DescriptionTerm => out.write_str("</dt>")?,
 | 
			
		||||
                    Container::CodeBlock { .. } => out.write_str("</code></pre>")?,
 | 
			
		||||
                    Container::Span => out.write_str("</span>")?,
 | 
			
		||||
                    Container::Link(..) => out.write_str("</a>")?,
 | 
			
		||||
                    Container::Image(src, ..) => {
 | 
			
		||||
                        if self.img_alt_text == 1 {
 | 
			
		||||
                            if !src.is_empty() {
 | 
			
		||||
                                    self.out.write_str(r#"" src=""#)?;
 | 
			
		||||
                                    self.write_attr(&src)?;
 | 
			
		||||
                                out.write_str(r#"" src=""#)?;
 | 
			
		||||
                                write_attr(src, &mut out)?;
 | 
			
		||||
                            }
 | 
			
		||||
                                self.out.write_str(r#"">"#)?;
 | 
			
		||||
                            out.write_str(r#"">"#)?;
 | 
			
		||||
                        }
 | 
			
		||||
                        self.img_alt_text -= 1;
 | 
			
		||||
                    }
 | 
			
		||||
                        Container::Verbatim => self.out.write_str("</code>")?,
 | 
			
		||||
                    Container::Verbatim => out.write_str("</code>")?,
 | 
			
		||||
                    Container::Math { display } => {
 | 
			
		||||
                            self.out.write_str(if display {
 | 
			
		||||
                        out.write_str(if *display {
 | 
			
		||||
                            r#"\]</span>"#
 | 
			
		||||
                        } else {
 | 
			
		||||
                            r#"\)</span>"#
 | 
			
		||||
| 
						 | 
				
			
			@ -391,62 +343,88 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
                    Container::RawBlock { .. } | Container::RawInline { .. } => {
 | 
			
		||||
                        self.raw = Raw::None;
 | 
			
		||||
                    }
 | 
			
		||||
                        Container::Subscript => self.out.write_str("</sub>")?,
 | 
			
		||||
                        Container::Superscript => self.out.write_str("</sup>")?,
 | 
			
		||||
                        Container::Insert => self.out.write_str("</ins>")?,
 | 
			
		||||
                        Container::Delete => self.out.write_str("</del>")?,
 | 
			
		||||
                        Container::Strong => self.out.write_str("</strong>")?,
 | 
			
		||||
                        Container::Emphasis => self.out.write_str("</em>")?,
 | 
			
		||||
                        Container::Mark => self.out.write_str("</mark>")?,
 | 
			
		||||
                    Container::Subscript => out.write_str("</sub>")?,
 | 
			
		||||
                    Container::Superscript => out.write_str("</sup>")?,
 | 
			
		||||
                    Container::Insert => out.write_str("</ins>")?,
 | 
			
		||||
                    Container::Delete => out.write_str("</del>")?,
 | 
			
		||||
                    Container::Strong => out.write_str("</strong>")?,
 | 
			
		||||
                    Container::Emphasis => out.write_str("</em>")?,
 | 
			
		||||
                    Container::Mark => out.write_str("</mark>")?,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Event::Str(s) => match self.raw {
 | 
			
		||||
                    Raw::None if self.img_alt_text > 0 => self.write_attr(&s)?,
 | 
			
		||||
                    Raw::None => self.write_text(&s)?,
 | 
			
		||||
                    Raw::Html => self.out.write_str(&s)?,
 | 
			
		||||
                Raw::None if self.img_alt_text > 0 => write_attr(s, &mut out)?,
 | 
			
		||||
                Raw::None => write_text(s, &mut out)?,
 | 
			
		||||
                Raw::Html => out.write_str(s)?,
 | 
			
		||||
                Raw::Other => {}
 | 
			
		||||
            },
 | 
			
		||||
            Event::FootnoteReference(_tag, number) => {
 | 
			
		||||
                if self.img_alt_text == 0 {
 | 
			
		||||
                    write!(
 | 
			
		||||
                            self.out,
 | 
			
		||||
                        out,
 | 
			
		||||
                        r##"<a id="fnref{}" href="#fn{}" role="doc-noteref"><sup>{}</sup></a>"##,
 | 
			
		||||
                        number, number, number
 | 
			
		||||
                    )?;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
                Event::Symbol(sym) => write!(self.out, ":{}:", sym)?,
 | 
			
		||||
                Event::LeftSingleQuote => self.out.write_str("‘")?,
 | 
			
		||||
                Event::RightSingleQuote => self.out.write_str("’")?,
 | 
			
		||||
                Event::LeftDoubleQuote => self.out.write_str("“")?,
 | 
			
		||||
                Event::RightDoubleQuote => self.out.write_str("”")?,
 | 
			
		||||
                Event::Ellipsis => self.out.write_str("…")?,
 | 
			
		||||
                Event::EnDash => self.out.write_str("–")?,
 | 
			
		||||
                Event::EmDash => self.out.write_str("—")?,
 | 
			
		||||
                Event::NonBreakingSpace => self.out.write_str(" ")?,
 | 
			
		||||
                Event::Hardbreak => self.out.write_str("<br>\n")?,
 | 
			
		||||
                Event::Softbreak => self.out.write_char('\n')?,
 | 
			
		||||
            Event::Symbol(sym) => write!(out, ":{}:", sym)?,
 | 
			
		||||
            Event::LeftSingleQuote => out.write_str("‘")?,
 | 
			
		||||
            Event::RightSingleQuote => out.write_str("’")?,
 | 
			
		||||
            Event::LeftDoubleQuote => out.write_str("“")?,
 | 
			
		||||
            Event::RightDoubleQuote => out.write_str("”")?,
 | 
			
		||||
            Event::Ellipsis => out.write_str("…")?,
 | 
			
		||||
            Event::EnDash => out.write_str("–")?,
 | 
			
		||||
            Event::EmDash => out.write_str("—")?,
 | 
			
		||||
            Event::NonBreakingSpace => out.write_str(" ")?,
 | 
			
		||||
            Event::Hardbreak => out.write_str("<br>\n")?,
 | 
			
		||||
            Event::Softbreak => out.write_char('\n')?,
 | 
			
		||||
            Event::Escape | Event::Blankline => unreachable!("filtered out"),
 | 
			
		||||
            Event::ThematicBreak(attrs) => {
 | 
			
		||||
                    self.out.write_str("\n<hr")?;
 | 
			
		||||
                out.write_str("\n<hr")?;
 | 
			
		||||
                for (a, v) in attrs.iter() {
 | 
			
		||||
                        write!(self.out, r#" {}=""#, a)?;
 | 
			
		||||
                        v.parts().try_for_each(|part| self.write_attr(part))?;
 | 
			
		||||
                        self.out.write_char('"')?;
 | 
			
		||||
                    write!(out, r#" {}=""#, a)?;
 | 
			
		||||
                    v.parts().try_for_each(|part| write_attr(part, &mut out))?;
 | 
			
		||||
                    out.write_char('"')?;
 | 
			
		||||
                }
 | 
			
		||||
                    self.out.write_str(">")?;
 | 
			
		||||
                out.write_str(">")?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        self.first_line = false;
 | 
			
		||||
        }
 | 
			
		||||
        if self.encountered_footnote {
 | 
			
		||||
            self.out.write_str("\n</ol>\n</section>")?;
 | 
			
		||||
        }
 | 
			
		||||
        self.out.write_char('\n')?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_escape(&mut self, mut s: &str, escape_quotes: bool) -> std::fmt::Result {
 | 
			
		||||
    fn render_epilogue<W>(&mut self, mut out: W) -> std::fmt::Result
 | 
			
		||||
    where
 | 
			
		||||
        W: std::fmt::Write,
 | 
			
		||||
    {
 | 
			
		||||
        if self.encountered_footnote {
 | 
			
		||||
            out.write_str("\n</ol>\n</section>")?;
 | 
			
		||||
        }
 | 
			
		||||
        out.write_char('\n')?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn write_text<W>(s: &str, out: W) -> std::fmt::Result
 | 
			
		||||
where
 | 
			
		||||
    W: std::fmt::Write,
 | 
			
		||||
{
 | 
			
		||||
    write_escape(s, false, out)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn write_attr<W>(s: &str, out: W) -> std::fmt::Result
 | 
			
		||||
where
 | 
			
		||||
    W: std::fmt::Write,
 | 
			
		||||
{
 | 
			
		||||
    write_escape(s, true, out)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn write_escape<W>(mut s: &str, escape_quotes: bool, mut out: W) -> std::fmt::Result
 | 
			
		||||
where
 | 
			
		||||
    W: std::fmt::Write,
 | 
			
		||||
{
 | 
			
		||||
    let mut ent = "";
 | 
			
		||||
    while let Some(i) = s.find(|c| {
 | 
			
		||||
        match c {
 | 
			
		||||
| 
						 | 
				
			
			@ -461,18 +439,9 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
 | 
			
		|||
            true
 | 
			
		||||
        })
 | 
			
		||||
    }) {
 | 
			
		||||
            self.out.write_str(&s[..i])?;
 | 
			
		||||
            self.out.write_str(ent)?;
 | 
			
		||||
        out.write_str(&s[..i])?;
 | 
			
		||||
        out.write_str(ent)?;
 | 
			
		||||
        s = &s[i + 1..];
 | 
			
		||||
    }
 | 
			
		||||
        self.out.write_str(s)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_text(&mut self, s: &str) -> std::fmt::Result {
 | 
			
		||||
        self.write_escape(s, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_attr(&mut self, s: &str) -> std::fmt::Result {
 | 
			
		||||
        self.write_escape(s, true)
 | 
			
		||||
    }
 | 
			
		||||
    out.write_str(s)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										186
									
								
								src/lib.rs
									
										
									
									
									
								
							
							
						
						
									
										186
									
								
								src/lib.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -20,7 +20,7 @@
 | 
			
		|||
//! let djot_input = "hello *world*!";
 | 
			
		||||
//! let events = jotdown::Parser::new(djot_input);
 | 
			
		||||
//! let mut html = String::new();
 | 
			
		||||
//! jotdown::html::Renderer.push(events, &mut html);
 | 
			
		||||
//! jotdown::html::Renderer::default().push(events, &mut html);
 | 
			
		||||
//! assert_eq!(html, "<p>hello <strong>world</strong>!</p>\n");
 | 
			
		||||
//! # }
 | 
			
		||||
//! ```
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +41,7 @@
 | 
			
		|||
//!         e => e,
 | 
			
		||||
//!     });
 | 
			
		||||
//! let mut html = String::new();
 | 
			
		||||
//! jotdown::html::Renderer.push(events, &mut html);
 | 
			
		||||
//! jotdown::html::Renderer::default().push(events, &mut html);
 | 
			
		||||
//! assert_eq!(html, "<p>a <a href=\"https://example.net\">link</a></p>\n");
 | 
			
		||||
//! # }
 | 
			
		||||
//! ```
 | 
			
		||||
| 
						 | 
				
			
			@ -67,52 +67,162 @@ pub use attr::{AttributeValue, AttributeValueParts, Attributes};
 | 
			
		|||
 | 
			
		||||
type CowStr<'s> = std::borrow::Cow<'s, str>;
 | 
			
		||||
 | 
			
		||||
/// A trait for rendering [`Event`]s to an output format.
 | 
			
		||||
///
 | 
			
		||||
/// The output can be written to either a [`std::fmt::Write`] or a [`std::io::Write`] object.
 | 
			
		||||
///
 | 
			
		||||
/// If ownership of the [`Event`]s cannot be given to the renderer, use [`Render::push_borrowed`]
 | 
			
		||||
/// or [`Render::write_borrowed`].
 | 
			
		||||
///
 | 
			
		||||
/// An implementor needs to at least implement the [`Render::render_event`] function that renders a
 | 
			
		||||
/// single event to the output. If anything needs to be rendered at the beginning or end of the
 | 
			
		||||
/// output, the [`Render::render_prologue`] and [`Render::render_epilogue`] can be implemented as
 | 
			
		||||
/// well.
 | 
			
		||||
///
 | 
			
		||||
/// # Examples
 | 
			
		||||
///
 | 
			
		||||
/// Push to a [`String`] (implements [`std::fmt::Write`]):
 | 
			
		||||
///
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use jotdown::Render;
 | 
			
		||||
/// # let events = std::iter::empty();
 | 
			
		||||
/// let mut output = String::new();
 | 
			
		||||
/// let mut renderer = jotdown::html::Renderer::default();
 | 
			
		||||
/// renderer.push(events, &mut output);
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// 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());
 | 
			
		||||
/// let mut renderer = jotdown::html::Renderer::default();
 | 
			
		||||
/// renderer.write(events, &mut out).unwrap();
 | 
			
		||||
/// ```
 | 
			
		||||
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;
 | 
			
		||||
    /// Render a single event.
 | 
			
		||||
    fn render_event<'s, W>(&mut self, e: &Event<'s>, out: W) -> std::fmt::Result
 | 
			
		||||
    where
 | 
			
		||||
        W: std::fmt::Write;
 | 
			
		||||
 | 
			
		||||
    /// Write [`Event`]s to a byte sink, encoded as UTF-8.
 | 
			
		||||
    /// Render something before any events have been provided.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This does nothing by default, but an implementation may choose to prepend data at the
 | 
			
		||||
    /// beginning of the output if needed.
 | 
			
		||||
    fn render_prologue<W>(&mut self, _out: W) -> std::fmt::Result
 | 
			
		||||
    where
 | 
			
		||||
        W: std::fmt::Write,
 | 
			
		||||
    {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Render something after all events have been provided.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This does nothing by default, but an implementation may choose to append extra data at the
 | 
			
		||||
    /// end of the output if needed.
 | 
			
		||||
    fn render_epilogue<W>(&mut self, _out: W) -> std::fmt::Result
 | 
			
		||||
    where
 | 
			
		||||
        W: std::fmt::Write,
 | 
			
		||||
    {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Push owned [`Event`]s to a unicode-accepting buffer or stream.
 | 
			
		||||
    fn push<'s, I, W>(&mut self, mut events: I, mut out: W) -> fmt::Result
 | 
			
		||||
    where
 | 
			
		||||
        I: Iterator<Item = Event<'s>>,
 | 
			
		||||
        W: fmt::Write,
 | 
			
		||||
    {
 | 
			
		||||
        self.render_prologue(&mut out)?;
 | 
			
		||||
        events.try_for_each(|e| self.render_event(&e, &mut out))?;
 | 
			
		||||
        self.render_epilogue(&mut out)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 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: 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 {
 | 
			
		||||
    fn write<'s, I, W>(&mut self, events: I, out: W) -> io::Result<()>
 | 
			
		||||
    where
 | 
			
		||||
        I: Iterator<Item = Event<'s>>,
 | 
			
		||||
        W: io::Write,
 | 
			
		||||
    {
 | 
			
		||||
        let mut out = WriteAdapter {
 | 
			
		||||
            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")),
 | 
			
		||||
            },
 | 
			
		||||
        self.push(events, &mut out).map_err(|_| match out.error {
 | 
			
		||||
            Err(e) => e,
 | 
			
		||||
            _ => io::Error::new(io::ErrorKind::Other, "formatter error"),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Push borrowed [`Event`]s to a unicode-accepting buffer or stream.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    ///
 | 
			
		||||
    /// Render a borrowed slice of [`Event`]s.
 | 
			
		||||
    /// ```
 | 
			
		||||
    /// # use jotdown::Render;
 | 
			
		||||
    /// # let events: &[jotdown::Event] = &[];
 | 
			
		||||
    /// let mut output = String::new();
 | 
			
		||||
    /// let mut renderer = jotdown::html::Renderer::default();
 | 
			
		||||
    /// renderer.push_borrowed(events.iter(), &mut output);
 | 
			
		||||
    /// ```
 | 
			
		||||
    fn push_borrowed<'s, E, I, W>(&mut self, mut events: I, mut out: W) -> fmt::Result
 | 
			
		||||
    where
 | 
			
		||||
        E: AsRef<Event<'s>>,
 | 
			
		||||
        I: Iterator<Item = E>,
 | 
			
		||||
        W: fmt::Write,
 | 
			
		||||
    {
 | 
			
		||||
        self.render_prologue(&mut out)?;
 | 
			
		||||
        events.try_for_each(|e| self.render_event(e.as_ref(), &mut out))?;
 | 
			
		||||
        self.render_epilogue(&mut out)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Write borrowed [`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_borrowed<'s, E, I, W>(&mut self, events: I, out: W) -> io::Result<()>
 | 
			
		||||
    where
 | 
			
		||||
        E: AsRef<Event<'s>>,
 | 
			
		||||
        I: Iterator<Item = E>,
 | 
			
		||||
        W: io::Write,
 | 
			
		||||
    {
 | 
			
		||||
        let mut out = WriteAdapter {
 | 
			
		||||
            inner: out,
 | 
			
		||||
            error: Ok(()),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.push_borrowed(events, &mut out)
 | 
			
		||||
            .map_err(|_| match out.error {
 | 
			
		||||
                Err(e) => e,
 | 
			
		||||
                _ => io::Error::new(io::ErrorKind::Other, "formatter error"),
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct WriteAdapter<T: io::Write> {
 | 
			
		||||
    inner: T,
 | 
			
		||||
    error: io::Result<()>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: io::Write> fmt::Write for WriteAdapter<T> {
 | 
			
		||||
    fn write_str(&mut self, s: &str) -> fmt::Result {
 | 
			
		||||
        self.inner.write_all(s.as_bytes()).map_err(|e| {
 | 
			
		||||
            self.error = Err(e);
 | 
			
		||||
            fmt::Error
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// XXX why is this not a blanket implementation?
 | 
			
		||||
impl<'s> AsRef<Event<'s>> for &Event<'s> {
 | 
			
		||||
    fn as_ref(&self) -> &Event<'s> {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,11 +68,11 @@ fn run() -> Result<(), std::io::Error> {
 | 
			
		|||
    };
 | 
			
		||||
 | 
			
		||||
    let parser = jotdown::Parser::new(&content);
 | 
			
		||||
    let html = jotdown::html::Renderer;
 | 
			
		||||
    let mut renderer = jotdown::html::Renderer::default();
 | 
			
		||||
 | 
			
		||||
    match app.output {
 | 
			
		||||
        Some(path) => html.write(parser, File::create(path)?)?,
 | 
			
		||||
        None => html.write(parser, BufWriter::new(std::io::stdout()))?,
 | 
			
		||||
        Some(path) => renderer.write(parser, File::create(path)?)?,
 | 
			
		||||
        None => renderer.write(parser, BufWriter::new(std::io::stdout()))?,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,9 @@ pub fn html(data: &[u8]) {
 | 
			
		|||
        if !s.contains("=html") {
 | 
			
		||||
            let p = jotdown::Parser::new(s);
 | 
			
		||||
            let mut html = "<!DOCTYPE html>\n".to_string();
 | 
			
		||||
            jotdown::html::Renderer.push(p, &mut html).unwrap();
 | 
			
		||||
            jotdown::html::Renderer::default()
 | 
			
		||||
                .push(p, &mut html)
 | 
			
		||||
                .unwrap();
 | 
			
		||||
            validate_html(&html);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,9 @@ macro_rules! suite_test {
 | 
			
		|||
        let expected = $expected;
 | 
			
		||||
        let p = jotdown::Parser::new(src);
 | 
			
		||||
        let mut actual = String::new();
 | 
			
		||||
        jotdown::html::Renderer.push(p, &mut actual).unwrap();
 | 
			
		||||
        jotdown::html::Renderer::default()
 | 
			
		||||
            .push(p, &mut actual)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            actual.trim(),
 | 
			
		||||
            expected.trim(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue