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)?,
"",
"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)?,
"",
"should support a table w/ a delimiter row ending in an eof (1)"
);
assert_eq!(
to_html_with_options("| a\n| -", &gfm)?,
"",
"should support a table w/ a delimiter row ending in an eof (2)"
);
assert_eq!(
to_html_with_options("| a |\n| - |\n| b |", &gfm)?,
"",
"should support a table w/ a body row ending in an eof (1)"
);
assert_eq!(
to_html_with_options("| a\n| -\n| b", &gfm)?,
"",
"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\na | \nb | \n
\n\n\n\nc | \nd | \n
\n\n
",
"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)?,
"",
"should support rows w/ trailing whitespace (1)"
);
assert_eq!(
to_html_with_options("| a | \n| - |", &gfm)?,
"",
"should support rows w/ trailing whitespace (2)"
);
assert_eq!(
to_html_with_options("| a |\n| - | ", &gfm)?,
"",
"should support rows w/ trailing whitespace (3)"
);
assert_eq!(
to_html_with_options("| a |\n| - |\n| b | ", &gfm)?,
"",
"should support rows w/ trailing whitespace (4)"
);
assert_eq!(
to_html_with_options("||a|\n|-|-|", &gfm)?,
"",
"should support empty first header cells"
);
assert_eq!(
to_html_with_options("|a||\n|-|-|", &gfm)?,
"",
"should support empty last header cells"
);
assert_eq!(
to_html_with_options("a||b\n-|-|-", &gfm)?,
"",
"should support empty header cells"
);
assert_eq!(
to_html_with_options("|a|b|\n|-|-|\n||c|", &gfm)?,
"\n\n\na | \nb | \n
\n\n\n\n | \nc | \n
\n\n
",
"should support empty first body cells"
);
assert_eq!(
to_html_with_options("|a|b|\n|-|-|\n|c||", &gfm)?,
"\n\n\na | \nb | \n
\n\n\n\nc | \n | \n
\n\n
",
"should support empty last body cells"
);
assert_eq!(
to_html_with_options("a|b|c\n-|-|-\nd||e", &gfm)?,
"\n\n\na | \nb | \nc | \n
\n\n\n\nd | \n | \ne | \n
\n\n
",
"should support empty body cells"
);
assert_eq!(
to_html_with_options("| a |\n| - |\n- b", &gfm)?,
"\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)?,
"\na\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)?,
"\na\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| b |
",
"should not support a lazy body row (1)"
);
assert_eq!(
to_html_with_options("> a\n> | b |\n> | - |\n| c |", &gfm)?,
"\na
\n\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| 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)?,
"",
"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()
})?,
"",
"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\nblock quote?
\n
",
"should be interrupted by a block quote"
);
assert_eq!(
to_html_with_options("| a |\n| - |\n>", &gfm)?,
"\n\n
",
"should be interrupted by a block quote (empty)"
);
assert_eq!(
to_html_with_options("| a |\n| - |\n- list?", &gfm)?,
"\n",
"should be interrupted by a list"
);
assert_eq!(
to_html_with_options("| a |\n| - |\n-", &gfm)?,
"\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",
"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()
}
})?,
"\ncode?\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()
}
})?,
"\ncode?\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
",
"should be interrupted by a thematic break"
);
assert_eq!(
to_html_with_options("| a |\n| - |\n# heading?", &gfm)?,
"\nheading?
",
"should be interrupted by a heading (ATX)"
);
assert_eq!(
to_html_with_options("| a |\n| - |\nheading\n=", &gfm)?,
"\n\n\na | \n
\n\n\n\nheading | \n
\n\n= | \n
\n\n
",
"should *not* be interrupted by a heading (setext)"
);
assert_eq!(
to_html_with_options("| a |\n| - |\nheading\n---", &gfm)?,
"\n\n\na | \n
\n\n\n\nheading | \n
\n\n
\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\na | \n
\n\n\n\nheading | \n
\n\n
\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",
"should support a single head row"
);
assert_eq!(
to_html_with_options("> | a |\n> | - |", &gfm)?,
"\n\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| 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\na | \n
\n\n\n\n]: b | \n
\n\n
",
"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
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|
|::|
Two at the start or end
|a|
|::-|
|a|
|-::|
In the middle
|a|
|-:-|
A space in the middle
|a|
|- -|
No pipe
A single colon
|a|
|:|
a
:
Alignment on empty cells
"###,
"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
No body
One column
"###,
"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
-
Ordered:
| 1 | 2 |
| 3 | 4 |
| 5 | 6 |
| 7 | 8 |
In block quotes
W/ space:
W/o space:
Lazy?
| 5 | 6 |
List interrupting delimiters
a |
"###,
"should match containers like GitHub"
);
assert_eq!(
to_html_with_options(
r###"| a |
| - |
| - |
| 1 |
"###,
&gfm
)?,
r###"
"###,
"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
B
C
D
bar
E
bar
F
| abc | def |
| --- |
| bar |
G
H
"###,
"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
Escaped grave accent in “inline code” in cell
“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
|-
Indented body
| 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
c ?>
## 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
c
Block quote
c
Code (fenced)
c
Code (indented)
c
Definition
Heading (atx)
c
Heading (setext) (rank 1)
Heading (setext) (rank 2)
HTML (flow, kind 1: raw)
a
HTML (flow, kind 2: comment)
HTML (flow, kind 3: instruction)
c ?>
HTML (flow, kind 4: declaration)
HTML (flow, kind 5: cdata)
HTML (flow, kind 6: basic)
HTML (flow, kind 7: complete)
List (ordered, 1)
- c
List (ordered, other)
- c
List (unordered)
List (unordered, blank)
c
List (unordered, blank start)
Thematic break
"###,
"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
"###,
"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(())
}