wip
This commit is contained in:
parent
8bdb598e6c
commit
4e1ada5836
2 changed files with 88 additions and 20 deletions
102
src/block.rs
102
src/block.rs
|
@ -22,7 +22,6 @@ pub enum Block {
|
||||||
pub enum Leaf {
|
pub enum Leaf {
|
||||||
Paragraph,
|
Paragraph,
|
||||||
Heading { level: u8 },
|
Heading { level: u8 },
|
||||||
Attributes,
|
|
||||||
Table,
|
Table,
|
||||||
LinkDefinition,
|
LinkDefinition,
|
||||||
CodeBlock { fence_length: u8 },
|
CodeBlock { fence_length: u8 },
|
||||||
|
@ -43,6 +42,8 @@ pub enum Atom {
|
||||||
Inline,
|
Inline,
|
||||||
/// A line with no non-whitespace characters.
|
/// A line with no non-whitespace characters.
|
||||||
Blankline,
|
Blankline,
|
||||||
|
/// A list of attributes.
|
||||||
|
Attributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Parser<'s> {
|
struct Parser<'s> {
|
||||||
|
@ -170,6 +171,7 @@ impl Block {
|
||||||
let start = line.chars().take_while(|c| c.is_whitespace()).count();
|
let start = line.chars().take_while(|c| c.is_whitespace()).count();
|
||||||
let line = &line[start..];
|
let line = &line[start..];
|
||||||
let mut chars = line.chars();
|
let mut chars = line.chars();
|
||||||
|
|
||||||
match chars.next().unwrap_or(EOF) {
|
match chars.next().unwrap_or(EOF) {
|
||||||
'#' => chars
|
'#' => chars
|
||||||
.find(|c| *c != '#')
|
.find(|c| *c != '#')
|
||||||
|
@ -200,6 +202,49 @@ impl Block {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
'|' => (&line[line.len() - 1..] == "|"
|
||||||
|
&& &line[line.len() - 2..line.len() - 1] != "\\")
|
||||||
|
.then(|| (Self::Leaf(Table), Span::by_len(start, 1))),
|
||||||
|
'[' => {
|
||||||
|
let first = chars.next();
|
||||||
|
let is_footnote = chars.next() == Some('^');
|
||||||
|
if first != Some(']') {
|
||||||
|
(&mut chars).take_while(|c| *c != ']').count();
|
||||||
|
}
|
||||||
|
(chars.next() == Some(':')).then(|| {
|
||||||
|
(
|
||||||
|
if is_footnote {
|
||||||
|
Self::Container(Footnote {
|
||||||
|
indent: u8::try_from(start).unwrap(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Self::Leaf(LinkDefinition)
|
||||||
|
},
|
||||||
|
Span::by_len(start, 0),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
'-' | '*' if Self::is_thematic_break(chars.clone()) => {
|
||||||
|
Some((Self::Leaf(ThematicBreak), Span::by_len(start, line.len())))
|
||||||
|
}
|
||||||
|
'-' => chars.next().map_or(true, char::is_whitespace).then(|| {
|
||||||
|
let task_list = chars.next() == Some('[')
|
||||||
|
&& matches!(chars.next(), Some('X' | ' '))
|
||||||
|
&& chars.next() == Some(']')
|
||||||
|
&& chars.next().map_or(true, char::is_whitespace);
|
||||||
|
(
|
||||||
|
Self::Container(ListItem {
|
||||||
|
indent: u8::try_from(start).unwrap(),
|
||||||
|
}),
|
||||||
|
Span::by_len(start, if task_list { 3 } else { 1 }),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
'+' | '*' | ':' if chars.next().map_or(true, char::is_whitespace) => Some((
|
||||||
|
Self::Container(ListItem {
|
||||||
|
indent: u8::try_from(start).unwrap(),
|
||||||
|
}),
|
||||||
|
Span::by_len(start, 1),
|
||||||
|
)),
|
||||||
f @ ('`' | ':') => {
|
f @ ('`' | ':') => {
|
||||||
let fence_length = (&mut chars).take_while(|c| *c == f).count() + 1;
|
let fence_length = (&mut chars).take_while(|c| *c == f).count() + 1;
|
||||||
let valid_spec = !line[fence_length..].trim().chars().any(char::is_whitespace);
|
let valid_spec = !line[fence_length..].trim().chars().any(char::is_whitespace);
|
||||||
|
@ -218,30 +263,30 @@ impl Block {
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
_ => {
|
_ => None,
|
||||||
let thematic_break = || {
|
|
||||||
let mut without_whitespace = line.chars().filter(|c| !c.is_whitespace());
|
|
||||||
let length = without_whitespace.clone().count();
|
|
||||||
(length >= 3
|
|
||||||
&& (without_whitespace.clone().all(|c| c == '-')
|
|
||||||
|| without_whitespace.all(|c| c == '*')))
|
|
||||||
.then(|| (Self::Leaf(ThematicBreak), Span::by_len(start, line.len())))
|
|
||||||
};
|
|
||||||
|
|
||||||
thematic_break()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.unwrap_or((Self::Leaf(Paragraph), Span::new(0, 0)))
|
.unwrap_or((Self::Leaf(Paragraph), Span::new(0, 0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n >= 3
|
||||||
|
}
|
||||||
|
|
||||||
/// Determine if this line continues a block of a certain type.
|
/// Determine if this line continues a block of a certain type.
|
||||||
fn continues(self, line: &str) -> bool {
|
fn continues(self, line: &str) -> bool {
|
||||||
//let start = Self::start(line); // TODO allow starting new block without blank line
|
//let start = Self::start(line); // TODO allow starting new block without blank line
|
||||||
match self {
|
match self {
|
||||||
Self::Leaf(Paragraph | Heading { .. } | Table | LinkDefinition) => {
|
Self::Leaf(Paragraph | Heading { .. } | Table) => !line.trim().is_empty(),
|
||||||
!line.trim().is_empty()
|
Self::Leaf(LinkDefinition) => line.starts_with(' ') && !line.trim().is_empty(),
|
||||||
}
|
Self::Leaf(ThematicBreak) => false,
|
||||||
Self::Leaf(Attributes | ThematicBreak) => false,
|
|
||||||
Self::Container(Blockquote) => line.trim().starts_with('>'),
|
Self::Container(Blockquote) => line.trim().starts_with('>'),
|
||||||
Self::Container(Footnote { indent } | ListItem { indent }) => {
|
Self::Container(Footnote { indent } | ListItem { indent }) => {
|
||||||
let spaces = line.chars().take_while(|c| c.is_whitespace()).count();
|
let spaces = line.chars().take_while(|c| c.is_whitespace()).count();
|
||||||
|
@ -550,4 +595,27 @@ mod test {
|
||||||
3,
|
3,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_link_definition() {
|
||||||
|
test_block!("[tag]: url\n", Block::Leaf(LinkDefinition), "", 1);
|
||||||
|
test_block!(
|
||||||
|
concat!(
|
||||||
|
"[tag]: uuu\n",
|
||||||
|
" rl\n", //
|
||||||
|
),
|
||||||
|
Block::Leaf(LinkDefinition),
|
||||||
|
"",
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
test_block!(
|
||||||
|
concat!(
|
||||||
|
"[tag]: url\n",
|
||||||
|
"para\n", //
|
||||||
|
),
|
||||||
|
Block::Leaf(LinkDefinition),
|
||||||
|
"",
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,7 @@ pub enum List {
|
||||||
Unordered,
|
Unordered,
|
||||||
Ordered { kind: OrderedListKind, start: u32 },
|
Ordered { kind: OrderedListKind, start: u32 },
|
||||||
Description,
|
Description,
|
||||||
Task(bool),
|
Task,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -286,9 +286,9 @@ impl<'s> Container<'s> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes are rare, better to pay 8 bytes always and sometimes an extra allocation instead of
|
// Attributes are rare, better to pay 8 bytes always and sometimes an extra indirection instead of
|
||||||
// always 24 bytes.
|
// always 24 bytes.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Attributes<'s>(Option<Box<Vec<(&'s str, &'s str)>>>);
|
pub struct Attributes<'s>(Option<Box<Vec<(&'s str, &'s str)>>>);
|
||||||
|
|
||||||
impl<'s> Attributes<'s> {
|
impl<'s> Attributes<'s> {
|
||||||
|
|
Loading…
Reference in a new issue