discard """
output: '''
[Suite] RST parsing
[Suite] RST tables
[Suite] RST indentation
[Suite] Markdown indentation
[Suite] Warnings
[Suite] RST include directive
[Suite] RST escaping
[Suite] RST inline markup
'''
matrix: "--mm:refc; --mm:orc"
"""
# tests for rst module
import ../../lib/packages/docutils/[rstgen, rst, rstast]
import unittest, strutils
import std/private/miscdollars
import os
import std/[assertions, syncio]
const preferMarkdown = {roPreferMarkdown, roSupportMarkdown, roNimFile, roSandboxDisabled}
# legacy nimforum / old default mode:
const preferRst = {roSupportMarkdown, roNimFile, roSandboxDisabled}
const pureRst = {roNimFile, roSandboxDisabled}
proc toAst(input: string,
rstOptions: RstParseOptions = preferMarkdown,
error: ref string = nil,
warnings: ref seq[string] = nil): string =
## If `error` is nil then no errors should be generated.
## The same goes for `warnings`.
proc testMsgHandler(filename: string, line, col: int, msgkind: MsgKind,
arg: string) =
let mc = msgkind.whichMsgClass
let a = $msgkind % arg
var message: string
toLocation(message, filename, line, col + ColRstOffset)
message.add " $1: $2" % [$mc, a]
if mc == mcError:
if error == nil:
raise newException(EParseError, "[unexpected error] " & message)
error[] = message
# we check only first error because subsequent ones may be meaningless
raise newException(EParseError, "")
else:
doAssert warnings != nil, "unexpected RST warning '" & message & "'"
warnings[].add message
try:
const filen = "input"
proc myFindFile(filename: string): string =
# we don't find any files in online mode:
result = ""
var (rst, _, _) = rstParse(input, filen, line=LineRstInit, column=ColRstInit,
rstOptions, myFindFile, nil, testMsgHandler)
result = treeRepr(rst)
except EParseError as e:
if e.msg != "":
result = e.msg
suite "RST parsing":
test "Standalone punctuation is not parsed as heading overlines":
check(dedent"""
Paragraph
!""".toAst ==
dedent"""
rnInner
rnParagraph
rnLeaf 'Paragraph'
rnParagraph
rnLeaf '!'
""")
check(dedent"""
Paragraph1
...
Paragraph2""".toAst ==
dedent"""
rnInner
rnParagraph
rnLeaf 'Paragraph1'
rnParagraph
rnLeaf '...'
rnParagraph
rnLeaf 'Paragraph2'
""")
check(dedent"""
---
Paragraph""".toAst ==
dedent"""
rnInner
rnLeaf '---'
rnLeaf ' '
rnLeaf 'Paragraph'
""")
test "References are whitespace-neutral and case-insensitive":
# refname is 'lexical-analysis', the same for all the 3 variants:
check(dedent"""
Lexical Analysis
================
Ref. `Lexical Analysis`_ or `Lexical analysis`_ or `lexical analysis`_.
""".toAst ==
dedent"""
rnInner
rnHeadline level=1 anchor='lexical-analysis'
rnLeaf 'Lexical'
rnLeaf ' '
rnLeaf 'Analysis'
rnParagraph
rnLeaf 'Ref'
rnLeaf '.'
rnLeaf ' '
rnInternalRef
rnInner
rnLeaf 'Lexical'
rnLeaf ' '
rnLeaf 'Analysis'
rnLeaf 'lexical-analysis'
rnLeaf ' '
rnLeaf 'or'
rnLeaf ' '
rnInternalRef
rnInner
rnLeaf 'Lexical'
rnLeaf ' '
rnLeaf 'analysis'
rnLeaf 'lexical-analysis'
rnLeaf ' '
rnLeaf 'or'
rnLeaf ' '
rnInternalRef
rnInner
rnLeaf 'lexical'
rnLeaf ' '
rnLeaf 'analysis'
rnLeaf 'lexical-analysis'
rnLeaf '.'
rnLeaf ' '
""")
test "RST quoted literal blocks":
let expected =
dedent"""
rnInner
rnLeaf 'Paragraph'
rnLeaf ':'
rnLiteralBlock
rnLeaf '>x'
"""
check(dedent"""
Paragraph::
>x""".toAst(rstOptions = preferRst) == expected)
check(dedent"""
Paragraph::
>x""".toAst(rstOptions = preferRst) == expected)
test "RST quoted literal blocks, :: at a separate line":
let expected =
dedent"""
rnInner
rnInner
rnLeaf 'Paragraph'
rnLiteralBlock
rnLeaf '>x
>>y'
"""
check(dedent"""
Paragraph
::
>x
>>y""".toAst(rstOptions = preferRst) == expected)
check(dedent"""
Paragraph
::
>x
>>y""".toAst(rstOptions = preferRst) == expected)
test "Markdown quoted blocks":
check(dedent"""
Paragraph.
>x""".toAst ==
dedent"""
rnInner
rnLeaf 'Paragraph'
rnLeaf '.'
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnLeaf 'x'
""")
# bug #17987
check(dedent"""
foo https://github.com/nim-lang/Nim/issues/8258
> bar""".toAst ==
dedent"""
rnInner
rnInner
rnLeaf 'foo'
rnLeaf ' '
rnStandaloneHyperlink
rnLeaf 'https://github.com/nim-lang/Nim/issues/8258'
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnLeaf 'bar'
""")
let expected = dedent"""
rnInner
rnLeaf 'Paragraph'
rnLeaf '.'
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnInner
rnLeaf 'x1'
rnLeaf ' '
rnLeaf 'x2'
rnMarkdownBlockQuoteItem quotationDepth=2
rnInner
rnLeaf 'y1'
rnLeaf ' '
rnLeaf 'y2'
rnMarkdownBlockQuoteItem quotationDepth=1
rnLeaf 'z'
"""
check(dedent"""
Paragraph.
>x1 x2
>>y1 y2
>z""".toAst == expected)
check(dedent"""
Paragraph.
> x1 x2
>> y1 y2
> z""".toAst == expected)
check(dedent"""
>x
>y
>z""".toAst ==
dedent"""
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnInner
rnLeaf 'x'
rnLeaf ' '
rnLeaf 'y'
rnLeaf ' '
rnLeaf 'z'
""")
check(dedent"""
> z
> > >y
""".toAst ==
dedent"""
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnLeaf 'z'
rnMarkdownBlockQuoteItem quotationDepth=3
rnLeaf 'y'
""")
test "Markdown quoted blocks: lazy":
let expected = dedent"""
rnInner
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=2
rnInner
rnLeaf 'x'
rnLeaf ' '
rnLeaf 'continuation1'
rnLeaf ' '
rnLeaf 'continuation2'
rnParagraph
rnLeaf 'newParagraph'
"""
check(dedent"""
>>x
continuation1
continuation2
newParagraph""".toAst == expected)
check(dedent"""
>> x
continuation1
continuation2
newParagraph""".toAst == expected)
# however mixing more than 1 non-lazy line and lazy one(s) splits quote
# in our parser, which appeared the easiest way to handle such cases:
var warnings = new seq[string]
check(dedent"""
>> x
>> continuation1
continuation2
newParagraph""".toAst(warnings=warnings) ==
dedent"""
rnInner
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=2
rnLeaf 'x'
rnMarkdownBlockQuoteItem quotationDepth=2
rnInner
rnLeaf 'continuation1'
rnLeaf ' '
rnLeaf 'continuation2'
rnParagraph
rnLeaf 'newParagraph'
""")
check(warnings[] == @[
"input(2, 1) Warning: RST style: two or more quoted lines " &
"are followed by unquoted line 3"])
test "Markdown quoted blocks: not lazy":
# here is where we deviate from CommonMark specification: 'bar' below is
# not considered as continuation of 2-level '>> foo' quote.
check(dedent"""
>>> foo
> bar
>> baz
""".toAst() ==
dedent"""
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=3
rnLeaf 'foo'
rnMarkdownBlockQuoteItem quotationDepth=1
rnLeaf 'bar'
rnMarkdownBlockQuoteItem quotationDepth=2
rnLeaf 'baz'
""")
test "Markdown quoted blocks: inline markup works":
check(dedent"""
> hi **bold** text
""".toAst == dedent"""
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnInner
rnLeaf 'hi'
rnLeaf ' '
rnStrongEmphasis
rnLeaf 'bold'
rnLeaf ' '
rnLeaf 'text'
""")
test "Markdown quoted blocks: blank line separator":
let expected = dedent"""
rnInner
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnInner
rnLeaf 'x'
rnLeaf ' '
rnLeaf 'y'
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnInner
rnLeaf 'z'
rnLeaf ' '
rnLeaf 't'
"""
check(dedent"""
>x
>y
> z
> t""".toAst == expected)
check(dedent"""
>x
y
> z
t""".toAst == expected)
test "Markdown quoted blocks: nested body blocks/elements work #1":
let expected = dedent"""
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnBulletList
rnBulletItem
rnInner
rnLeaf 'x'
rnBulletItem
rnInner
rnLeaf 'y'
"""
check(dedent"""
> - x
- y
""".toAst == expected)
# TODO: if bug #17340 point 28 is resolved then this may work:
# check(dedent"""
# > - x
# - y
# """.toAst == expected)
check(dedent"""
> - x
> - y
""".toAst == expected)
check(dedent"""
>
> - x
>
> - y
>
""".toAst == expected)
test "Markdown quoted blocks: nested body blocks/elements work #2":
let expected = dedent"""
rnAdmonition adType=note
[nil]
[nil]
rnDefList
rnDefItem
rnDefName
rnLeaf 'deflist'
rnLeaf ':'
rnDefBody
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=2
rnInner
rnLeaf 'quote'
rnLeaf ' '
rnLeaf 'continuation'
"""
check(dedent"""
.. Note:: deflist:
>> quote
continuation
""".toAst(rstOptions = preferRst) == expected)
check(dedent"""
.. Note::
deflist:
>> quote
continuation
""".toAst(rstOptions = preferRst) == expected)
check(dedent"""
.. Note::
deflist:
>> quote
>> continuation
""".toAst(rstOptions = preferRst) == expected)
# spaces are not significant between `>`:
check(dedent"""
.. Note::
deflist:
> > quote
> > continuation
""".toAst(rstOptions = preferRst) == expected)
test "Markdown quoted blocks: de-indent handled well":
check(dedent"""
>
> - x
> - y
>
> Paragraph.
""".toAst(rstOptions = preferRst) == dedent"""
rnMarkdownBlockQuote
rnMarkdownBlockQuoteItem quotationDepth=1
rnInner
rnBlockQuote
rnBulletList
rnBulletItem
rnInner
rnLeaf 'x'
rnBulletItem
rnInner
rnLeaf 'y'
rnParagraph
rnLeaf 'Paragraph'
rnLeaf '.'
""")
let expectCodeBlock = dedent"""
rnCodeBlock
[nil]
rnFieldList
rnField
rnFieldName
rnLeaf 'default-language'
rnFieldBody
rnLeaf 'Nim'
rnLiteralBlock
rnLeaf '
let a = 1
```'
"""
test "Markdown footnotes":
# Testing also 1) correct order of manually-numbered and automatically-
# numbered footnotes; 2) no spaces between references (html & 3 below):
check(dedent"""
Paragraph [^1] [^html-hyphen][^3] and [^latex]
[^1]: footnote1
[^html-hyphen]: footnote2
continuation2
[^latex]: footnote4
[^3]: footnote3
continuation3
""".toAst ==
dedent"""
rnInner
rnInner
rnLeaf 'Paragraph'
rnLeaf ' '
rnFootnoteRef
rnInner
rnLeaf '1'
rnLeaf 'footnote-1'
rnLeaf ' '
rnFootnoteRef
rnInner
rnLeaf '2'
rnLeaf 'footnote-htmlminushyphen'
rnFootnoteRef
rnInner
rnLeaf '3'
rnLeaf 'footnote-3'
rnLeaf ' '
rnLeaf 'and'
rnLeaf ' '
rnFootnoteRef
rnInner
rnLeaf '4'
rnLeaf 'footnote-latex'
rnFootnoteGroup
rnFootnote anchor='footnote-1'
rnInner
rnLeaf '1'
rnLeaf 'footnote1'
rnFootnote anchor='footnote-htmlminushyphen'
rnInner
rnLeaf '2'
rnInner
rnLeaf 'footnote2'
rnLeaf ' '
rnLeaf 'continuation2'
rnFootnote anchor='footnote-latex'
rnInner
rnLeaf '4'
rnLeaf 'footnote4'
rnFootnote anchor='footnote-3'
rnInner
rnLeaf '3'
rnInner
rnLeaf 'footnote3'
rnLeaf ' '
rnLeaf 'continuation3'
""")
test "Markdown code blocks with more > 3 backticks":
check(dedent"""
````
let a = 1
```
````""".toAst == expectCodeBlock)
test "Markdown code blocks with ~~~":
check(dedent"""
~~~
let a = 1
```
~~~""".toAst == expectCodeBlock)
check(dedent"""
~~~~~
let a = 1
```
~~~~~""".toAst == expectCodeBlock)
test "Markdown code blocks with Nim-specific arguments":
check(dedent"""
```nim number-lines=1 test
let a = 1
```""".toAst ==
dedent"""
rnCodeBlock
rnDirArg
rnLeaf 'nim'
rnFieldList
rnField
rnFieldName
rnLeaf 'number-lines'
rnFieldBody
rnLeaf '1'
rnField
rnFieldName
rnLeaf 'test'
rnFieldBody
rnLiteralBlock
rnLeaf '
let a = 1'
""")
check(dedent"""
```nim test = "nim c $1" number-lines = 1
let a = 1
```""".toAst ==
dedent"""
rnCodeBlock
rnDirArg
rnLeaf 'nim'
rnFieldList
rnField
rnFieldName
rnLeaf 'test'
rnFieldBody
rnLeaf '"nim c $1"'
rnField
rnFieldName
rnLeaf 'number-lines'
rnFieldBody
rnLeaf '1'
rnLiteralBlock
rnLeaf '
let a = 1'
""")
test "additional indentation < 4 spaces is handled fine":
check(dedent"""
Indentation
```nim
let a = 1
```""".toAst ==
dedent"""
rnInner
rnParagraph
rnLeaf 'Indentation'
rnParagraph
rnCodeBlock
rnDirArg
rnLeaf 'nim'
[nil]
rnLiteralBlock
rnLeaf '
let a = 1'
""")
# | |
# | \ indentation of exactly two spaces before 'let a = 1'
test "no blank line is required before or after Markdown code block":
let inputBacktick = dedent"""
Some text
```
CodeBlock()
```
Other text"""
let inputTilde = dedent"""
Some text
~~~~~~~~~
CodeBlock()
~~~~~~~~~
Other text"""
let expected = dedent"""
rnInner
rnParagraph
rnLeaf 'Some'
rnLeaf ' '
rnLeaf 'text'
rnParagraph
rnCodeBlock
[nil]
rnFieldList
rnField
rnFieldName
rnLeaf 'default-language'
rnFieldBody
rnLeaf 'Nim'
rnLiteralBlock
rnLeaf '
CodeBlock()'
rnLeaf ' '
rnLeaf 'Other'
rnLeaf ' '
rnLeaf 'text'
"""
check inputBacktick.toAst == expected
check inputTilde.toAst == expected
test "option list has priority over definition list":
for opt in [preferMarkdown, preferRst]:
check(dedent"""
--defusages
file
-o set
""".toAst(rstOptions = opt) ==
dedent"""
rnOptionList
rnOptionListItem order=1
rnOptionGroup
rnLeaf '--'
rnLeaf 'defusages'
rnDescription
rnInner
rnLeaf 'file'
rnOptionListItem order=2
rnOptionGroup
rnLeaf '-'
rnLeaf 'o'
rnDescription
rnLeaf 'set'
""")
test "items of 1 option list can be separated by blank lines":
check(dedent"""
-a desc1
-b desc2
""".toAst ==
dedent"""
rnOptionList
rnOptionListItem order=1
rnOptionGroup
rnLeaf '-'
rnLeaf 'a'
rnDescription
rnLeaf 'desc1'
rnOptionListItem order=2
rnOptionGroup
rnLeaf '-'
rnLeaf 'b'
rnDescription
rnLeaf 'desc2'
""")
test "definition list does not gobble up the following blocks":
check(dedent"""
defName
defBody
-b desc2
""".toAst(rstOptions = preferRst) ==
dedent"""
rnInner
rnDefList
rnDefItem
rnDefName
rnLeaf 'defName'
rnDefBody
rnInner
rnLeaf 'defBody'
rnOptionList
rnOptionListItem order=1
rnOptionGroup
rnLeaf '-'
rnLeaf 'b'
rnDescription
rnLeaf 'desc2'
""")
test "definition lists work correctly with additional indentation in Markdown":
check(dedent"""
Paragraph:
-c desc1
-b desc2
""".toAst() ==
dedent"""
rnInner
rnInner
rnLeaf 'Paragraph'
rnLeaf ':'
rnOptionList
rnOptionListItem order=1
rnOptionGroup
rnLeaf '-'
rnLeaf 'c'
rnDescription
rnLeaf 'desc1'
rnOptionListItem order=2
rnOptionGroup
rnLeaf '-'
rnLeaf 'b'
rnDescription
rnLeaf 'desc2'
""")
test "RST comment":
check(dedent"""
.. comment1
comment2
someParagraph""".toAst ==
dedent"""
rnLeaf 'someParagraph'
""")
check(dedent"""
..
comment1
comment2
someParagraph""".toAst ==
dedent"""
rnLeaf 'someParagraph'
""")
test "check that additional line right after .. ends comment":
check(dedent"""
..
notAcomment1
notAcomment2
someParagraph""".toAst(rstOptions = preferRst) ==
dedent"""
rnInner
rnBlockQuote
rnInner
rnLeaf 'notAcomment1'
rnLeaf ' '
rnLeaf 'notAcomment2'
rnParagraph
rnLeaf 'someParagraph'
""")
test "check that additional line right after .. ends comment (Markdown mode)":
# in Markdown small indentation does not matter so this should
# just be split to 2 paragraphs.
check(dedent"""
..
notAcomment1
notAcomment2
someParagraph""".toAst ==
dedent"""
rnInner
rnInner
rnLeaf 'notAcomment1'
rnLeaf ' '
rnLeaf 'notAcomment2'
rnParagraph
rnLeaf 'someParagraph'
""")
test "but blank lines after 2nd non-empty line don't end the comment":
check(dedent"""
..
comment1
comment2
someParagraph""".toAst ==
dedent"""
rnLeaf 'someParagraph'
""")
test "using .. as separator b/w directives and block quotes":
check(dedent"""
.. note:: someNote
..
someBlockQuote""".toAst(rstOptions = preferRst) ==
dedent"""
rnInner
rnAdmonition adType=note
[nil]
[nil]
rnLeaf 'someNote'
rnBlockQuote
rnInner
rnLeaf 'someBlockQuote'
""")
test "no redundant blank lines in literal blocks":
check(dedent"""
Check::
code
""".toAst(rstOptions = preferRst) ==
dedent"""
rnInner
rnLeaf 'Check'
rnLeaf ':'
rnLiteralBlock
rnLeaf 'code'
""")
test "Markdown indented code blocks":
check(dedent"""
See
some code""".toAst ==
dedent"""
rnInner
rnInner
rnLeaf 'See'
rnLiteralBlock
rnLeaf 'some code'
""")
# not a code block -- no blank line before:
check(dedent"""
See
some code""".toAst ==
dedent"""
rnInner
rnLeaf 'See'
rnLeaf ' '
rnLeaf 'some'
rnLeaf ' '
rnLeaf 'code'
""")
suite "RST tables":
test "formatting in tables works":
check(
dedent"""
========= ===
`build` `a`
========= ===
""".toAst ==
dedent"""
rnTable colCount=2
rnTableRow
rnTableDataCell
rnInlineCode
rnDirArg
rnLeaf 'nim'
[nil]
rnLiteralBlock
rnLeaf 'build'
rnTableDataCell
rnInlineCode
rnDirArg
rnLeaf 'nim'
[nil]
rnLiteralBlock
rnLeaf 'a'
""")
test "tables with slightly overflowed cells cause an error (1)":
var error = new string
check(
dedent"""
====== ======
Inputs Output
====== ======
""".toAst(rstOptions = pureRst, error = error) == "")
check(error[] == "input(2, 2) Error: Illformed table: " &
"this word crosses table column from the right")
# In nimforum compatibility mode & Markdown we raise a warning instead:
let expected = dedent"""
rnTable colCount=2
rnTableRow
rnTableDataCell
rnLeaf 'Inputs'
rnTableDataCell
rnLeaf 'Output'
"""
for opt in [preferRst, preferMarkdown]:
var warnings = new seq[string]
check(
dedent"""
====== ======
Inputs Output
====== ======
""".toAst(rstOptions = opt, warnings = warnings) == expected)
check(warnings[] == @[
"input(2, 2) Warning: RST style: this word crosses table column from the right"])
test "tables with slightly overflowed cells cause an error (2)":
var error = new string
check("" == dedent"""
===== ===== ======
Input Output
===== ===== ======
False False False
===== ===== ======
""".toAst(rstOptions = pureRst, error = error))
check(error[] == "input(2, 8) Error: Illformed table: " &
"this word crosses table column from the right")
test "tables with slightly underflowed cells cause an error":
var error = new string
check("" == dedent"""
===== ===== ======
Input Output
===== ===== ======
False False False
===== ===== ======
""".toAst(rstOptions = pureRst, error = error))
check(error[] == "input(2, 7) Error: Illformed table: " &
"this word crosses table column from the left")
test "tables with unequal underlines should be reported (1)":
var error = new string
error[] = "none"
check("" == dedent"""
===== ======
Input Output
===== ======
False False
===== =======
""".toAst(rstOptions = pureRst, error = error))
check(error[] == "input(5, 14) Error: Illformed table: " &
"end of table column #2 should end at position 13")
test "tables with unequal underlines should be reported (2)":
var error = new string
check("" == dedent"""
===== ======
Input Output
===== =======
False False
===== ======
""".toAst(rstOptions = pureRst, error = error))
check(error[] == "input(3, 14) Error: Illformed table: " &
"end of table column #2 should end at position 13")
test "tables with empty first cells":
check(
dedent"""
= = =
x y z
t
= = =
""".toAst ==
dedent"""
rnTable colCount=3
rnTableRow
rnTableDataCell
rnLeaf 'x'
rnTableDataCell
rnInner
rnLeaf 'y'
rnLeaf ' '
rnTableDataCell
rnInner
rnLeaf 'z'
rnLeaf ' '
rnLeaf 't'
""")
test "tables with spanning cells & separators":
check(
dedent"""
===== ===== ======
Inputs Output
------------ ------
A B A or B
===== ===== ======
False False False
True False True
----- ----- ------
False True True
True True True
===== ===== ======
""".toAst ==
dedent"""
rnTable colCount=3
rnTableRow
rnTableHeaderCell span=2
rnLeaf 'Inputs'
rnTableHeaderCell span=1
rnLeaf 'Output'
rnTableRow endsHeader
rnTableHeaderCell
rnLeaf 'A'
rnTableHeaderCell
rnLeaf 'B'
rnTableHeaderCell
rnInner
rnLeaf 'A'
rnLeaf ' '
rnLeaf 'or'
rnLeaf ' '
rnLeaf 'B'
rnTableRow
rnTableDataCell
rnLeaf 'False'
rnTableDataCell
rnLeaf 'False'
rnTableDataCell
rnLeaf 'False'
rnTableRow
rnTableDataCell span=1
rnLeaf 'True'
rnTableDataCell span=1
rnLeaf 'False'
rnTableDataCell span=1
rnLeaf 'True'
rnTableRow
rnTableDataCell
rnLeaf 'False'
rnTableDataCell
rnLeaf 'True'
rnTableDataCell
rnLeaf 'True'
rnTableRow
rnTableDataCell
rnLeaf 'True'
rnTableDataCell
rnLeaf 'True'
rnTableDataCell
rnLeaf 'True'
""")
test "tables with spanning cells with uneqal underlines cause an error":
var error = new string
check(
dedent"""
===== ===== ======
Inputs Output
------------- ------
A B A or B
===== ===== ======
""".toAst(error=error) == "")
check(error[] == "input(3, 1) Error: Illformed table: " &
"spanning underline does not match main table columns")
let expTable = dedent"""
rnTable colCount=2
rnTableRow
rnTableDataCell
rnLeaf 'Inputs'
rnTableDataCell
rnLeaf 'Output'
"""
test "only tables with `=` columns specs are allowed (1)":
var warnings = new seq[string]
check(
dedent"""
------ ------
Inputs Output
------ ------
""".toAst(warnings=warnings) ==
expTable)
check(warnings[] ==
@["input(1, 1) Warning: RST style: " &
"only tables with `=` columns specification are allowed",
"input(3, 1) Warning: RST style: " &
"only tables with `=` columns specification are allowed"])
test "only tables with `=` columns specs are allowed (2)":
var warnings = new seq[string]
check(
dedent"""
====== ======
Inputs Output
~~~~~~ ~~~~~~
""".toAst(warnings=warnings) ==
expTable)
check(warnings[] ==
@["input(3, 1) Warning: RST style: "&
"only tables with `=` columns specification are allowed"])
suite "RST indentation":
test "nested bullet lists":
let input = dedent """
* - bullet1
- bullet2
* - bullet3
- bullet4
"""
let output = input.toAst
check(output == dedent"""
rnBulletList
rnBulletItem
rnBulletList
rnBulletItem
rnInner
rnLeaf 'bullet1'
rnBulletItem
rnInner
rnLeaf 'bullet2'
rnBulletItem
rnBulletList
rnBulletItem
rnInner
rnLeaf 'bullet3'
rnBulletItem
rnInner
rnLeaf 'bullet4'
""")
test "nested markup blocks":
let input = dedent"""
#) .. Hint:: .. Error:: none
#) .. Warning:: term0
Definition0
#) some
paragraph1
#) term1
Definition1
term2
Definition2
"""
check(input.toAst(rstOptions = preferRst) == dedent"""
rnEnumList labelFmt=1)
rnEnumItem
rnAdmonition adType=hint
[nil]
[nil]
rnAdmonition adType=error
[nil]
[nil]
rnLeaf 'none'
rnEnumItem
rnAdmonition adType=warning
[nil]
[nil]
rnDefList
rnDefItem
rnDefName
rnLeaf 'term0'
rnDefBody
rnInner
rnLeaf 'Definition0'
rnEnumItem
rnInner
rnLeaf 'some'
rnLeaf ' '
rnLeaf 'paragraph1'
rnEnumItem
rnDefList
rnDefItem
rnDefName
rnLeaf 'term1'
rnDefBody
rnInner
rnLeaf 'Definition1'
rnDefItem
rnDefName
rnLeaf 'term2'
rnDefBody
rnInner
rnLeaf 'Definition2'
""")
test "code-block parsing":
let input1 = dedent"""
.. code-block:: nim
:test: "nim c $1"
template additive(typ: typedesc) =
discard
"""
let input2 = dedent"""
.. code-block:: nim
:test: "nim c $1"
template additive(typ: typedesc) =
discard
"""
let input3 = dedent"""
.. code-block:: nim
:test: "nim c $1"
template additive(typ: typedesc) =
discard
"""
let inputWrong = dedent"""
.. code-block:: nim
:test: "nim c $1"
template additive(typ: typedesc) =
discard
"""
let ast = dedent"""
rnCodeBlock
rnDirArg
rnLeaf 'nim'
rnFieldList
rnField
rnFieldName
rnLeaf 'test'
rnFieldBody
rnInner
rnLeaf '"'
rnLeaf 'nim'
rnLeaf ' '
rnLeaf 'c'
rnLeaf ' '
rnLeaf '$'
rnLeaf '1'
rnLeaf '"'
rnField
rnFieldName
rnLeaf 'default-language'
rnFieldBody
rnLeaf 'Nim'
rnLiteralBlock
rnLeaf 'template additive(typ: typedesc) =
discard'
"""
check input1.toAst == ast
check input2.toAst == ast
check input3.toAst == ast
# "template..." should be parsed as a definition list attached to ":test:":
check inputWrong.toAst != ast
test "Markdown definition lists work in conjunction with bullet lists":
check(dedent"""
* some term
: the definition
Paragraph.""".toAst ==
dedent"""
rnInner
rnBulletList
rnBulletItem
rnMdDefList
rnDefItem
rnDefName
rnLeaf 'some'
rnLeaf ' '
rnLeaf 'term'
rnDefBody
rnInner
rnLeaf 'the'
rnLeaf ' '
rnLeaf 'definition'
rnParagraph
rnLeaf 'Paragraph'
rnLeaf '.'
""")
test "Markdown definition lists work with blank lines and extra paragraphs":
check(dedent"""
Term1
: Definition1
Term2 *inline markup*
: Definition2
Paragraph2
Term3
: * point1
* point2
: term3definition2
""".toAst == dedent"""
rnMdDefList
rnDefItem
rnDefName
rnLeaf 'Term1'
rnDefBody
rnInner
rnLeaf 'Definition1'
rnDefItem
rnDefName
rnLeaf 'Term2'
rnLeaf ' '
rnEmphasis
rnLeaf 'inline'
rnLeaf ' '
rnLeaf 'markup'
rnDefBody
rnParagraph
rnLeaf 'Definition2'
rnParagraph
rnLeaf 'Paragraph2'
rnDefItem
rnDefName
rnLeaf 'Term3'
rnDefBody
rnBulletList
rnBulletItem
rnInner
rnLeaf 'point1'
rnBulletItem
rnInner
rnLeaf 'point2'
rnDefBody
rnInner
rnLeaf 'term3definition2'
""")
suite "Markdown indentation":
test "Markdown paragraph indentation":
# Additional spaces (<=3) of indentation does not break the paragraph.
# TODO: in 2nd case de-indentation causes paragraph to break, this is
# reasonable but does not seem to conform the Markdown spec.
check(dedent"""
Start1
stop1
Start2
stop2
""".toAst ==
dedent"""
rnInner
rnParagraph
rnLeaf 'Start1'
rnLeaf ' '
rnLeaf 'stop1'
rnParagraph
rnLeaf 'Start2'
rnParagraph
rnLeaf 'stop2'
rnLeaf ' '
""")
suite "Warnings":
test "warnings for broken footnotes/links/substitutions":
let input = dedent"""
firstParagraph
footnoteRef [som]_
link `a broken Link`_
substitution |undefined subst|
link short.link_
lastParagraph
"""
var warnings = new seq[string]
let output = input.toAst(rstOptions=preferRst, warnings=warnings)
check(warnings[] == @[
"input(3, 14) Warning: broken link 'citation-som'",
"input(5, 7) Warning: broken link 'a broken Link'",
"input(7, 15) Warning: unknown substitution 'undefined subst'",
"input(9, 6) Warning: broken link 'short.link'"
])
test "Pandoc Markdown concise link warning points to target":
var warnings = new seq[string]
check(
"ref [here][target]".toAst(warnings=warnings) ==
dedent"""
rnInner
rnLeaf 'ref'
rnLeaf ' '
rnPandocRef
rnInner
rnLeaf 'here'
rnInner
rnLeaf 'target'
""")
check warnings[] == @["input(1, 12) Warning: broken link 'target'"]
test "With include directive and blank lines at the beginning":
"other.rst".writeFile(dedent"""
firstParagraph
here brokenLink_""")
let input = ".. include:: other.rst"
var warnings = new seq[string]
let output = input.toAst(warnings=warnings)
check warnings[] == @["other.rst(5, 6) Warning: broken link 'brokenLink'"]
check(output == dedent"""
rnInner
rnParagraph
rnLeaf 'firstParagraph'
rnParagraph
rnLeaf 'here'
rnLeaf ' '
rnRstRef
rnLeaf 'brokenLink'
""")
removeFile("other.rst")
test "warnings for ambiguous links (references + anchors)":
# Reference like `x`_ generates a link alias x that may clash with others
let input = dedent"""
Manual reference: `foo <#foo,string,string>`_
.. _foo:
Paragraph.
Ref foo_
"""
var warnings = new seq[string]
let output = input.toAst(warnings=warnings)
check(warnings[] == @[
dedent """
input(7, 5) Warning: ambiguous doc link `foo`
clash:
(3, 8): (manual directive anchor)
(1, 45): (implicitly-generated hyperlink alias)"""
])
# reference should be resolved to the manually set anchor:
check(output ==
dedent"""
rnInner
rnParagraph
rnLeaf 'Manual'
rnLeaf ' '
rnLeaf 'reference'
rnLeaf ':'
rnLeaf ' '
rnHyperlink
rnInner
rnLeaf 'foo'
rnInner
rnLeaf '#foo,string,string'
rnParagraph anchor='foo'
rnLeaf 'Paragraph'
rnLeaf '.'
rnParagraph
rnLeaf 'Ref'
rnLeaf ' '
rnInternalRef
rnInner
rnLeaf 'foo'
rnLeaf 'foo'
rnLeaf ' '
""")
suite "RST include directive":
test "Include whole":
"other.rst".writeFile("**test1**")
let input = ".. include:: other.rst"
doAssert "test1" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("other.rst")
test "Include starting from":
"other.rst".writeFile("""
And this should **NOT** be visible in `docs.html`
OtherStart
*Visible*
""")
let input = """
.. include:: other.rst
:start-after: OtherStart
"""
check "Visible" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("other.rst")
test "Include everything before":
"other.rst".writeFile("""
*Visible*
OtherEnd
And this should **NOT** be visible in `docs.html`
""")
let input = """
.. include:: other.rst
:end-before: OtherEnd
"""
doAssert "Visible" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("other.rst")
test "Include everything between":
"other.rst".writeFile("""
And this should **NOT** be visible in `docs.html`
OtherStart
*Visible*
OtherEnd
And this should **NOT** be visible in `docs.html`
""")
let input = """
.. include:: other.rst
:start-after: OtherStart
:end-before: OtherEnd
"""
check "Visible" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("other.rst")
test "Ignore premature ending string":
"other.rst".writeFile("""
OtherEnd
And this should **NOT** be visible in `docs.html`
OtherStart
*Visible*
OtherEnd
And this should **NOT** be visible in `docs.html`
""")
let input = """
.. include:: other.rst
:start-after: OtherStart
:end-before: OtherEnd
"""
doAssert "Visible" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("other.rst")
suite "RST escaping":
test "backspaces":
check("""\ this""".toAst == dedent"""
rnLeaf 'this'
""")
check("""\\ this""".toAst == dedent"""
rnInner
rnLeaf '\'
rnLeaf ' '
rnLeaf 'this'
""")
check("""\\\ this""".toAst == dedent"""
rnInner
rnLeaf '\'
rnLeaf 'this'
""")
check("""\\\\ this""".toAst == dedent"""
rnInner
rnLeaf '\'
rnLeaf '\'
rnLeaf ' '
rnLeaf 'this'
""")
suite "RST inline markup":
test "* and ** surrounded by spaces are not inline markup":
check("a * b * c ** d ** e".toAst == dedent"""
rnInner
rnLeaf 'a'
rnLeaf ' '
rnLeaf '*'
rnLeaf ' '
rnLeaf 'b'
rnLeaf ' '
rnLeaf '*'
rnLeaf ' '
rnLeaf 'c'
rnLeaf ' '
rnLeaf '**'
rnLeaf ' '
rnLeaf 'd'
rnLeaf ' '
rnLeaf '**'
rnLeaf ' '
rnLeaf 'e'
""")
test "end-string has repeating symbols":
check("*emphasis content****".toAst == dedent"""
rnEmphasis
rnLeaf 'emphasis'
rnLeaf ' '
rnLeaf 'content'
rnLeaf '***'
""")
check("""*emphasis content\****""".toAst == dedent"""
rnEmphasis
rnLeaf 'emphasis'
rnLeaf ' '
rnLeaf 'content'
rnLeaf '*'
rnLeaf '**'
""") # exact configuration of leafs with * is not really essential,
# only total number of * is essential
check("**strong content****".toAst == dedent"""
rnStrongEmphasis
rnLeaf 'strong'
rnLeaf ' '
rnLeaf 'content'
rnLeaf '**'
""")
check("""**strong content*\****""".toAst == dedent"""
rnStrongEmphasis
rnLeaf 'strong'
rnLeaf ' '
rnLeaf 'content'
rnLeaf '*'
rnLeaf '*'
rnLeaf '*'
""")
check("``lit content`````".toAst == dedent"""
rnInlineLiteral
rnLeaf 'lit'
rnLeaf ' '
rnLeaf 'content'
rnLeaf '```'
""")
test "interpreted text parsing: code fragments":
check(dedent"""
.. default-role:: option
`--gc:refc`""".toAst ==
dedent"""
rnInner
rnDefaultRole
rnDirArg
rnLeaf 'option'
[nil]
[nil]
rnParagraph
rnCodeFragment
rnInner
rnLeaf '--'
rnLeaf 'gc'
rnLeaf ':'
rnLeaf 'refc'
rnLeaf 'option'
""")
test """interpreted text can be ended with \` """:
let output = (".. default-role:: literal\n" & """`\``""").toAst
check(output.endsWith """
rnParagraph
rnInlineLiteral
rnLeaf '`'""" & "\n")
let output2 = """`\``""".toAst
check(output2 == dedent"""
rnInlineCode
rnDirArg
rnLeaf 'nim'
[nil]
rnLiteralBlock
rnLeaf '`'
""")
let output3 = """`proc \`+\``""".toAst
check(output3 == dedent"""
rnInlineCode
rnDirArg
rnLeaf 'nim'
[nil]
rnLiteralBlock
rnLeaf 'proc `+`'
""")
check("""`\\`""".toAst ==
dedent"""
rnInlineCode
rnDirArg
rnLeaf 'nim'
[nil]
rnLiteralBlock
rnLeaf '\\'
""")
test "Markdown-style code/backtick":
# no whitespace is required before `
check("`try`...`except`".toAst ==
dedent"""
rnInner
rnInlineCode
rnDirArg
rnLeaf 'nim'
[nil]
rnLiteralBlock
rnLeaf 'try'
rnLeaf '...'
rnInlineCode
rnDirArg
rnLeaf 'nim'
[nil]
rnLiteralBlock
rnLeaf 'except'
""")
test """inline literals can contain \ anywhere""":
check("""``\``""".toAst == dedent"""
rnInlineLiteral
rnLeaf '\'
""")
check("""``\\``""".toAst == dedent"""
rnInlineLiteral
rnLeaf '\'
rnLeaf '\'
""")
check("""``\```""".toAst == dedent"""
rnInlineLiteral
rnLeaf '\'
rnLeaf '`'
""")
check("""``\\```""".toAst == dedent"""
rnInlineLiteral
rnLeaf '\'
rnLeaf '\'
rnLeaf '`'
""")
check("""``\````""".toAst == dedent"""
rnInlineLiteral
rnLeaf '\'
rnLeaf '`'
rnLeaf '`'
""")
test "references with _ at the end":
check(dedent"""
.. _lnk: https
lnk_""".toAst ==
dedent"""
rnHyperlink
rnInner
rnLeaf 'lnk'
rnInner
rnLeaf 'https'
""")
test "not a hyper link":
check(dedent"""
.. _lnk: https
lnk___""".toAst ==
dedent"""
rnInner
rnLeaf 'lnk'
rnLeaf '___'
""")
test "no punctuation in the end of a standalone URI is allowed":
check(dedent"""
[see (http://no.org)], end""".toAst(rstOptions = preferRst) ==
dedent"""
rnInner
rnLeaf '['
rnLeaf 'see'
rnLeaf ' '
rnLeaf '('
rnStandaloneHyperlink
rnLeaf 'http://no.org'
rnLeaf ')'
rnLeaf ']'
rnLeaf ','
rnLeaf ' '
rnLeaf 'end'
""")
# but `/` at the end is OK
check(
dedent"""
See http://no.org/ end""".toAst ==
dedent"""
rnInner
rnLeaf 'See'
rnLeaf ' '
rnStandaloneHyperlink
rnLeaf 'http://no.org/'
rnLeaf ' '
rnLeaf 'end'
""")
# a more complex URL with some made-up ending '&='.
# Github Markdown would include final &= and
# so would rst2html.py in contradiction with RST spec.
check(
dedent"""
See https://www.google.com/url?sa=t&source=web&cd=&cad=rja&url=https%3A%2F%2Fnim-lang.github.io%2FNim%2Frst.html%23features&usg=AO&= end""".toAst ==
dedent"""
rnInner
rnLeaf 'See'
rnLeaf ' '
rnStandaloneHyperlink
rnLeaf 'https://www.google.com/url?sa=t&source=web&cd=&cad=rja&url=https%3A%2F%2Fnim-lang.github.io%2FNim%2Frst.html%23features&usg=AO'
rnLeaf '&'
rnLeaf '='
rnLeaf ' '
rnLeaf 'end'
""")
test "Markdown-style link can be split to a few lines":
check(dedent"""
is [term-rewriting
macros](manual.html#term-rewriting-macros)""".toAst ==
dedent"""
rnInner
rnLeaf 'is'
rnLeaf ' '
rnHyperlink
rnLeaf 'term-rewriting macros'
rnLeaf 'manual.html#term-rewriting-macros'
""")
test "URL with balanced parentheses (Markdown rule)":
# 2 balanced parens, 1 unbalanced:
check(dedent"""
https://en.wikipedia.org/wiki/APL_((programming_language)))""".toAst ==
dedent"""
rnInner
rnStandaloneHyperlink
rnLeaf 'https://en.wikipedia.org/wiki/APL_((programming_language))'
rnLeaf ')'
""")
# the same for Markdown-style link:
check(dedent"""
[foo [bar]](https://en.wikipedia.org/wiki/APL_((programming_language))))""".toAst ==
dedent"""
rnInner
rnHyperlink
rnLeaf 'foo [bar]'
rnLeaf 'https://en.wikipedia.org/wiki/APL_((programming_language))'
rnLeaf ')'
""")
# unbalanced (here behavior is more RST-like actually):
check(dedent"""
https://en.wikipedia.org/wiki/APL_(programming_language(""".toAst ==
dedent"""
rnInner
rnStandaloneHyperlink
rnLeaf 'https://en.wikipedia.org/wiki/APL_(programming_language'
rnLeaf '('
""")
# unbalanced [, but still acceptable:
check(dedent"""
[my {link example](http://example.com/bracket_(symbol_[))""".toAst ==
dedent"""
rnHyperlink
rnLeaf 'my {link example'
rnLeaf 'http://example.com/bracket_(symbol_[)'
""")
test "not a Markdown link":
# bug #17340 (27) `f` will be considered as a protocol and blocked as unsafe
var warnings = new seq[string]
check("[T](f: var Foo)".toAst(warnings = warnings) ==
dedent"""
rnInner
rnLeaf '['
rnLeaf 'T'
rnLeaf ']'
rnLeaf '('
rnLeaf 'f'
rnLeaf ':'
rnLeaf ' '
rnLeaf 'var'
rnLeaf ' '
rnLeaf 'Foo'
rnLeaf ')'
""")
check(warnings[] == @["input(1, 5) Warning: broken link 'f'"])