rst: add support for markdown tables (#15854)
* rst: add support for markdown tables * change template into proc * don't create unnecessary `seq[string]`
This commit is contained in:
parent
d8e7caf5dd
commit
ee78d76108
6 changed files with 116 additions and 16 deletions
|
|
@ -127,6 +127,7 @@ template declareClosures =
|
||||||
of meCannotOpenFile: k = errCannotOpenFile
|
of meCannotOpenFile: k = errCannotOpenFile
|
||||||
of meExpected: k = errXExpected
|
of meExpected: k = errXExpected
|
||||||
of meGridTableNotImplemented: k = errGridTableNotImplemented
|
of meGridTableNotImplemented: k = errGridTableNotImplemented
|
||||||
|
of meMarkdownIllformedTable: k = errMarkdownIllformedTable
|
||||||
of meNewSectionExpected: k = errNewSectionExpected
|
of meNewSectionExpected: k = errNewSectionExpected
|
||||||
of meGeneralParseError: k = errGeneralParseError
|
of meGeneralParseError: k = errGeneralParseError
|
||||||
of meInvalidDirective: k = errInvalidDirectiveX
|
of meInvalidDirective: k = errInvalidDirectiveX
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ type
|
||||||
errUnknown, errInternal, errIllFormedAstX, errCannotOpenFile,
|
errUnknown, errInternal, errIllFormedAstX, errCannotOpenFile,
|
||||||
errXExpected,
|
errXExpected,
|
||||||
errGridTableNotImplemented,
|
errGridTableNotImplemented,
|
||||||
|
errMarkdownIllformedTable,
|
||||||
errGeneralParseError,
|
errGeneralParseError,
|
||||||
errNewSectionExpected,
|
errNewSectionExpected,
|
||||||
errInvalidDirectiveX,
|
errInvalidDirectiveX,
|
||||||
|
|
@ -79,6 +80,7 @@ const
|
||||||
errCannotOpenFile: "cannot open '$1'",
|
errCannotOpenFile: "cannot open '$1'",
|
||||||
errXExpected: "'$1' expected",
|
errXExpected: "'$1' expected",
|
||||||
errGridTableNotImplemented: "grid table is not implemented",
|
errGridTableNotImplemented: "grid table is not implemented",
|
||||||
|
errMarkdownIllformedTable: "illformed delimiter row of a markdown table",
|
||||||
errGeneralParseError: "general parse error",
|
errGeneralParseError: "general parse error",
|
||||||
errNewSectionExpected: "new section expected",
|
errNewSectionExpected: "new section expected",
|
||||||
errInvalidDirectiveX: "invalid directive: '$1'",
|
errInvalidDirectiveX: "invalid directive: '$1'",
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ type
|
||||||
meCannotOpenFile,
|
meCannotOpenFile,
|
||||||
meExpected,
|
meExpected,
|
||||||
meGridTableNotImplemented,
|
meGridTableNotImplemented,
|
||||||
|
meMarkdownIllformedTable,
|
||||||
meNewSectionExpected,
|
meNewSectionExpected,
|
||||||
meGeneralParseError,
|
meGeneralParseError,
|
||||||
meInvalidDirective,
|
meInvalidDirective,
|
||||||
|
|
@ -53,6 +54,7 @@ const
|
||||||
meCannotOpenFile: "cannot open '$1'",
|
meCannotOpenFile: "cannot open '$1'",
|
||||||
meExpected: "'$1' expected",
|
meExpected: "'$1' expected",
|
||||||
meGridTableNotImplemented: "grid table is not implemented",
|
meGridTableNotImplemented: "grid table is not implemented",
|
||||||
|
meMarkdownIllformedTable: "illformed delimiter row of a markdown table",
|
||||||
meNewSectionExpected: "new section expected",
|
meNewSectionExpected: "new section expected",
|
||||||
meGeneralParseError: "general parse error",
|
meGeneralParseError: "general parse error",
|
||||||
meInvalidDirective: "invalid directive: '$1'",
|
meInvalidDirective: "invalid directive: '$1'",
|
||||||
|
|
@ -1091,6 +1093,13 @@ proc isMarkdownHeadline(p: RstParser): bool =
|
||||||
if p.tok[p.idx+2].kind in {tkWord, tkOther, tkPunct}:
|
if p.tok[p.idx+2].kind in {tkWord, tkOther, tkPunct}:
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
|
proc findPipe(p: RstParser, start: int): bool =
|
||||||
|
var i = start
|
||||||
|
while true:
|
||||||
|
if p.tok[i].symbol == "|": return true
|
||||||
|
if p.tok[i].kind in {tkIndent, tkEof}: return false
|
||||||
|
inc i
|
||||||
|
|
||||||
proc whichSection(p: RstParser): RstNodeKind =
|
proc whichSection(p: RstParser): RstNodeKind =
|
||||||
case p.tok[p.idx].kind
|
case p.tok[p.idx].kind
|
||||||
of tkAdornment:
|
of tkAdornment:
|
||||||
|
|
@ -1104,6 +1113,9 @@ proc whichSection(p: RstParser): RstNodeKind =
|
||||||
of tkPunct:
|
of tkPunct:
|
||||||
if isMarkdownHeadline(p):
|
if isMarkdownHeadline(p):
|
||||||
result = rnHeadline
|
result = rnHeadline
|
||||||
|
elif roSupportMarkdown in p.s.options and predNL(p) and
|
||||||
|
match(p, p.idx, "| w") and findPipe(p, p.idx+3):
|
||||||
|
result = rnMarkdownTable
|
||||||
elif p.tok[p.idx].symbol == "```":
|
elif p.tok[p.idx].symbol == "```":
|
||||||
result = rnCodeBlock
|
result = rnCodeBlock
|
||||||
elif match(p, tokenAfterNewline(p), "ai"):
|
elif match(p, tokenAfterNewline(p), "ai"):
|
||||||
|
|
@ -1206,6 +1218,9 @@ proc parseHeadline(p: var RstParser): PRstNode =
|
||||||
|
|
||||||
type
|
type
|
||||||
IntSeq = seq[int]
|
IntSeq = seq[int]
|
||||||
|
ColumnLimits = tuple
|
||||||
|
first, last: int
|
||||||
|
ColSeq = seq[ColumnLimits]
|
||||||
|
|
||||||
proc tokEnd(p: RstParser): int =
|
proc tokEnd(p: RstParser): int =
|
||||||
result = p.tok[p.idx].col + len(p.tok[p.idx].symbol) - 1
|
result = p.tok[p.idx].col + len(p.tok[p.idx].symbol) - 1
|
||||||
|
|
@ -1280,6 +1295,63 @@ proc parseSimpleTable(p: var RstParser): PRstNode =
|
||||||
add(a, b)
|
add(a, b)
|
||||||
add(result, a)
|
add(result, a)
|
||||||
|
|
||||||
|
proc readTableRow(p: var RstParser): ColSeq =
|
||||||
|
if p.tok[p.idx].symbol == "|": inc p.idx
|
||||||
|
while p.tok[p.idx].kind notin {tkIndent, tkEof}:
|
||||||
|
var limits: ColumnLimits
|
||||||
|
limits.first = p.idx
|
||||||
|
while p.tok[p.idx].kind notin {tkIndent, tkEof}:
|
||||||
|
if p.tok[p.idx].symbol == "|" and p.tok[p.idx-1].symbol != "\\": break
|
||||||
|
inc p.idx
|
||||||
|
limits.last = p.idx
|
||||||
|
result.add(limits)
|
||||||
|
if p.tok[p.idx].kind in {tkIndent, tkEof}: break
|
||||||
|
inc p.idx
|
||||||
|
p.idx = tokenAfterNewline(p)
|
||||||
|
|
||||||
|
proc getColContents(p: var RstParser, colLim: ColumnLimits): string =
|
||||||
|
for i in colLim.first ..< colLim.last:
|
||||||
|
result.add(p.tok[i].symbol)
|
||||||
|
result.strip
|
||||||
|
|
||||||
|
proc isValidDelimiterRow(p: var RstParser, colNum: int): bool =
|
||||||
|
let row = readTableRow(p)
|
||||||
|
if row.len != colNum: return false
|
||||||
|
for limits in row:
|
||||||
|
let content = getColContents(p, limits)
|
||||||
|
if content.len < 3 or not (content.startsWith("--") or content.startsWith(":-")):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc parseMarkdownTable(p: var RstParser): PRstNode =
|
||||||
|
var
|
||||||
|
row: ColSeq
|
||||||
|
colNum: int
|
||||||
|
a, b: PRstNode
|
||||||
|
q: RstParser
|
||||||
|
result = newRstNode(rnMarkdownTable)
|
||||||
|
|
||||||
|
proc parseRow(p: var RstParser, cellKind: RstNodeKind, result: PRstNode) =
|
||||||
|
row = readTableRow(p)
|
||||||
|
if colNum == 0: colNum = row.len # table header
|
||||||
|
elif row.len < colNum: row.setLen(colNum)
|
||||||
|
a = newRstNode(rnTableRow)
|
||||||
|
for j in 0 ..< colNum:
|
||||||
|
b = newRstNode(cellKind)
|
||||||
|
initParser(q, p.s)
|
||||||
|
q.col = p.col
|
||||||
|
q.line = p.tok[p.idx].line - 1
|
||||||
|
q.filename = p.filename
|
||||||
|
q.col += getTokens(getColContents(p, row[j]), false, q.tok)
|
||||||
|
b.add(parseDoc(q))
|
||||||
|
a.add(b)
|
||||||
|
result.add(a)
|
||||||
|
|
||||||
|
parseRow(p, rnTableHeaderCell, result)
|
||||||
|
if not isValidDelimiterRow(p, colNum): rstMessage(p, meMarkdownIllformedTable)
|
||||||
|
while predNL(p) and p.tok[p.idx].symbol == "|":
|
||||||
|
parseRow(p, rnTableDataCell, result)
|
||||||
|
|
||||||
proc parseTransition(p: var RstParser): PRstNode =
|
proc parseTransition(p: var RstParser): PRstNode =
|
||||||
result = newRstNode(rnTransition)
|
result = newRstNode(rnTransition)
|
||||||
inc(p.idx)
|
inc(p.idx)
|
||||||
|
|
@ -1461,6 +1533,7 @@ proc parseSection(p: var RstParser, result: PRstNode) =
|
||||||
of rnHeadline: a = parseHeadline(p)
|
of rnHeadline: a = parseHeadline(p)
|
||||||
of rnOverline: a = parseOverline(p)
|
of rnOverline: a = parseOverline(p)
|
||||||
of rnTable: a = parseSimpleTable(p)
|
of rnTable: a = parseSimpleTable(p)
|
||||||
|
of rnMarkdownTable: a = parseMarkdownTable(p)
|
||||||
of rnOptionList: a = parseOptionList(p)
|
of rnOptionList: a = parseOptionList(p)
|
||||||
else:
|
else:
|
||||||
#InternalError("rst.parseSection()")
|
#InternalError("rst.parseSection()")
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ type
|
||||||
rnLineBlock, # the | thingie
|
rnLineBlock, # the | thingie
|
||||||
rnLineBlockItem, # sons of the | thing
|
rnLineBlockItem, # sons of the | thing
|
||||||
rnBlockQuote, # text just indented
|
rnBlockQuote, # text just indented
|
||||||
rnTable, rnGridTable, rnTableRow, rnTableHeaderCell, rnTableDataCell,
|
rnTable, rnGridTable, rnMarkdownTable, rnTableRow, rnTableHeaderCell, rnTableDataCell,
|
||||||
rnLabel, # used for footnotes and other things
|
rnLabel, # used for footnotes and other things
|
||||||
rnFootnote, # a footnote
|
rnFootnote, # a footnote
|
||||||
rnCitation, # similar to footnote
|
rnCitation, # similar to footnote
|
||||||
|
|
|
||||||
|
|
@ -1094,7 +1094,7 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
|
||||||
of rnBlockQuote:
|
of rnBlockQuote:
|
||||||
renderAux(d, n, "<blockquote><p>$1</p></blockquote>\n",
|
renderAux(d, n, "<blockquote><p>$1</p></blockquote>\n",
|
||||||
"\\begin{quote}$1\\end{quote}\n", result)
|
"\\begin{quote}$1\\end{quote}\n", result)
|
||||||
of rnTable, rnGridTable:
|
of rnTable, rnGridTable, rnMarkdownTable:
|
||||||
renderAux(d, n,
|
renderAux(d, n,
|
||||||
"<table border=\"1\" class=\"docutils\">$1</table>",
|
"<table border=\"1\" class=\"docutils\">$1</table>",
|
||||||
"\\begin{table}\\begin{rsttab}{" &
|
"\\begin{table}\\begin{rsttab}{" &
|
||||||
|
|
|
||||||
|
|
@ -153,3 +153,27 @@ suite "YAML syntax highlighting":
|
||||||
assert a == """(( <a class="reference external" href="https://nim-lang.org/">Nim</a> ))"""
|
assert a == """(( <a class="reference external" href="https://nim-lang.org/">Nim</a> ))"""
|
||||||
assert b == """((<a class="reference external" href="https://nim-lang.org/">Nim</a>))"""
|
assert b == """((<a class="reference external" href="https://nim-lang.org/">Nim</a>))"""
|
||||||
assert c == """[<a class="reference external" href="https://nim-lang.org/">Nim</a>]"""
|
assert c == """[<a class="reference external" href="https://nim-lang.org/">Nim</a>]"""
|
||||||
|
|
||||||
|
test "Markdown tables":
|
||||||
|
let input1 = """
|
||||||
|
| A1 header | A2 \| not fooled
|
||||||
|
| :--- | ----: |
|
||||||
|
| C1 | C2 **bold** | ignored |
|
||||||
|
| D1 `code \|` | D2 | also ignored
|
||||||
|
| E1 \| text |
|
||||||
|
| | F2 without pipe
|
||||||
|
not in table"""
|
||||||
|
let output1 = rstToHtml(input1, {roSupportMarkdown}, defaultConfig())
|
||||||
|
assert output1 == """<table border="1" class="docutils"><tr><th>A1 header</th><th>A2 | not fooled</th></tr>
|
||||||
|
<tr><td>C1</td><td>C2 <strong>bold</strong></td></tr>
|
||||||
|
<tr><td>D1 <tt class="docutils literal"><span class="pre">code |</span></tt></td><td>D2</td></tr>
|
||||||
|
<tr><td>E1 | text</td><td></td></tr>
|
||||||
|
<tr><td></td><td>F2 without pipe</td></tr>
|
||||||
|
</table><p>not in table</p>
|
||||||
|
"""
|
||||||
|
let input2 = """
|
||||||
|
| A1 header | A2 |
|
||||||
|
| --- | --- |"""
|
||||||
|
let output2 = rstToHtml(input2, {roSupportMarkdown}, defaultConfig())
|
||||||
|
assert output2 == """<table border="1" class="docutils"><tr><th>A1 header</th><th>A2</th></tr>
|
||||||
|
</table>"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue