Implement Pandoc Markdown concise link extension (#20304)

* Implement Pandoc Markdown concise link extension

This implements https://github.com/nim-lang/Nim/issues/20127.
Besides reference to headings we also support doing references
to Nim symbols inside Nim modules.

Markdown:
```
Some heading
------------

Ref. [Some heading].
```

Nim:
```
proc someFunction*() ...

... ## Ref. [someFunction]
```

This is substitution for RST syntax like `` `target`_ ``.
All 3 syntax variants of extension from Pandoc Markdown are supported:
`[target]`, `[target][]`, `[description][target]`.

This PR also fixes clashes in existing files, particularly
conflicts with RST footnote feature, which does not work with
this PR (but there is a plan to adopt a popular [Markdown footnote
extension](https://pandoc.org/MANUAL.html#footnotes) to make footnotes work).

Also the PR fixes a bug that Markdown links did not work when `[...]`
section had a line break.

The implementation is straightforward since link resolution did not
change w.r.t. RST implementation, it's almost only about new syntax
addition. The only essential difference is a possibility to add a custom
link description: form `[description][target]` which does not have an
RST equivalent.

* fix nim 1.0 gotcha
This commit is contained in:
Andrey Makarov 2022-09-04 21:52:21 +03:00 • committed by GitHub
commit cde6b2aab8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 325 additions and 152 deletions

View file

@ -10,74 +10,101 @@ discard """
import ../../lib/packages/docutils/[rstast, rst, dochelpers]
import unittest
proc rstParseTest(text: string): PRstNode =
proc testMsgHandler(filename: string, line, col: int, msgkind: MsgKind,
arg: string) =
doAssert msgkind == mwBrokenLink
proc testMsgHandler(filename: string, line, col: int, msgkind: MsgKind,
arg: string) =
doAssert msgkind == mwBrokenLink
proc fromRst(text: string): LangSymbol =
let r = rstParse(text, "-input-", LineRstInit, ColRstInit,
{roNimFile},
msgHandler=testMsgHandler)
assert r.node.kind == rnRstRef
result = toLangSymbol(r.node)
proc fromMd(text: string): LangSymbol =
let r = rstParse(text, "-input-", LineRstInit, ColRstInit,
{roPreferMarkdown, roSupportMarkdown, roNimFile},
msgHandler=testMsgHandler)
result = r.node
assert r.node.kind == rnPandocRef
assert r.node.len == 2
# this son is the target:
assert r.node.sons[1].kind == rnInner
result = toLangSymbol(r.node.sons[1])
suite "Integration with Nim":
test "simple symbol parsing (shortest form)":
let input1 = "g_".rstParseTest
check input1.toLangSymbol == LangSymbol(symKind: "", name: "g")
let expected = LangSymbol(symKind: "", name: "g")
check "g_".fromRst == expected
check "[g]".fromMd == expected
# test also alternative syntax variants of Pandoc Markdown:
check "[g][]".fromMd == expected
check "[this symbol][g]".fromMd == expected
test "simple symbol parsing (group of words)":
let input1 = "`Y`_".rstParseTest
check input1.toLangSymbol == LangSymbol(symKind: "", name: "Y")
#let input1 = "`Y`_".rstParseTest
let expected1 = LangSymbol(symKind: "", name: "Y")
check "`Y`_".fromRst == expected1
check "[Y]".fromMd == expected1
# this means not a statement 'type', it's a backticked identifier `type`:
let input2 = "`type`_".rstParseTest
check input2.toLangSymbol == LangSymbol(symKind: "", name: "type")
let expected2 = LangSymbol(symKind: "", name: "type")
check "`type`_".fromRst == expected2
check "[type]".fromMd == expected2
let input3 = "`[]`_".rstParseTest
check input3.toLangSymbol == LangSymbol(symKind: "", name: "[]")
let expected3 = LangSymbol(symKind: "", name: "[]")
check "`[]`_".fromRst == expected3
# Markdown syntax for this case is NOT [[]]
check "[`[]`]".fromMd == expected3
let input4 = "`X Y Z`_".rstParseTest
check input4.toLangSymbol == LangSymbol(symKind: "", name: "Xyz")
let expected4 = LangSymbol(symKind: "", name: "Xyz")
check "`X Y Z`_".fromRst == expected4
check "[X Y Z]".fromMd == expected4
test "simple proc parsing":
let input1 = "proc f".rstParseTest
check input1.toLangSymbol == LangSymbol(symKind: "proc", name: "f")
let expected = LangSymbol(symKind: "proc", name: "f")
check "`proc f`_".fromRst == expected
check "[proc f]".fromMd == expected
test "another backticked name":
let input1 = """`template \`type\``_""".rstParseTest
check input1.toLangSymbol == LangSymbol(symKind: "template", name: "type")
let expected = LangSymbol(symKind: "template", name: "type")
check """`template \`type\``_""".fromRst == expected
# no backslash in Markdown:
check """[template `type`]""".fromMd == expected
test "simple proc parsing with parameters":
let input1 = "`proc f*()`_".rstParseTest
let input2 = "`proc f()`_".rstParseTest
let expected = LangSymbol(symKind: "proc", name: "f",
parametersProvided: true)
check input1.toLangSymbol == expected
check input2.toLangSymbol == expected
check "`proc f*()`_".fromRst == expected
check "`proc f()`_".fromRst == expected
check "[proc f*()]".fromMd == expected
check "[proc f()]".fromMd == expected
test "symbol parsing with 1 parameter":
let input = "`f(G[int])`_".rstParseTest
let expected = LangSymbol(symKind: "", name: "f",
parameters: @[("G[int]", "")],
parametersProvided: true)
check input.toLangSymbol == expected
check "`f(G[int])`_".fromRst == expected
check "[f(G[int])]".fromMd == expected
test "more proc parsing":
let input1 = "`proc f[T](x:G[T]):M[T]`_".rstParseTest
let input2 = "`proc f[ T ] ( x: G [T] ): M[T]`_".rstParseTest
let input3 = "`proc f*[T](x: G[T]): M[T]`_".rstParseTest
let input1 = "`proc f[T](x:G[T]):M[T]`_".fromRst
let input2 = "`proc f[ T ] ( x: G [T] ): M[T]`_".fromRst
let input3 = "`proc f*[T](x: G[T]): M[T]`_".fromRst
let expected = LangSymbol(symKind: "proc",
name: "f",
generics: "[T]",
parameters: @[("x", "G[T]")],
parametersProvided: true,
outType: "M[T]")
check(input1.toLangSymbol == expected)
check(input2.toLangSymbol == expected)
check(input3.toLangSymbol == expected)
check(input1 == expected)
check(input2 == expected)
check(input3 == expected)
test "advanced proc parsing with Nim identifier normalization":
let input = """`proc binarySearch*[T, K](a: openarray[T]; key: K;
cmp: proc (x: T; y: K): int)`_""".rstParseTest
let inputRst = """`proc binarySearch*[T, K](a: openarray[T]; key: K;
cmp: proc (x: T; y: K): int)`_"""
let inputMd = """[proc binarySearch*[T, K](a: openarray[T]; key: K;
cmp: proc (x: T; y: K): int)]"""
let expected = LangSymbol(symKind: "proc",
name: "binarysearch",
generics: "[T,K]",
@ -87,11 +114,12 @@ suite "Integration with Nim":
("cmp", "proc(x:T;y:K):int")],
parametersProvided: true,
outType: "")
check(input.toLangSymbol == expected)
check(inputRst.fromRst == expected)
check(inputMd.fromMd == expected)
test "the same without proc":
let input = """`binarySearch*[T, K](a: openarray[T]; key: K;
cmp: proc (x: T; y: K): int {.closure.})`_""".rstParseTest
cmp: proc (x: T; y: K): int {.closure.})`_"""
let expected = LangSymbol(symKind: "",
name: "binarysearch",
generics: "[T,K]",
@ -101,27 +129,32 @@ suite "Integration with Nim":
("cmp", "proc(x:T;y:K):int")],
parametersProvided: true,
outType: "")
check(input.toLangSymbol == expected)
check(input.fromRst == expected)
let inputMd = """[binarySearch*[T, K](a: openarray[T]; key: K;
cmp: proc (x: T; y: K): int {.closure.})]"""
check(inputMd.fromMd == expected)
test "operator $ with and without backticks":
let input1 = """`func \`$\`*[T](a: \`open Array\`[T]): string`_""".
rstParseTest
let input2 = """`func $*[T](a: \`open Array\`[T]): string`_""".
rstParseTest
let input1 = """`func \`$\`*[T](a: \`open Array\`[T]): string`_"""
let input1md = "[func `$`*[T](a: `open Array`[T]): string]"
let input2 = """`func $*[T](a: \`open Array\`[T]): string`_"""
let input2md = "[func $*[T](a: `open Array`[T]): string]"
let expected = LangSymbol(symKind: "func",
name: "$",
generics: "[T]",
parameters: @[("a", "openarray[T]")],
parametersProvided: true,
outType: "string")
check(input1.toLangSymbol == expected)
check(input2.toLangSymbol == expected)
check input1.fromRst == expected
check input2.fromRst == expected
check input1md.fromMd == expected
check input2md.fromMd == expected
test "operator [] with and without backticks":
let input1 = """`func \`[]\`[T](a: \`open Array\`[T], idx: int): T`_""".
rstParseTest
let input2 = """`func [][T](a: \`open Array\`[T], idx: int): T`_""".
rstParseTest
let input1 = """`func \`[]\`[T](a: \`open Array\`[T], idx: int): T`_"""
let input1md = "[func `[]`[T](a: `open Array`[T], idx: int): T]"
let input2 = """`func [][T](a: \`open Array\`[T], idx: int): T`_"""
let input2md = "[func [][T](a: `open Array`[T], idx: int): T]"
let expected = LangSymbol(symKind: "func",
name: "[]",
generics: "[T]",
@ -129,21 +162,25 @@ suite "Integration with Nim":
("idx", "int")],
parametersProvided: true,
outType: "T")
check(input1.toLangSymbol == expected)
check(input2.toLangSymbol == expected)
check input1.fromRst == expected
check input2.fromRst == expected
check input1md.fromMd == expected
check input2md.fromMd == expected
test "postfix symbol specifier #1":
let input = """`walkDir iterator`_""".
rstParseTest
let input = "`walkDir iterator`_"
let inputMd = "[walkDir iterator]"
let expected = LangSymbol(symKind: "iterator",
name: "walkdir")
check(input.toLangSymbol == expected)
check input.fromRst == expected
check inputMd.fromMd == expected
test "postfix symbol specifier #2":
let input1 = """`\`[]\`[T](a: \`open Array\`[T], idx: int): T func`_""".
rstParseTest
let input2 = """`[][T](a: \`open Array\`[T], idx: int): T func`_""".
rstParseTest
let input1 = """`\`[]\`[T](a: \`open Array\`[T], idx: int): T func`_"""
let input1md = "[`[]`[T](a: `open Array`[T], idx: int): T func]"
let input2 = """`[][T](a: \`open Array\`[T], idx: int): T func`_"""
# note again that ` is needed between 1st and second [
let input2md = "[`[]`[T](a: `open Array`[T], idx: int): T func]"
let expected = LangSymbol(symKind: "func",
name: "[]",
generics: "[T]",
@ -151,11 +188,16 @@ suite "Integration with Nim":
("idx", "int")],
parametersProvided: true,
outType: "T")
check(input1.toLangSymbol == expected)
check(input2.toLangSymbol == expected)
check input1.fromRst == expected
check input2.fromRst == expected
check input1md.fromMd == expected
check input2md.fromMd == expected
test "type of type":
check ("`CopyFlag enum`_".rstParseTest.toLangSymbol ==
LangSymbol(symKind: "type",
symTypeKind: "enum",
name: "Copyflag"))
let inputRst = "`CopyFlag enum`_"
let inputMd = "[CopyFlag enum]"
let expected = LangSymbol(symKind: "type",
symTypeKind: "enum",
name: "Copyflag")
check inputRst.fromRst == expected
check inputMd.fromMd == expected