mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-29 17:34:43 +00:00
This is more Markdown-ish way to fix issue #21055, then PR #21625. It does not enable RST definition lists, instead it makes adding additional indentation (less than 4) right after a paragraph be ignored, as it's done for all block elements in Markdown. (In this case this tenet is applied to option lists that are not part of CommonMark spec by themselves).
1858 lines
46 KiB
Nim
1858 lines
46 KiB
Nim
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
|
|
'''
|
|
"""
|
|
|
|
# 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}
|
|
const preferRst = {roSupportMarkdown, 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 "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 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(error=error) == "")
|
|
check(error[] == "input(2, 2) Error: Illformed table: " &
|
|
"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(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(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(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(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 "<strong>test1</strong>" == 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 "<em>Visible</em>" == 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 "<em>Visible</em>" == 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 "<em>Visible</em>" == 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 "<em>Visible</em>" == 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'"])
|