2023-01-21 05:13:24 -05:00
|
|
|
use crate::OrderedListNumbering::*;
|
|
|
|
use crate::OrderedListStyle::*;
|
2022-11-12 12:45:17 -05:00
|
|
|
use crate::Span;
|
|
|
|
use crate::EOF;
|
|
|
|
|
2022-12-18 12:05:39 -05:00
|
|
|
use crate::attr;
|
2022-11-12 12:45:17 -05:00
|
|
|
use crate::tree;
|
|
|
|
|
2022-12-10 04:26:06 -05:00
|
|
|
use Atom::*;
|
2022-11-12 12:45:17 -05:00
|
|
|
use Container::*;
|
|
|
|
use Leaf::*;
|
2023-01-21 05:13:24 -05:00
|
|
|
use ListType::*;
|
2022-11-12 12:45:17 -05:00
|
|
|
|
2022-12-12 12:22:13 -05:00
|
|
|
pub type Tree = tree::Tree<Node, Atom>;
|
|
|
|
pub type TreeBuilder = tree::Builder<Node, Atom>;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
pub enum Node {
|
|
|
|
Container(Container),
|
|
|
|
Leaf(Leaf),
|
|
|
|
}
|
2022-11-12 12:45:17 -05:00
|
|
|
|
2022-12-07 12:44:03 -05:00
|
|
|
#[must_use]
|
2022-11-12 12:45:17 -05:00
|
|
|
pub fn parse(src: &str) -> Tree {
|
2022-12-10 04:57:15 -05:00
|
|
|
TreeParser::new(src).parse()
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
|
2022-11-28 14:12:49 -05:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
2022-11-12 12:45:17 -05:00
|
|
|
pub enum Block {
|
2022-12-10 04:26:06 -05:00
|
|
|
/// An atomic block, containing no children elements.
|
|
|
|
Atom(Atom),
|
|
|
|
|
2022-12-07 12:44:03 -05:00
|
|
|
/// A leaf block, containing only inline elements.
|
2022-11-12 12:45:17 -05:00
|
|
|
Leaf(Leaf),
|
2022-12-07 12:44:03 -05:00
|
|
|
|
|
|
|
/// A container block, containing children blocks.
|
2022-11-12 12:45:17 -05:00
|
|
|
Container(Container),
|
|
|
|
}
|
|
|
|
|
2022-12-10 04:26:06 -05:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
pub enum Atom {
|
|
|
|
/// A line with no non-whitespace characters.
|
|
|
|
Blankline,
|
|
|
|
|
|
|
|
/// A list of attributes.
|
|
|
|
Attributes,
|
|
|
|
|
|
|
|
/// A thematic break.
|
|
|
|
ThematicBreak,
|
|
|
|
}
|
|
|
|
|
2022-11-28 14:12:49 -05:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
2022-11-12 12:45:17 -05:00
|
|
|
pub enum Leaf {
|
2022-12-07 12:44:03 -05:00
|
|
|
/// Span is empty, before first character of paragraph.
|
|
|
|
/// Each inline is a line.
|
2022-11-12 12:45:17 -05:00
|
|
|
Paragraph,
|
2022-12-07 12:44:03 -05:00
|
|
|
|
|
|
|
/// Span is `#` characters.
|
|
|
|
/// Each inline is a line.
|
2022-12-10 04:57:15 -05:00
|
|
|
Heading,
|
2022-12-07 12:44:03 -05:00
|
|
|
|
|
|
|
/// Span is first `|` character.
|
|
|
|
/// Each inline is a line (row).
|
2022-11-12 12:45:17 -05:00
|
|
|
Table,
|
2022-12-07 12:44:03 -05:00
|
|
|
|
|
|
|
/// Span is the link tag.
|
|
|
|
/// Inlines are lines of the URL.
|
2022-11-12 12:45:17 -05:00
|
|
|
LinkDefinition,
|
2022-12-07 12:44:03 -05:00
|
|
|
|
|
|
|
/// Span is language specifier.
|
|
|
|
/// Each inline is a line.
|
2022-12-10 04:57:15 -05:00
|
|
|
CodeBlock,
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
|
2022-11-28 14:12:49 -05:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
2022-11-12 12:45:17 -05:00
|
|
|
pub enum Container {
|
2022-12-18 12:05:39 -05:00
|
|
|
/// Span is `>`.
|
2022-11-12 12:45:17 -05:00
|
|
|
Blockquote,
|
2022-12-07 12:44:03 -05:00
|
|
|
|
2023-01-21 06:33:41 -05:00
|
|
|
/// Span is class specifier, possibly empty.
|
2022-12-10 04:57:15 -05:00
|
|
|
Div,
|
2022-12-07 12:44:03 -05:00
|
|
|
|
2023-01-22 15:55:14 -05:00
|
|
|
/// Span is `:`.
|
|
|
|
DescriptionList,
|
|
|
|
|
2023-01-21 14:53:12 -05:00
|
|
|
/// Span is the list marker of the first list item in the list.
|
2023-01-22 15:55:14 -05:00
|
|
|
List { ty: ListType, tight: bool },
|
2023-01-21 14:53:12 -05:00
|
|
|
|
2022-12-07 12:44:03 -05:00
|
|
|
/// Span is the list marker.
|
2023-01-21 05:13:24 -05:00
|
|
|
ListItem(ListType),
|
2022-12-07 12:44:03 -05:00
|
|
|
|
2023-01-17 12:11:36 -05:00
|
|
|
/// Span is footnote tag.
|
2022-12-10 04:57:15 -05:00
|
|
|
Footnote,
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
|
2023-01-21 05:13:24 -05:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
pub enum ListType {
|
2023-01-21 06:33:41 -05:00
|
|
|
Unordered(u8),
|
2023-01-21 05:13:24 -05:00
|
|
|
Ordered(crate::OrderedListNumbering, crate::OrderedListStyle),
|
2023-01-21 06:33:41 -05:00
|
|
|
Task,
|
2023-01-21 05:13:24 -05:00
|
|
|
}
|
|
|
|
|
2023-01-21 14:53:12 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct OpenList {
|
|
|
|
/// Type of the list, used to determine whether this list should be continued or a new one
|
|
|
|
/// should be created.
|
|
|
|
ty: ListType,
|
|
|
|
/// Depth in the tree where the direct list items of the list are. Needed to determine when to
|
|
|
|
/// close the list.
|
|
|
|
depth: u16,
|
2023-01-22 15:55:14 -05:00
|
|
|
/// Index to node in tree, required to update tightness.
|
|
|
|
node: tree::NodeIndex,
|
2023-01-21 14:53:12 -05:00
|
|
|
}
|
|
|
|
|
2022-12-10 04:57:15 -05:00
|
|
|
/// Parser for block-level tree structure of entire document.
|
|
|
|
struct TreeParser<'s> {
|
2022-11-12 12:45:17 -05:00
|
|
|
src: &'s str,
|
2022-12-12 12:22:13 -05:00
|
|
|
tree: TreeBuilder,
|
2023-01-21 14:53:12 -05:00
|
|
|
|
2023-01-22 15:55:14 -05:00
|
|
|
/// The previous block element was a blank line.
|
|
|
|
prev_blankline: bool,
|
|
|
|
/// Stack of currently open lists.
|
|
|
|
open_lists: Vec<OpenList>,
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
|
2022-12-10 04:57:15 -05:00
|
|
|
impl<'s> TreeParser<'s> {
|
2022-11-12 12:45:17 -05:00
|
|
|
#[must_use]
|
|
|
|
pub fn new(src: &'s str) -> Self {
|
|
|
|
Self {
|
|
|
|
src,
|
2022-12-12 12:22:13 -05:00
|
|
|
tree: TreeBuilder::new(),
|
2023-01-22 15:55:14 -05:00
|
|
|
prev_blankline: false,
|
|
|
|
open_lists: Vec::new(),
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
pub fn parse(mut self) -> Tree {
|
|
|
|
let mut lines = lines(self.src).collect::<Vec<_>>();
|
|
|
|
let mut line_pos = 0;
|
2022-11-28 14:12:49 -05:00
|
|
|
while line_pos < lines.len() {
|
2022-11-12 12:45:17 -05:00
|
|
|
let line_count = self.parse_block(&mut lines[line_pos..]);
|
|
|
|
if line_count == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
line_pos += line_count;
|
|
|
|
}
|
2023-01-22 15:55:14 -05:00
|
|
|
for _ in self.open_lists.drain(..) {
|
2023-01-21 14:53:12 -05:00
|
|
|
self.tree.exit(); // list
|
|
|
|
}
|
2022-11-12 12:45:17 -05:00
|
|
|
self.tree.finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Recursively parse a block and all of its children. Return number of lines the block uses.
|
|
|
|
fn parse_block(&mut self, lines: &mut [Span]) -> usize {
|
2022-12-10 04:57:15 -05:00
|
|
|
BlockParser::parse(lines.iter().map(|sp| sp.of(self.src))).map_or(
|
2022-12-10 04:26:06 -05:00
|
|
|
0,
|
2022-12-10 04:57:15 -05:00
|
|
|
|(indent, kind, span, line_count)| {
|
2023-01-21 06:33:41 -05:00
|
|
|
let truncated = line_count > lines.len();
|
|
|
|
let l = line_count.min(lines.len());
|
|
|
|
let lines = &mut lines[..l];
|
2022-12-07 12:44:03 -05:00
|
|
|
let span = span.translate(lines[0].start());
|
|
|
|
|
|
|
|
// skip part of first inline that is shared with the block span
|
|
|
|
lines[0] = lines[0].with_start(span.end());
|
|
|
|
|
|
|
|
// remove junk from footnotes / link defs
|
|
|
|
if matches!(
|
|
|
|
kind,
|
|
|
|
Block::Leaf(LinkDefinition) | Block::Container(Footnote { .. })
|
|
|
|
) {
|
|
|
|
assert_eq!(&lines[0].of(self.src).chars().as_str()[0..2], "]:");
|
|
|
|
lines[0] = lines[0].skip(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip closing fence of code blocks / divs
|
|
|
|
let lines = if !truncated
|
2022-12-10 04:57:15 -05:00
|
|
|
&& matches!(kind, Block::Leaf(CodeBlock) | Block::Container(Div))
|
|
|
|
{
|
2022-12-07 12:44:03 -05:00
|
|
|
let l = lines.len();
|
|
|
|
&mut lines[..l - 1]
|
|
|
|
} else {
|
|
|
|
lines
|
|
|
|
};
|
|
|
|
|
2023-01-22 15:55:14 -05:00
|
|
|
// close list if a non list item or a list item of new type appeared
|
|
|
|
if let Some(OpenList { ty, depth, .. }) = self.open_lists.last() {
|
|
|
|
assert!(usize::from(*depth) <= self.tree.depth());
|
|
|
|
if self.tree.depth() == (*depth).into()
|
|
|
|
&& !matches!(
|
|
|
|
kind,
|
|
|
|
Block::Container(Container::ListItem(ty_new)) if *ty == ty_new,
|
|
|
|
)
|
|
|
|
{
|
|
|
|
self.tree.exit(); // list
|
|
|
|
self.open_lists.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// set list to loose if blankline discovered
|
|
|
|
if matches!(kind, Block::Atom(Atom::Blankline)) {
|
|
|
|
self.prev_blankline = true;
|
|
|
|
} else {
|
|
|
|
if self.prev_blankline {
|
|
|
|
for OpenList { node, depth, .. } in &self.open_lists {
|
|
|
|
if usize::from(*depth) < self.tree.depth()
|
|
|
|
&& matches!(kind, Block::Container(Container::ListItem { .. }))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if let tree::Element::Container(Node::Container(Container::List {
|
|
|
|
tight,
|
|
|
|
..
|
|
|
|
})) = self.tree.elem_mut(*node)
|
|
|
|
{
|
|
|
|
*tight = false;
|
|
|
|
} else {
|
|
|
|
panic!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.prev_blankline = false;
|
|
|
|
}
|
|
|
|
|
2022-12-10 04:26:06 -05:00
|
|
|
match kind {
|
2022-12-11 14:49:57 -05:00
|
|
|
Block::Atom(a) => self.tree.atom(a, span),
|
2022-11-28 14:30:18 -05:00
|
|
|
Block::Leaf(l) => {
|
2022-12-12 12:22:13 -05:00
|
|
|
self.tree.enter(Node::Leaf(l), span);
|
2022-12-07 12:44:03 -05:00
|
|
|
|
2023-01-19 16:58:33 -05:00
|
|
|
if matches!(l, Leaf::CodeBlock) {
|
|
|
|
lines[0] = lines[0].trim_start(self.src);
|
2022-12-07 12:44:03 -05:00
|
|
|
} else {
|
2023-01-19 16:58:33 -05:00
|
|
|
// trim starting whitespace of each inline
|
|
|
|
for line in lines.iter_mut() {
|
|
|
|
*line = line.trim_start(self.src);
|
|
|
|
}
|
2022-12-07 12:44:03 -05:00
|
|
|
|
2023-01-19 16:58:33 -05:00
|
|
|
// trim ending whitespace of block
|
2022-12-10 02:37:00 -05:00
|
|
|
let l = lines.len();
|
|
|
|
if l > 0 {
|
|
|
|
let last = &mut lines[l - 1];
|
|
|
|
*last = last.trim_end(self.src);
|
|
|
|
}
|
2022-12-01 14:34:23 -05:00
|
|
|
}
|
2022-12-07 12:44:03 -05:00
|
|
|
|
2023-01-19 16:58:33 -05:00
|
|
|
// skip first inline if empty (e.g. code block)
|
|
|
|
let lines = if lines[0].is_empty() {
|
|
|
|
&mut lines[1..]
|
|
|
|
} else {
|
|
|
|
lines
|
|
|
|
};
|
|
|
|
|
2022-12-11 14:49:57 -05:00
|
|
|
lines.iter().for_each(|line| self.tree.inline(*line));
|
2022-12-10 04:26:06 -05:00
|
|
|
self.tree.exit();
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
2022-11-28 14:30:18 -05:00
|
|
|
Block::Container(c) => {
|
2023-01-23 15:10:29 -05:00
|
|
|
let line_count_inner = lines.len() - usize::from(matches!(c, Div));
|
2022-11-28 14:30:18 -05:00
|
|
|
|
|
|
|
// update spans, remove indentation / container prefix
|
|
|
|
lines
|
|
|
|
.iter_mut()
|
|
|
|
.skip(1)
|
|
|
|
.take(line_count_inner)
|
|
|
|
.for_each(|sp| {
|
2023-01-23 15:10:29 -05:00
|
|
|
let spaces = sp
|
2022-11-28 14:30:18 -05:00
|
|
|
.of(self.src)
|
|
|
|
.chars()
|
|
|
|
.take_while(|c| c.is_whitespace())
|
2023-01-23 15:10:29 -05:00
|
|
|
.count();
|
|
|
|
let skip = match c {
|
|
|
|
Blockquote => spaces + 2,
|
|
|
|
ListItem(..) | Footnote | Div => spaces.min(indent),
|
|
|
|
List { .. } | DescriptionList => panic!(),
|
|
|
|
};
|
|
|
|
let len = sp.len() - usize::from(sp.of(self.src).ends_with('\n'));
|
|
|
|
*sp = sp.skip(skip.min(len));
|
2022-11-28 14:30:18 -05:00
|
|
|
});
|
|
|
|
|
2023-01-21 14:53:12 -05:00
|
|
|
if let Container::ListItem(ty) = c {
|
|
|
|
if self
|
2023-01-22 15:55:14 -05:00
|
|
|
.open_lists
|
2023-01-21 14:53:12 -05:00
|
|
|
.last()
|
|
|
|
.map_or(true, |OpenList { depth, .. }| {
|
|
|
|
usize::from(*depth) < self.tree.depth()
|
|
|
|
})
|
|
|
|
{
|
2023-01-22 15:55:14 -05:00
|
|
|
let tight = true;
|
|
|
|
let node = self
|
|
|
|
.tree
|
|
|
|
.enter(Node::Container(Container::List { ty, tight }), span);
|
|
|
|
self.open_lists.push(OpenList {
|
2023-01-21 14:53:12 -05:00
|
|
|
ty,
|
|
|
|
depth: self.tree.depth().try_into().unwrap(),
|
2023-01-22 15:55:14 -05:00
|
|
|
node,
|
2023-01-21 14:53:12 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-12 12:22:13 -05:00
|
|
|
self.tree.enter(Node::Container(c), span);
|
2022-11-28 14:30:18 -05:00
|
|
|
let mut l = 0;
|
|
|
|
while l < line_count_inner {
|
|
|
|
l += self.parse_block(&mut lines[l..line_count_inner]);
|
|
|
|
}
|
2023-01-21 14:53:12 -05:00
|
|
|
|
2023-01-22 15:55:14 -05:00
|
|
|
if let Some(OpenList { depth, .. }) = self.open_lists.last() {
|
2023-01-21 14:53:12 -05:00
|
|
|
assert!(usize::from(*depth) <= self.tree.depth());
|
|
|
|
if self.tree.depth() == (*depth).into() {
|
2023-01-23 15:11:49 -05:00
|
|
|
self.prev_blankline = false;
|
2023-01-21 14:53:12 -05:00
|
|
|
self.tree.exit(); // list
|
2023-01-22 15:55:14 -05:00
|
|
|
self.open_lists.pop();
|
2023-01-21 14:53:12 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-10 04:26:06 -05:00
|
|
|
self.tree.exit();
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
}
|
2022-12-10 04:26:06 -05:00
|
|
|
|
|
|
|
line_count
|
2022-11-28 14:30:18 -05:00
|
|
|
},
|
|
|
|
)
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-10 04:57:15 -05:00
|
|
|
/// Parser for a single block.
|
|
|
|
struct BlockParser {
|
|
|
|
indent: usize,
|
|
|
|
kind: Block,
|
|
|
|
span: Span,
|
|
|
|
fence: Option<(char, usize)>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BlockParser {
|
2022-11-12 12:45:17 -05:00
|
|
|
/// Parse a single block. Return number of lines the block uses.
|
2022-12-10 04:57:15 -05:00
|
|
|
fn parse<'s, I: Iterator<Item = &'s str>>(mut lines: I) -> Option<(usize, Block, Span, usize)> {
|
2022-11-27 15:59:54 -05:00
|
|
|
lines.next().map(|l| {
|
2022-12-10 04:57:15 -05:00
|
|
|
let mut p = BlockParser::new(l);
|
|
|
|
let has_end_delimiter =
|
|
|
|
matches!(p.kind, Block::Leaf(CodeBlock) | Block::Container(Div));
|
|
|
|
let line_count_match = lines.take_while(|l| p.continues(l)).count();
|
2022-12-02 02:16:47 -05:00
|
|
|
let line_count = 1 + line_count_match + usize::from(has_end_delimiter);
|
2022-12-10 04:57:15 -05:00
|
|
|
(p.indent, p.kind, p.span, line_count)
|
2022-11-27 15:59:54 -05:00
|
|
|
})
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
|
2022-12-10 04:57:15 -05:00
|
|
|
fn new(line: &str) -> Self {
|
2022-12-10 04:26:06 -05:00
|
|
|
let start = line
|
|
|
|
.chars()
|
|
|
|
.take_while(|c| *c != '\n' && c.is_whitespace())
|
2023-01-23 15:11:49 -05:00
|
|
|
.map(char::len_utf8)
|
|
|
|
.sum();
|
2022-12-07 12:44:03 -05:00
|
|
|
let line_t = &line[start..];
|
|
|
|
let mut chars = line_t.chars();
|
2022-12-06 15:31:08 -05:00
|
|
|
|
2022-12-10 04:57:15 -05:00
|
|
|
let mut fence = None;
|
|
|
|
let (kind, span) = match chars.next().unwrap_or(EOF) {
|
|
|
|
EOF => Some((Block::Atom(Blankline), Span::empty_at(start))),
|
|
|
|
'\n' => Some((Block::Atom(Blankline), Span::by_len(start, 1))),
|
2022-11-12 12:45:17 -05:00
|
|
|
'#' => chars
|
|
|
|
.find(|c| *c != '#')
|
|
|
|
.map_or(true, char::is_whitespace)
|
|
|
|
.then(|| {
|
2022-12-10 04:57:15 -05:00
|
|
|
(
|
|
|
|
Block::Leaf(Heading),
|
|
|
|
Span::by_len(start, line_t.len() - chars.as_str().len() - 1),
|
|
|
|
)
|
|
|
|
}),
|
2022-11-30 13:56:08 -05:00
|
|
|
'>' => {
|
|
|
|
if let Some(c) = chars.next() {
|
|
|
|
c.is_whitespace().then(|| {
|
|
|
|
(
|
2022-12-10 04:57:15 -05:00
|
|
|
Block::Container(Blockquote),
|
2022-12-07 12:44:03 -05:00
|
|
|
Span::by_len(start, line_t.len() - chars.as_str().len() - 1),
|
2022-11-30 13:56:08 -05:00
|
|
|
)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
Some((
|
2022-12-10 04:57:15 -05:00
|
|
|
Block::Container(Blockquote),
|
2022-12-07 12:44:03 -05:00
|
|
|
Span::by_len(start, line_t.len() - chars.as_str().len()),
|
2022-11-30 13:56:08 -05:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2023-01-16 11:24:27 -05:00
|
|
|
'{' => (attr::valid(line_t.chars()).0 == line_t.trim_end().len())
|
2022-12-18 12:05:39 -05:00
|
|
|
.then(|| (Block::Atom(Attributes), Span::by_len(start, line_t.len()))),
|
2022-12-07 12:44:03 -05:00
|
|
|
'|' => (&line_t[line_t.len() - 1..] == "|"
|
|
|
|
&& &line_t[line_t.len() - 2..line_t.len() - 1] != "\\")
|
2022-12-10 04:57:15 -05:00
|
|
|
.then(|| (Block::Leaf(Table), Span::by_len(start, 1))),
|
2022-12-07 12:44:03 -05:00
|
|
|
'[' => chars.as_str().find("]:").map(|l| {
|
|
|
|
let tag = &chars.as_str()[0..l];
|
|
|
|
let (tag, is_footnote) = if let Some(tag) = tag.strip_prefix('^') {
|
|
|
|
(tag, true)
|
|
|
|
} else {
|
|
|
|
(tag, false)
|
|
|
|
};
|
|
|
|
(
|
|
|
|
if is_footnote {
|
2022-12-10 04:57:15 -05:00
|
|
|
Block::Container(Footnote)
|
2022-12-07 12:44:03 -05:00
|
|
|
} else {
|
2022-12-10 04:57:15 -05:00
|
|
|
Block::Leaf(LinkDefinition)
|
2022-12-07 12:44:03 -05:00
|
|
|
},
|
|
|
|
Span::from_slice(line, tag),
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
'-' | '*' if Self::is_thematic_break(chars.clone()) => Some((
|
2022-12-10 04:57:15 -05:00
|
|
|
Block::Atom(ThematicBreak),
|
2022-12-07 12:44:03 -05:00
|
|
|
Span::from_slice(line, line_t.trim()),
|
|
|
|
)),
|
2023-01-21 05:13:24 -05:00
|
|
|
b @ ('-' | '*' | '+') => chars.next().map_or(true, char::is_whitespace).then(|| {
|
2022-12-06 15:31:08 -05:00
|
|
|
let task_list = chars.next() == Some('[')
|
2023-01-21 05:13:24 -05:00
|
|
|
&& matches!(chars.next(), Some('x' | 'X' | ' '))
|
2022-12-06 15:31:08 -05:00
|
|
|
&& chars.next() == Some(']')
|
|
|
|
&& chars.next().map_or(true, char::is_whitespace);
|
2023-01-21 05:13:24 -05:00
|
|
|
if task_list {
|
|
|
|
(Block::Container(ListItem(Task)), Span::by_len(start, 5))
|
|
|
|
} else {
|
|
|
|
(
|
2023-01-21 06:33:41 -05:00
|
|
|
Block::Container(ListItem(Unordered(b as u8))),
|
2023-01-21 05:13:24 -05:00
|
|
|
Span::by_len(start, 1),
|
|
|
|
)
|
|
|
|
}
|
2022-12-06 15:31:08 -05:00
|
|
|
}),
|
2023-01-22 15:55:14 -05:00
|
|
|
':' if chars.clone().next().map_or(true, char::is_whitespace) => {
|
|
|
|
Some((Block::Container(DescriptionList), Span::by_len(start, 1)))
|
|
|
|
}
|
2022-12-07 12:44:03 -05:00
|
|
|
f @ ('`' | ':' | '~') => {
|
2022-12-02 14:07:37 -05:00
|
|
|
let fence_length = (&mut chars).take_while(|c| *c == f).count() + 1;
|
2022-12-10 04:57:15 -05:00
|
|
|
fence = Some((f, fence_length));
|
2022-12-07 12:44:03 -05:00
|
|
|
let lang = line_t[fence_length..].trim();
|
2022-12-08 11:42:54 -05:00
|
|
|
let valid_spec =
|
|
|
|
!lang.chars().any(char::is_whitespace) && !lang.chars().any(|c| c == '`');
|
2022-12-10 04:57:15 -05:00
|
|
|
(valid_spec && fence_length >= 3).then(|| {
|
|
|
|
(
|
|
|
|
match f {
|
|
|
|
':' => Block::Container(Div),
|
|
|
|
_ => Block::Leaf(CodeBlock),
|
|
|
|
},
|
|
|
|
Span::from_slice(line, lang),
|
|
|
|
)
|
|
|
|
})
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
2023-01-22 14:28:09 -05:00
|
|
|
c => Self::maybe_ordered_list_item(c, &mut chars).map(|(num, style, len)| {
|
2023-01-21 05:13:24 -05:00
|
|
|
(
|
2023-01-21 06:33:41 -05:00
|
|
|
Block::Container(ListItem(Ordered(num, style))),
|
2023-01-21 05:13:24 -05:00
|
|
|
Span::by_len(start, len),
|
|
|
|
)
|
|
|
|
}),
|
2022-12-06 15:31:08 -05:00
|
|
|
}
|
2022-12-10 04:57:15 -05:00
|
|
|
.unwrap_or((Block::Leaf(Paragraph), Span::new(0, 0)));
|
|
|
|
|
|
|
|
Self {
|
|
|
|
indent: start,
|
|
|
|
kind,
|
|
|
|
span,
|
|
|
|
fence,
|
|
|
|
}
|
2022-12-06 15:31:08 -05:00
|
|
|
}
|
2022-11-28 14:12:49 -05:00
|
|
|
|
2022-12-06 15:31:08 -05:00
|
|
|
fn is_thematic_break(chars: std::str::Chars) -> bool {
|
|
|
|
let mut n = 1;
|
|
|
|
for c in chars {
|
|
|
|
if matches!(c, '-' | '*') {
|
|
|
|
n += 1;
|
|
|
|
} else if !c.is_whitespace() {
|
|
|
|
return false;
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
}
|
2022-12-06 15:31:08 -05:00
|
|
|
n >= 3
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
|
2022-12-10 04:57:15 -05:00
|
|
|
/// Determine if this line continues the block.
|
|
|
|
fn continues(&mut self, line: &str) -> bool {
|
2023-01-21 06:33:41 -05:00
|
|
|
let line_t = line.trim();
|
|
|
|
let empty = line_t.is_empty();
|
2022-12-10 04:57:15 -05:00
|
|
|
match self.kind {
|
|
|
|
Block::Atom(..) => false,
|
|
|
|
Block::Leaf(Paragraph | Heading | Table) => !line.trim().is_empty(),
|
|
|
|
Block::Leaf(LinkDefinition) => line.starts_with(' ') && !line.trim().is_empty(),
|
|
|
|
Block::Container(Blockquote) => line.trim().starts_with('>'),
|
2023-01-22 14:28:09 -05:00
|
|
|
Block::Container(ListItem(..)) => {
|
|
|
|
let spaces = line.chars().take_while(|c| c.is_whitespace()).count();
|
|
|
|
empty
|
|
|
|
|| spaces > self.indent
|
|
|
|
|| matches!(
|
|
|
|
Self::parse(std::iter::once(line)),
|
|
|
|
Some((_, Block::Leaf(Leaf::Paragraph), _, _)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Block::Container(Footnote) => {
|
2022-11-12 12:45:17 -05:00
|
|
|
let spaces = line.chars().take_while(|c| c.is_whitespace()).count();
|
2023-01-21 06:33:41 -05:00
|
|
|
empty || spaces > self.indent
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
2022-12-10 04:57:15 -05:00
|
|
|
Block::Container(Div) | Block::Leaf(CodeBlock) => {
|
|
|
|
let (fence, fence_length) = self.fence.unwrap();
|
2022-11-27 15:59:54 -05:00
|
|
|
let mut c = line.chars();
|
2022-12-10 04:57:15 -05:00
|
|
|
!((&mut c).take(fence_length).all(|c| c == fence)
|
2022-12-02 14:07:37 -05:00
|
|
|
&& c.next().map_or(true, char::is_whitespace))
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
2023-01-22 15:55:14 -05:00
|
|
|
Block::Container(List { .. } | DescriptionList) => panic!(),
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-22 14:28:09 -05:00
|
|
|
fn maybe_ordered_list_item(
|
|
|
|
mut first: char,
|
|
|
|
chars: &mut std::str::Chars,
|
|
|
|
) -> Option<(crate::OrderedListNumbering, crate::OrderedListStyle, usize)> {
|
|
|
|
fn is_roman_lower_digit(c: char) -> bool {
|
|
|
|
matches!(c, 'i' | 'v' | 'x' | 'l' | 'c' | 'd' | 'm')
|
|
|
|
}
|
2023-01-21 05:13:24 -05:00
|
|
|
|
2023-01-22 14:28:09 -05:00
|
|
|
fn is_roman_upper_digit(c: char) -> bool {
|
|
|
|
matches!(c, 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M')
|
|
|
|
}
|
|
|
|
|
|
|
|
let start_paren = first == '(';
|
|
|
|
if start_paren {
|
|
|
|
first = chars.next().unwrap_or(EOF);
|
|
|
|
}
|
2023-01-21 05:13:24 -05:00
|
|
|
|
2023-01-22 14:28:09 -05:00
|
|
|
let numbering = if first.is_ascii_digit() {
|
|
|
|
Decimal
|
|
|
|
} else if first.is_ascii_lowercase() {
|
|
|
|
AlphaLower
|
|
|
|
} else if first.is_ascii_uppercase() {
|
|
|
|
AlphaUpper
|
|
|
|
} else if is_roman_lower_digit(first) {
|
|
|
|
RomanLower
|
|
|
|
} else if is_roman_upper_digit(first) {
|
|
|
|
RomanUpper
|
2023-01-21 05:13:24 -05:00
|
|
|
} else {
|
|
|
|
return None;
|
2023-01-22 14:28:09 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
let chars_num = chars.clone();
|
|
|
|
let len_num = 1 + chars_num
|
|
|
|
.clone()
|
|
|
|
.take_while(|c| match numbering {
|
|
|
|
Decimal => c.is_ascii_digit(),
|
|
|
|
AlphaLower => c.is_ascii_lowercase(),
|
|
|
|
AlphaUpper => c.is_ascii_uppercase(),
|
|
|
|
RomanLower => is_roman_lower_digit(*c),
|
|
|
|
RomanUpper => is_roman_upper_digit(*c),
|
|
|
|
})
|
|
|
|
.count();
|
|
|
|
|
|
|
|
let post_num = chars.nth(len_num - 1)?;
|
|
|
|
let style = if start_paren {
|
|
|
|
if post_num == ')' {
|
|
|
|
ParenParen
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
} else if post_num == ')' {
|
|
|
|
Paren
|
|
|
|
} else if post_num == '.' {
|
|
|
|
Period
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
};
|
|
|
|
let len_style = usize::from(start_paren) + 1;
|
|
|
|
|
|
|
|
let chars_num = std::iter::once(first).chain(chars_num.take(len_num - 1));
|
|
|
|
let numbering = if matches!(numbering, AlphaLower)
|
|
|
|
&& chars_num.clone().all(is_roman_lower_digit)
|
|
|
|
{
|
2023-01-21 05:13:24 -05:00
|
|
|
RomanLower
|
|
|
|
} else if matches!(numbering, AlphaUpper) && chars_num.clone().all(is_roman_upper_digit) {
|
|
|
|
RomanUpper
|
|
|
|
} else {
|
|
|
|
numbering
|
|
|
|
};
|
|
|
|
|
2023-01-22 14:28:09 -05:00
|
|
|
if chars.next().map_or(true, char::is_whitespace) {
|
|
|
|
Some((numbering, style, len_num + len_style))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2023-01-21 05:13:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-12 12:45:17 -05:00
|
|
|
impl std::fmt::Display for Block {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
match self {
|
2022-12-10 04:26:06 -05:00
|
|
|
Block::Atom(a) => std::fmt::Debug::fmt(a, f),
|
2022-11-12 12:45:17 -05:00
|
|
|
Block::Leaf(e) => std::fmt::Debug::fmt(e, f),
|
|
|
|
Block::Container(c) => std::fmt::Debug::fmt(c, f),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::fmt::Display for Atom {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
write!(f, "Inline")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Similar to `std::str::split('\n')` but newline is included and spans are used instead of `str`.
|
|
|
|
fn lines(src: &str) -> impl Iterator<Item = Span> + '_ {
|
|
|
|
let mut chars = src.chars();
|
|
|
|
std::iter::from_fn(move || {
|
|
|
|
if chars.as_str().is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
let start = src.len() - chars.as_str().len();
|
|
|
|
chars.find(|c| *c == '\n');
|
|
|
|
let end = src.len() - chars.as_str().len();
|
|
|
|
if start == end {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(Span::new(start, end))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2022-12-12 12:22:13 -05:00
|
|
|
use crate::tree::EventKind;
|
2023-01-21 05:13:24 -05:00
|
|
|
use crate::tree::EventKind::*;
|
|
|
|
use crate::OrderedListNumbering::*;
|
|
|
|
use crate::OrderedListStyle::*;
|
2022-11-12 12:45:17 -05:00
|
|
|
|
|
|
|
use super::Atom::*;
|
|
|
|
use super::Block;
|
|
|
|
use super::Container::*;
|
|
|
|
use super::Leaf::*;
|
2023-01-21 05:13:24 -05:00
|
|
|
use super::ListType::*;
|
2022-12-12 12:22:13 -05:00
|
|
|
use super::Node::*;
|
2022-11-12 12:45:17 -05:00
|
|
|
|
2022-11-27 16:19:15 -05:00
|
|
|
macro_rules! test_parse {
|
2023-01-23 15:11:49 -05:00
|
|
|
($src:expr $(,$($event:expr),* $(,)?)?) => {
|
|
|
|
let t = super::TreeParser::new($src).parse();
|
|
|
|
let actual = t.map(|ev| (ev.kind, ev.span.of($src))).collect::<Vec<_>>();
|
|
|
|
let expected = &[$($($event),*,)?];
|
|
|
|
assert_eq!(
|
|
|
|
actual,
|
|
|
|
expected,
|
|
|
|
concat!(
|
|
|
|
"\n",
|
|
|
|
"\x1b[0;1m====================== INPUT =========================\x1b[0m\n",
|
|
|
|
"\x1b[2m{}",
|
|
|
|
"\x1b[0;1m================ ACTUAL vs EXPECTED ==================\x1b[0m\n",
|
|
|
|
"{}",
|
|
|
|
"\x1b[0;1m======================================================\x1b[0m\n",
|
|
|
|
),
|
|
|
|
$src,
|
|
|
|
{
|
|
|
|
let a = actual.iter().map(|n| format!("{:?}", n)).collect::<Vec<_>>();
|
|
|
|
let b = expected.iter().map(|n| format!("{:?}", n)).collect::<Vec<_>>();
|
|
|
|
let max = a.len().max(b.len());
|
|
|
|
let a_width = a.iter().map(|a| a.len()).max().unwrap_or(0);
|
|
|
|
a.iter()
|
|
|
|
.map(AsRef::as_ref)
|
|
|
|
.chain(std::iter::repeat(""))
|
|
|
|
.zip(b.iter().map(AsRef::as_ref).chain(std::iter::repeat("")))
|
|
|
|
.take(max)
|
|
|
|
.map(|(a, b)|
|
|
|
|
format!(
|
|
|
|
"\x1b[{}m{:a_width$}\x1b[0m {}= \x1b[{}m{}\x1b[0m\n",
|
|
|
|
if a == b { "2" } else { "31" },
|
|
|
|
a,
|
|
|
|
if a == b { '=' } else { '!' },
|
|
|
|
if a == b { "2" } else { "32" },
|
|
|
|
b,
|
|
|
|
a_width = a_width,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.collect::<String>()
|
|
|
|
},
|
|
|
|
);
|
|
|
|
};
|
|
|
|
}
|
2022-11-27 16:19:15 -05:00
|
|
|
|
2022-11-12 12:45:17 -05:00
|
|
|
#[test]
|
2022-11-28 14:12:49 -05:00
|
|
|
fn parse_para_oneline() {
|
2022-11-27 16:19:15 -05:00
|
|
|
test_parse!(
|
|
|
|
"para\n",
|
2022-11-28 14:12:49 -05:00
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "para"),
|
2022-11-28 18:33:43 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2022-11-12 12:45:17 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-11-28 14:12:49 -05:00
|
|
|
fn parse_para_multiline() {
|
2022-11-27 16:19:15 -05:00
|
|
|
test_parse!(
|
2022-11-28 14:12:49 -05:00
|
|
|
"para0\npara1\n",
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "para0\n"),
|
|
|
|
(Inline, "para1"),
|
2022-11-28 18:33:43 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2022-11-12 12:45:17 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-11-28 14:12:49 -05:00
|
|
|
fn parse_heading_multi() {
|
2022-11-27 16:19:15 -05:00
|
|
|
test_parse!(
|
|
|
|
concat!(
|
2022-12-12 12:22:13 -05:00
|
|
|
"# 2\n",
|
|
|
|
"\n",
|
|
|
|
" # 8\n",
|
|
|
|
" 12\n",
|
|
|
|
"15\n", //
|
|
|
|
),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Leaf(Heading)), "#"),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "2"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Leaf(Heading)), "#"),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Atom(Blankline), "\n"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Leaf(Heading)), "#"),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "8\n"),
|
2023-01-19 16:58:33 -05:00
|
|
|
(Inline, "12\n"),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "15"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Leaf(Heading)), "#"),
|
2022-11-12 12:45:17 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-11-28 14:12:49 -05:00
|
|
|
fn parse_blockquote() {
|
2022-11-30 13:56:08 -05:00
|
|
|
test_parse!(
|
|
|
|
"> a\n",
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Container(Blockquote)), ">"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "a"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(Blockquote)), ">"),
|
2022-11-30 13:56:08 -05:00
|
|
|
);
|
|
|
|
test_parse!(
|
2022-12-12 12:22:13 -05:00
|
|
|
"> a\nb\nc\n",
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Container(Blockquote)), ">"),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "a\n"),
|
|
|
|
(Inline, "b\n"),
|
|
|
|
(Inline, "c"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Container(Blockquote)), ">"),
|
2022-11-30 13:56:08 -05:00
|
|
|
);
|
2022-11-27 16:19:15 -05:00
|
|
|
test_parse!(
|
|
|
|
concat!(
|
|
|
|
"> a\n",
|
|
|
|
">\n",
|
|
|
|
"> ## hl\n",
|
|
|
|
">\n",
|
2022-11-28 14:12:49 -05:00
|
|
|
"> para\n", //
|
2022-11-27 16:19:15 -05:00
|
|
|
),
|
2022-11-28 14:12:49 -05:00
|
|
|
(Enter(Container(Blockquote)), ">"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "a"),
|
2022-11-28 18:33:43 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Atom(Blankline), "\n"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Leaf(Heading)), "##"),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "hl"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Leaf(Heading)), "##"),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Atom(Blankline), "\n"),
|
2022-11-28 14:12:49 -05:00
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "para"),
|
2022-11-28 18:33:43 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(Blockquote)), ">"),
|
2022-11-12 12:45:17 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-02 14:07:37 -05:00
|
|
|
#[test]
|
|
|
|
fn parse_blockquote_empty() {
|
|
|
|
test_parse!(
|
|
|
|
"> \n",
|
|
|
|
(Enter(Container(Blockquote)), ">"),
|
2022-12-10 04:26:06 -05:00
|
|
|
(EventKind::Atom(Blankline), "\n"),
|
2022-12-02 14:07:37 -05:00
|
|
|
(Exit(Container(Blockquote)), ">"),
|
|
|
|
);
|
|
|
|
test_parse!(
|
|
|
|
">",
|
|
|
|
(Enter(Container(Blockquote)), ">"),
|
2022-12-10 04:26:06 -05:00
|
|
|
(EventKind::Atom(Blankline), ""),
|
2022-12-02 14:07:37 -05:00
|
|
|
(Exit(Container(Blockquote)), ">"),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-11-27 15:59:54 -05:00
|
|
|
#[test]
|
|
|
|
fn parse_code_block() {
|
2022-12-07 12:44:03 -05:00
|
|
|
test_parse!(
|
|
|
|
concat!("```\n", "l0\n"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Leaf(CodeBlock)), "",),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "l0\n"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Leaf(CodeBlock)), "",),
|
2022-12-07 12:44:03 -05:00
|
|
|
);
|
2022-11-27 16:19:15 -05:00
|
|
|
test_parse!(
|
|
|
|
concat!(
|
2022-11-28 14:30:18 -05:00
|
|
|
"```\n",
|
2022-11-27 16:19:15 -05:00
|
|
|
"l0\n",
|
2022-12-02 14:07:37 -05:00
|
|
|
"```\n",
|
|
|
|
"\n",
|
|
|
|
"para\n", //
|
2022-11-27 16:19:15 -05:00
|
|
|
),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Leaf(CodeBlock)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "l0\n"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Leaf(CodeBlock)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Atom(Blankline), "\n"),
|
2022-11-28 18:33:43 -05:00
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "para"),
|
2022-11-28 18:33:43 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2022-11-28 14:30:18 -05:00
|
|
|
);
|
|
|
|
test_parse!(
|
|
|
|
concat!(
|
2022-12-02 02:16:47 -05:00
|
|
|
"```` lang\n",
|
2022-11-28 14:30:18 -05:00
|
|
|
"l0\n",
|
|
|
|
"```\n",
|
|
|
|
" l1\n",
|
|
|
|
"````", //
|
|
|
|
),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Leaf(CodeBlock)), "lang"),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "l0\n"),
|
|
|
|
(Inline, "```\n"),
|
|
|
|
(Inline, " l1\n"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Leaf(CodeBlock)), "lang"),
|
2022-11-27 15:59:54 -05:00
|
|
|
);
|
2022-12-02 02:16:47 -05:00
|
|
|
test_parse!(
|
|
|
|
concat!(
|
|
|
|
"```\n", //
|
|
|
|
"a\n", //
|
|
|
|
"```\n", //
|
|
|
|
"```\n", //
|
|
|
|
"bbb\n", //
|
|
|
|
"```\n", //
|
|
|
|
),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Leaf(CodeBlock)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "a\n"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Leaf(CodeBlock)), ""),
|
|
|
|
(Enter(Leaf(CodeBlock)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "bbb\n"),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Leaf(CodeBlock)), ""),
|
2022-12-07 12:44:03 -05:00
|
|
|
);
|
|
|
|
test_parse!(
|
|
|
|
concat!(
|
|
|
|
"~~~\n",
|
|
|
|
"code\n",
|
|
|
|
" block\n",
|
|
|
|
"~~~\n", //
|
|
|
|
),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Enter(Leaf(CodeBlock)), ""),
|
|
|
|
(Inline, "code\n"),
|
|
|
|
(Inline, " block\n"),
|
|
|
|
(Exit(Leaf(CodeBlock)), ""),
|
2022-12-07 12:44:03 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_link_definition() {
|
|
|
|
test_parse!(
|
|
|
|
"[tag]: url\n",
|
|
|
|
(Enter(Leaf(LinkDefinition)), "tag"),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "url"),
|
2022-12-07 12:44:03 -05:00
|
|
|
(Exit(Leaf(LinkDefinition)), "tag"),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_footnote() {
|
|
|
|
test_parse!(
|
|
|
|
"[^tag]: description\n",
|
2022-12-10 04:57:15 -05:00
|
|
|
(Enter(Container(Footnote)), "tag"),
|
2022-12-07 12:44:03 -05:00
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2022-12-12 12:22:13 -05:00
|
|
|
(Inline, "description"),
|
2022-12-07 12:44:03 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2022-12-10 04:57:15 -05:00
|
|
|
(Exit(Container(Footnote)), "tag"),
|
2022-12-02 02:16:47 -05:00
|
|
|
);
|
2022-11-27 16:19:15 -05:00
|
|
|
}
|
2022-11-27 15:59:54 -05:00
|
|
|
|
2023-01-18 16:30:24 -05:00
|
|
|
#[test]
|
|
|
|
fn parse_footnote_post() {
|
|
|
|
test_parse!(
|
|
|
|
concat!(
|
|
|
|
"[^a]\n",
|
|
|
|
"\n",
|
|
|
|
"[^a]: note\n",
|
|
|
|
"\n",
|
|
|
|
"para\n", //
|
|
|
|
),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "[^a]"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Atom(Blankline), "\n"),
|
|
|
|
(Enter(Container(Footnote)), "a"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "note"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Atom(Blankline), "\n"),
|
|
|
|
(Exit(Container(Footnote)), "a"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "para"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-18 12:05:39 -05:00
|
|
|
#[test]
|
|
|
|
fn parse_attr() {
|
|
|
|
test_parse!(
|
|
|
|
"{.some_class}\npara\n",
|
|
|
|
(Atom(Attributes), "{.some_class}\n"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "para"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-21 05:13:24 -05:00
|
|
|
#[test]
|
2023-01-21 14:53:12 -05:00
|
|
|
fn parse_list_single_item() {
|
2023-01-21 05:13:24 -05:00
|
|
|
test_parse!(
|
2023-01-22 15:55:14 -05:00
|
|
|
"- abc",
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"-"
|
2023-01-21 14:53:12 -05:00
|
|
|
),
|
2023-01-21 06:33:41 -05:00
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
2023-01-21 05:13:24 -05:00
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "abc"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
2023-01-22 15:55:14 -05:00
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
2023-01-21 14:53:12 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2023-01-22 15:55:14 -05:00
|
|
|
fn parse_list_tight() {
|
2023-01-21 14:53:12 -05:00
|
|
|
test_parse!(
|
2023-01-22 15:55:14 -05:00
|
|
|
concat!(
|
|
|
|
"- a\n", //
|
|
|
|
"- b\n", //
|
|
|
|
),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2023-01-22 15:55:14 -05:00
|
|
|
(Inline, "a"),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2023-01-22 15:55:14 -05:00
|
|
|
(Inline, "b"),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
2023-01-22 15:55:14 -05:00
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
2023-01-21 14:53:12 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2023-01-22 15:55:14 -05:00
|
|
|
fn parse_list_loose() {
|
2023-01-21 14:53:12 -05:00
|
|
|
test_parse!(
|
|
|
|
concat!(
|
2023-01-22 15:55:14 -05:00
|
|
|
"- a\n", //
|
|
|
|
"- b\n", //
|
|
|
|
"\n", //
|
|
|
|
"- c\n", //
|
|
|
|
),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: false,
|
|
|
|
})),
|
|
|
|
"-"
|
2023-01-21 14:53:12 -05:00
|
|
|
),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "a"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2023-01-22 15:55:14 -05:00
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "b"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Atom(Blankline), "\n"),
|
2023-01-22 15:55:14 -05:00
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2023-01-22 15:55:14 -05:00
|
|
|
(Inline, "c"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: false,
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_list_tight_nest() {
|
|
|
|
test_parse!(
|
|
|
|
concat!(
|
|
|
|
"- a\n", //
|
|
|
|
"\n", //
|
|
|
|
" + aa\n", //
|
|
|
|
" + ab\n", //
|
|
|
|
"\n", //
|
|
|
|
"- b\n", //
|
|
|
|
),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "a"),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Atom(Blankline), "\n"),
|
2023-01-22 15:55:14 -05:00
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'+'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"+",
|
|
|
|
),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "aa"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "ab"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Atom(Blankline), "\n"),
|
2023-01-23 15:11:49 -05:00
|
|
|
(Exit(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'+'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"+",
|
|
|
|
),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "b"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
2023-01-22 15:55:14 -05:00
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_list_nest() {
|
|
|
|
test_parse!(
|
|
|
|
concat!(
|
|
|
|
"- a\n", //
|
|
|
|
" \n", //
|
|
|
|
" + b\n", //
|
|
|
|
" \n", //
|
|
|
|
" * c\n", //
|
|
|
|
),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2023-01-23 15:11:49 -05:00
|
|
|
(Inline, "a"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Atom(Blankline), "\n"),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'+'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"+",
|
|
|
|
),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
2023-01-21 14:53:12 -05:00
|
|
|
(Inline, "b"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
2023-01-23 15:11:49 -05:00
|
|
|
(Atom(Blankline), "\n"),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'*'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"*",
|
|
|
|
),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'*')))), "*"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "c"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'*')))), "*"),
|
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'*'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"*",
|
|
|
|
),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'+'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"+",
|
|
|
|
),
|
2023-01-21 06:33:41 -05:00
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
2023-01-22 15:55:14 -05:00
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true,
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_list_post() {
|
|
|
|
test_parse!(
|
|
|
|
concat!(
|
|
|
|
"- a\n", //
|
|
|
|
"\n", //
|
|
|
|
" * b\n", //
|
|
|
|
"cd\n", //
|
|
|
|
),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(45),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
|
|
|
(Enter(Container(ListItem(Unordered(45)))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "a"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Atom(Blankline), "\n"),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(42),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"*"
|
|
|
|
),
|
|
|
|
(Enter(Container(ListItem(Unordered(42)))), "*"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "b\n"),
|
|
|
|
(Inline, "cd"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(ListItem(Unordered(42)))), "*"),
|
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(42),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"*"
|
|
|
|
),
|
|
|
|
(Exit(Container(ListItem(Unordered(45)))), "-"),
|
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(45),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn parse_list_mixed() {
|
|
|
|
test_parse!(
|
|
|
|
concat!(
|
|
|
|
"- a\n", //
|
|
|
|
"+ b\n", //
|
|
|
|
"+ c\n", //
|
|
|
|
),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "a"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'-')))), "-"),
|
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'-'),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"-"
|
|
|
|
),
|
|
|
|
(
|
|
|
|
Enter(Container(List {
|
|
|
|
ty: Unordered(b'+'),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"+"
|
|
|
|
),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "b"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(Enter(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(Enter(Leaf(Paragraph)), ""),
|
|
|
|
(Inline, "c"),
|
|
|
|
(Exit(Leaf(Paragraph)), ""),
|
|
|
|
(Exit(Container(ListItem(Unordered(b'+')))), "+"),
|
|
|
|
(
|
|
|
|
Exit(Container(List {
|
|
|
|
ty: Unordered(b'+'),
|
|
|
|
tight: true
|
|
|
|
})),
|
|
|
|
"+"
|
|
|
|
),
|
2023-01-21 05:13:24 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-11-27 16:19:15 -05:00
|
|
|
macro_rules! test_block {
|
|
|
|
($src:expr, $kind:expr, $str:expr, $len:expr $(,)?) => {
|
|
|
|
let lines = super::lines($src).map(|sp| sp.of($src));
|
2022-12-10 04:57:15 -05:00
|
|
|
let (_indent, kind, sp, len) = super::BlockParser::parse(lines).unwrap();
|
2022-11-27 16:19:15 -05:00
|
|
|
assert_eq!(
|
|
|
|
(kind, sp.of($src), len),
|
|
|
|
($kind, $str, $len),
|
|
|
|
"\n\n{}\n\n",
|
|
|
|
$src
|
|
|
|
);
|
|
|
|
};
|
2022-11-27 15:59:54 -05:00
|
|
|
}
|
|
|
|
|
2022-12-10 04:26:06 -05:00
|
|
|
#[test]
|
|
|
|
fn block_blankline() {
|
|
|
|
test_block!("\n", Block::Atom(Blankline), "\n", 1);
|
|
|
|
test_block!(" \n", Block::Atom(Blankline), "\n", 1);
|
|
|
|
}
|
|
|
|
|
2022-11-12 12:45:17 -05:00
|
|
|
#[test]
|
|
|
|
fn block_multiline() {
|
2022-12-12 12:22:13 -05:00
|
|
|
test_block!(
|
|
|
|
"# heading\n spanning two lines\n",
|
|
|
|
Block::Leaf(Heading),
|
|
|
|
"#",
|
|
|
|
2
|
|
|
|
);
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-12-02 14:07:37 -05:00
|
|
|
fn block_blockquote() {
|
2022-11-27 16:19:15 -05:00
|
|
|
test_block!(
|
|
|
|
concat!(
|
|
|
|
"> a\n", //
|
|
|
|
">\n", //
|
|
|
|
" > b\n", //
|
|
|
|
">\n", //
|
|
|
|
"> c\n", //
|
|
|
|
),
|
|
|
|
Block::Container(Blockquote),
|
|
|
|
">",
|
|
|
|
5,
|
|
|
|
);
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|
2022-12-02 14:07:37 -05:00
|
|
|
|
2022-12-10 02:37:00 -05:00
|
|
|
#[test]
|
|
|
|
fn block_thematic_break() {
|
2022-12-10 04:26:06 -05:00
|
|
|
test_block!("---\n", Block::Atom(ThematicBreak), "---", 1);
|
2022-12-10 02:37:00 -05:00
|
|
|
test_block!(
|
|
|
|
concat!(
|
|
|
|
" -*- -*-\n",
|
2023-01-21 05:14:00 -05:00
|
|
|
"\n",
|
2022-12-10 02:37:00 -05:00
|
|
|
"para", //
|
|
|
|
),
|
2022-12-10 04:26:06 -05:00
|
|
|
Block::Atom(ThematicBreak),
|
2022-12-10 02:37:00 -05:00
|
|
|
"-*- -*-",
|
|
|
|
1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-02 14:07:37 -05:00
|
|
|
#[test]
|
2022-12-04 11:56:49 -05:00
|
|
|
fn block_code_block() {
|
2022-12-02 14:07:37 -05:00
|
|
|
test_block!(
|
|
|
|
concat!(
|
|
|
|
"```` lang\n",
|
|
|
|
"l0\n",
|
|
|
|
"```\n",
|
|
|
|
" l1\n",
|
|
|
|
"````", //
|
|
|
|
),
|
2022-12-12 12:22:13 -05:00
|
|
|
Block::Leaf(CodeBlock),
|
2022-12-07 12:44:03 -05:00
|
|
|
"lang",
|
2022-12-02 14:07:37 -05:00
|
|
|
5,
|
|
|
|
);
|
|
|
|
test_block!(
|
|
|
|
concat!(
|
|
|
|
"```\n", //
|
|
|
|
"a\n", //
|
|
|
|
"```\n", //
|
|
|
|
"```\n", //
|
|
|
|
"bbb\n", //
|
|
|
|
"```\n", //
|
|
|
|
),
|
2022-12-12 12:22:13 -05:00
|
|
|
Block::Leaf(CodeBlock),
|
2022-12-07 12:44:03 -05:00
|
|
|
"",
|
2022-12-02 14:07:37 -05:00
|
|
|
3,
|
|
|
|
);
|
2022-12-04 11:56:49 -05:00
|
|
|
test_block!(
|
|
|
|
concat!(
|
|
|
|
"``` no space in lang specifier\n",
|
|
|
|
"l0\n",
|
|
|
|
"```\n", //
|
|
|
|
),
|
2022-12-12 12:22:13 -05:00
|
|
|
Block::Leaf(Paragraph),
|
2022-12-04 11:56:49 -05:00
|
|
|
"",
|
|
|
|
3,
|
|
|
|
);
|
2022-12-02 14:07:37 -05:00
|
|
|
}
|
2022-12-06 15:31:08 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_link_definition() {
|
2022-12-12 12:22:13 -05:00
|
|
|
test_block!("[tag]: url\n", Block::Leaf(LinkDefinition), "tag", 1);
|
2023-01-17 12:05:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_link_definition_multiline() {
|
2022-12-06 15:31:08 -05:00
|
|
|
test_block!(
|
|
|
|
concat!(
|
|
|
|
"[tag]: uuu\n",
|
|
|
|
" rl\n", //
|
|
|
|
),
|
2022-12-12 12:22:13 -05:00
|
|
|
Block::Leaf(LinkDefinition),
|
2022-12-07 12:44:03 -05:00
|
|
|
"tag",
|
2022-12-06 15:31:08 -05:00
|
|
|
2,
|
|
|
|
);
|
|
|
|
test_block!(
|
|
|
|
concat!(
|
|
|
|
"[tag]: url\n",
|
|
|
|
"para\n", //
|
|
|
|
),
|
2022-12-12 12:22:13 -05:00
|
|
|
Block::Leaf(LinkDefinition),
|
2022-12-07 12:44:03 -05:00
|
|
|
"tag",
|
2022-12-06 15:31:08 -05:00
|
|
|
1,
|
|
|
|
);
|
|
|
|
}
|
2023-01-18 16:30:24 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_footnote_empty() {
|
|
|
|
test_block!("[^tag]:\n", Block::Container(Footnote), "tag", 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_footnote_single() {
|
|
|
|
test_block!("[^tag]: a\n", Block::Container(Footnote), "tag", 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_footnote_multiline() {
|
|
|
|
test_block!(
|
|
|
|
concat!(
|
|
|
|
"[^tag]: a\n",
|
|
|
|
" b\n", //
|
|
|
|
),
|
|
|
|
Block::Container(Footnote),
|
|
|
|
"tag",
|
|
|
|
2,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_footnote_multiline_post() {
|
|
|
|
test_block!(
|
|
|
|
concat!(
|
|
|
|
"[^tag]: a\n",
|
|
|
|
" b\n",
|
|
|
|
"\n",
|
|
|
|
"para\n", //
|
|
|
|
),
|
|
|
|
Block::Container(Footnote),
|
|
|
|
"tag",
|
|
|
|
3,
|
|
|
|
);
|
|
|
|
}
|
2023-01-21 05:13:24 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_list_bullet() {
|
2023-01-21 06:33:41 -05:00
|
|
|
test_block!(
|
|
|
|
"- abc\n",
|
|
|
|
Block::Container(ListItem(Unordered(b'-'))),
|
|
|
|
"-",
|
|
|
|
1
|
|
|
|
);
|
|
|
|
test_block!(
|
|
|
|
"+ abc\n",
|
|
|
|
Block::Container(ListItem(Unordered(b'+'))),
|
|
|
|
"+",
|
|
|
|
1
|
|
|
|
);
|
|
|
|
test_block!(
|
|
|
|
"* abc\n",
|
|
|
|
Block::Container(ListItem(Unordered(b'*'))),
|
|
|
|
"*",
|
|
|
|
1
|
|
|
|
);
|
2023-01-21 05:13:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_list_description() {
|
2023-01-22 15:55:14 -05:00
|
|
|
test_block!(": abc\n", Block::Container(DescriptionList), ":", 1);
|
2023-01-21 05:13:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_list_task() {
|
|
|
|
test_block!("- [ ] abc\n", Block::Container(ListItem(Task)), "- [ ]", 1);
|
|
|
|
test_block!("+ [x] abc\n", Block::Container(ListItem(Task)), "+ [x]", 1);
|
|
|
|
test_block!("* [X] abc\n", Block::Container(ListItem(Task)), "* [X]", 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn block_list_ordered() {
|
|
|
|
test_block!(
|
|
|
|
"123. abc\n",
|
|
|
|
Block::Container(ListItem(Ordered(Decimal, Period))),
|
|
|
|
"123.",
|
|
|
|
1
|
|
|
|
);
|
|
|
|
test_block!(
|
|
|
|
"i. abc\n",
|
|
|
|
Block::Container(ListItem(Ordered(RomanLower, Period))),
|
|
|
|
"i.",
|
|
|
|
1
|
|
|
|
);
|
|
|
|
test_block!(
|
|
|
|
"I. abc\n",
|
|
|
|
Block::Container(ListItem(Ordered(RomanUpper, Period))),
|
|
|
|
"I.",
|
|
|
|
1
|
|
|
|
);
|
|
|
|
test_block!(
|
|
|
|
"IJ. abc\n",
|
|
|
|
Block::Container(ListItem(Ordered(AlphaUpper, Period))),
|
|
|
|
"IJ.",
|
|
|
|
1
|
|
|
|
);
|
|
|
|
test_block!(
|
|
|
|
"(a) abc\n",
|
|
|
|
Block::Container(ListItem(Ordered(AlphaLower, ParenParen))),
|
|
|
|
"(a)",
|
|
|
|
1
|
|
|
|
);
|
|
|
|
test_block!(
|
|
|
|
"a) abc\n",
|
|
|
|
Block::Container(ListItem(Ordered(AlphaLower, Paren))),
|
|
|
|
"a)",
|
|
|
|
1
|
|
|
|
);
|
|
|
|
}
|
2022-11-12 12:45:17 -05:00
|
|
|
}
|