commit
				
					
						e8f2002c69
					
				
			
		
					 8 changed files with 323 additions and 372 deletions
				
			
		
							
								
								
									
										4
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
										
									
									
									
								
							| 
						 | 
					@ -29,7 +29,7 @@ check:
 | 
				
			||||||
suite:
 | 
					suite:
 | 
				
			||||||
	git submodule update --init modules/djot.js
 | 
						git submodule update --init modules/djot.js
 | 
				
			||||||
	for f in $$(find modules/djot.js/test -name '*.test' | xargs basename -a); do \
 | 
						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
 | 
						done
 | 
				
			||||||
	(cd tests/suite && make)
 | 
						(cd tests/suite && make)
 | 
				
			||||||
	cargo test --features suite suite::
 | 
						cargo test --features suite suite::
 | 
				
			||||||
| 
						 | 
					@ -102,7 +102,7 @@ afl_tmin:
 | 
				
			||||||
clean:
 | 
					clean:
 | 
				
			||||||
	cargo clean
 | 
						cargo clean
 | 
				
			||||||
	git submodule deinit -f --all
 | 
						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)
 | 
						(cd tests/suite && make clean)
 | 
				
			||||||
	rm -f tests/bench/*.dj
 | 
						rm -f tests/bench/*.dj
 | 
				
			||||||
	(cd tests/bench && make clean)
 | 
						(cd tests/bench && make clean)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										24
									
								
								bench/input/block_footnotes.dj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								bench/input/block_footnotes.dj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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.
 | 
				
			||||||
							
								
								
									
										234
									
								
								src/html.rs
									
										
									
									
									
								
							
							
						
						
									
										234
									
								
								src/html.rs
									
										
									
									
									
								
							| 
						 | 
					@ -5,61 +5,93 @@ use crate::Container;
 | 
				
			||||||
use crate::Event;
 | 
					use crate::Event;
 | 
				
			||||||
use crate::LinkType;
 | 
					use crate::LinkType;
 | 
				
			||||||
use crate::ListKind;
 | 
					use crate::ListKind;
 | 
				
			||||||
 | 
					use crate::Map;
 | 
				
			||||||
use crate::OrderedListNumbering::*;
 | 
					use crate::OrderedListNumbering::*;
 | 
				
			||||||
use crate::Render;
 | 
					use crate::Render;
 | 
				
			||||||
use crate::SpanLinkType;
 | 
					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<Item = Event<'s>>,
 | 
				
			||||||
 | 
					        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<Event<'s>>,
 | 
				
			||||||
 | 
					        I: Iterator<Item = E>,
 | 
				
			||||||
 | 
					        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 {
 | 
					enum Raw {
 | 
				
			||||||
    None,
 | 
					    None,
 | 
				
			||||||
    Html,
 | 
					    Html,
 | 
				
			||||||
    Other,
 | 
					    Other,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Renderer {
 | 
					impl Default for Raw {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self::None
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Default)]
 | 
				
			||||||
 | 
					struct Writer<'s> {
 | 
				
			||||||
    raw: Raw,
 | 
					    raw: Raw,
 | 
				
			||||||
    img_alt_text: usize,
 | 
					    img_alt_text: usize,
 | 
				
			||||||
    list_tightness: Vec<bool>,
 | 
					    list_tightness: Vec<bool>,
 | 
				
			||||||
    encountered_footnote: bool,
 | 
					    not_first_line: bool,
 | 
				
			||||||
    footnote_number: Option<std::num::NonZeroUsize>,
 | 
					    ignore: bool,
 | 
				
			||||||
    first_line: bool,
 | 
					    footnotes: Footnotes<'s>,
 | 
				
			||||||
    close_para: bool,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for Renderer {
 | 
					impl<'s> Writer<'s> {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn render_event<W>(&mut self, e: &Event<'s>, mut out: W) -> std::fmt::Result
 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            raw: Raw::None,
 | 
					 | 
				
			||||||
            img_alt_text: 0,
 | 
					 | 
				
			||||||
            list_tightness: Vec::new(),
 | 
					 | 
				
			||||||
            encountered_footnote: false,
 | 
					 | 
				
			||||||
            footnote_number: None,
 | 
					 | 
				
			||||||
            first_line: true,
 | 
					 | 
				
			||||||
            close_para: false,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Render for Renderer {
 | 
					 | 
				
			||||||
    fn render_event<'s, W>(&mut self, e: &Event<'s>, mut out: W) -> std::fmt::Result
 | 
					 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        W: std::fmt::Write,
 | 
					        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(());
 | 
					            return Ok(());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let close_para = self.close_para;
 | 
					        if matches!(&e, Event::Start(Container::LinkDefinition { .. }, ..)) {
 | 
				
			||||||
        if close_para {
 | 
					            self.ignore = true;
 | 
				
			||||||
            self.close_para = false;
 | 
					            return Ok(());
 | 
				
			||||||
            if !matches!(&e, Event::End(Container::Footnote { .. })) {
 | 
					 | 
				
			||||||
                // no need to add href before para close
 | 
					 | 
				
			||||||
                out.write_str("</p>")?;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if matches!(&e, Event::End(Container::LinkDefinition { .. })) {
 | 
				
			||||||
 | 
					            self.ignore = false;
 | 
				
			||||||
 | 
					            return Ok(());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.ignore {
 | 
				
			||||||
 | 
					            return Ok(());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match e {
 | 
					        match e {
 | 
				
			||||||
            Event::Start(c, attrs) => {
 | 
					            Event::Start(c, attrs) => {
 | 
				
			||||||
                if c.is_block() && !self.first_line {
 | 
					                if c.is_block() && self.not_first_line {
 | 
				
			||||||
                    out.write_char('\n')?;
 | 
					                    out.write_char('\n')?;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
 | 
					                if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
 | 
				
			||||||
| 
						 | 
					@ -95,16 +127,7 @@ impl Render for Renderer {
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    Container::DescriptionList => out.write_str("<dl")?,
 | 
					                    Container::DescriptionList => out.write_str("<dl")?,
 | 
				
			||||||
                    Container::DescriptionDetails => out.write_str("<dd")?,
 | 
					                    Container::DescriptionDetails => out.write_str("<dd")?,
 | 
				
			||||||
                    Container::Footnote { number, .. } => {
 | 
					                    Container::Footnote { .. } => unreachable!(),
 | 
				
			||||||
                        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("<section role=\"doc-endnotes\">\n<hr>\n<ol>\n")?;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        write!(out, "<li id=\"fn{}\">", number)?;
 | 
					 | 
				
			||||||
                        return Ok(());
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    Container::Table => out.write_str("<table")?,
 | 
					                    Container::Table => out.write_str("<table")?,
 | 
				
			||||||
                    Container::TableRow { .. } => out.write_str("<tr")?,
 | 
					                    Container::TableRow { .. } => out.write_str("<tr")?,
 | 
				
			||||||
                    Container::Section { .. } => out.write_str("<section")?,
 | 
					                    Container::Section { .. } => out.write_str("<section")?,
 | 
				
			||||||
| 
						 | 
					@ -158,6 +181,7 @@ impl Render for Renderer {
 | 
				
			||||||
                    Container::Strong => out.write_str("<strong")?,
 | 
					                    Container::Strong => out.write_str("<strong")?,
 | 
				
			||||||
                    Container::Emphasis => out.write_str("<em")?,
 | 
					                    Container::Emphasis => out.write_str("<em")?,
 | 
				
			||||||
                    Container::Mark => out.write_str("<mark")?,
 | 
					                    Container::Mark => out.write_str("<mark")?,
 | 
				
			||||||
 | 
					                    Container::LinkDefinition { .. } => return Ok(()),
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for (a, v) in attrs.iter().filter(|(a, _)| *a != "class") {
 | 
					                for (a, v) in attrs.iter().filter(|(a, _)| *a != "class") {
 | 
				
			||||||
| 
						 | 
					@ -263,7 +287,7 @@ impl Render for Renderer {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Event::End(c) => {
 | 
					            Event::End(c) => {
 | 
				
			||||||
                if c.is_block_container() && !matches!(c, Container::Footnote { .. }) {
 | 
					                if c.is_block_container() {
 | 
				
			||||||
                    out.write_char('\n')?;
 | 
					                    out.write_char('\n')?;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
 | 
					                if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
 | 
				
			||||||
| 
						 | 
					@ -287,19 +311,7 @@ impl Render for Renderer {
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    Container::DescriptionList => out.write_str("</dl>")?,
 | 
					                    Container::DescriptionList => out.write_str("</dl>")?,
 | 
				
			||||||
                    Container::DescriptionDetails => out.write_str("</dd>")?,
 | 
					                    Container::DescriptionDetails => out.write_str("</dd>")?,
 | 
				
			||||||
                    Container::Footnote { number, .. } => {
 | 
					                    Container::Footnote { .. } => unreachable!(),
 | 
				
			||||||
                        if !close_para {
 | 
					 | 
				
			||||||
                            // create a new paragraph
 | 
					 | 
				
			||||||
                            out.write_str("\n<p>")?;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        write!(
 | 
					 | 
				
			||||||
                            out,
 | 
					 | 
				
			||||||
                            r##"<a href="#fnref{}" role="doc-backlink">↩︎︎</a></p>"##,
 | 
					 | 
				
			||||||
                            number,
 | 
					 | 
				
			||||||
                        )?;
 | 
					 | 
				
			||||||
                        out.write_str("\n</li>")?;
 | 
					 | 
				
			||||||
                        self.footnote_number = None;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    Container::Table => out.write_str("</table>")?,
 | 
					                    Container::Table => out.write_str("</table>")?,
 | 
				
			||||||
                    Container::TableRow { .. } => out.write_str("</tr>")?,
 | 
					                    Container::TableRow { .. } => out.write_str("</tr>")?,
 | 
				
			||||||
                    Container::Section { .. } => out.write_str("</section>")?,
 | 
					                    Container::Section { .. } => out.write_str("</section>")?,
 | 
				
			||||||
| 
						 | 
					@ -308,10 +320,8 @@ impl Render for Renderer {
 | 
				
			||||||
                        if matches!(self.list_tightness.last(), Some(true)) {
 | 
					                        if matches!(self.list_tightness.last(), Some(true)) {
 | 
				
			||||||
                            return Ok(());
 | 
					                            return Ok(());
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        if self.footnote_number.is_none() {
 | 
					                        if !self.footnotes.in_epilogue() {
 | 
				
			||||||
                            out.write_str("</p>")?;
 | 
					                            out.write_str("</p>")?;
 | 
				
			||||||
                        } else {
 | 
					 | 
				
			||||||
                            self.close_para = true;
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    Container::Heading { level, .. } => write!(out, "</h{}>", level)?,
 | 
					                    Container::Heading { level, .. } => write!(out, "</h{}>", level)?,
 | 
				
			||||||
| 
						 | 
					@ -350,6 +360,7 @@ impl Render for Renderer {
 | 
				
			||||||
                    Container::Strong => out.write_str("</strong>")?,
 | 
					                    Container::Strong => out.write_str("</strong>")?,
 | 
				
			||||||
                    Container::Emphasis => out.write_str("</em>")?,
 | 
					                    Container::Emphasis => out.write_str("</em>")?,
 | 
				
			||||||
                    Container::Mark => out.write_str("</mark>")?,
 | 
					                    Container::Mark => out.write_str("</mark>")?,
 | 
				
			||||||
 | 
					                    Container::LinkDefinition { .. } => unreachable!(),
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Event::Str(s) => match self.raw {
 | 
					            Event::Str(s) => match self.raw {
 | 
				
			||||||
| 
						 | 
					@ -358,7 +369,8 @@ impl Render for Renderer {
 | 
				
			||||||
                Raw::Html => out.write_str(s)?,
 | 
					                Raw::Html => out.write_str(s)?,
 | 
				
			||||||
                Raw::Other => {}
 | 
					                Raw::Other => {}
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            Event::FootnoteReference(_tag, number) => {
 | 
					            Event::FootnoteReference(label) => {
 | 
				
			||||||
 | 
					                let number = self.footnotes.reference(label);
 | 
				
			||||||
                if self.img_alt_text == 0 {
 | 
					                if self.img_alt_text == 0 {
 | 
				
			||||||
                    write!(
 | 
					                    write!(
 | 
				
			||||||
                        out,
 | 
					                        out,
 | 
				
			||||||
| 
						 | 
					@ -378,7 +390,7 @@ impl Render for Renderer {
 | 
				
			||||||
            Event::NonBreakingSpace => out.write_str(" ")?,
 | 
					            Event::NonBreakingSpace => out.write_str(" ")?,
 | 
				
			||||||
            Event::Hardbreak => out.write_str("<br>\n")?,
 | 
					            Event::Hardbreak => out.write_str("<br>\n")?,
 | 
				
			||||||
            Event::Softbreak => out.write_char('\n')?,
 | 
					            Event::Softbreak => out.write_char('\n')?,
 | 
				
			||||||
            Event::Escape | Event::Blankline => unreachable!("filtered out"),
 | 
					            Event::Escape | Event::Blankline => {}
 | 
				
			||||||
            Event::ThematicBreak(attrs) => {
 | 
					            Event::ThematicBreak(attrs) => {
 | 
				
			||||||
                out.write_str("\n<hr")?;
 | 
					                out.write_str("\n<hr")?;
 | 
				
			||||||
                for (a, v) in attrs.iter() {
 | 
					                for (a, v) in attrs.iter() {
 | 
				
			||||||
| 
						 | 
					@ -389,7 +401,7 @@ impl Render for Renderer {
 | 
				
			||||||
                out.write_str(">")?;
 | 
					                out.write_str(">")?;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        self.first_line = false;
 | 
					        self.not_first_line = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -398,9 +410,41 @@ impl Render for Renderer {
 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        W: std::fmt::Write,
 | 
					        W: std::fmt::Write,
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if self.encountered_footnote {
 | 
					        if self.footnotes.reference_encountered() {
 | 
				
			||||||
 | 
					            out.write_str("\n<section role=\"doc-endnotes\">\n<hr>\n<ol>")?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while let Some((number, events)) = self.footnotes.next() {
 | 
				
			||||||
 | 
					                write!(out, "\n<li id=\"fn{}\">", 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("</p>")?;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    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<p>")?;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                write!(
 | 
				
			||||||
 | 
					                    out,
 | 
				
			||||||
 | 
					                    r##"<a href="#fnref{}" role="doc-backlink">↩︎︎</a></p>"##,
 | 
				
			||||||
 | 
					                    number,
 | 
				
			||||||
 | 
					                )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                out.write_str("\n</li>")?;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            out.write_str("\n</ol>\n</section>")?;
 | 
					            out.write_str("\n</ol>\n</section>")?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        out.write_char('\n')?;
 | 
					        out.write_char('\n')?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
| 
						 | 
					@ -445,3 +489,73 @@ where
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    out.write_str(s)
 | 
					    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<Event<'s>>)>,
 | 
				
			||||||
 | 
					    /// Footnote references in the order they were first encountered.
 | 
				
			||||||
 | 
					    references: Vec<&'s str>,
 | 
				
			||||||
 | 
					    /// Events for each footnote.
 | 
				
			||||||
 | 
					    events: Map<&'s str, Vec<Event<'s>>>,
 | 
				
			||||||
 | 
					    /// 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<Event<'s>>) {
 | 
				
			||||||
 | 
					        self.open.push((label, events));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Obtain the current (most recently started) footnote.
 | 
				
			||||||
 | 
					    fn current(&mut self) -> Option<&mut Vec<Event<'s>>> {
 | 
				
			||||||
 | 
					        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<Vec<Event<'s>>>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn next(&mut self) -> Option<Self::Item> {
 | 
				
			||||||
 | 
					        self.references.get(self.number).map(|label| {
 | 
				
			||||||
 | 
					            self.number += 1;
 | 
				
			||||||
 | 
					            (self.number, self.events.remove(label))
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										259
									
								
								src/lib.rs
									
										
									
									
									
								
							
							
						
						
									
										259
									
								
								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`]
 | 
					/// If ownership of the [`Event`]s cannot be given to the renderer, use [`Render::push_borrowed`]
 | 
				
			||||||
/// or [`Render::write_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
 | 
					/// # Examples
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// Push to a [`String`] (implements [`std::fmt::Write`]):
 | 
					/// Push to a [`String`] (implements [`std::fmt::Write`]):
 | 
				
			||||||
| 
						 | 
					@ -90,7 +85,7 @@ type CowStr<'s> = std::borrow::Cow<'s, str>;
 | 
				
			||||||
/// # use jotdown::Render;
 | 
					/// # use jotdown::Render;
 | 
				
			||||||
/// # let events = std::iter::empty();
 | 
					/// # let events = std::iter::empty();
 | 
				
			||||||
/// let mut output = String::new();
 | 
					/// let mut output = String::new();
 | 
				
			||||||
/// let mut renderer = jotdown::html::Renderer::default();
 | 
					/// let renderer = jotdown::html::Renderer::default();
 | 
				
			||||||
/// renderer.push(events, &mut output);
 | 
					/// renderer.push(events, &mut output);
 | 
				
			||||||
/// # }
 | 
					/// # }
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
| 
						 | 
					@ -103,54 +98,22 @@ type CowStr<'s> = std::borrow::Cow<'s, str>;
 | 
				
			||||||
/// # use jotdown::Render;
 | 
					/// # use jotdown::Render;
 | 
				
			||||||
/// # let events = std::iter::empty();
 | 
					/// # let events = std::iter::empty();
 | 
				
			||||||
/// let mut out = std::io::BufWriter::new(std::io::stdout());
 | 
					/// 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();
 | 
					/// renderer.write(events, &mut out).unwrap();
 | 
				
			||||||
/// # }
 | 
					/// # }
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
pub trait Render {
 | 
					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<W>(&mut self, _out: W) -> std::fmt::Result
 | 
					 | 
				
			||||||
    where
 | 
					 | 
				
			||||||
        W: std::fmt::Write,
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Render something after all events have been provided.
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// This does nothing by default, but an implementation may choose to append extra data at the
 | 
					 | 
				
			||||||
    /// end of the output if needed.
 | 
					 | 
				
			||||||
    fn render_epilogue<W>(&mut self, _out: W) -> std::fmt::Result
 | 
					 | 
				
			||||||
    where
 | 
					 | 
				
			||||||
        W: std::fmt::Write,
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Push owned [`Event`]s to a unicode-accepting buffer or stream.
 | 
					    /// 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
 | 
					    where
 | 
				
			||||||
        I: Iterator<Item = Event<'s>>,
 | 
					        I: Iterator<Item = Event<'s>>,
 | 
				
			||||||
        W: fmt::Write,
 | 
					        W: fmt::Write;
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        self.render_prologue(&mut out)?;
 | 
					 | 
				
			||||||
        events.try_for_each(|e| self.render_event(&e, &mut out))?;
 | 
					 | 
				
			||||||
        self.render_epilogue(&mut out)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Write owned [`Event`]s to a byte sink, encoded as UTF-8.
 | 
					    /// 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.
 | 
					    /// NOTE: This performs many small writes, so IO writes should be buffered with e.g.
 | 
				
			||||||
    /// [`std::io::BufWriter`].
 | 
					    /// [`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
 | 
					    where
 | 
				
			||||||
        I: Iterator<Item = Event<'s>>,
 | 
					        I: Iterator<Item = Event<'s>>,
 | 
				
			||||||
        W: io::Write,
 | 
					        W: io::Write,
 | 
				
			||||||
| 
						 | 
					@ -177,26 +140,21 @@ pub trait Render {
 | 
				
			||||||
    /// # use jotdown::Render;
 | 
					    /// # use jotdown::Render;
 | 
				
			||||||
    /// # let events: &[jotdown::Event] = &[];
 | 
					    /// # let events: &[jotdown::Event] = &[];
 | 
				
			||||||
    /// let mut output = String::new();
 | 
					    /// 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);
 | 
					    /// 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
 | 
					    where
 | 
				
			||||||
        E: AsRef<Event<'s>>,
 | 
					        E: AsRef<Event<'s>>,
 | 
				
			||||||
        I: Iterator<Item = E>,
 | 
					        I: Iterator<Item = E>,
 | 
				
			||||||
        W: fmt::Write,
 | 
					        W: fmt::Write;
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        self.render_prologue(&mut out)?;
 | 
					 | 
				
			||||||
        events.try_for_each(|e| self.render_event(e.as_ref(), &mut out))?;
 | 
					 | 
				
			||||||
        self.render_epilogue(&mut out)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Write borrowed [`Event`]s to a byte sink, encoded as UTF-8.
 | 
					    /// 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.
 | 
					    /// NOTE: This performs many small writes, so IO writes should be buffered with e.g.
 | 
				
			||||||
    /// [`std::io::BufWriter`].
 | 
					    /// [`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
 | 
					    where
 | 
				
			||||||
        E: AsRef<Event<'s>>,
 | 
					        E: AsRef<Event<'s>>,
 | 
				
			||||||
        I: Iterator<Item = E>,
 | 
					        I: Iterator<Item = E>,
 | 
				
			||||||
| 
						 | 
					@ -251,7 +209,7 @@ pub enum Event<'s> {
 | 
				
			||||||
    /// A string object, text only.
 | 
					    /// A string object, text only.
 | 
				
			||||||
    Str(CowStr<'s>),
 | 
					    Str(CowStr<'s>),
 | 
				
			||||||
    /// A footnote reference.
 | 
					    /// A footnote reference.
 | 
				
			||||||
    FootnoteReference(&'s str, usize),
 | 
					    FootnoteReference(&'s str),
 | 
				
			||||||
    /// A symbol, by default rendered literally but may be treated specially.
 | 
					    /// A symbol, by default rendered literally but may be treated specially.
 | 
				
			||||||
    Symbol(CowStr<'s>),
 | 
					    Symbol(CowStr<'s>),
 | 
				
			||||||
    /// Left single quotation mark.
 | 
					    /// Left single quotation mark.
 | 
				
			||||||
| 
						 | 
					@ -304,7 +262,7 @@ pub enum Container<'s> {
 | 
				
			||||||
    /// Details describing a term within a description list.
 | 
					    /// Details describing a term within a description list.
 | 
				
			||||||
    DescriptionDetails,
 | 
					    DescriptionDetails,
 | 
				
			||||||
    /// A footnote definition.
 | 
					    /// A footnote definition.
 | 
				
			||||||
    Footnote { tag: &'s str, number: usize },
 | 
					    Footnote { label: &'s str },
 | 
				
			||||||
    /// A table element.
 | 
					    /// A table element.
 | 
				
			||||||
    Table,
 | 
					    Table,
 | 
				
			||||||
    /// A row element of a table.
 | 
					    /// A row element of a table.
 | 
				
			||||||
| 
						 | 
					@ -327,6 +285,8 @@ pub enum Container<'s> {
 | 
				
			||||||
    Caption,
 | 
					    Caption,
 | 
				
			||||||
    /// A term within a description list.
 | 
					    /// A term within a description list.
 | 
				
			||||||
    DescriptionTerm,
 | 
					    DescriptionTerm,
 | 
				
			||||||
 | 
					    /// A link definition.
 | 
				
			||||||
 | 
					    LinkDefinition { label: &'s str },
 | 
				
			||||||
    /// A block with raw markup for a specific output format.
 | 
					    /// A block with raw markup for a specific output format.
 | 
				
			||||||
    RawBlock { format: &'s str },
 | 
					    RawBlock { format: &'s str },
 | 
				
			||||||
    /// A block with code in a specific language.
 | 
					    /// A block with code in a specific language.
 | 
				
			||||||
| 
						 | 
					@ -381,6 +341,7 @@ impl<'s> Container<'s> {
 | 
				
			||||||
            | Self::TableCell { .. }
 | 
					            | Self::TableCell { .. }
 | 
				
			||||||
            | Self::Caption
 | 
					            | Self::Caption
 | 
				
			||||||
            | Self::DescriptionTerm
 | 
					            | Self::DescriptionTerm
 | 
				
			||||||
 | 
					            | Self::LinkDefinition { .. }
 | 
				
			||||||
            | Self::RawBlock { .. }
 | 
					            | Self::RawBlock { .. }
 | 
				
			||||||
            | Self::CodeBlock { .. } => true,
 | 
					            | Self::CodeBlock { .. } => true,
 | 
				
			||||||
            Self::Span
 | 
					            Self::Span
 | 
				
			||||||
| 
						 | 
					@ -419,6 +380,7 @@ impl<'s> Container<'s> {
 | 
				
			||||||
            | Self::TableCell { .. }
 | 
					            | Self::TableCell { .. }
 | 
				
			||||||
            | Self::Caption
 | 
					            | Self::Caption
 | 
				
			||||||
            | Self::DescriptionTerm
 | 
					            | Self::DescriptionTerm
 | 
				
			||||||
 | 
					            | Self::LinkDefinition { .. }
 | 
				
			||||||
            | Self::RawBlock { .. }
 | 
					            | Self::RawBlock { .. }
 | 
				
			||||||
            | Self::CodeBlock { .. }
 | 
					            | Self::CodeBlock { .. }
 | 
				
			||||||
            | Self::Span
 | 
					            | Self::Span
 | 
				
			||||||
| 
						 | 
					@ -607,15 +569,6 @@ pub struct Parser<'s> {
 | 
				
			||||||
    /// Currently within a verbatim code block.
 | 
					    /// Currently within a verbatim code block.
 | 
				
			||||||
    verbatim: bool,
 | 
					    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: inline::Parser<'s>,
 | 
					    inline_parser: inline::Parser<'s>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -793,10 +746,6 @@ impl<'s> Parser<'s> {
 | 
				
			||||||
            block_attributes: Attributes::new(),
 | 
					            block_attributes: Attributes::new(),
 | 
				
			||||||
            table_head_row: false,
 | 
					            table_head_row: false,
 | 
				
			||||||
            verbatim: false,
 | 
					            verbatim: false,
 | 
				
			||||||
            footnote_references: Vec::new(),
 | 
					 | 
				
			||||||
            footnotes: Map::new(),
 | 
					 | 
				
			||||||
            footnote_index: 0,
 | 
					 | 
				
			||||||
            footnote_active: false,
 | 
					 | 
				
			||||||
            inline_parser,
 | 
					            inline_parser,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -885,19 +834,7 @@ impl<'s> Parser<'s> {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                inline::EventKind::Atom(a) => match a {
 | 
					                inline::EventKind::Atom(a) => match a {
 | 
				
			||||||
                    inline::Atom::FootnoteReference => {
 | 
					                    inline::Atom::FootnoteReference => {
 | 
				
			||||||
                        let tag = inline.span.of(self.src);
 | 
					                        Event::FootnoteReference(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)
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    inline::Atom::Symbol => Event::Symbol(inline.span.of(self.src).into()),
 | 
					                    inline::Atom::Symbol => Event::Symbol(inline.span.of(self.src).into()),
 | 
				
			||||||
                    inline::Atom::Quote { ty, left } => match (ty, left) {
 | 
					                    inline::Atom::Quote { ty, left } => match (ty, left) {
 | 
				
			||||||
| 
						 | 
					@ -941,14 +878,6 @@ impl<'s> Parser<'s> {
 | 
				
			||||||
                    let cont = match c {
 | 
					                    let cont = match c {
 | 
				
			||||||
                        block::Node::Leaf(l) => {
 | 
					                        block::Node::Leaf(l) => {
 | 
				
			||||||
                            self.inline_parser.reset();
 | 
					                            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 {
 | 
					                            match l {
 | 
				
			||||||
                                block::Leaf::Paragraph => Container::Paragraph,
 | 
					                                block::Leaf::Paragraph => Container::Paragraph,
 | 
				
			||||||
                                block::Leaf::Heading { has_section } => Container::Heading {
 | 
					                                block::Leaf::Heading { has_section } => Container::Heading {
 | 
				
			||||||
| 
						 | 
					@ -977,7 +906,9 @@ impl<'s> Parser<'s> {
 | 
				
			||||||
                                    head: self.table_head_row,
 | 
					                                    head: self.table_head_row,
 | 
				
			||||||
                                },
 | 
					                                },
 | 
				
			||||||
                                block::Leaf::Caption => Container::Caption,
 | 
					                                block::Leaf::Caption => Container::Caption,
 | 
				
			||||||
                                block::Leaf::LinkDefinition => unreachable!(),
 | 
					                                block::Leaf::LinkDefinition => {
 | 
				
			||||||
 | 
					                                    Container::LinkDefinition { label: content }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        block::Node::Container(c) => match c {
 | 
					                        block::Node::Container(c) => match c {
 | 
				
			||||||
| 
						 | 
					@ -985,12 +916,7 @@ impl<'s> Parser<'s> {
 | 
				
			||||||
                            block::Container::Div { .. } => Container::Div {
 | 
					                            block::Container::Div { .. } => Container::Div {
 | 
				
			||||||
                                class: (!ev.span.is_empty()).then(|| content),
 | 
					                                class: (!ev.span.is_empty()).then(|| content),
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
                            block::Container::Footnote => {
 | 
					                            block::Container::Footnote => Container::Footnote { label: content },
 | 
				
			||||||
                                debug_assert!(enter);
 | 
					 | 
				
			||||||
                                self.footnotes.insert(content, self.tree.take_branch());
 | 
					 | 
				
			||||||
                                self.block_attributes = Attributes::new();
 | 
					 | 
				
			||||||
                                continue;
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            block::Container::List(block::ListKind { ty, tight }) => {
 | 
					                            block::Container::List(block::ListKind { ty, tight }) => {
 | 
				
			||||||
                                if matches!(ty, block::ListType::Description) {
 | 
					                                if matches!(ty, block::ListType::Description) {
 | 
				
			||||||
                                    Container::DescriptionList
 | 
					                                    Container::DescriptionList
 | 
				
			||||||
| 
						 | 
					@ -1057,43 +983,13 @@ impl<'s> Parser<'s> {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        None
 | 
					        None
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn footnote(&mut self) -> Option<Event<'s>> {
 | 
					 | 
				
			||||||
        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> {
 | 
					impl<'s> Iterator for Parser<'s> {
 | 
				
			||||||
    type Item = Event<'s>;
 | 
					    type Item = Event<'s>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn next(&mut self) -> Option<Self::Item> {
 | 
					    fn next(&mut self) -> Option<Self::Item> {
 | 
				
			||||||
        self.inline()
 | 
					        self.inline().or_else(|| self.block())
 | 
				
			||||||
            .or_else(|| self.block())
 | 
					 | 
				
			||||||
            .or_else(|| self.footnote())
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1418,6 +1314,9 @@ mod test {
 | 
				
			||||||
            End(Link("url".into(), LinkType::Span(SpanLinkType::Reference))),
 | 
					            End(Link("url".into(), LinkType::Span(SpanLinkType::Reference))),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            Blankline,
 | 
					            Blankline,
 | 
				
			||||||
 | 
					            Start(LinkDefinition { label: "tag" }, Attributes::new()),
 | 
				
			||||||
 | 
					            Str("url".into()),
 | 
				
			||||||
 | 
					            End(LinkDefinition { label: "tag" }),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        test_parse!(
 | 
					        test_parse!(
 | 
				
			||||||
            concat!(
 | 
					            concat!(
 | 
				
			||||||
| 
						 | 
					@ -1434,6 +1333,9 @@ mod test {
 | 
				
			||||||
            End(Image("url".into(), SpanLinkType::Reference)),
 | 
					            End(Image("url".into(), SpanLinkType::Reference)),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            Blankline,
 | 
					            Blankline,
 | 
				
			||||||
 | 
					            Start(LinkDefinition { label: "tag" }, Attributes::new()),
 | 
				
			||||||
 | 
					            Str("url".into()),
 | 
				
			||||||
 | 
					            End(LinkDefinition { label: "tag" }),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1483,6 +1385,9 @@ mod test {
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            End(Blockquote),
 | 
					            End(Blockquote),
 | 
				
			||||||
            Blankline,
 | 
					            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(Link("url".into(), LinkType::Span(SpanLinkType::Reference))),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            Blankline,
 | 
					            Blankline,
 | 
				
			||||||
 | 
					            Start(LinkDefinition { label: "tag" }, Attributes::new()),
 | 
				
			||||||
 | 
					            Str("u".into()),
 | 
				
			||||||
 | 
					            Softbreak,
 | 
				
			||||||
 | 
					            Str("rl".into()),
 | 
				
			||||||
 | 
					            End(LinkDefinition { label: "tag" }),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        test_parse!(
 | 
					        test_parse!(
 | 
				
			||||||
            concat!(
 | 
					            concat!(
 | 
				
			||||||
| 
						 | 
					@ -1521,6 +1431,9 @@ mod test {
 | 
				
			||||||
            End(Link("url".into(), LinkType::Span(SpanLinkType::Reference))),
 | 
					            End(Link("url".into(), LinkType::Span(SpanLinkType::Reference))),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            Blankline,
 | 
					            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(Link("url".into(), LinkType::Span(SpanLinkType::Reference))),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            Blankline,
 | 
					            Blankline,
 | 
				
			||||||
 | 
					            Start(
 | 
				
			||||||
 | 
					                LinkDefinition { label: "tag" },
 | 
				
			||||||
 | 
					                [("a", "b")].into_iter().collect()
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Str("url".into()),
 | 
				
			||||||
 | 
					            End(LinkDefinition { label: "tag" }),
 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
            Str("para".into()),
 | 
					            Str("para".into()),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
| 
						 | 
					@ -1584,43 +1503,10 @@ mod test {
 | 
				
			||||||
        test_parse!(
 | 
					        test_parse!(
 | 
				
			||||||
            "[^a][^b][^c]",
 | 
					            "[^a][^b][^c]",
 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
            FootnoteReference("a", 1),
 | 
					            FootnoteReference("a"),
 | 
				
			||||||
            FootnoteReference("b", 2),
 | 
					            FootnoteReference("b"),
 | 
				
			||||||
            FootnoteReference("c", 3),
 | 
					            FootnoteReference("c"),
 | 
				
			||||||
            End(Paragraph),
 | 
					            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!(
 | 
					        test_parse!(
 | 
				
			||||||
            "[^a]\n\n[^a]: a\n",
 | 
					            "[^a]\n\n[^a]: a\n",
 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
            FootnoteReference("a", 1),
 | 
					            FootnoteReference("a"),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            Blankline,
 | 
					            Blankline,
 | 
				
			||||||
            Start(
 | 
					            Start(Footnote { label: "a" }, Attributes::new()),
 | 
				
			||||||
                Footnote {
 | 
					 | 
				
			||||||
                    tag: "a",
 | 
					 | 
				
			||||||
                    number: 1
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Attributes::new()
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
            Str("a".into()),
 | 
					            Str("a".into()),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            End(Footnote {
 | 
					            End(Footnote { label: "a" }),
 | 
				
			||||||
                tag: "a",
 | 
					 | 
				
			||||||
                number: 1
 | 
					 | 
				
			||||||
            }),
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1660,16 +1537,10 @@ mod test {
 | 
				
			||||||
                " def", //
 | 
					                " def", //
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
            FootnoteReference("a", 1),
 | 
					            FootnoteReference("a"),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            Blankline,
 | 
					            Blankline,
 | 
				
			||||||
            Start(
 | 
					            Start(Footnote { label: "a" }, Attributes::new()),
 | 
				
			||||||
                Footnote {
 | 
					 | 
				
			||||||
                    tag: "a",
 | 
					 | 
				
			||||||
                    number: 1
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Attributes::new()
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
            Str("abc".into()),
 | 
					            Str("abc".into()),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
| 
						 | 
					@ -1677,10 +1548,7 @@ mod test {
 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
            Str("def".into()),
 | 
					            Str("def".into()),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            End(Footnote {
 | 
					            End(Footnote { label: "a" }),
 | 
				
			||||||
                tag: "a",
 | 
					 | 
				
			||||||
                number: 1
 | 
					 | 
				
			||||||
            }),
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1694,26 +1562,17 @@ mod test {
 | 
				
			||||||
                "para\n", //
 | 
					                "para\n", //
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
            FootnoteReference("a", 1),
 | 
					            FootnoteReference("a"),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            Blankline,
 | 
					            Blankline,
 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Footnote { label: "a" }, Attributes::new()),
 | 
				
			||||||
            Str("para".into()),
 | 
					 | 
				
			||||||
            End(Paragraph),
 | 
					 | 
				
			||||||
            Start(
 | 
					 | 
				
			||||||
                Footnote {
 | 
					 | 
				
			||||||
                    tag: "a",
 | 
					 | 
				
			||||||
                    number: 1
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Attributes::new()
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            Start(Paragraph, Attributes::new()),
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
            Str("note".into()),
 | 
					            Str("note".into()),
 | 
				
			||||||
            End(Paragraph),
 | 
					            End(Paragraph),
 | 
				
			||||||
            End(Footnote {
 | 
					            End(Footnote { label: "a" }),
 | 
				
			||||||
                tag: "a",
 | 
					            Start(Paragraph, Attributes::new()),
 | 
				
			||||||
                number: 1
 | 
					            Str("para".into()),
 | 
				
			||||||
            }),
 | 
					            End(Paragraph),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,7 +68,7 @@ fn run() -> Result<(), std::io::Error> {
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let parser = jotdown::Parser::new(&content);
 | 
					    let parser = jotdown::Parser::new(&content);
 | 
				
			||||||
    let mut renderer = jotdown::html::Renderer::default();
 | 
					    let renderer = jotdown::html::Renderer::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match app.output {
 | 
					    match app.output {
 | 
				
			||||||
        Some(path) => renderer.write(parser, File::create(path)?)?,
 | 
					        Some(path) => renderer.write(parser, File::create(path)?)?,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										104
									
								
								src/tree.rs
									
										
									
									
									
								
							
							
						
						
									
										104
									
								
								src/tree.rs
									
										
									
									
									
								
							| 
						 | 
					@ -36,14 +36,6 @@ pub struct Tree<C: 'static, A: 'static> {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<C: Clone, A: Clone> Tree<C, A> {
 | 
					impl<C: Clone, A: Clone> Tree<C, A> {
 | 
				
			||||||
    pub fn empty() -> Self {
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            nodes: vec![].into_boxed_slice().into(),
 | 
					 | 
				
			||||||
            branch: Vec::new(),
 | 
					 | 
				
			||||||
            head: None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Count number of direct children nodes.
 | 
					    /// Count number of direct children nodes.
 | 
				
			||||||
    pub fn count_children(&self) -> usize {
 | 
					    pub fn count_children(&self) -> usize {
 | 
				
			||||||
        let mut head = self.head;
 | 
					        let mut head = self.head;
 | 
				
			||||||
| 
						 | 
					@ -56,22 +48,6 @@ impl<C: Clone, A: Clone> Tree<C, A> {
 | 
				
			||||||
        count
 | 
					        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
 | 
					    /// Retrieve all inlines until the end of the current container. Panics if any upcoming node is
 | 
				
			||||||
    /// not an inline node.
 | 
					    /// not an inline node.
 | 
				
			||||||
    pub fn take_inlines(&mut self) -> impl Iterator<Item = Span> + '_ {
 | 
					    pub fn take_inlines(&mut self) -> impl Iterator<Item = Span> + '_ {
 | 
				
			||||||
| 
						 | 
					@ -410,9 +386,6 @@ impl<C: std::fmt::Debug + Clone, A: std::fmt::Debug + Clone> std::fmt::Debug for
 | 
				
			||||||
mod test {
 | 
					mod test {
 | 
				
			||||||
    use crate::Span;
 | 
					    use crate::Span;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use super::Event;
 | 
					 | 
				
			||||||
    use super::EventKind;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn fmt() {
 | 
					    fn fmt() {
 | 
				
			||||||
        let mut tree = super::Builder::new();
 | 
					        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::<Vec<_>>(),
 | 
					 | 
				
			||||||
            &[
 | 
					 | 
				
			||||||
                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::<Vec<_>>(),
 | 
					 | 
				
			||||||
            &[
 | 
					 | 
				
			||||||
                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::<Vec<_>>(),
 | 
					 | 
				
			||||||
            &[
 | 
					 | 
				
			||||||
                Event {
 | 
					 | 
				
			||||||
                    kind: EventKind::Enter(3),
 | 
					 | 
				
			||||||
                    span: sp
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Event {
 | 
					 | 
				
			||||||
                    kind: EventKind::Atom(31),
 | 
					 | 
				
			||||||
                    span: sp
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Event {
 | 
					 | 
				
			||||||
                    kind: EventKind::Exit(3),
 | 
					 | 
				
			||||||
                    span: sp
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,9 +5,9 @@
 | 
				
			||||||
TEST=$(shell find . -name '*.test' | sort)
 | 
					TEST=$(shell find . -name '*.test' | sort)
 | 
				
			||||||
TEST_RS=${TEST:.test=.rs}
 | 
					TEST_RS=${TEST:.test=.rs}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BLACKLIST += filters # lua filters not implemented
 | 
					BLACKLIST += djot_js_filters # lua filters not implemented
 | 
				
			||||||
BLACKLIST += symb # uses ast
 | 
					BLACKLIST += djot_js_symb # uses ast
 | 
				
			||||||
BLACKLIST += sourcepos # not parsable
 | 
					BLACKLIST += djot_js_sourcepos # not parsable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: suite
 | 
					.PHONY: suite
 | 
				
			||||||
suite: mod.rs
 | 
					suite: mod.rs
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										58
									
								
								tests/suite/footnotes.test
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								tests/suite/footnotes.test
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					Footnote references may appear within a footnote.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					[^a]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[^a]: a[^b][^c]
 | 
				
			||||||
 | 
					[^b]: b
 | 
				
			||||||
 | 
					.
 | 
				
			||||||
 | 
					<p><a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a></p>
 | 
				
			||||||
 | 
					<section role="doc-endnotes">
 | 
				
			||||||
 | 
					<hr>
 | 
				
			||||||
 | 
					<ol>
 | 
				
			||||||
 | 
					<li id="fn1">
 | 
				
			||||||
 | 
					<p>a<a id="fnref2" href="#fn2" role="doc-noteref"><sup>2</sup></a><a id="fnref3" href="#fn3" role="doc-noteref"><sup>3</sup></a><a href="#fnref1" role="doc-backlink">↩︎︎</a></p>
 | 
				
			||||||
 | 
					</li>
 | 
				
			||||||
 | 
					<li id="fn2">
 | 
				
			||||||
 | 
					<p>b<a href="#fnref2" role="doc-backlink">↩︎︎</a></p>
 | 
				
			||||||
 | 
					</li>
 | 
				
			||||||
 | 
					<li id="fn3">
 | 
				
			||||||
 | 
					<p><a href="#fnref3" role="doc-backlink">↩︎︎</a></p>
 | 
				
			||||||
 | 
					</li>
 | 
				
			||||||
 | 
					</ol>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Footnote references in unreferenced footnotes are ignored.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					para
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[^a]: a[^b][^c]
 | 
				
			||||||
 | 
					[^b]: b
 | 
				
			||||||
 | 
					.
 | 
				
			||||||
 | 
					<p>para</p>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Footnotes may appear within footnotes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					[^b]
 | 
				
			||||||
 | 
					[^a]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[^a]: [^b]: inner
 | 
				
			||||||
 | 
					.
 | 
				
			||||||
 | 
					<p><a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a>
 | 
				
			||||||
 | 
					<a id="fnref2" href="#fn2" role="doc-noteref"><sup>2</sup></a></p>
 | 
				
			||||||
 | 
					<section role="doc-endnotes">
 | 
				
			||||||
 | 
					<hr>
 | 
				
			||||||
 | 
					<ol>
 | 
				
			||||||
 | 
					<li id="fn1">
 | 
				
			||||||
 | 
					<p>inner<a href="#fnref1" role="doc-backlink">↩︎︎</a></p>
 | 
				
			||||||
 | 
					</li>
 | 
				
			||||||
 | 
					<li id="fn2">
 | 
				
			||||||
 | 
					<p><a href="#fnref2" role="doc-backlink">↩︎︎</a></p>
 | 
				
			||||||
 | 
					</li>
 | 
				
			||||||
 | 
					</ol>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue