parser: determine tight vs loose lists
This commit is contained in:
parent
36470af114
commit
70d29c65e4
4 changed files with 161 additions and 44 deletions
|
@ -13,6 +13,7 @@ use ListType::*;
|
||||||
|
|
||||||
pub type Tree = tree::Tree<Node, Atom>;
|
pub type Tree = tree::Tree<Node, Atom>;
|
||||||
pub type TreeBuilder = tree::Builder<Node, Atom>;
|
pub type TreeBuilder = tree::Builder<Node, Atom>;
|
||||||
|
pub type Element = tree::Element<Node, Atom>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
|
|
37
src/html.rs
37
src/html.rs
|
@ -1,7 +1,7 @@
|
||||||
use crate::Atom;
|
use crate::Atom;
|
||||||
use crate::Container;
|
use crate::Container;
|
||||||
use crate::Event;
|
use crate::Event;
|
||||||
use crate::List;
|
use crate::ListKind;
|
||||||
use crate::OrderedListNumbering::*;
|
use crate::OrderedListNumbering::*;
|
||||||
|
|
||||||
/// Generate HTML from parsed events and push it to a unicode-accepting buffer or stream.
|
/// Generate HTML from parsed events and push it to a unicode-accepting buffer or stream.
|
||||||
|
@ -101,12 +101,19 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
}
|
}
|
||||||
match &c {
|
match &c {
|
||||||
Container::Blockquote => self.out.write_str("<blockquote")?,
|
Container::Blockquote => self.out.write_str("<blockquote")?,
|
||||||
Container::List(List::Unordered | List::Task) => {
|
Container::List {
|
||||||
|
kind: ListKind::Unordered | ListKind::Task,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
self.out.write_str("<ul")?;
|
self.out.write_str("<ul")?;
|
||||||
}
|
}
|
||||||
Container::List(List::Ordered {
|
Container::List {
|
||||||
numbering, start, ..
|
kind:
|
||||||
}) => {
|
ListKind::Ordered {
|
||||||
|
numbering, start, ..
|
||||||
|
},
|
||||||
|
..
|
||||||
|
} => {
|
||||||
self.out.write_str("<ol")?;
|
self.out.write_str("<ol")?;
|
||||||
if *start > 1 {
|
if *start > 1 {
|
||||||
write!(self.out, r#" start="{}""#, start)?;
|
write!(self.out, r#" start="{}""#, start)?;
|
||||||
|
@ -191,14 +198,20 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
c,
|
c,
|
||||||
Container::Div { class: Some(_) }
|
Container::Div { class: Some(_) }
|
||||||
| Container::Math { .. }
|
| Container::Math { .. }
|
||||||
| Container::List(List::Task)
|
| Container::List {
|
||||||
|
kind: ListKind::Task,
|
||||||
|
..
|
||||||
|
}
|
||||||
| Container::TaskListItem { .. }
|
| Container::TaskListItem { .. }
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
self.out.write_str(r#" class=""#)?;
|
self.out.write_str(r#" class=""#)?;
|
||||||
let mut first_written = false;
|
let mut first_written = false;
|
||||||
if let Some(cls) = match c {
|
if let Some(cls) = match c {
|
||||||
Container::List(List::Task) => Some("task-list"),
|
Container::List {
|
||||||
|
kind: ListKind::Task,
|
||||||
|
..
|
||||||
|
} => Some("task-list"),
|
||||||
Container::TaskListItem { checked: false } => Some("unchecked"),
|
Container::TaskListItem { checked: false } => Some("unchecked"),
|
||||||
Container::TaskListItem { checked: true } => Some("checked"),
|
Container::TaskListItem { checked: true } => Some("checked"),
|
||||||
Container::Math { display: false } => Some("math inline"),
|
Container::Math { display: false } => Some("math inline"),
|
||||||
|
@ -256,10 +269,16 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
}
|
}
|
||||||
match c {
|
match c {
|
||||||
Container::Blockquote => self.out.write_str("</blockquote>")?,
|
Container::Blockquote => self.out.write_str("</blockquote>")?,
|
||||||
Container::List(List::Unordered | List::Task) => {
|
Container::List {
|
||||||
|
kind: ListKind::Unordered | ListKind::Task,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
self.out.write_str("</ul>")?;
|
self.out.write_str("</ul>")?;
|
||||||
}
|
}
|
||||||
Container::List(List::Ordered { .. }) => self.out.write_str("</ol>")?,
|
Container::List {
|
||||||
|
kind: ListKind::Ordered { .. },
|
||||||
|
..
|
||||||
|
} => self.out.write_str("</ol>")?,
|
||||||
Container::ListItem | Container::TaskListItem { .. } => {
|
Container::ListItem | Container::TaskListItem { .. } => {
|
||||||
self.out.write_str("</li>")?;
|
self.out.write_str("</li>")?;
|
||||||
}
|
}
|
||||||
|
|
114
src/lib.rs
114
src/lib.rs
|
@ -33,7 +33,7 @@ pub enum Container<'s> {
|
||||||
/// A blockquote element.
|
/// A blockquote element.
|
||||||
Blockquote,
|
Blockquote,
|
||||||
/// A list.
|
/// A list.
|
||||||
List(List),
|
List { kind: ListKind, tight: bool },
|
||||||
/// An item of a list
|
/// An item of a list
|
||||||
ListItem,
|
ListItem,
|
||||||
/// An item of a task list, either checked or unchecked.
|
/// An item of a task list, either checked or unchecked.
|
||||||
|
@ -99,7 +99,7 @@ impl<'s> Container<'s> {
|
||||||
fn is_block(&self) -> bool {
|
fn is_block(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Blockquote
|
Self::Blockquote
|
||||||
| Self::List(..)
|
| Self::List { .. }
|
||||||
| Self::ListItem
|
| Self::ListItem
|
||||||
| Self::TaskListItem { .. }
|
| Self::TaskListItem { .. }
|
||||||
| Self::DescriptionList
|
| Self::DescriptionList
|
||||||
|
@ -136,7 +136,7 @@ impl<'s> Container<'s> {
|
||||||
fn is_block_container(&self) -> bool {
|
fn is_block_container(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Blockquote
|
Self::Blockquote
|
||||||
| Self::List(..)
|
| Self::List { .. }
|
||||||
| Self::ListItem
|
| Self::ListItem
|
||||||
| Self::TaskListItem { .. }
|
| Self::TaskListItem { .. }
|
||||||
| Self::DescriptionList
|
| Self::DescriptionList
|
||||||
|
@ -184,7 +184,7 @@ pub enum LinkType {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum List {
|
pub enum ListKind {
|
||||||
Unordered,
|
Unordered,
|
||||||
Ordered {
|
Ordered {
|
||||||
numbering: OrderedListNumbering,
|
numbering: OrderedListNumbering,
|
||||||
|
@ -502,20 +502,41 @@ impl<'s> Parser<'s> {
|
||||||
self.footnotes.insert(content, self.tree.take_branch());
|
self.footnotes.insert(content, self.tree.take_branch());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
block::Container::List(ty) => match ty {
|
block::Container::List(block::ListType::Description) => {
|
||||||
block::ListType::Unordered(..) => Container::List(List::Unordered),
|
Container::DescriptionList
|
||||||
block::ListType::Task => Container::List(List::Task),
|
}
|
||||||
block::ListType::Ordered(numbering, style) => {
|
block::Container::List(ty) => {
|
||||||
let marker = ev.span.of(self.src);
|
let kind = match ty {
|
||||||
let start = numbering.parse_number(style.number(marker)).max(1);
|
block::ListType::Unordered(..) => ListKind::Unordered,
|
||||||
Container::List(List::Ordered {
|
block::ListType::Ordered(numbering, style) => {
|
||||||
numbering,
|
let marker = ev.span.of(self.src);
|
||||||
style,
|
let start =
|
||||||
start,
|
numbering.parse_number(style.number(marker)).max(1);
|
||||||
})
|
ListKind::Ordered {
|
||||||
}
|
numbering,
|
||||||
block::ListType::Description => panic!(),
|
style,
|
||||||
},
|
start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block::ListType::Task => ListKind::Task,
|
||||||
|
block::ListType::Description => unreachable!(),
|
||||||
|
};
|
||||||
|
let tight =
|
||||||
|
!self.tree.linear().any(|elem| {
|
||||||
|
matches!(elem, block::Element::Atom(block::Atom::Blankline))
|
||||||
|
}) && !self.tree.linear_containers().any(|(c, tree)| {
|
||||||
|
matches!(
|
||||||
|
c,
|
||||||
|
block::Node::Container(block::Container::ListItem(..))
|
||||||
|
) && tree.linear().any(|elem| {
|
||||||
|
matches!(
|
||||||
|
elem,
|
||||||
|
block::Element::Atom(block::Atom::Blankline)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Container::List { kind, tight }
|
||||||
|
}
|
||||||
block::Container::ListItem(ty) => {
|
block::Container::ListItem(ty) => {
|
||||||
if matches!(ty, block::ListType::Task) {
|
if matches!(ty, block::ListType::Task) {
|
||||||
let marker = ev.span.of(self.src);
|
let marker = ev.span.of(self.src);
|
||||||
|
@ -587,8 +608,7 @@ mod test {
|
||||||
use super::Container::*;
|
use super::Container::*;
|
||||||
use super::Event::*;
|
use super::Event::*;
|
||||||
use super::LinkType;
|
use super::LinkType;
|
||||||
use super::List;
|
use super::ListKind;
|
||||||
use super::List::*;
|
|
||||||
use super::OrderedListNumbering::*;
|
use super::OrderedListNumbering::*;
|
||||||
use super::OrderedListStyle::*;
|
use super::OrderedListStyle::*;
|
||||||
use super::SpanLinkType;
|
use super::SpanLinkType;
|
||||||
|
@ -1013,13 +1033,22 @@ mod test {
|
||||||
fn list_item_unordered() {
|
fn list_item_unordered() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"- abc",
|
"- abc",
|
||||||
Start(List(List::Unordered), Attributes::new()),
|
Start(
|
||||||
|
List {
|
||||||
|
kind: ListKind::Unordered,
|
||||||
|
tight: true,
|
||||||
|
},
|
||||||
|
Attributes::new(),
|
||||||
|
),
|
||||||
Start(ListItem, Attributes::new()),
|
Start(ListItem, Attributes::new()),
|
||||||
Start(Paragraph, Attributes::new()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Str("abc".into()),
|
Str("abc".into()),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
End(ListItem),
|
End(ListItem),
|
||||||
End(List(List::Unordered)),
|
End(List {
|
||||||
|
kind: ListKind::Unordered,
|
||||||
|
tight: true,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1028,23 +1057,29 @@ mod test {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
"123. abc",
|
"123. abc",
|
||||||
Start(
|
Start(
|
||||||
List(List::Ordered {
|
List {
|
||||||
numbering: Decimal,
|
kind: ListKind::Ordered {
|
||||||
style: Period,
|
numbering: Decimal,
|
||||||
start: 123
|
style: Period,
|
||||||
}),
|
start: 123
|
||||||
Attributes::new()
|
},
|
||||||
|
tight: true,
|
||||||
|
},
|
||||||
|
Attributes::new(),
|
||||||
),
|
),
|
||||||
Start(ListItem, Attributes::new()),
|
Start(ListItem, Attributes::new()),
|
||||||
Start(Paragraph, Attributes::new()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Str("abc".into()),
|
Str("abc".into()),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
End(ListItem),
|
End(ListItem),
|
||||||
End(List(List::Ordered {
|
End(List {
|
||||||
numbering: Decimal,
|
kind: ListKind::Ordered {
|
||||||
style: Period,
|
numbering: Decimal,
|
||||||
start: 123
|
style: Period,
|
||||||
})),
|
start: 123
|
||||||
|
},
|
||||||
|
tight: true,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1056,7 +1091,13 @@ mod test {
|
||||||
"- [x] b\n", //
|
"- [x] b\n", //
|
||||||
"- [X] c\n", //
|
"- [X] c\n", //
|
||||||
),
|
),
|
||||||
Start(List(List::Task), Attributes::new()),
|
Start(
|
||||||
|
List {
|
||||||
|
kind: ListKind::Task,
|
||||||
|
tight: true,
|
||||||
|
},
|
||||||
|
Attributes::new(),
|
||||||
|
),
|
||||||
Start(TaskListItem { checked: false }, Attributes::new()),
|
Start(TaskListItem { checked: false }, Attributes::new()),
|
||||||
Start(Paragraph, Attributes::new()),
|
Start(Paragraph, Attributes::new()),
|
||||||
Str("a".into()),
|
Str("a".into()),
|
||||||
|
@ -1072,7 +1113,10 @@ mod test {
|
||||||
Str("c".into()),
|
Str("c".into()),
|
||||||
End(Paragraph),
|
End(Paragraph),
|
||||||
End(TaskListItem { checked: true }),
|
End(TaskListItem { checked: true }),
|
||||||
End(List(List::Task)),
|
End(List {
|
||||||
|
kind: ListKind::Task,
|
||||||
|
tight: true,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
53
src/tree.rs
53
src/tree.rs
|
@ -8,6 +8,13 @@ pub enum EventKind<C, A> {
|
||||||
Atom(A),
|
Atom(A),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Element<C, A> {
|
||||||
|
Container(C),
|
||||||
|
Atom(A),
|
||||||
|
Inline,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Event<C, A> {
|
pub struct Event<C, A> {
|
||||||
pub kind: EventKind<C, A>,
|
pub kind: EventKind<C, A>,
|
||||||
|
@ -22,6 +29,14 @@ pub struct Tree<C: 'static, A: 'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Clone, A: Clone> Tree<C, A> {
|
impl<C: Clone, A: Clone> Tree<C, A> {
|
||||||
|
fn with_head(&self, head: Option<NodeIndex>) -> Self {
|
||||||
|
Self {
|
||||||
|
nodes: self.nodes.clone(),
|
||||||
|
branch: Vec::new(),
|
||||||
|
head,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
nodes: vec![].into_boxed_slice().into(),
|
nodes: vec![].into_boxed_slice().into(),
|
||||||
|
@ -42,6 +57,44 @@ impl<C: Clone, A: Clone> Tree<C, A> {
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve upcoming direct events without entering branches.
|
||||||
|
pub fn linear(&self) -> impl Iterator<Item = Element<C, A>> + '_ {
|
||||||
|
let mut head = self.head;
|
||||||
|
std::iter::from_fn(move || {
|
||||||
|
head.take().map(|h| {
|
||||||
|
let n = &self.nodes[h.index()];
|
||||||
|
head = n.next;
|
||||||
|
match &n.kind {
|
||||||
|
NodeKind::Root => unreachable!(),
|
||||||
|
NodeKind::Container(c, ..) => Element::Container(c.clone()),
|
||||||
|
NodeKind::Atom(a) => Element::Atom(a.clone()),
|
||||||
|
NodeKind::Inline => Element::Inline,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the upcoming branches.
|
||||||
|
pub fn linear_containers(&self) -> impl Iterator<Item = (C, Self)> + '_ {
|
||||||
|
let mut head = self.head;
|
||||||
|
std::iter::from_fn(move || {
|
||||||
|
while let Some(h) = head.take() {
|
||||||
|
let n = &self.nodes[h.index()];
|
||||||
|
head = n.next;
|
||||||
|
match &n.kind {
|
||||||
|
NodeKind::Root => unreachable!(),
|
||||||
|
NodeKind::Container(c, child) => {
|
||||||
|
return Some((c.clone(), self.with_head(*child)));
|
||||||
|
}
|
||||||
|
NodeKind::Atom(_) | NodeKind::Inline => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
pub fn take_branch(&mut self) -> Self {
|
||||||
let head = self.head.take();
|
let head = self.head.take();
|
||||||
self.head = self.branch.pop();
|
self.head = self.branch.pop();
|
||||||
|
|
Loading…
Reference in a new issue