diff --git a/src/block.rs b/src/block.rs index 7d627e71..976d7652 100644 --- a/src/block.rs +++ b/src/block.rs @@ -77,6 +77,7 @@ pub enum Leaf<'s> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Container<'s> { + Document, Blockquote, Div { class: &'s str }, List { ty: ListType, tight: bool }, @@ -223,6 +224,8 @@ impl<'s> TreeParser<'s> { #[must_use] fn parse(mut self) -> Vec> { + self.enter(Node::Container(Document), 0..0); + let mut lines = lines(self.src).collect::>(); let mut line_pos = 0; while line_pos < lines.len() { @@ -239,7 +242,10 @@ impl<'s> TreeParser<'s> { for _ in std::mem::take(&mut self.open_sections).drain(..) { self.exit(self.src.len()..self.src.len()); } + + self.exit(self.src.len()..self.src.len()); // Document debug_assert_eq!(self.open, &[]); + self.events } @@ -1317,7 +1323,11 @@ mod test { ($src:expr $(,$($event:expr),* $(,)?)?) => { let t = super::TreeParser::new($src).parse(); let actual = t.into_iter().map(|ev| (ev.kind, &$src[ev.span])).collect::>(); - let expected = &[$($($event),*,)?]; + let expected = &[ + (Enter(Container(Document)), ""), + $($($event),*,)? + (Exit(Container(Document)), ""), + ]; assert_eq!( actual, expected, diff --git a/src/html.rs b/src/html.rs index 35039c45..3cba2403 100644 --- a/src/html.rs +++ b/src/html.rs @@ -13,8 +13,8 @@ use crate::SpanLinkType; /// Render events into a string. /// -/// This is a convenience function for using [`Renderer::push`] with fewer imports and without an -/// intermediate variable. +/// This is a convenience function for using [`Renderer::push_events`] with fewer imports and +/// without an intermediate variable. /// /// # Examples /// @@ -27,7 +27,7 @@ where I: Iterator>, { let mut s = String::new(); - Renderer::default().push(events, &mut s).unwrap(); + Renderer::default().push_events(events, &mut s).unwrap(); s } @@ -49,8 +49,8 @@ pub struct Indentation { /// let events = Parser::new(src); /// /// let mut html = String::new(); - /// let renderer = Renderer::indented(Indentation::default()); - /// renderer.push(events.clone(), &mut html).unwrap(); + /// let mut renderer = Renderer::indented(Indentation::default()); + /// renderer.push_events(events.clone(), &mut html).unwrap(); /// assert_eq!( /// html, /// concat!( @@ -69,11 +69,11 @@ pub struct Indentation { /// # let src = "> a\n"; /// # let events = Parser::new(src); /// # let mut html = String::new(); - /// let renderer = Renderer::indented(Indentation { + /// let mut renderer = Renderer::indented(Indentation { /// string: " ".to_string(), /// ..Indentation::default() /// }); - /// renderer.push(events.clone(), &mut html).unwrap(); + /// renderer.push_events(events.clone(), &mut html).unwrap(); /// assert_eq!( /// html, /// concat!( @@ -97,8 +97,8 @@ pub struct Indentation { /// let events = Parser::new(src); /// /// let mut html = String::new(); - /// let renderer = Renderer::indented(Indentation::default()); - /// renderer.push(events.clone(), &mut html).unwrap(); + /// let mut renderer = Renderer::indented(Indentation::default()); + /// renderer.push_events(events.clone(), &mut html).unwrap(); /// assert_eq!( /// html, /// concat!( @@ -117,11 +117,11 @@ pub struct Indentation { /// # let src = "> a\n"; /// # let events = Parser::new(src); /// # let mut html = String::new(); - /// let renderer = Renderer::indented(Indentation { + /// let mut renderer = Renderer::indented(Indentation { /// initial_level: 2, /// ..Indentation::default() /// }); - /// renderer.push(events.clone(), &mut html).unwrap(); + /// renderer.push_events(events.clone(), &mut html).unwrap(); /// assert_eq!( /// html, /// concat!( @@ -143,16 +143,7 @@ impl Default for Indentation { } } -/// [`Render`] implementor that writes HTML output. -/// -/// By default, block elements are placed on separate lines. To configure the formatting of the -/// output, see the [`Renderer::minified`] and [`Renderer::indented`] constructors. -#[derive(Clone)] -pub struct Renderer { - indent: Option, -} - -impl Renderer { +impl<'s> Renderer<'s> { /// Create a renderer that emits no whitespace between elements. /// /// # Examples @@ -168,15 +159,15 @@ impl Renderer { /// " - c\n", /// ); /// let mut actual = String::new(); - /// let renderer = Renderer::minified(); - /// renderer.push(Parser::new(src), &mut actual).unwrap(); + /// let mut renderer = Renderer::minified(); + /// renderer.push_events(Parser::new(src), &mut actual).unwrap(); /// let expected = /// "
  • a
    • b

    • c

"; /// assert_eq!(actual, expected); /// ``` #[must_use] pub fn minified() -> Self { - Self { indent: None } + Self::new(None) } /// Create a renderer that indents lines based on their block element depth. @@ -196,8 +187,8 @@ impl Renderer { /// " - c\n", /// ); /// let mut actual = String::new(); - /// let renderer = Renderer::indented(Indentation::default()); - /// renderer.push(Parser::new(src), &mut actual).unwrap(); + /// let mut renderer = Renderer::indented(Indentation::default()); + /// renderer.push_events(Parser::new(src), &mut actual).unwrap(); /// let expected = concat!( /// "
    \n", /// "\t
  • \n", @@ -217,13 +208,11 @@ impl Renderer { /// ``` #[must_use] pub fn indented(indent: Indentation) -> Self { - Self { - indent: Some(indent), - } + Self::new(Some(indent)) } } -impl Default for Renderer { +impl<'s> Default for Renderer<'s> { /// Place block elements on separate lines. /// /// This is the default behavior and matches the reference implementation. @@ -241,8 +230,8 @@ impl Default for Renderer { /// " - c\n", /// ); /// let mut actual = String::new(); - /// let renderer = Renderer::default(); - /// renderer.push(Parser::new(src), &mut actual).unwrap(); + /// let mut renderer = Renderer::default(); + /// renderer.push_events(Parser::new(src), &mut actual).unwrap(); /// let expected = concat!( /// "
      \n", /// "
    • \n", @@ -261,24 +250,19 @@ impl Default for Renderer { /// assert_eq!(actual, expected); /// ``` fn default() -> Self { - Self { - indent: Some(Indentation { - string: String::new(), - initial_level: 0, - }), - } + Self::new(Some(Indentation { + string: String::new(), + initial_level: 0, + })) } } -impl Render for Renderer { - fn push<'s, I, W>(&self, mut events: I, mut out: W) -> std::fmt::Result +impl<'s> Render<'s> for Renderer<'s> { + fn push_event(&mut self, event: Event<'s>, mut out: W) -> std::fmt::Result where - I: Iterator>, W: std::fmt::Write, { - let mut w = Writer::new(&self.indent); - events.try_for_each(|e| w.render_event(e, &mut out))?; - w.render_epilogue(&mut out) + self.render_event(event, &mut out) } } @@ -294,8 +278,12 @@ impl Default for Raw { } } -struct Writer<'s, 'f> { - indent: &'f Option, +/// [`Render`] implementor that writes HTML output. +/// +/// By default, block elements are placed on separate lines. To configure the formatting of the +/// output, see the [`Renderer::minified`] and [`Renderer::indented`] constructors. +pub struct Renderer<'s> { + indent: Option, depth: usize, raw: Raw, img_alt_text: usize, @@ -305,9 +293,9 @@ struct Writer<'s, 'f> { footnotes: Footnotes<'s>, } -impl<'s, 'f> Writer<'s, 'f> { - fn new(indent: &'f Option) -> Self { - let depth = if let Some(indent) = indent { +impl<'s> Renderer<'s> { + fn new(indent: Option) -> Self { + let depth = if let Some(indent) = &indent { indent.initial_level } else { 0 @@ -324,7 +312,7 @@ impl<'s, 'f> Writer<'s, 'f> { } } - fn block(&mut self, mut out: W, depth_change: isize) -> std::fmt::Result + fn block(&mut self, out: &mut W, depth_change: isize) -> std::fmt::Result where W: std::fmt::Write, { @@ -340,7 +328,7 @@ impl<'s, 'f> Writer<'s, 'f> { if depth_change < 0 { self.depth = next_depth; } - self.indent(&mut out)?; + self.indent(out)?; if depth_change > 0 { self.depth = next_depth; } @@ -348,11 +336,11 @@ impl<'s, 'f> Writer<'s, 'f> { Ok(()) } - fn indent(&self, mut out: W) -> std::fmt::Result + fn indent(&self, out: &mut W) -> std::fmt::Result where W: std::fmt::Write, { - if let Some(indent) = self.indent { + if let Some(indent) = &self.indent { if !indent.string.is_empty() { for _ in 0..self.depth { out.write_str(&indent.string)?; @@ -362,7 +350,7 @@ impl<'s, 'f> Writer<'s, 'f> { Ok(()) } - fn render_event(&mut self, e: Event<'s>, mut out: W) -> std::fmt::Result + fn render_event(&mut self, e: Event<'s>, out: &mut W) -> std::fmt::Result where W: std::fmt::Write, { @@ -395,12 +383,13 @@ impl<'s, 'f> Writer<'s, 'f> { match e { Event::Start(c, attrs) => { if c.is_block() { - self.block(&mut out, c.is_block_container().into())?; + self.block(out, c.is_block_container().into())?; } if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) { return Ok(()); } match &c { + Container::Document => return Ok(()), Container::Blockquote => out.write_str(" { self.list_tightness.push(*tight); @@ -456,7 +445,7 @@ impl<'s, 'f> Writer<'s, 'f> { if matches!(ty, LinkType::Email) { out.write_str("mailto:")?; } - write_attr(dst, &mut out)?; + write_attr(dst, out)?; out.write_char('"')?; } } @@ -491,11 +480,11 @@ impl<'s, 'f> Writer<'s, 'f> { let mut class_written = false; for (a, v) in attrs.unique_pairs() { write!(out, r#" {}=""#, a)?; - v.parts().try_for_each(|part| write_attr(part, &mut out))?; + v.parts().try_for_each(|part| write_attr(part, out))?; match a { "class" => { class_written = true; - write_class(&c, true, &mut out)?; + write_class(&c, true, out)?; } "id" => id_written = true, _ => {} @@ -512,7 +501,7 @@ impl<'s, 'f> Writer<'s, 'f> { { if !id_written { out.write_str(r#" id=""#)?; - write_attr(id, &mut out)?; + write_attr(id, out)?; out.write_char('"')?; } } else if (matches!(c.clone(), Container::Div { class } if !class.is_empty()) @@ -527,7 +516,7 @@ impl<'s, 'f> Writer<'s, 'f> { && !class_written { out.write_str(r#" class=""#)?; - write_class(&c, false, &mut out)?; + write_class(&c, false, out)?; out.write_char('"')?; } @@ -548,7 +537,7 @@ impl<'s, 'f> Writer<'s, 'f> { out.write_str(">")?; } else { out.write_str(r#">"#)?; } } @@ -562,7 +551,7 @@ impl<'s, 'f> Writer<'s, 'f> { } Container::TaskListItem { checked } => { out.write_char('>')?; - self.block(&mut out, 0)?; + self.block(out, 0)?; if checked { out.write_str(r#""#)?; } else { @@ -574,12 +563,13 @@ impl<'s, 'f> Writer<'s, 'f> { } Event::End(c) => { if c.is_block_container() { - self.block(&mut out, -1)?; + self.block(out, -1)?; } if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) { return Ok(()); } match c { + Container::Document => return self.render_epilogue(out), Container::Blockquote => out.write_str("")?, Container::List { kind, .. } => { self.list_tightness.pop(); @@ -620,7 +610,7 @@ impl<'s, 'f> Writer<'s, 'f> { if self.img_alt_text == 1 { if !src.is_empty() { out.write_str(r#"" src=""#)?; - write_attr(&src, &mut out)?; + write_attr(&src, out)?; } out.write_str(r#"">"#)?; } @@ -644,8 +634,8 @@ impl<'s, 'f> Writer<'s, 'f> { } } Event::Str(s) => match self.raw { - Raw::None if self.img_alt_text > 0 => write_attr(&s, &mut out)?, - Raw::None => write_text(&s, &mut out)?, + Raw::None if self.img_alt_text > 0 => write_attr(&s, out)?, + Raw::None => write_text(&s, out)?, Raw::Html => out.write_str(&s)?, Raw::Other => {} }, @@ -674,15 +664,15 @@ impl<'s, 'f> Writer<'s, 'f> { } Event::Softbreak => { out.write_char('\n')?; - self.indent(&mut out)?; + self.indent(out)?; } Event::Escape | Event::Blankline | Event::Attributes(..) => {} Event::ThematicBreak(attrs) => { - self.block(&mut out, 0)?; + self.block(out, 0)?; out.write_str("")?; @@ -693,20 +683,20 @@ impl<'s, 'f> Writer<'s, 'f> { Ok(()) } - fn render_epilogue(&mut self, mut out: W) -> std::fmt::Result + fn render_epilogue(&mut self, out: &mut W) -> std::fmt::Result where W: std::fmt::Write, { if self.footnotes.reference_encountered() { - self.block(&mut out, 0)?; + self.block(out, 0)?; out.write_str("
      ")?; - self.block(&mut out, 0)?; + self.block(out, 0)?; out.write_str("
      ")?; - self.block(&mut out, 0)?; + self.block(out, 0)?; out.write_str("
        ")?; while let Some((number, events)) = self.footnotes.next() { - self.block(&mut out, 0)?; + self.block(out, 0)?; write!(out, "
      1. ", number)?; let mut unclosed_para = false; @@ -718,13 +708,13 @@ impl<'s, 'f> Writer<'s, 'f> { // not a footnote, so no need to add href before para close out.write_str("

        ")?; } - self.render_event(e.clone(), &mut out)?; + self.render_event(e.clone(), out)?; unclosed_para = matches!(e, Event::End(Container::Paragraph { .. })) && !matches!(self.list_tightness.last(), Some(true)); } if !unclosed_para { // create a new paragraph - self.block(&mut out, 0)?; + self.block(out, 0)?; out.write_str("

        ")?; } write!( @@ -733,13 +723,13 @@ impl<'s, 'f> Writer<'s, 'f> { number, )?; - self.block(&mut out, 0)?; + self.block(out, 0)?; out.write_str("

      2. ")?; } - self.block(&mut out, 0)?; + self.block(out, 0)?; out.write_str("
      ")?; - self.block(&mut out, 0)?; + self.block(out, 0)?; out.write_str("
      ")?; } @@ -778,21 +768,21 @@ where Ok(()) } -fn write_text(s: &str, out: W) -> std::fmt::Result +fn write_text(s: &str, out: &mut W) -> std::fmt::Result where W: std::fmt::Write, { write_escape(s, false, out) } -fn write_attr(s: &str, out: W) -> std::fmt::Result +fn write_attr(s: &str, out: &mut W) -> std::fmt::Result where W: std::fmt::Write, { write_escape(s, true, out) } -fn write_escape(mut s: &str, escape_quotes: bool, mut out: W) -> std::fmt::Result +fn write_escape(mut s: &str, escape_quotes: bool, out: &mut W) -> std::fmt::Result where W: std::fmt::Write, { diff --git a/src/lib.rs b/src/lib.rs index 71374f38..c01e5353 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,8 +71,8 @@ type CowStr<'s> = std::borrow::Cow<'s, str>; /// # use jotdown::Render; /// # let events = std::iter::empty(); /// let mut output = String::new(); -/// let renderer = jotdown::html::Renderer::default(); -/// renderer.push(events, &mut output); +/// let mut renderer = jotdown::html::Renderer::default(); +/// renderer.push_events(events, &mut output); /// # } /// ``` /// @@ -84,24 +84,22 @@ type CowStr<'s> = std::borrow::Cow<'s, str>; /// # use jotdown::Render; /// # let events = std::iter::empty(); /// let mut out = std::io::BufWriter::new(std::io::stdout()); -/// let renderer = jotdown::html::Renderer::default(); -/// renderer.write(events, &mut out).unwrap(); +/// let mut renderer = jotdown::html::Renderer::default(); +/// renderer.write_events(events, &mut out).unwrap(); /// # } /// ``` -pub trait Render { - /// Push owned [`Event`]s to a unicode-accepting buffer or stream. - fn push<'s, I, W>(&self, events: I, out: W) -> std::fmt::Result +pub trait Render<'s>: Sized { + /// Push a single owned [`Event`]s to a unicode-accepting buffer or stream. + fn push_event(&mut self, event: Event<'s>, out: W) -> std::fmt::Result where - I: Iterator>, W: std::fmt::Write; - /// Write owned [`Event`]s to a byte sink, encoded as UTF-8. + /// Write a single owned [`Event`] 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, W>(&self, events: I, out: W) -> std::io::Result<()> + fn write_event(&mut self, event: Event<'s>, out: W) -> std::io::Result<()> where - I: Iterator>, W: std::io::Write, { struct WriteAdapter { @@ -123,10 +121,64 @@ pub trait Render { error: Ok(()), }; - self.push(events, &mut out).map_err(|_| match out.error { - Err(e) => e, - _ => std::io::Error::new(std::io::ErrorKind::Other, "formatter error"), - }) + self.push_event(event, &mut out) + .map_err(|_| match out.error { + Err(e) => e, + _ => std::io::Error::new(std::io::ErrorKind::Other, "formatter error"), + }) + } + + /// Push a single owned [`Event`]s to a unicode-accepting buffer or stream. + fn push_events(&mut self, mut events: I, mut out: W) -> std::fmt::Result + where + I: Iterator>, + W: std::fmt::Write, + { + events.try_for_each(|e| self.push_event(e, &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_events(&mut self, mut events: I, mut out: W) -> std::io::Result<()> + where + I: Iterator>, + W: std::io::Write, + { + events.try_for_each(|e| self.write_event(e, &mut out)) + } + + fn with_filter(self, filter: F) -> FilteredRenderer { + FilteredRenderer { + filter, + renderer: self, + } + } +} + +pub trait Filter<'s> { + fn push_event

      (&mut self, event: Event<'s>, push: &mut P) -> std::fmt::Result + where + P: FnMut(Event<'s>) -> std::fmt::Result; +} + +pub struct FilteredRenderer { + filter: F, + renderer: R, +} + +impl<'s, F, R> Render<'s> for FilteredRenderer +where + F: Filter<'s>, + R: Render<'s>, +{ + fn push_event(&mut self, event: Event<'s>, mut out: W) -> std::fmt::Result + where + W: std::fmt::Write, + { + self.filter + .push_event(event, &mut |e| self.renderer.push_event(e, &mut out)) } } @@ -161,6 +213,7 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::Paragraph, /// [(AttributeKind::Id, "a".into())].into_iter().collect(), @@ -172,6 +225,7 @@ pub enum Event<'s> { /// Event::Str("word".into()), /// Event::End(Container::Span), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      word

      \n"; @@ -196,9 +250,11 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("str".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      str

      \n"; @@ -216,11 +272,13 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("txt".into()), /// Event::FootnoteReference("nb".into()), /// Event::Str(".".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -248,10 +306,12 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("a ".into()), /// Event::Symbol("sym".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      a :sym:

      \n"; @@ -269,11 +329,13 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::LeftSingleQuote, /// Event::Str("quote".into()), /// Event::RightSingleQuote, /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      ‘quote’

      \n"; @@ -291,11 +353,13 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::RightSingleQuote, /// Event::Str("Tis Socrates".into()), /// Event::RightSingleQuote, /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      ’Tis Socrates’

      \n"; @@ -313,12 +377,14 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::LeftDoubleQuote, /// Event::Str("Hello,".into()), /// Event::RightDoubleQuote, /// Event::Str(" he said".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      “Hello,” he said

      \n"; @@ -338,10 +404,12 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("yes".into()), /// Event::Ellipsis, /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      yes…

      \n"; @@ -359,11 +427,13 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("57".into()), /// Event::EnDash, /// Event::Str("33".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      57–33

      \n"; @@ -381,11 +451,13 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("oxen".into()), /// Event::EmDash, /// Event::Str("and".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      oxen—and

      \n"; @@ -403,12 +475,14 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("no".into()), /// Event::Escape, /// Event::NonBreakingSpace, /// Event::Str("break".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      no break

      \n"; @@ -429,11 +503,13 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("soft".into()), /// Event::Softbreak, /// Event::Str("break".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -457,12 +533,14 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("hard".into()), /// Event::Escape, /// Event::Hardbreak, /// Event::Str("break".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -483,12 +561,14 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Escape, /// Event::Str("*a".into()), /// Event::Escape, /// Event::Str("*".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      *a*

      \n"; @@ -510,6 +590,7 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("para0".into()), /// Event::End(Container::Paragraph), @@ -517,6 +598,7 @@ pub enum Event<'s> { /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("para1".into()), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -545,6 +627,7 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("para0".into()), /// Event::End(Container::Paragraph), @@ -559,6 +642,7 @@ pub enum Event<'s> { /// .into_iter() /// .collect(), /// ), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -587,6 +671,7 @@ pub enum Event<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Attributes( /// [(AttributeKind::Id, "a".into())] /// .into_iter() @@ -607,6 +692,7 @@ pub enum Event<'s> { /// .into_iter() /// .collect(), /// ), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -627,6 +713,10 @@ pub enum Event<'s> { /// - block container, may contain any block-level elements. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Container<'s> { + /// A document. + /// + /// Should appear once at the start and end of the event stream. + Document, /// A blockquote element. /// /// # Examples @@ -641,6 +731,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Blockquote, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("a".into()), @@ -648,6 +739,7 @@ pub enum Container<'s> { /// Event::Str("b".into()), /// Event::End(Container::Paragraph), /// Event::End(Container::Blockquote), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -674,6 +766,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::List { /// kind: ListKind::Unordered(ListBulletType::Dash), @@ -696,6 +789,7 @@ pub enum Container<'s> { /// kind: ListKind::Unordered(ListBulletType::Dash), /// tight: false /// }), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -722,6 +816,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::List { /// kind: ListKind::Unordered(ListBulletType::Dash), @@ -738,6 +833,7 @@ pub enum Container<'s> { /// kind: ListKind::Unordered(ListBulletType::Dash), /// tight: true, /// }), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -761,6 +857,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::List { /// kind: ListKind::Task(ListBulletType::Dash), @@ -780,6 +877,7 @@ pub enum Container<'s> { /// kind: ListKind::Task(ListBulletType::Dash), /// tight: true, /// }), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -811,6 +909,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::DescriptionList, Attributes::new()), /// Event::Start(Container::DescriptionTerm, Attributes::new()), /// Event::Str("orange".into()), @@ -831,6 +930,7 @@ pub enum Container<'s> { /// Event::End(Container::Paragraph), /// Event::End(Container::DescriptionDetails), /// Event::End(Container::DescriptionList), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -865,6 +965,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("txt".into()), /// Event::FootnoteReference("nb".into()), @@ -878,6 +979,7 @@ pub enum Container<'s> { /// Event::Str("actually..".into()), /// Event::End(Container::Paragraph), /// Event::End(Container::Footnote { label: "nb".into() }), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -909,6 +1011,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Table, Attributes::new()), /// Event::Start( /// Container::TableRow { head: true }, @@ -969,6 +1072,7 @@ pub enum Container<'s> { /// }), /// Event::End(Container::TableRow { head: false } ), /// Event::End(Container::Table), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1003,6 +1107,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::Section { id: "outer".into() }, /// Attributes::new(), @@ -1042,6 +1147,7 @@ pub enum Container<'s> { /// }), /// Event::End(Container::Section { id: "inner".into() }), /// Event::End(Container::Section { id: "outer".into() }), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1070,6 +1176,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::Div { class: "note".into() }, /// Attributes::new(), @@ -1078,6 +1185,7 @@ pub enum Container<'s> { /// Event::Str("this is a note".into()), /// Event::End(Container::Paragraph), /// Event::End(Container::Div { class: "note".into() }), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1101,6 +1209,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::Section { id: "heading".into() }, /// Attributes::new(), @@ -1120,6 +1229,7 @@ pub enum Container<'s> { /// id: "heading".into(), /// }), /// Event::End(Container::Section { id: "heading".into() }), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1150,6 +1260,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Table, Attributes::new()), /// Event::Start(Container::Caption, Attributes::new()), /// Event::Str("caption".into()), @@ -1172,6 +1283,7 @@ pub enum Container<'s> { /// }), /// Event::End(Container::TableRow { head: false } ), /// Event::End(Container::Table), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1198,12 +1310,14 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::LinkDefinition { label: "label".into() }, /// Attributes::new(), /// ), /// Event::Str("url".into()), /// Event::End(Container::LinkDefinition { label: "label".into() }), + /// Event::End(Container::Document), /// ], /// ); /// let html = "\n"; @@ -1225,12 +1339,14 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::RawBlock { format: "html".into() }, /// Attributes::new(), /// ), /// Event::Str("x".into()), /// Event::End(Container::RawBlock { format: "html".into() }), + /// Event::End(Container::Document), /// ], /// ); /// let html = "x\n"; @@ -1252,12 +1368,14 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start( /// Container::CodeBlock { language: "html".into() }, /// Attributes::new(), /// ), /// Event::Str("x\n".into()), /// Event::End(Container::CodeBlock { language: "html".into() }), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1283,6 +1401,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start( /// Container::Span, @@ -1298,6 +1417,7 @@ pub enum Container<'s> { /// Event::Str("two words".into()), /// Event::End(Container::Span), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1323,6 +1443,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start( /// Container::Link( @@ -1350,6 +1471,7 @@ pub enum Container<'s> { /// LinkType::Email, /// )), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1368,6 +1490,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start( /// Container::Link( @@ -1382,6 +1505,7 @@ pub enum Container<'s> { /// LinkType::Span(SpanLinkType::Inline)), /// ), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      anchor

      \n"; @@ -1403,6 +1527,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start( /// Container::Link( @@ -1437,6 +1562,7 @@ pub enum Container<'s> { /// ), /// Event::Str("url".into()), /// Event::End(Container::LinkDefinition { label: "label".into() }), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1459,6 +1585,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start( /// Container::Image("img.png".into(), SpanLinkType::Inline), @@ -1469,6 +1596,7 @@ pub enum Container<'s> { /// Container::Image("img.png".into(), SpanLinkType::Inline), /// ), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      \"alt

      \n"; @@ -1486,12 +1614,14 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("inline ".into()), /// Event::Start(Container::Verbatim, Attributes::new()), /// Event::Str("verbatim".into()), /// Event::End(Container::Verbatim), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      inline verbatim

      \n"; @@ -1512,6 +1642,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Str("inline ".into()), /// Event::Start( @@ -1530,6 +1661,7 @@ pub enum Container<'s> { /// Event::Str(r"\frac{a}{b}".into()), /// Event::End(Container::Math { display: true }), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = concat!( @@ -1550,6 +1682,7 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start( /// Container::RawInline { format: "html".into() }, Attributes::new(), @@ -1557,6 +1690,7 @@ pub enum Container<'s> { /// Event::Str("a".into()), /// Event::End(Container::RawInline { format: "html".into() }), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      a

      \n"; @@ -1574,11 +1708,13 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start(Container::Subscript, Attributes::new()), /// Event::Str("SUB".into()), /// Event::End(Container::Subscript), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      SUB

      \n"; @@ -1596,11 +1732,13 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start(Container::Superscript, Attributes::new()), /// Event::Str("SUP".into()), /// Event::End(Container::Superscript), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      SUP

      \n"; @@ -1618,11 +1756,13 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start(Container::Insert, Attributes::new()), /// Event::Str("INS".into()), /// Event::End(Container::Insert), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      INS

      \n"; @@ -1640,11 +1780,13 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start(Container::Delete, Attributes::new()), /// Event::Str("DEL".into()), /// Event::End(Container::Delete), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      DEL

      \n"; @@ -1662,11 +1804,13 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start(Container::Strong, Attributes::new()), /// Event::Str("STRONG".into()), /// Event::End(Container::Strong), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      STRONG

      \n"; @@ -1684,11 +1828,13 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start(Container::Emphasis, Attributes::new()), /// Event::Str("EM".into()), /// Event::End(Container::Emphasis), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      EM

      \n"; @@ -1706,11 +1852,13 @@ pub enum Container<'s> { /// assert_eq!( /// &events, /// &[ + /// Event::Start(Container::Document, Attributes::new()), /// Event::Start(Container::Paragraph, Attributes::new()), /// Event::Start(Container::Mark, Attributes::new()), /// Event::Str("MARK".into()), /// Event::End(Container::Mark), /// Event::End(Container::Paragraph), + /// Event::End(Container::Document), /// ], /// ); /// let html = "

      MARK

      \n"; @@ -1743,7 +1891,8 @@ impl Container<'_> { | Self::LinkDefinition { .. } | Self::RawBlock { .. } | Self::CodeBlock { .. } => true, - Self::Span + Self::Document + | Self::Span | Self::Link(..) | Self::Image(..) | Self::Verbatim @@ -1774,7 +1923,8 @@ impl Container<'_> { | Self::TableRow { .. } | Self::Section { .. } | Self::Div { .. } => true, - Self::Paragraph + Self::Document + | Self::Paragraph | Self::Heading { .. } | Self::TableCell { .. } | Self::Caption @@ -2282,6 +2432,7 @@ impl<'s> Parser<'s> { /// .collect::>() /// .as_slice(), /// &[ + /// ("", Start(Document, ..)), /// (">", Start(Blockquote, ..)), /// ("", Start(Paragraph, ..)), /// ("_", Start(Emphasis, ..)), @@ -2293,6 +2444,7 @@ impl<'s> Parser<'s> { /// ("](url)", End(Link { .. })), /// ("", End(Paragraph)), /// ("", End(Blockquote)), + /// ("", End(Document)), /// ], /// )); /// ``` @@ -2314,6 +2466,7 @@ impl<'s> Parser<'s> { /// .collect::>() /// .as_slice(), /// &[ + /// ("", Start(Document, ..)), /// ("\n", Blankline), /// ("{.quote}\n>", Start(Blockquote, ..)), /// ("", Start(Paragraph, ..)), @@ -2323,6 +2476,7 @@ impl<'s> Parser<'s> { /// (" world!", Str(..)), /// ("", End(Paragraph)), /// ("", End(Blockquote)), + /// ("", End(Document)), /// ], /// )); /// ``` @@ -2344,6 +2498,7 @@ impl<'s> Parser<'s> { /// .collect::>() /// .as_slice(), /// &[ + /// ("", Start(Document, ..)), /// ("\n", Blankline), /// (">", Start(Blockquote, ..)), /// ("", Start(Paragraph, ..)), @@ -2352,6 +2507,7 @@ impl<'s> Parser<'s> { /// ("](multi\n> line)", End(Link { .. })), /// ("", End(Paragraph)), /// ("", End(Blockquote)), + /// ("", End(Document)), /// ], /// )); /// ``` @@ -2567,6 +2723,7 @@ impl<'s> Parser<'s> { } } block::Node::Container(c) => match c { + block::Container::Document => Container::Document, block::Container::Blockquote => Container::Blockquote, block::Container::Div { class } => Container::Div { class: class.into(), diff --git a/src/main.rs b/src/main.rs index 3d382ff7..d66a641f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -94,7 +94,7 @@ fn run() -> Result<(), std::io::Error> { }; let parser = jotdown::Parser::new(&content); - let renderer = if app.minified { + let mut renderer = if app.minified { jotdown::html::Renderer::minified() } else { jotdown::html::Renderer::indented(jotdown::html::Indentation { @@ -104,8 +104,8 @@ fn run() -> Result<(), std::io::Error> { }; match app.output { - Some(path) => renderer.write(parser, std::fs::File::create(path)?)?, - None => renderer.write(parser, std::io::BufWriter::new(std::io::stdout()))?, + Some(path) => renderer.write_events(parser, std::fs::File::create(path)?)?, + None => renderer.write_events(parser, std::io::BufWriter::new(std::io::stdout()))?, } Ok(()) diff --git a/tests/afl/src/lib.rs b/tests/afl/src/lib.rs index 591f46c5..8d2ed6c4 100644 --- a/tests/afl/src/lib.rs +++ b/tests/afl/src/lib.rs @@ -63,7 +63,7 @@ pub fn html(data: &[u8]) { let p = jotdown::Parser::new(s); let mut html = "\n".to_string(); jotdown::html::Renderer::default() - .push(p, &mut html) + .push_events(p, &mut html) .unwrap(); validate_html(&html); } @@ -137,7 +137,7 @@ impl<'a> tree_builder::TreeSink for Dom<'a> { x == y } - fn elem_name(&self, i: &usize) -> html5ever::ExpandedName { + fn elem_name(&self, i: &usize) -> html5ever::ExpandedName<'_> { self.names[i - 1].expanded() } diff --git a/tests/html-ref/lib.rs b/tests/html-ref/lib.rs index fabada5f..e7557d2f 100644 --- a/tests/html-ref/lib.rs +++ b/tests/html-ref/lib.rs @@ -10,7 +10,7 @@ macro_rules! compare { let p = jotdown::Parser::new(src); let mut actual = String::new(); jotdown::html::Renderer::default() - .push(p, &mut actual) + .push_events(p, &mut actual) .unwrap(); assert_eq!(actual, expected, "\n{}", { use std::io::Write; diff --git a/tests/html.rs b/tests/html.rs index e808fb2e..8066d450 100644 --- a/tests/html.rs +++ b/tests/html.rs @@ -8,7 +8,7 @@ macro_rules! test_html { $(renderer = jotdown::html::Renderer::indented($indent);)? let mut actual = String::new(); renderer - .push(jotdown::Parser::new($src), &mut actual) + .push_events(jotdown::Parser::new($src), &mut actual) .unwrap(); assert_eq!(actual, $expected); }; diff --git a/tests/parse_events.rs b/tests/parse_events.rs index 7c201f73..7a77a87c 100644 --- a/tests/parse_events.rs +++ b/tests/parse_events.rs @@ -22,7 +22,11 @@ macro_rules! test_parse { .into_offset_iter() .map(|(e, r)| (e, &$src[r])) .collect::>(); - let expected = &[$($($token),*,)?]; + let expected = &[ + (Start(Document, Attributes::new()), ""), + $($($token),*,)? + (End(Document), ""), + ]; assert_eq!( actual, expected,