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, "- ", 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")?;
- 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- ", 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 ")?;
+ }
+
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
+
+```
+
+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
+
+
+
+-
+
inner↩︎︎
+
+-
+
↩︎︎
+
+
+
+```