diff --git a/Makefile b/Makefile index 51516ae..3dcb494 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ check: suite: git submodule update --init modules/djot.js for f in $$(find modules/djot.js/test -name '*.test' | xargs basename -a); do \ - ln -fs ../../modules/djot.js/test/$$f tests/suite/$$f; \ + ln -fs ../../modules/djot.js/test/$$f tests/suite/djot_js_$$f; \ done (cd tests/suite && make) cargo test --features suite suite:: @@ -102,7 +102,7 @@ afl_tmin: clean: cargo clean git submodule deinit -f --all - rm -f tests/suite/*.test + find tests -type l -path 'tests/suite/*.test' -print0 | xargs -0 rm -f (cd tests/suite && make clean) rm -f tests/bench/*.dj (cd tests/bench && make clean) diff --git a/bench/input/block_footnotes.dj b/bench/input/block_footnotes.dj new file mode 100644 index 0000000..842aa35 --- /dev/null +++ b/bench/input/block_footnotes.dj @@ -0,0 +1,24 @@ +[^abc]: footnotes may appear before + +Some[^a] paragraph[^b] with[^c] a[^d] lot[^e] of[^f] footnotes[^g]. + +[^a]: A typical footnote may have a single paragraph. +[^b]: A typical footnote may have a single paragraph. +[^c]: A typical footnote may have a single paragraph. +[^d]: A typical footnote may have a single paragraph. +[^e]: A typical footnote may have a single paragraph. +[^f]: A typical footnote may have a single paragraph. +[^g]: Footnotes may also be + + - long and, + - contain multiple block elements. + + such as + + > blockquotes. + +Footnote [^labels may be long but not multi line] + +[^labels may be long but not multi line]: longer than the footnote.. + +the reference[^abc] to it. diff --git a/src/html.rs b/src/html.rs index 2dfd22a..32f6bc0 100644 --- a/src/html.rs +++ b/src/html.rs @@ -5,61 +5,93 @@ use crate::Container; use crate::Event; use crate::LinkType; use crate::ListKind; +use crate::Map; use crate::OrderedListNumbering::*; use crate::Render; use crate::SpanLinkType; +#[derive(Default)] +pub struct Renderer {} + +impl Render for Renderer { + fn push<'s, I, W>(&self, mut events: I, mut out: W) -> std::fmt::Result + where + I: Iterator>, + W: std::fmt::Write, + { + let mut w = Writer::default(); + events.try_for_each(|e| w.render_event(&e, &mut out))?; + w.render_epilogue(&mut out) + } + + fn push_borrowed<'s, E, I, W>(&self, mut events: I, mut out: W) -> std::fmt::Result + where + E: AsRef>, + I: Iterator, + W: std::fmt::Write, + { + let mut w = Writer::default(); + events.try_for_each(|e| w.render_event(e.as_ref(), &mut out))?; + w.render_epilogue(&mut out) + } +} + enum Raw { None, Html, Other, } -pub struct Renderer { - raw: Raw, - img_alt_text: usize, - list_tightness: Vec, - encountered_footnote: bool, - footnote_number: Option, - first_line: bool, - close_para: bool, -} - -impl Default for Renderer { +impl Default for Raw { fn default() -> Self { - Self { - raw: Raw::None, - img_alt_text: 0, - list_tightness: Vec::new(), - encountered_footnote: false, - footnote_number: None, - first_line: true, - close_para: false, - } + Self::None } } -impl Render for Renderer { - fn render_event<'s, W>(&mut self, e: &Event<'s>, mut out: W) -> std::fmt::Result +#[derive(Default)] +struct Writer<'s> { + raw: Raw, + img_alt_text: usize, + list_tightness: Vec, + not_first_line: bool, + ignore: bool, + footnotes: Footnotes<'s>, +} + +impl<'s> Writer<'s> { + fn render_event(&mut self, e: &Event<'s>, mut out: W) -> std::fmt::Result where W: std::fmt::Write, { - if matches!(&e, Event::Blankline | Event::Escape) { + if let Event::Start(Container::Footnote { label }, ..) = e { + self.footnotes.start(label, Vec::new()); + return Ok(()); + } else if let Some(events) = self.footnotes.current() { + if matches!(e, Event::End(Container::Footnote { .. })) { + self.footnotes.end(); + } else { + events.push(e.clone()); + } 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("

")?; - } + if matches!(&e, Event::Start(Container::LinkDefinition { .. }, ..)) { + self.ignore = true; + return Ok(()); + } + + if matches!(&e, Event::End(Container::LinkDefinition { .. })) { + self.ignore = false; + return Ok(()); + } + + if self.ignore { + return Ok(()); } match e { Event::Start(c, attrs) => { - if c.is_block() && !self.first_line { + if c.is_block() && self.not_first_line { out.write_char('\n')?; } if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) { @@ -95,16 +127,7 @@ impl Render for Renderer { } Container::DescriptionList => out.write_str(" out.write_str(" { - debug_assert!(self.footnote_number.is_none()); - self.footnote_number = Some((*number).try_into().unwrap()); - if !self.encountered_footnote { - self.encountered_footnote = true; - out.write_str("
\n
\n
    \n")?; - } - write!(out, "
  1. ", number)?; - return Ok(()); - } + Container::Footnote { .. } => unreachable!(), Container::Table => out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" out.write_str(" return Ok(()), } for (a, v) in attrs.iter().filter(|(a, _)| *a != "class") { @@ -263,7 +287,7 @@ impl Render for Renderer { } } Event::End(c) => { - if c.is_block_container() && !matches!(c, Container::Footnote { .. }) { + if c.is_block_container() { out.write_char('\n')?; } if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) { @@ -287,19 +311,7 @@ impl Render for Renderer { } Container::DescriptionList => out.write_str("")?, Container::DescriptionDetails => out.write_str("")?, - Container::Footnote { number, .. } => { - if !close_para { - // create a new paragraph - out.write_str("\n

    ")?; - } - write!( - out, - r##"↩︎︎

    "##, - number, - )?; - out.write_str("\n
  2. ")?; - self.footnote_number = None; - } + Container::Footnote { .. } => unreachable!(), Container::Table => out.write_str("")?, Container::TableRow { .. } => out.write_str("")?, Container::Section { .. } => out.write_str("
")?, @@ -308,10 +320,8 @@ impl Render for Renderer { if matches!(self.list_tightness.last(), Some(true)) { return Ok(()); } - if self.footnote_number.is_none() { + if !self.footnotes.in_epilogue() { out.write_str("

")?; - } else { - self.close_para = true; } } Container::Heading { level, .. } => write!(out, "", level)?, @@ -350,6 +360,7 @@ impl Render for Renderer { Container::Strong => out.write_str("")?, Container::Emphasis => out.write_str("")?, Container::Mark => out.write_str("")?, + Container::LinkDefinition { .. } => unreachable!(), } } Event::Str(s) => match self.raw { @@ -358,7 +369,8 @@ impl Render for Renderer { Raw::Html => out.write_str(s)?, Raw::Other => {} }, - Event::FootnoteReference(_tag, number) => { + Event::FootnoteReference(label) => { + let number = self.footnotes.reference(label); if self.img_alt_text == 0 { write!( out, @@ -378,7 +390,7 @@ impl Render for Renderer { Event::NonBreakingSpace => out.write_str(" ")?, Event::Hardbreak => out.write_str("
\n")?, Event::Softbreak => out.write_char('\n')?, - Event::Escape | Event::Blankline => unreachable!("filtered out"), + Event::Escape | Event::Blankline => {} Event::ThematicBreak(attrs) => { out.write_str("\n")?; } } - self.first_line = false; + self.not_first_line = true; Ok(()) } @@ -398,9 +410,41 @@ impl Render for Renderer { where W: std::fmt::Write, { - if self.encountered_footnote { + if self.footnotes.reference_encountered() { + out.write_str("\n
\n
\n
    ")?; + + while let Some((number, events)) = self.footnotes.next() { + write!(out, "\n
  1. ", number)?; + + let mut unclosed_para = false; + for e in events.iter().flatten() { + if matches!(&e, Event::Blankline | Event::Escape) { + continue; + } + if unclosed_para { + // not a footnote, so no need to add href before para close + out.write_str("

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

    ")?; + } + write!( + out, + r##"↩︎︎

    "##, + number, + )?; + + out.write_str("\n
  2. ")?; + } + out.write_str("\n
\n
")?; } + out.write_char('\n')?; Ok(()) @@ -445,3 +489,73 @@ where } out.write_str(s) } + +/// Helper to aggregate footnotes for rendering at the end of the document. It will cache footnote +/// events until they should be emitted at the end. +/// +/// When footnotes should be rendered, they can be pulled with the [`Footnotes::next`] function in +/// the order they were first referenced. +#[derive(Default)] +struct Footnotes<'s> { + /// Stack of current open footnotes, with label and staging buffer. + open: Vec<(&'s str, Vec>)>, + /// Footnote references in the order they were first encountered. + references: Vec<&'s str>, + /// Events for each footnote. + events: Map<&'s str, Vec>>, + /// Number of last footnote that was emitted. + number: usize, +} + +impl<'s> Footnotes<'s> { + /// Returns `true` if any reference has been encountered. + fn reference_encountered(&self) -> bool { + !self.references.is_empty() + } + + /// Returns `true` if within the epilogue, i.e. if any footnotes have been pulled. + fn in_epilogue(&self) -> bool { + self.number > 0 + } + + /// Add a footnote reference. + fn reference(&mut self, label: &'s str) -> usize { + self.references + .iter() + .position(|t| *t == label) + .map_or_else( + || { + self.references.push(label); + self.references.len() + }, + |i| i + 1, + ) + } + + /// Start aggregating a footnote. + fn start(&mut self, label: &'s str, events: Vec>) { + self.open.push((label, events)); + } + + /// Obtain the current (most recently started) footnote. + fn current(&mut self) -> Option<&mut Vec>> { + self.open.last_mut().map(|(_, e)| e) + } + + /// End the current (most recently started) footnote. + fn end(&mut self) { + let (label, stage) = self.open.pop().unwrap(); + self.events.insert(label, stage); + } +} + +impl<'s> Iterator for Footnotes<'s> { + type Item = (usize, Option>>); + + fn next(&mut self) -> Option { + self.references.get(self.number).map(|label| { + self.number += 1; + (self.number, self.events.remove(label)) + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 05e1228..9ce7812 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,11 +75,6 @@ type CowStr<'s> = std::borrow::Cow<'s, str>; /// 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`]): @@ -90,7 +85,7 @@ type CowStr<'s> = std::borrow::Cow<'s, str>; /// # use jotdown::Render; /// # let events = std::iter::empty(); /// let mut output = String::new(); -/// let mut renderer = jotdown::html::Renderer::default(); +/// let renderer = jotdown::html::Renderer::default(); /// renderer.push(events, &mut output); /// # } /// ``` @@ -103,54 +98,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 mut renderer = jotdown::html::Renderer::default(); +/// let renderer = jotdown::html::Renderer::default(); /// renderer.write(events, &mut out).unwrap(); /// # } /// ``` pub trait Render { - /// Render a single event. - fn render_event<'s, W>(&mut self, e: &Event<'s>, out: W) -> std::fmt::Result - where - W: std::fmt::Write; - - /// 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(&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(&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 + fn push<'s, I, W>(&self, events: I, out: W) -> fmt::Result where I: Iterator>, - W: fmt::Write, - { - self.render_prologue(&mut out)?; - events.try_for_each(|e| self.render_event(&e, &mut out))?; - self.render_epilogue(&mut out) - } + W: fmt::Write; /// 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, W>(&mut self, events: I, out: W) -> io::Result<()> + fn write<'s, I, W>(&self, events: I, out: W) -> io::Result<()> where I: Iterator>, W: io::Write, @@ -177,26 +140,21 @@ pub trait Render { /// # use jotdown::Render; /// # let events: &[jotdown::Event] = &[]; /// let mut output = String::new(); - /// let mut renderer = jotdown::html::Renderer::default(); + /// let 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 + fn push_borrowed<'s, E, I, W>(&self, events: I, out: W) -> fmt::Result where E: AsRef>, I: Iterator, - 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) - } + W: fmt::Write; /// 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<()> + fn write_borrowed<'s, E, I, W>(&self, events: I, out: W) -> io::Result<()> where E: AsRef>, I: Iterator, @@ -251,7 +209,7 @@ pub enum Event<'s> { /// A string object, text only. Str(CowStr<'s>), /// A footnote reference. - FootnoteReference(&'s str, usize), + FootnoteReference(&'s str), /// A symbol, by default rendered literally but may be treated specially. Symbol(CowStr<'s>), /// Left single quotation mark. @@ -304,7 +262,7 @@ pub enum Container<'s> { /// Details describing a term within a description list. DescriptionDetails, /// A footnote definition. - Footnote { tag: &'s str, number: usize }, + Footnote { label: &'s str }, /// A table element. Table, /// A row element of a table. @@ -327,6 +285,8 @@ pub enum Container<'s> { Caption, /// A term within a description list. DescriptionTerm, + /// A link definition. + LinkDefinition { label: &'s str }, /// A block with raw markup for a specific output format. RawBlock { format: &'s str }, /// A block with code in a specific language. @@ -381,6 +341,7 @@ impl<'s> Container<'s> { | Self::TableCell { .. } | Self::Caption | Self::DescriptionTerm + | Self::LinkDefinition { .. } | Self::RawBlock { .. } | Self::CodeBlock { .. } => true, Self::Span @@ -419,6 +380,7 @@ impl<'s> Container<'s> { | Self::TableCell { .. } | Self::Caption | Self::DescriptionTerm + | Self::LinkDefinition { .. } | Self::RawBlock { .. } | Self::CodeBlock { .. } | Self::Span @@ -607,15 +569,6 @@ pub struct Parser<'s> { /// Currently within a verbatim code block. verbatim: bool, - /// Footnote references in the order they were encountered, without duplicates. - footnote_references: Vec<&'s str>, - /// Cache of footnotes to emit at the end. - footnotes: Map<&'s str, block::Tree>, - /// Next or current footnote being parsed and emitted. - footnote_index: usize, - /// Currently within a footnote. - footnote_active: bool, - /// Inline parser. inline_parser: inline::Parser<'s>, } @@ -793,10 +746,6 @@ impl<'s> Parser<'s> { block_attributes: Attributes::new(), table_head_row: false, verbatim: false, - footnote_references: Vec::new(), - footnotes: Map::new(), - footnote_index: 0, - footnote_active: false, inline_parser, } } @@ -885,19 +834,7 @@ impl<'s> Parser<'s> { } inline::EventKind::Atom(a) => match a { inline::Atom::FootnoteReference => { - let tag = inline.span.of(self.src); - let number = self - .footnote_references - .iter() - .position(|t| *t == tag) - .map_or_else( - || { - self.footnote_references.push(tag); - self.footnote_references.len() - }, - |i| i + 1, - ); - Event::FootnoteReference(inline.span.of(self.src), number) + Event::FootnoteReference(inline.span.of(self.src)) } inline::Atom::Symbol => Event::Symbol(inline.span.of(self.src).into()), inline::Atom::Quote { ty, left } => match (ty, left) { @@ -941,14 +878,6 @@ impl<'s> Parser<'s> { let cont = match c { block::Node::Leaf(l) => { self.inline_parser.reset(); - if matches!(l, block::Leaf::LinkDefinition) { - // ignore link definitions - if enter { - self.tree.take_inlines().last(); - } - self.block_attributes = Attributes::new(); - continue; - } match l { block::Leaf::Paragraph => Container::Paragraph, block::Leaf::Heading { has_section } => Container::Heading { @@ -977,7 +906,9 @@ impl<'s> Parser<'s> { head: self.table_head_row, }, block::Leaf::Caption => Container::Caption, - block::Leaf::LinkDefinition => unreachable!(), + block::Leaf::LinkDefinition => { + Container::LinkDefinition { label: content } + } } } block::Node::Container(c) => match c { @@ -985,12 +916,7 @@ impl<'s> Parser<'s> { block::Container::Div { .. } => Container::Div { class: (!ev.span.is_empty()).then(|| content), }, - block::Container::Footnote => { - debug_assert!(enter); - self.footnotes.insert(content, self.tree.take_branch()); - self.block_attributes = Attributes::new(); - continue; - } + block::Container::Footnote => Container::Footnote { label: content }, block::Container::List(block::ListKind { ty, tight }) => { if matches!(ty, block::ListType::Description) { Container::DescriptionList @@ -1057,43 +983,13 @@ impl<'s> Parser<'s> { } None } - - fn footnote(&mut self) -> Option> { - if self.footnote_active { - let tag = self.footnote_references.get(self.footnote_index).unwrap(); - self.footnote_index += 1; - self.footnote_active = false; - Some(Event::End(Container::Footnote { - tag, - number: self.footnote_index, - })) - } else if let Some(tag) = self.footnote_references.get(self.footnote_index) { - self.tree = self - .footnotes - .remove(tag) - .unwrap_or_else(block::Tree::empty); - self.footnote_active = true; - - Some(Event::Start( - Container::Footnote { - tag, - number: self.footnote_index + 1, - }, - Attributes::new(), - )) - } else { - None - } - } } impl<'s> Iterator for Parser<'s> { type Item = Event<'s>; fn next(&mut self) -> Option { - self.inline() - .or_else(|| self.block()) - .or_else(|| self.footnote()) + self.inline().or_else(|| self.block()) } } @@ -1418,6 +1314,9 @@ mod test { End(Link("url".into(), LinkType::Span(SpanLinkType::Reference))), End(Paragraph), Blankline, + Start(LinkDefinition { label: "tag" }, Attributes::new()), + Str("url".into()), + End(LinkDefinition { label: "tag" }), ); test_parse!( concat!( @@ -1434,6 +1333,9 @@ mod test { End(Image("url".into(), SpanLinkType::Reference)), End(Paragraph), Blankline, + Start(LinkDefinition { label: "tag" }, Attributes::new()), + Str("url".into()), + End(LinkDefinition { label: "tag" }), ); } @@ -1483,6 +1385,9 @@ mod test { End(Paragraph), End(Blockquote), Blankline, + Start(LinkDefinition { label: "a b" }, Attributes::new()), + Str("url".into()), + End(LinkDefinition { label: "a b" }), ); } @@ -1504,6 +1409,11 @@ mod test { End(Link("url".into(), LinkType::Span(SpanLinkType::Reference))), End(Paragraph), Blankline, + Start(LinkDefinition { label: "tag" }, Attributes::new()), + Str("u".into()), + Softbreak, + Str("rl".into()), + End(LinkDefinition { label: "tag" }), ); test_parse!( concat!( @@ -1521,6 +1431,9 @@ mod test { End(Link("url".into(), LinkType::Span(SpanLinkType::Reference))), End(Paragraph), Blankline, + Start(LinkDefinition { label: "tag" }, Attributes::new()), + Str("url".into()), + End(LinkDefinition { label: "tag" }), ); } @@ -1543,6 +1456,12 @@ mod test { End(Link("url".into(), LinkType::Span(SpanLinkType::Reference))), End(Paragraph), Blankline, + Start( + LinkDefinition { label: "tag" }, + [("a", "b")].into_iter().collect() + ), + Str("url".into()), + End(LinkDefinition { label: "tag" }), Start(Paragraph, Attributes::new()), Str("para".into()), End(Paragraph), @@ -1584,43 +1503,10 @@ mod test { test_parse!( "[^a][^b][^c]", Start(Paragraph, Attributes::new()), - FootnoteReference("a", 1), - FootnoteReference("b", 2), - FootnoteReference("c", 3), + FootnoteReference("a"), + FootnoteReference("b"), + FootnoteReference("c"), End(Paragraph), - Start( - Footnote { - tag: "a", - number: 1 - }, - Attributes::new() - ), - End(Footnote { - tag: "a", - number: 1 - }), - Start( - Footnote { - tag: "b", - number: 2 - }, - Attributes::new() - ), - End(Footnote { - tag: "b", - number: 2 - }), - Start( - Footnote { - tag: "c", - number: 3 - }, - Attributes::new() - ), - End(Footnote { - tag: "c", - number: 3 - }), ); } @@ -1629,23 +1515,14 @@ mod test { test_parse!( "[^a]\n\n[^a]: a\n", Start(Paragraph, Attributes::new()), - FootnoteReference("a", 1), + FootnoteReference("a"), End(Paragraph), Blankline, - Start( - Footnote { - tag: "a", - number: 1 - }, - Attributes::new() - ), + Start(Footnote { label: "a" }, Attributes::new()), Start(Paragraph, Attributes::new()), Str("a".into()), End(Paragraph), - End(Footnote { - tag: "a", - number: 1 - }), + End(Footnote { label: "a" }), ); } @@ -1660,16 +1537,10 @@ mod test { " def", // ), Start(Paragraph, Attributes::new()), - FootnoteReference("a", 1), + FootnoteReference("a"), End(Paragraph), Blankline, - Start( - Footnote { - tag: "a", - number: 1 - }, - Attributes::new() - ), + Start(Footnote { label: "a" }, Attributes::new()), Start(Paragraph, Attributes::new()), Str("abc".into()), End(Paragraph), @@ -1677,10 +1548,7 @@ mod test { Start(Paragraph, Attributes::new()), Str("def".into()), End(Paragraph), - End(Footnote { - tag: "a", - number: 1 - }), + End(Footnote { label: "a" }), ); } @@ -1694,26 +1562,17 @@ mod test { "para\n", // ), Start(Paragraph, Attributes::new()), - FootnoteReference("a", 1), + FootnoteReference("a"), End(Paragraph), Blankline, - Start(Paragraph, Attributes::new()), - Str("para".into()), - End(Paragraph), - Start( - Footnote { - tag: "a", - number: 1 - }, - Attributes::new() - ), + Start(Footnote { label: "a" }, Attributes::new()), Start(Paragraph, Attributes::new()), Str("note".into()), End(Paragraph), - End(Footnote { - tag: "a", - number: 1 - }), + End(Footnote { label: "a" }), + Start(Paragraph, Attributes::new()), + Str("para".into()), + End(Paragraph), ); } diff --git a/src/main.rs b/src/main.rs index e73c081..d207dbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,7 +68,7 @@ fn run() -> Result<(), std::io::Error> { }; let parser = jotdown::Parser::new(&content); - let mut renderer = jotdown::html::Renderer::default(); + let renderer = jotdown::html::Renderer::default(); match app.output { Some(path) => renderer.write(parser, File::create(path)?)?, diff --git a/src/tree.rs b/src/tree.rs index 4992a75..0101370 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -36,14 +36,6 @@ pub struct Tree { } impl Tree { - pub fn empty() -> Self { - Self { - nodes: vec![].into_boxed_slice().into(), - branch: Vec::new(), - head: None, - } - } - /// Count number of direct children nodes. pub fn count_children(&self) -> usize { let mut head = self.head; @@ -56,22 +48,6 @@ impl Tree { count } - /// Split off the remaining part of the current branch. The returned [`Tree`] will continue on - /// the branch, this [`Tree`] will skip over the current branch. - pub fn take_branch(&mut self) -> Self { - let head = self.head.take(); - self.head = self.branch.pop(); - if let Some(h) = self.head { - let n = &self.nodes[h.index()]; - self.head = n.next; - } - Self { - nodes: self.nodes.clone(), - branch: Vec::new(), - head, - } - } - /// Retrieve all inlines until the end of the current container. Panics if any upcoming node is /// not an inline node. pub fn take_inlines(&mut self) -> impl Iterator + '_ { @@ -410,9 +386,6 @@ impl std::fmt::Debug for mod test { use crate::Span; - use super::Event; - use super::EventKind; - #[test] fn fmt() { let mut tree = super::Builder::new(); @@ -451,81 +424,4 @@ mod test { ) ); } - - #[test] - fn branch_take_branch() { - let mut b = super::Builder::new(); - let sp = Span::new(0, 0); - b.enter(1, sp); - b.atom(11, sp); - b.exit(); - b.enter(2, sp); - b.enter(21, sp); - b.atom(211, sp); - b.exit(); - b.exit(); - b.enter(3, sp); - b.atom(31, sp); - b.exit(); - let mut tree = b.finish(); - - assert_eq!( - (&mut tree).take(3).collect::>(), - &[ - Event { - kind: EventKind::Enter(1), - span: sp - }, - Event { - kind: EventKind::Atom(11), - span: sp - }, - Event { - kind: EventKind::Exit(1), - span: sp - }, - ] - ); - assert_eq!( - tree.next(), - Some(Event { - kind: EventKind::Enter(2), - span: sp - }) - ); - assert_eq!( - tree.take_branch().collect::>(), - &[ - Event { - kind: EventKind::Enter(21), - span: sp - }, - Event { - kind: EventKind::Atom(211), - span: sp - }, - Event { - kind: EventKind::Exit(21), - span: sp - }, - ] - ); - assert_eq!( - tree.collect::>(), - &[ - Event { - kind: EventKind::Enter(3), - span: sp - }, - Event { - kind: EventKind::Atom(31), - span: sp - }, - Event { - kind: EventKind::Exit(3), - span: sp - }, - ] - ); - } } diff --git a/tests/suite/Makefile b/tests/suite/Makefile index ed00f7e..e52b030 100644 --- a/tests/suite/Makefile +++ b/tests/suite/Makefile @@ -5,9 +5,9 @@ TEST=$(shell find . -name '*.test' | sort) TEST_RS=${TEST:.test=.rs} -BLACKLIST += filters # lua filters not implemented -BLACKLIST += symb # uses ast -BLACKLIST += sourcepos # not parsable +BLACKLIST += djot_js_filters # lua filters not implemented +BLACKLIST += djot_js_symb # uses ast +BLACKLIST += djot_js_sourcepos # not parsable .PHONY: suite suite: mod.rs diff --git a/tests/suite/footnotes.test b/tests/suite/footnotes.test new file mode 100644 index 0000000..3132f9e --- /dev/null +++ b/tests/suite/footnotes.test @@ -0,0 +1,58 @@ +Footnote references may appear within a footnote. + +``` +[^a] + +[^a]: a[^b][^c] +[^b]: b +. +

1

+
+
+
    +
  1. +

    a23↩︎︎

    +
  2. +
  3. +

    b↩︎︎

    +
  4. +
  5. +

    ↩︎︎

    +
  6. +
+
+``` + +Footnote references in unreferenced footnotes are ignored. + +``` +para + +[^a]: a[^b][^c] +[^b]: b +. +

para

+``` + +Footnotes may appear within footnotes. + +``` +[^b] +[^a] + +[^a]: [^b]: inner +. +

1 +2

+
+
+
    +
  1. +

    inner↩︎︎

    +
  2. +
  3. +

    ↩︎︎

    +
  4. +
+
+```