aboutsummaryrefslogtreecommitdiffstats
path: root/src/construct/list.rs
diff options
context:
space:
mode:
authorLibravatar Titus Wormer <tituswormer@gmail.com>2022-07-12 13:00:53 +0200
committerLibravatar Titus Wormer <tituswormer@gmail.com>2022-07-12 13:00:53 +0200
commit75522b867b15b9a400275cfec9a2ead4ff535473 (patch)
tree4a9511f6b8899e1ead2ca02686ffd571b1bd4e1f /src/construct/list.rs
parent2ce19d9fd8f75ee1e3d62762e91f5d18303d4d6b (diff)
downloadmarkdown-rs-75522b867b15b9a400275cfec9a2ead4ff535473.tar.gz
markdown-rs-75522b867b15b9a400275cfec9a2ead4ff535473.tar.bz2
markdown-rs-75522b867b15b9a400275cfec9a2ead4ff535473.zip
Add initial support for lists
Diffstat (limited to 'src/construct/list.rs')
-rw-r--r--src/construct/list.rs195
1 files changed, 195 insertions, 0 deletions
diff --git a/src/construct/list.rs b/src/construct/list.rs
new file mode 100644
index 0000000..96b2496
--- /dev/null
+++ b/src/construct/list.rs
@@ -0,0 +1,195 @@
+//! To do.
+
+use crate::constant::{LIST_ITEM_VALUE_SIZE_MAX, TAB_SIZE};
+use crate::construct::partial_space_or_tab::space_or_tab_min_max;
+use crate::token::Token;
+use crate::tokenizer::{Code, State, StateFnResult, Tokenizer};
+
+/// Type of title.
+#[derive(Debug, PartialEq)]
+enum Kind {
+ /// In a dot (`.`) list.
+ ///
+ /// ## Example
+ ///
+ /// ```markdown
+ /// 1. a
+ /// ```
+ Dot,
+ /// In a paren (`)`) list.
+ ///
+ /// ## Example
+ ///
+ /// ```markdown
+ /// 1) a
+ /// ```
+ Paren,
+ /// In an asterisk (`*`) list.
+ ///
+ /// ## Example
+ ///
+ /// ```markdown
+ /// * a
+ /// ```
+ Asterisk,
+ /// In a plus (`+`) list.
+ ///
+ /// ## Example
+ ///
+ /// ```markdown
+ /// + a
+ /// ```
+ Plus,
+ /// In a dash (`-`) list.
+ ///
+ /// ## Example
+ ///
+ /// ```markdown
+ /// - a
+ /// ```
+ Dash,
+}
+
+impl Kind {
+ /// Turn the kind into a [char].
+ fn as_char(&self) -> char {
+ match self {
+ Kind::Dot => '.',
+ Kind::Paren => ')',
+ Kind::Asterisk => '*',
+ Kind::Plus => '+',
+ Kind::Dash => '-',
+ }
+ }
+ /// Turn a [char] into a kind.
+ ///
+ /// ## Panics
+ ///
+ /// Panics if `char` is not `.`, `)`, `*`, `+`, or `-`.
+ fn from_char(char: char) -> Kind {
+ match char {
+ '.' => Kind::Dot,
+ ')' => Kind::Paren,
+ '*' => Kind::Asterisk,
+ '+' => Kind::Plus,
+ '-' => Kind::Dash,
+ _ => unreachable!("invalid char"),
+ }
+ }
+ /// Turn [Code] into a kind.
+ ///
+ /// ## Panics
+ ///
+ /// Panics if `code` is not `Code::Char('.' | ')' | '*' | '+' | '-')`.
+ fn from_code(code: Code) -> Kind {
+ match code {
+ Code::Char(char) => Kind::from_char(char),
+ _ => unreachable!("invalid code"),
+ }
+ }
+}
+
+/// To do.
+pub fn start(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult {
+ // To do: allow arbitrary when code (indented) is turned off.
+ tokenizer.go(space_or_tab_min_max(0, TAB_SIZE - 1), before)(tokenizer, code)
+}
+
+/// To do.
+fn before(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult {
+ match code {
+ // Unordered.
+ Code::Char('*' | '+' | '-') => {
+ // To do: check if this is a thematic break?
+ tokenizer.enter(Token::List);
+ tokenizer.enter(Token::ListItemPrefix);
+ marker(tokenizer, code)
+ }
+ // Ordered.
+ Code::Char(char) if char.is_ascii_digit() => {
+ tokenizer.enter(Token::List);
+ tokenizer.enter(Token::ListItemPrefix);
+ tokenizer.enter(Token::ListItemValue);
+ // To do: `interrupt || !1`?
+ inside(tokenizer, code, 0)
+ }
+ _ => (State::Nok, None),
+ }
+}
+
+/// To do.
+fn inside(tokenizer: &mut Tokenizer, code: Code, mut size: usize) -> StateFnResult {
+ match code {
+ Code::Char(char) if char.is_ascii_digit() && size < LIST_ITEM_VALUE_SIZE_MAX => {
+ tokenizer.consume(code);
+ size += 1;
+ (State::Fn(Box::new(move |t, c| inside(t, c, size))), None)
+ }
+ // To do: `(!self.interrupt || size < 2)`
+ Code::Char('.' | ')') => {
+ tokenizer.exit(Token::ListItemValue);
+ marker(tokenizer, code)
+ }
+ _ => (State::Nok, None),
+ }
+}
+
+/// To do.
+fn marker(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult {
+ let kind = Kind::from_code(code);
+ println!("list item kind: {:?}", kind);
+ tokenizer.enter(Token::ListItemMarker);
+ tokenizer.consume(code);
+ tokenizer.exit(Token::ListItemMarker);
+ // To do: check blank line, if true `State::Nok` else `on_blank`.
+ (State::Fn(Box::new(marker_after)), None)
+}
+
+/// To do.
+fn marker_after(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult {
+ tokenizer.attempt(list_item_prefix_whitespace, |ok| {
+ let func = if ok { prefix_end } else { prefix_other };
+ Box::new(func)
+ })(tokenizer, code)
+}
+
+// To do: `on_blank`.
+
+/// To do.
+fn prefix_other(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult {
+ match code {
+ Code::VirtualSpace | Code::Char('\t' | ' ') => {
+ tokenizer.enter(Token::SpaceOrTab);
+ tokenizer.consume(code);
+ tokenizer.exit(Token::SpaceOrTab);
+ (State::Fn(Box::new(prefix_end)), None)
+ }
+ _ => (State::Nok, None),
+ }
+}
+
+/// To do.
+fn prefix_end(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult {
+ // To do: calculate size.
+ tokenizer.exit(Token::ListItemPrefix);
+ (State::Ok, Some(vec![code]))
+}
+
+/// To do.
+fn list_item_prefix_whitespace(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult {
+ // To do: check how big this should be?
+ tokenizer.go(
+ space_or_tab_min_max(1, TAB_SIZE - 1),
+ list_item_prefix_whitespace_after,
+ )(tokenizer, code)
+}
+
+fn list_item_prefix_whitespace_after(_tokenizer: &mut Tokenizer, code: Code) -> StateFnResult {
+ // To do: check some stuff?
+ (State::Ok, Some(vec![code]))
+}
+
+/// End of a block quote.
+pub fn end() -> Vec<Token> {
+ vec![Token::List]
+}