extern crate markdown; use markdown::{ mdast::{AlignKind, InlineCode, Node, Root, Table, TableCell, TableRow, Text}, to_html, to_html_with_options, to_mdast, unist::Position, CompileOptions, Constructs, Options, ParseOptions, }; use pretty_assertions::assert_eq; #[test] fn gfm_table() -> Result<(), String> { let gfm = Options { parse: ParseOptions { constructs: Constructs::gfm(), ..ParseOptions::default() }, ..Options::default() }; assert_eq!( to_html("| a |\n| - |\n| b |"), "

| a |\n| - |\n| b |

", "should ignore tables by default" ); assert_eq!( to_html_with_options("| a |\n| - |\n| b |", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n
a
b
", "should support tables" ); assert_eq!( to_html_with_options("| a |", &gfm)?, "

| a |

", "should not support a table w/ the head row ending in an eof (1)" ); assert_eq!( to_html_with_options("| a", &gfm)?, "

| a

", "should not support a table w/ the head row ending in an eof (2)" ); assert_eq!( to_html_with_options("a |", &gfm)?, "

a |

", "should not support a table w/ the head row ending in an eof (3)" ); assert_eq!( to_html_with_options("| a |\n| - |", &gfm)?, "\n\n\n\n\n\n
a
", "should support a table w/ a delimiter row ending in an eof (1)" ); assert_eq!( to_html_with_options("| a\n| -", &gfm)?, "\n\n\n\n\n\n
a
", "should support a table w/ a delimiter row ending in an eof (2)" ); assert_eq!( to_html_with_options("| a |\n| - |\n| b |", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n
a
b
", "should support a table w/ a body row ending in an eof (1)" ); assert_eq!( to_html_with_options("| a\n| -\n| b", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n
a
b
", "should support a table w/ a body row ending in an eof (2)" ); assert_eq!( to_html_with_options("a|b\n-|-\nc|d", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n\n\n
ab
cd
", "should support a table w/ a body row ending in an eof (3)" ); assert_eq!( to_html_with_options("| a \n| -\t\n| b | ", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n
a
b
", "should support rows w/ trailing whitespace (1)" ); assert_eq!( to_html_with_options("| a | \n| - |", &gfm)?, "\n\n\n\n\n\n
a
", "should support rows w/ trailing whitespace (2)" ); assert_eq!( to_html_with_options("| a |\n| - | ", &gfm)?, "\n\n\n\n\n\n
a
", "should support rows w/ trailing whitespace (3)" ); assert_eq!( to_html_with_options("| a |\n| - |\n| b | ", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n
a
b
", "should support rows w/ trailing whitespace (4)" ); assert_eq!( to_html_with_options("||a|\n|-|-|", &gfm)?, "\n\n\n\n\n\n\n
a
", "should support empty first header cells" ); assert_eq!( to_html_with_options("|a||\n|-|-|", &gfm)?, "\n\n\n\n\n\n\n
a
", "should support empty last header cells" ); assert_eq!( to_html_with_options("a||b\n-|-|-", &gfm)?, "\n\n\n\n\n\n\n\n
ab
", "should support empty header cells" ); assert_eq!( to_html_with_options("|a|b|\n|-|-|\n||c|", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n\n\n
ab
c
", "should support empty first body cells" ); assert_eq!( to_html_with_options("|a|b|\n|-|-|\n|c||", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n\n\n
ab
c
", "should support empty last body cells" ); assert_eq!( to_html_with_options("a|b|c\n-|-|-\nd||e", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
abc
de
", "should support empty body cells" ); assert_eq!( to_html_with_options("| a |\n| - |\n- b", &gfm)?, "\n\n\n\n\n\n
a
\n", "should support a list after a table" ); assert_eq!( to_html_with_options("> | a |\n| - |", &gfm)?, "
\n

| a |\n| - |

\n
", "should not support a lazy delimiter row (1)" ); assert_eq!( to_html_with_options("> a\n> | b |\n| - |", &gfm)?, "
\n

a\n| b |\n| - |

\n
", "should not support a lazy delimiter row (2)" ); assert_eq!( to_html_with_options("| a |\n> | - |", &gfm)?, "

| a |

\n
\n

| - |

\n
", "should not support a piercing delimiter row" ); assert_eq!( to_html_with_options("> a\n> | b |\n|-", &gfm)?, "
\n

a\n| b |\n|-

\n
", "should not support a lazy body row (2)" ); assert_eq!( to_html_with_options("> | a |\n> | - |\n| b |", &gfm)?, "
\n\n\n\n\n\n\n
a
\n
\n

| b |

", "should not support a lazy body row (1)" ); assert_eq!( to_html_with_options("> a\n> | b |\n> | - |\n| c |", &gfm)?, "
\n

a

\n\n\n\n\n\n\n
b
\n
\n

| c |

", "should not support a lazy body row (2)" ); assert_eq!( to_html_with_options("> | A |\n> | - |\n> | 1 |\n| 2 |", &gfm)?, "
\n\n\n\n\n\n\n\n\n\n\n\n
A
1
\n
\n

| 2 |

", "should not support a lazy body row (3)" ); assert_eq!( to_html_with_options(" - d\n - e", &gfm)?, to_html(" - d\n - e"), "should not change how lists and lazyness work" ); assert_eq!( to_html_with_options("| a |\n | - |", &gfm)?, "\n\n\n\n\n\n
a
", "should form a table if the delimiter row is indented w/ 3 spaces" ); assert_eq!( to_html_with_options("| a |\n | - |", &gfm)?, "

| a |\n| - |

", "should not form a table if the delimiter row is indented w/ 4 spaces" ); assert_eq!( to_html_with_options("| a |\n | - |", &Options { parse: ParseOptions { constructs: Constructs { code_indented: false, ..Constructs::gfm() }, ..ParseOptions::default() }, ..Options::default() })?, "\n\n\n\n\n\n
a
", "should form a table if the delimiter row is indented w/ 4 spaces and indented code is turned off" ); assert_eq!( to_html_with_options("| a |\n| - |\n> block quote?", &gfm)?, "\n\n\n\n\n\n
a
\n
\n

block quote?

\n
", "should be interrupted by a block quote" ); assert_eq!( to_html_with_options("| a |\n| - |\n>", &gfm)?, "\n\n\n\n\n\n
a
\n
\n
", "should be interrupted by a block quote (empty)" ); assert_eq!( to_html_with_options("| a |\n| - |\n- list?", &gfm)?, "\n\n\n\n\n\n
a
\n", "should be interrupted by a list" ); assert_eq!( to_html_with_options("| a |\n| - |\n-", &gfm)?, "\n\n\n\n\n\n
a
\n", "should be interrupted by a list (empty)" ); assert_eq!( to_html_with_options( "| a |\n| - |\n", &Options { parse: ParseOptions { constructs: Constructs::gfm(), ..ParseOptions::default() }, compile: CompileOptions { allow_dangerous_html: true, allow_dangerous_protocol: true, ..CompileOptions::default() } } )?, "\n\n\n\n\n\n
a
\n", "should be interrupted by HTML (flow)" ); assert_eq!( to_html_with_options("| a |\n| - |\n\tcode?", &Options { parse: ParseOptions { constructs: Constructs::gfm(), ..ParseOptions::default() }, compile: CompileOptions { allow_dangerous_html: true, allow_dangerous_protocol: true, ..CompileOptions::default() } })?, "\n\n\n\n\n\n
a
\n
code?\n
", "should be interrupted by code (indented)" ); assert_eq!( to_html_with_options("| a |\n| - |\n```js\ncode?", &Options { parse: ParseOptions { constructs: Constructs::gfm(), ..ParseOptions::default() }, compile: CompileOptions { allow_dangerous_html: true, allow_dangerous_protocol: true, ..CompileOptions::default() } })?, "\n\n\n\n\n\n
a
\n
code?\n
\n", "should be interrupted by code (fenced)" ); assert_eq!( to_html_with_options( "| a |\n| - |\n***", &Options { parse: ParseOptions { constructs: Constructs::gfm(), ..ParseOptions::default() }, compile: CompileOptions { allow_dangerous_html: true, allow_dangerous_protocol: true, ..CompileOptions::default() } } )?, "\n\n\n\n\n\n
a
\n
", "should be interrupted by a thematic break" ); assert_eq!( to_html_with_options("| a |\n| - |\n# heading?", &gfm)?, "\n\n\n\n\n\n
a
\n

heading?

", "should be interrupted by a heading (ATX)" ); assert_eq!( to_html_with_options("| a |\n| - |\nheading\n=", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n
a
heading
=
", "should *not* be interrupted by a heading (setext)" ); assert_eq!( to_html_with_options("| a |\n| - |\nheading\n---", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n
a
heading
\n
", "should *not* be interrupted by a heading (setext), but interrupt if the underline is also a thematic break" ); assert_eq!( to_html_with_options("| a |\n| - |\nheading\n-", &gfm)?, "\n\n\n\n\n\n\n\n\n\n\n
a
heading
\n", "should *not* be interrupted by a heading (setext), but interrupt if the underline is also an empty list item bullet" ); assert_eq!( to_html_with_options("a\nb\n-:", &gfm)?, "

a

\n\n\n\n\n\n\n
b
", "should support a single head row" ); assert_eq!( to_html_with_options("> | a |\n> | - |", &gfm)?, "
\n\n\n\n\n\n\n
a
\n
", "should support a table in a container" ); assert_eq!( to_html_with_options("> | a |\n| - |", &gfm)?, "
\n

| a |\n| - |

\n
", "should not support a lazy delimiter row if the head row is in a container" ); assert_eq!( to_html_with_options("| a |\n> | - |", &gfm)?, "

| a |

\n
\n

| - |

\n
", "should not support a “piercing” container for the delimiter row, if the head row was not in that container" ); assert_eq!( to_html_with_options("> | a |\n> | - |\n| c |", &gfm)?, "
\n\n\n\n\n\n\n
a
\n
\n

| c |

", "should not support a lazy body row if the head row and delimiter row are in a container" ); assert_eq!( to_html_with_options("> | a |\n| - |\n> | c |", &gfm)?, "
\n

| a |\n| - |\n| c |

\n
", "should not support a lazy delimiter row if the head row and a further body row are in a container" ); assert_eq!( to_html_with_options("[\na\n:-\n]: b", &gfm)?, "

[

\n\n\n\n\n\n\n\n\n\n\n\n
a
]: b
", "should prefer GFM tables over definitions" ); assert_eq!( to_html_with_options( r###"# Align ## An empty initial cell | | a|c| |--|:----:|:---| |a|b|c| |a|b|c| ## Missing alignment characters | a | b | c | | |---|---| | d | e | f | * * * | a | b | c | |---|---| | | d | e | f | ## Incorrect characters | a | b | c | |---|-*-|---| | d | e | f | ## Two alignments |a| |::| |a| |:-:| ## Two at the start or end |a| |::-| |a| |-::| ## In the middle |a| |-:-| ## A space in the middle |a| |- -| ## No pipe a :-: a :- a -: ## A single colon |a| |:| a : ## Alignment on empty cells | a | b | c | d | e | | - | - | :- | -: | :-: | | f | "###, &gfm )?, r###"

Align

An empty initial cell

a c
a b c
a b c

Missing alignment characters

| a | b | c | | |---|---| | d | e | f |


| a | b | c | |---|---| | | d | e | f |

Incorrect characters

| a | b | c | |---|-*-|---| | d | e | f |

Two alignments

|a| |::|

a

Two at the start or end

|a| |::-|

|a| |-::|

In the middle

|a| |-:-|

A space in the middle

|a| |- -|

No pipe

a
a
a

A single colon

|a| |:|

a :

Alignment on empty cells

a b c d e
f
"###, "should match alignment like GitHub" ); assert_eq!( to_html_with_options( r###"# Tables | a | b | c | | - | - | - | | d | e | f | ## No body | a | b | c | | - | - | - | ## One column | a | | - | | b | "###, &gfm )?, r###"

Tables

a b c
d e f

No body

a b c

One column

a
b
"###, "should match basic like GitHub" ); assert_eq!( to_html_with_options( r###"# Tables in things ## In lists * Unordered: | A | B | | - | - | | 1 | 2 | 1. Ordered: | A | B | | - | - | | 1 | 2 | * Lazy? | A | B | | - | - | | 1 | 2 | | 3 | 4 | | 5 | 6 | | 7 | 8 | ## In block quotes > W/ space: > | A | B | > | - | - | > | 1 | 2 | >W/o space: >| A | B | >| - | - | >| 1 | 2 | > Lazy? > | A | B | > | - | - | > | 1 | 2 | >| 3 | 4 | | 5 | 6 | ### List interrupting delimiters a | - | a -| a |- "###, &gfm )?, r###"

Tables in things

In lists

  1. Ordered:

    A B
    1 2

| 1 | 2 | | 3 | 4 | | 5 | 6 | | 7 | 8 |

In block quotes

W/ space:

A B
1 2

W/o space:

A B
1 2

Lazy?

A B
1 2
3 4

| 5 | 6 |

List interrupting delimiters

a |

a
a
"###, "should match containers like GitHub" ); assert_eq!( to_html_with_options( r###"| a | | - | | - | | 1 | "###, &gfm )?, r###"
a
-
1
"###, "should match a double delimiter row like GitHub" ); assert_eq!( to_html_with_options( r###"# Examples from GFM ## A | foo | bar | | --- | --- | | baz | bim | ## B | abc | defghi | :-: | -----------: bar | baz ## C | f\|oo | | ------ | | b `\|` az | | b **\|** im | ## D | abc | def | | --- | --- | | bar | baz | > bar ## E | abc | def | | --- | --- | | bar | baz | bar bar ## F | abc | def | | --- | | bar | ## G | abc | def | | --- | --- | | bar | | bar | baz | boo | ## H | abc | def | | --- | --- | "###, &gfm )?, r###"

Examples from GFM

A

foo bar
baz bim

B

abc defghi
bar baz

C

f|oo
b | az
b | im

D

abc def
bar baz

bar

E

abc def
bar baz
bar

bar

F

| abc | def | | --- | | bar |

G

abc def
bar
bar baz

H

abc def
"###, "should match examples from the GFM spec like GitHub" ); assert_eq!( to_html_with_options( r###"# Grave accents ## Grave accent in cell | A | B | |--------------|---| | ` | C | ## Escaped grave accent in “inline code” in cell | A | |-----| | `\` | ## “Empty” inline code | 1 | 2 | 3 | |---|------|----| | a | `` | | | b | `` | `` | | c | ` | ` | | d | `|` | | e | `\|` | | | f | \| | | ## Escaped pipes in code in cells | `\|\\` | | --- | | `\|\\` | `\|\\` "###, &Options { parse: ParseOptions { constructs: Constructs::gfm(), ..ParseOptions::default() }, compile: CompileOptions { allow_dangerous_html: true, allow_dangerous_protocol: true, ..CompileOptions::default() } } )?, r###"

Grave accents

Grave accent in cell

A B
` C

Escaped grave accent in “inline code” in cell

A
\

“Empty” inline code

1 2 3
a ``
b `` ``
c ` `
d ` `
e |
f |

Escaped pipes in code in cells

|\\
|\\

\|\\

"###, "should match grave accent like GitHub" ); assert_eq!( to_html_with_options( r###"# Code ## Indented delimiter row a |- a |- ## Indented body | a | | - | | C | | D | | E | "###, &gfm )?, r###"

Code

Indented delimiter row

a

a |-

Indented body

a
C
D
| E |
"###, "should match indent like GitHub" ); assert_eq!( to_html_with_options( r###"## Blank line a :- b c ## Block quote a :- b > c ## Code (fenced) a :- b ``` c ``` ## Code (indented) a :- b c ## Definition a :- b [c]: d ## Heading (atx) a :- b # c ## Heading (setext) (rank 1) a :- b == c ## Heading (setext) (rank 2) a :- b -- c ## HTML (flow, kind 1: raw) a :- b
  a
## HTML (flow, kind 2: comment) a :- b ## HTML (flow, kind 3: instruction) a :- b ## HTML (flow, kind 4: declaration) a :- b ## HTML (flow, kind 5: cdata) a :- b ## HTML (flow, kind 6: basic) a :- b
## HTML (flow, kind 7: complete) a :- b ## List (ordered, 1) a :- b 1. c ## List (ordered, other) a :- b 2. c ## List (unordered) a :- b * c ## List (unordered, blank) a :- b * c ## List (unordered, blank start) a :- b * c ## Thematic break a :- b *** "###, &Options { parse: ParseOptions { constructs: Constructs::gfm(), ..ParseOptions::default() }, compile: CompileOptions { allow_dangerous_html: true, allow_dangerous_protocol: true, ..CompileOptions::default() } } )?, r###"

Blank line

a
b

c

Block quote

a
b

c

Code (fenced)

a
b
c

Code (indented)

a
b
c

Definition

a
b
[c]: d

Heading (atx)

a
b

c

Heading (setext) (rank 1)

a
b
==
c

Heading (setext) (rank 2)

a
b
--
c

HTML (flow, kind 1: raw)

a
b
  a

HTML (flow, kind 2: comment)

a
b

HTML (flow, kind 3: instruction)

a
b

HTML (flow, kind 4: declaration)

a
b

HTML (flow, kind 5: cdata)

a
b

HTML (flow, kind 6: basic)

a
b

HTML (flow, kind 7: complete)

a
b

List (ordered, 1)

a
b
  1. c

List (ordered, other)

a
b
  1. c

List (unordered)

a
b
  • c

List (unordered, blank)

a
b

c

List (unordered, blank start)

a
b
  • c

Thematic break

a
b

"###, "should match interrupt like GitHub" ); assert_eq!( to_html_with_options( r###"# Loose ## Loose Header 1 | Header 2 -------- | -------- Cell 1 | Cell 2 Cell 3 | Cell 4 ## One “column”, loose a - b ## No pipe in first row a | - | "###, &gfm )?, r###"

Loose

Loose

Header 1 Header 2
Cell 1 Cell 2
Cell 3 Cell 4

One “column”, loose

a

b

No pipe in first row

a
"###, "should match loose tables like GitHub" ); assert_eq!( to_html_with_options( r###"# Some more escapes | Head | | ------------- | | A | Alpha | | B \| Bravo | | C \\| Charlie | | D \\\| Delta | | E \\\\| Echo | Note: GH has a bug where in case C and E, the escaped escape is treated as a normal escape: . "###, &gfm )?, r###"

Some more escapes

Head
A
B | Bravo
C \
D \| Delta
E \\

Note: GH has a bug where in case C and E, the escaped escape is treated as a normal escape: https://github.com/github/cmark-gfm/issues/277.

"###, "should match loose escapes like GitHub" ); assert_eq!( to_mdast( "| none | left | right | center |\n| - | :- | -: | :-: |\n| a |\n| b | c | d | e | f |", &gfm.parse )?, Node::Root(Root { children: vec![Node::Table(Table { align: vec![ AlignKind::None, AlignKind::Left, AlignKind::Right, AlignKind::Center ], children: vec![ Node::TableRow(TableRow { children: vec![ Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "none".into(), position: Some(Position::new(1, 3, 2, 1, 7, 6)) }),], position: Some(Position::new(1, 1, 0, 1, 8, 7)) }), Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "left".into(), position: Some(Position::new(1, 10, 9, 1, 14, 13)) }),], position: Some(Position::new(1, 8, 7, 1, 15, 14)) }), Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "right".into(), position: Some(Position::new(1, 17, 16, 1, 22, 21)) }),], position: Some(Position::new(1, 15, 14, 1, 23, 22)) }), Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "center".into(), position: Some(Position::new(1, 25, 24, 1, 31, 30)) }),], position: Some(Position::new(1, 23, 22, 1, 33, 32)) }), ], position: Some(Position::new(1, 1, 0, 1, 33, 32)) }), Node::TableRow(TableRow { children: vec![Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "a".into(), position: Some(Position::new(3, 3, 57, 3, 4, 58)) }),], position: Some(Position::new(3, 1, 55, 3, 6, 60)) }),], position: Some(Position::new(3, 1, 55, 3, 6, 60)) }), Node::TableRow(TableRow { children: vec![ Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "b".into(), position: Some(Position::new(4, 3, 63, 4, 4, 64)) }),], position: Some(Position::new(4, 1, 61, 4, 5, 65)) }), Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "c".into(), position: Some(Position::new(4, 7, 67, 4, 8, 68)) }),], position: Some(Position::new(4, 5, 65, 4, 9, 69)) }), Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "d".into(), position: Some(Position::new(4, 11, 71, 4, 12, 72)) }),], position: Some(Position::new(4, 9, 69, 4, 13, 73)) }), Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "e".into(), position: Some(Position::new(4, 15, 75, 4, 16, 76)) }),], position: Some(Position::new(4, 13, 73, 4, 17, 77)) }), Node::TableCell(TableCell { children: vec![Node::Text(Text { value: "f".into(), position: Some(Position::new(4, 19, 79, 4, 20, 80)) }),], position: Some(Position::new(4, 17, 77, 4, 22, 82)) }), ], position: Some(Position::new(4, 1, 61, 4, 22, 82)) }), ], position: Some(Position::new(1, 1, 0, 4, 22, 82)) })], position: Some(Position::new(1, 1, 0, 4, 22, 82)) }), "should support GFM tables as `Table`, `TableRow`, `TableCell`s in mdast" ); assert_eq!( to_mdast("| `a\\|b` |\n| - |", &gfm.parse)?, Node::Root(Root { children: vec![Node::Table(Table { align: vec![AlignKind::None,], children: vec![Node::TableRow(TableRow { children: vec![Node::TableCell(TableCell { children: vec![Node::InlineCode(InlineCode { value: "a|b".into(), position: Some(Position::new(1, 3, 2, 1, 9, 8)) }),], position: Some(Position::new(1, 1, 0, 1, 11, 10)) }),], position: Some(Position::new(1, 1, 0, 1, 11, 10)) }),], position: Some(Position::new(1, 1, 0, 2, 6, 16)) })], position: Some(Position::new(1, 1, 0, 2, 6, 16)) }), "should support weird pipe escapes in code in tables" ); Ok(()) }