RST: improve simple tables (#19859)

* RST: improve simple tables

* nim 1.0 gotchas

* Still allow legacy boundaries like `----`
This commit is contained in:
Andrey Makarov 2022-06-04 08:03:03 +03:00 • committed by GitHub
commit 4341b06f65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 487 additions and 120 deletions

View file

@ -232,6 +232,7 @@ template declareClosures =
of meExpected: k = errXExpected of meExpected: k = errXExpected
of meGridTableNotImplemented: k = errRstGridTableNotImplemented of meGridTableNotImplemented: k = errRstGridTableNotImplemented
of meMarkdownIllformedTable: k = errRstMarkdownIllformedTable of meMarkdownIllformedTable: k = errRstMarkdownIllformedTable
of meIllformedTable: k = errRstIllformedTable
of meNewSectionExpected: k = errRstNewSectionExpected of meNewSectionExpected: k = errRstNewSectionExpected
of meGeneralParseError: k = errRstGeneralParseError of meGeneralParseError: k = errRstGeneralParseError
of meInvalidDirective: k = errRstInvalidDirectiveX of meInvalidDirective: k = errRstInvalidDirectiveX

View file

@ -34,6 +34,7 @@ type
errXExpected, errXExpected,
errRstGridTableNotImplemented, errRstGridTableNotImplemented,
errRstMarkdownIllformedTable, errRstMarkdownIllformedTable,
errRstIllformedTable,
errRstNewSectionExpected, errRstNewSectionExpected,
errRstGeneralParseError, errRstGeneralParseError,
errRstInvalidDirectiveX, errRstInvalidDirectiveX,
@ -106,6 +107,7 @@ const
errXExpected: "'$1' expected", errXExpected: "'$1' expected",
errRstGridTableNotImplemented: "grid table is not implemented", errRstGridTableNotImplemented: "grid table is not implemented",
errRstMarkdownIllformedTable: "illformed delimiter row of a markdown table", errRstMarkdownIllformedTable: "illformed delimiter row of a markdown table",
errRstIllformedTable: "Illformed table: $1",
errRstNewSectionExpected: "new section expected $1", errRstNewSectionExpected: "new section expected $1",
errRstGeneralParseError: "general parse error", errRstGeneralParseError: "general parse error",
errRstInvalidDirectiveX: "invalid directive: '$1'", errRstInvalidDirectiveX: "invalid directive: '$1'",

View file

@ -18,9 +18,9 @@ been renamed to fit this scheme. The ultimate goal is that the programmer can
*guess* a name. *guess* a name.
------------------- ------------ -------------------------------------- =================== ============ ======================================
English word To use Notes English word To use Notes
------------------- ------------ -------------------------------------- =================== ============ ======================================
initialize initT `init` is used to create a initialize initT `init` is used to create a
value type `T` value type `T`
new newP `new` is used to create a new newP `new` is used to create a
@ -82,4 +82,4 @@ literal lit
string str string str
identifier ident identifier ident
indentation indent indentation indent
------------------- ------------ -------------------------------------- =================== ============ ======================================

View file

@ -50,9 +50,9 @@ A leaf of the AST often corresponds to a terminal symbol in the concrete
syntax. Note that the default ``float`` in Nim maps to ``float64`` such that syntax. Note that the default ``float`` in Nim maps to ``float64`` such that
the default AST for a float is ``nnkFloat64Lit`` as below. the default AST for a float is ``nnkFloat64Lit`` as below.
----------------- --------------------------------------------- ================= =============================================
Nim expression Corresponding AST Nim expression Corresponding AST
----------------- --------------------------------------------- ================= =============================================
``42`` ``nnkIntLit(intVal = 42)`` ``42`` ``nnkIntLit(intVal = 42)``
``42'i8`` ``nnkInt8Lit(intVal = 42)`` ``42'i8`` ``nnkInt8Lit(intVal = 42)``
``42'i16`` ``nnkInt16Lit(intVal = 42)`` ``42'i16`` ``nnkInt16Lit(intVal = 42)``
@ -72,7 +72,7 @@ Nim expression Corresponding AST
``nil`` ``nnkNilLit()`` ``nil`` ``nnkNilLit()``
``myIdentifier`` ``nnkIdent(strVal = "myIdentifier")`` ``myIdentifier`` ``nnkIdent(strVal = "myIdentifier")``
``myIdentifier`` after lookup pass: ``nnkSym(strVal = "myIdentifier", ...)`` ``myIdentifier`` after lookup pass: ``nnkSym(strVal = "myIdentifier", ...)``
----------------- --------------------------------------------- ================= =============================================
Identifiers are ``nnkIdent`` nodes. After the name lookup pass these nodes Identifiers are ``nnkIdent`` nodes. After the name lookup pass these nodes
get transferred into ``nnkSym`` nodes. get transferred into ``nnkSym`` nodes.
@ -1211,9 +1211,9 @@ AST:
In general, declaring types mirrors this syntax (i.e., ``nnkStaticTy`` for In general, declaring types mirrors this syntax (i.e., ``nnkStaticTy`` for
``static``, etc.). Examples follow (exceptions marked by ``*``): ``static``, etc.). Examples follow (exceptions marked by ``*``):
------------- --------------------------------------------- ============= =============================================
Nim type Corresponding AST Nim type Corresponding AST
------------- --------------------------------------------- ============= =============================================
``static`` ``nnkStaticTy`` ``static`` ``nnkStaticTy``
``tuple`` ``nnkTupleTy`` ``tuple`` ``nnkTupleTy``
``var`` ``nnkVarTy`` ``var`` ``nnkVarTy``
@ -1226,7 +1226,7 @@ Nim type Corresponding AST
``proc`` ``nnkProcTy`` ``proc`` ``nnkProcTy``
``iterator`` ``nnkIteratorTy`` ``iterator`` ``nnkIteratorTy``
``object`` ``nnkObjectTy`` ``object`` ``nnkObjectTy``
------------- --------------------------------------------- ============= =============================================
Take special care when declaring types as ``proc``. The behavior is similar Take special care when declaring types as ``proc``. The behavior is similar
to ``Procedure declaration``, below, but does not treat ``nnkGenericParams``. to ``Procedure declaration``, below, but does not treat ``nnkGenericParams``.

View file

@ -363,7 +363,7 @@ contain the following `escape sequences`:idx:\ :
``\\`` `backslash`:idx: ``\\`` `backslash`:idx:
``\"`` `quotation mark`:idx: ``\"`` `quotation mark`:idx:
``\'`` `apostrophe`:idx: ``\'`` `apostrophe`:idx:
``\`` '0'..'9'+ `character with decimal value d`:idx:; ``\`` '0'..'9'+ `character with decimal value d`:idx:;
all decimal digits directly all decimal digits directly
following are used for the character following are used for the character
``\a`` `alert`:idx: ``\a`` `alert`:idx:

View file

@ -153,9 +153,9 @@ The library uses a simple naming scheme that makes use of common abbreviations
to keep the names short but meaningful. to keep the names short but meaningful.
------------------- ------------ -------------------------------------- =================== ============ ======================================
English word To use Notes English word To use Notes
------------------- ------------ -------------------------------------- =================== ============ ======================================
initialize initFoo initializes a value type `Foo` initialize initFoo initializes a value type `Foo`
new newFoo initializes a reference type `Foo` new newFoo initializes a reference type `Foo`
via `new` or a value type `Foo` via `new` or a value type `Foo`
@ -220,7 +220,7 @@ literal lit
string str string str
identifier ident identifier ident
indentation indent indentation indent
------------------- ------------ -------------------------------------- =================== ============ ======================================
Coding Conventions Coding Conventions

View file

@ -63,7 +63,7 @@
\usepackage{scrextend} % for the `addmargin` environment \usepackage{scrextend} % for the `addmargin` environment
\usepackage{xcolor} \usepackage[table]{xcolor}
\usepackage[urlbordercolor=blue,linkbordercolor=cyan, \usepackage[urlbordercolor=blue,linkbordercolor=cyan,
pdfborderstyle={/S/U/W 1}]{hyperref} pdfborderstyle={/S/U/W 1}]{hyperref}
\usepackage{enumitem} % for option list, enumList, and rstfootnote \usepackage{enumitem} % for option list, enumList, and rstfootnote
@ -84,6 +84,11 @@
\definecolor{rstframecolor}{rgb}{0.85, 0.8, 0.6} \definecolor{rstframecolor}{rgb}{0.85, 0.8, 0.6}
\usepackage{booktabs}
\belowrulesep=0ex
\aboverulesep=0ex
\renewcommand{\arraystretch}{1.1}
\newtcolorbox{rstprebox}[1][]{blanker, breakable, \newtcolorbox{rstprebox}[1][]{blanker, breakable,
left=3mm, right=3mm, top=1mm, bottom=1mm, left=3mm, right=3mm, top=1mm, bottom=1mm,
borderline ={0.1em}{0pt}{rstframecolor}, borderline ={0.1em}{0pt}{rstframecolor},
@ -127,6 +132,7 @@
\newenvironment{rstoptlist}{% \newenvironment{rstoptlist}{%
\begin{description}[font=\sffamily\bfseries,style=nextline,leftmargin=\rstoptleftmargin,labelwidth=\rstoptlabelwidth]}{\end{description}} \begin{description}[font=\sffamily\bfseries,style=nextline,leftmargin=\rstoptleftmargin,labelwidth=\rstoptlabelwidth]}{\end{description}}
\usepackage{multirow}
\usepackage{tabulary} % tables with adjustable cell width and no overflow \usepackage{tabulary} % tables with adjustable cell width and no overflow
% make tabulary prevent overflows (https://tex.stackexchange.com/a/195088) % make tabulary prevent overflows (https://tex.stackexchange.com/a/195088)
\tymin=60pt \tymin=60pt

View file

@ -49,20 +49,20 @@ contain the following key-value pairs:
==================== ======================================================= ==================== =======================================================
Key description Key description
==================== ======================================================= ==================== =======================================================
`Name` the project's name; this needs to be a single word `Name` the project's name; this needs to be a single word
`DisplayName` the project's long name; this can contain spaces. If `DisplayName` the project's long name; this can contain spaces. If
not specified, this is the same as `Name`. not specified, this is the same as `Name`.
`Version` the project's version `Version` the project's version
`OS` the OSes to generate C code for; for example: `OS` the OSes to generate C code for; for example:
`"windows;linux;macosx"` `"windows;linux;macosx"`
`CPU` the CPUs to generate C code for; for example: `CPU` the CPUs to generate C code for; for example:
`"i386;amd64;powerpc"` `"i386;amd64;powerpc"`
`Authors` the project's authors `Authors` the project's authors
`Description` the project's description `Description` the project's description
`App` the application's type: "Console" or "GUI". If `App` the application's type: "Console" or "GUI". If
"Console", niminst generates a special batch file "Console", niminst generates a special batch file
for Windows to open up the command-line shell. for Windows to open up the command-line shell.
`License` the filename of the application's license `License` the filename of the application's license
==================== ======================================================= ==================== =======================================================
@ -149,9 +149,9 @@ Possible options are:
==================== ======================================================= ==================== =======================================================
Key description Key description
==================== ======================================================= ==================== =======================================================
`InstallScript` boolean flag whether an installation shell script `InstallScript` boolean flag whether an installation shell script
should be generated. Example: `InstallScript: "Yes"` should be generated. Example: `InstallScript: "Yes"`
`UninstallScript` boolean flag whether a de-installation shell script `UninstallScript` boolean flag whether a de-installation shell script
should be generated. should be generated.
Example: `UninstallScript: "Yes"` Example: `UninstallScript: "Yes"`
==================== ======================================================= ==================== =======================================================

View file

@ -1185,9 +1185,9 @@ subranges) are called ordinal types. Ordinal types have quite
a few special operations: a few special operations:
----------------- -------------------------------------------------------- ================= ========================================================
Operation Comment Operation Comment
----------------- -------------------------------------------------------- ================= ========================================================
`ord(x)` returns the integer value that is used to `ord(x)` returns the integer value that is used to
represent `x`'s value represent `x`'s value
`inc(x)` increments `x` by one `inc(x)` increments `x` by one
@ -1198,7 +1198,7 @@ Operation Comment
`succ(x, n)` returns the `n`'th successor of `x` `succ(x, n)` returns the `n`'th successor of `x`
`pred(x)` returns the predecessor of `x` `pred(x)` returns the predecessor of `x`
`pred(x, n)` returns the `n`'th predecessor of `x` `pred(x, n)` returns the `n`'th predecessor of `x`
----------------- -------------------------------------------------------- ================= ========================================================
The `inc <system.html#inc,T,int>`_, `dec <system.html#dec,T,int>`_, `succ The `inc <system.html#inc,T,int>`_, `dec <system.html#dec,T,int>`_, `succ

View file

@ -255,6 +255,7 @@ type
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", meMarkdownIllformedTable = "illformed delimiter row of a Markdown table",
meIllformedTable = "Illformed table: $1",
meNewSectionExpected = "new section expected $1", meNewSectionExpected = "new section expected $1",
meGeneralParseError = "general parse error", meGeneralParseError = "general parse error",
meInvalidDirective = "invalid directive: '$1'", meInvalidDirective = "invalid directive: '$1'",
@ -2467,81 +2468,175 @@ proc parseOverline(p: var RstParser): PRstNode =
anchorType=headlineAnchor) anchorType=headlineAnchor)
type type
IntSeq = seq[int] ColSpec = object
ColumnLimits = tuple start, stop: int
RstCols = seq[ColSpec]
ColumnLimits = tuple # for Markdown
first, last: int first, last: int
ColSeq = seq[ColumnLimits] ColSeq = seq[ColumnLimits]
proc tokEnd(p: RstParser): int = proc tokStart(p: RstParser, idx: int): int =
result = currentTok(p).col + currentTok(p).symbol.len - 1 result = p.tok[idx].col
proc getColumns(p: var RstParser, cols: var IntSeq) = proc tokStart(p: RstParser): int =
result = tokStart(p, p.idx)
proc tokEnd(p: RstParser, idx: int): int =
result = p.tok[idx].col + p.tok[idx].symbol.len - 1
proc tokEnd(p: RstParser): int =
result = tokEnd(p, p.idx)
proc getColumns(p: RstParser, cols: var RstCols, startIdx: int): int =
# Fills table column specification (or separator) `cols` and returns
# the next parser index after it.
var L = 0 var L = 0
result = startIdx
while true: while true:
inc L inc L
setLen(cols, L) setLen(cols, L)
cols[L - 1] = tokEnd(p) cols[L - 1].start = tokStart(p, result)
assert(currentTok(p).kind == tkAdornment) cols[L - 1].stop = tokEnd(p, result)
inc p.idx assert(p.tok[result].kind == tkAdornment)
if currentTok(p).kind != tkWhite: break inc result
inc p.idx if p.tok[result].kind != tkWhite: break
if currentTok(p).kind != tkAdornment: break inc result
if currentTok(p).kind == tkIndent: inc p.idx if p.tok[result].kind != tkAdornment: break
# last column has no limit: if p.tok[result].kind == tkIndent: inc result
cols[L - 1] = 32000
proc checkColumns(p: RstParser, cols: RstCols) =
var
i = p.idx
col = 0
if p.tok[i].symbol[0] != '=':
rstMessage(p, mwRstStyle,
"only tables with `=` columns specification are allowed")
for col in 0 ..< cols.len:
if tokEnd(p, i) != cols[col].stop:
rstMessage(p, meIllformedTable,
"end of table column #$1 should end at position $2" % [
$(col+1), $(cols[col].stop+ColRstOffset)],
p.tok[i].line, tokEnd(p, i))
inc i
if col == cols.len - 1:
if p.tok[i].kind == tkWhite:
inc i
if p.tok[i].kind notin {tkIndent, tkEof}:
rstMessage(p, meIllformedTable, "extraneous column specification")
elif p.tok[i].kind == tkWhite:
inc i
else:
rstMessage(p, meIllformedTable, "no enough table columns",
p.tok[i].line, p.tok[i].col)
proc getSpans(p: RstParser, nextLine: int,
cols: RstCols, unitedCols: RstCols): seq[int] =
## Calculates how many columns a joined cell occupies.
if unitedCols.len > 0:
result = newSeq[int](unitedCols.len)
var
iCell = 0
jCell = 0
uCell = 0
while jCell < cols.len:
if cols[jCell].stop < unitedCols[uCell].stop:
inc jCell
elif cols[jCell].stop == unitedCols[uCell].stop:
result[uCell] = jCell - iCell + 1
iCell = jCell + 1
jCell = jCell + 1
inc uCell
else:
rstMessage(p, meIllformedTable,
"spanning underline does not match main table columns",
p.tok[nextLine].line, p.tok[nextLine].col)
proc parseSimpleTableRow(p: var RstParser, cols: RstCols, colChar: char): PRstNode =
## Parses 1 row in RST simple table.
# Consider that columns may be spanning (united by using underline like ----):
let nextLine = tokenAfterNewline(p)
var unitedCols: RstCols
var afterSpan: int
if p.tok[nextLine].kind == tkAdornment and p.tok[nextLine].symbol[0] == '-':
afterSpan = getColumns(p, unitedCols, nextLine)
if unitedCols == cols and p.tok[nextLine].symbol[0] == colChar:
# legacy rst.nim compat.: allow punctuation like `----` in main boundaries
afterSpan = nextLine
unitedCols.setLen 0
else:
afterSpan = nextLine
template colEnd(i): int =
if i == cols.len - 1: high(int) # last column has no limit
elif unitedCols.len > 0: unitedCols[i].stop else: cols[i].stop
template colStart(i): int =
if unitedCols.len > 0: unitedCols[i].start else: cols[i].start
var row = newSeq[string](if unitedCols.len > 0: unitedCols.len else: cols.len)
var spans: seq[int] = getSpans(p, nextLine, cols, unitedCols)
let line = currentTok(p).line
# Iterate over the lines a single cell may span:
while true:
var nCell = 0
# distribute tokens between cells in the current line:
while currentTok(p).kind notin {tkIndent, tkEof}:
if tokEnd(p) <= colEnd(nCell):
if tokStart(p) < colStart(nCell):
if currentTok(p).kind != tkWhite:
rstMessage(p, meIllformedTable,
"this word crosses table column from the left")
else:
inc p.idx
else:
row[nCell].add(currentTok(p).symbol)
inc p.idx
else:
if tokStart(p) < colEnd(nCell) and currentTok(p).kind != tkWhite:
rstMessage(p, meIllformedTable,
"this word crosses table column from the right")
inc nCell
if currentTok(p).kind == tkIndent: inc p.idx
if tokEnd(p) <= colEnd(0): break
# Continued current cells because the 1st column is empty.
if currentTok(p).kind in {tkEof, tkAdornment}:
break
for nCell in countup(1, high(row)): row[nCell].add('\n')
result = newRstNode(rnTableRow)
var q: RstParser
for uCell in 0 ..< row.len:
initParser(q, p.s)
q.col = colStart(uCell)
q.line = line - 1
getTokens(row[uCell], q.tok)
let cell = newRstNode(rnTableDataCell)
cell.span = if spans.len == 0: 0 else: spans[uCell]
cell.add(parseDoc(q))
result.add(cell)
if afterSpan > p.idx:
p.idx = afterSpan
proc parseSimpleTable(p: var RstParser): PRstNode = proc parseSimpleTable(p: var RstParser): PRstNode =
var var cols: RstCols
cols: IntSeq
row: seq[string]
i, last, line: int
c: char
q: RstParser
a, b: PRstNode
result = newRstNodeA(p, rnTable) result = newRstNodeA(p, rnTable)
cols = @[] let startIdx = getColumns(p, cols, p.idx)
row = @[] let colChar = currentTok(p).symbol[0]
a = nil checkColumns(p, cols)
c = currentTok(p).symbol[0] p.idx = startIdx
result.colCount = cols.len
while true: while true:
if currentTok(p).kind == tkAdornment: if currentTok(p).kind == tkAdornment:
last = tokenAfterNewline(p) checkColumns(p, cols)
if p.tok[last].kind in {tkEof, tkIndent}: p.idx = tokenAfterNewline(p)
if currentTok(p).kind in {tkEof, tkIndent}:
# skip last adornment line: # skip last adornment line:
p.idx = last
break break
getColumns(p, cols) if result.sons.len > 0: result.sons[^1].endsHeader = true
setLen(row, cols.len) # fix rnTableDataCell -> rnTableHeaderCell for previous table rows:
if a != nil: for nRow in 0 ..< result.sons.len:
for j in 0 ..< a.len: # fix rnTableDataCell -> rnTableHeaderCell for nCell in 0 ..< result.sons[nRow].len:
a.sons[j] = newRstNode(rnTableHeaderCell, a.sons[j].sons) result.sons[nRow].sons[nCell].kind = rnTableHeaderCell
if currentTok(p).kind == tkEof: break if currentTok(p).kind == tkEof: break
for j in countup(0, high(row)): row[j] = "" let tabRow = parseSimpleTableRow(p, cols, colChar)
# the following while loop iterates over the lines a single cell may span: result.add tabRow
line = currentTok(p).line
while true:
i = 0
while currentTok(p).kind notin {tkIndent, tkEof}:
if tokEnd(p) <= cols[i]:
row[i].add(currentTok(p).symbol)
inc p.idx
else:
if currentTok(p).kind == tkWhite: inc p.idx
inc i
if currentTok(p).kind == tkIndent: inc p.idx
if tokEnd(p) <= cols[0]: break
if currentTok(p).kind in {tkEof, tkAdornment}: break
for j in countup(1, high(row)): row[j].add('\n')
a = newRstNode(rnTableRow)
for j in countup(0, high(row)):
initParser(q, p.s)
q.col = cols[j]
q.line = line - 1
getTokens(row[j], q.tok)
b = newRstNode(rnTableDataCell)
b.add(parseDoc(q))
a.add(b)
result.add(a)
proc readTableRow(p: var RstParser): ColSeq = proc readTableRow(p: var RstParser): ColSeq =
if currentTok(p).symbol == "|": inc p.idx if currentTok(p).symbol == "|": inc p.idx
@ -2574,17 +2669,16 @@ proc isValidDelimiterRow(p: var RstParser, colNum: int): bool =
proc parseMarkdownTable(p: var RstParser): PRstNode = proc parseMarkdownTable(p: var RstParser): PRstNode =
var var
row: ColSeq row: ColSeq
colNum: int
a, b: PRstNode a, b: PRstNode
q: RstParser q: RstParser
result = newRstNodeA(p, rnMarkdownTable) result = newRstNodeA(p, rnMarkdownTable)
proc parseRow(p: var RstParser, cellKind: RstNodeKind, result: PRstNode) = proc parseRow(p: var RstParser, cellKind: RstNodeKind, result: PRstNode) =
row = readTableRow(p) row = readTableRow(p)
if colNum == 0: colNum = row.len # table header if result.colCount == 0: result.colCount = row.len # table header
elif row.len < colNum: row.setLen(colNum) elif row.len < result.colCount: row.setLen(result.colCount)
a = newRstNode(rnTableRow) a = newRstNode(rnTableRow)
for j in 0 ..< colNum: for j in 0 ..< result.colCount:
b = newRstNode(cellKind) b = newRstNode(cellKind)
initParser(q, p.s) initParser(q, p.s)
q.col = p.col q.col = p.col
@ -2595,7 +2689,8 @@ proc parseMarkdownTable(p: var RstParser): PRstNode =
result.add(a) result.add(a)
parseRow(p, rnTableHeaderCell, result) parseRow(p, rnTableHeaderCell, result)
if not isValidDelimiterRow(p, colNum): rstMessage(p, meMarkdownIllformedTable) if not isValidDelimiterRow(p, result.colCount):
rstMessage(p, meMarkdownIllformedTable)
while predNL(p) and currentTok(p).symbol == "|": while predNL(p) and currentTok(p).symbol == "|":
parseRow(p, rnTableDataCell, result) parseRow(p, rnTableDataCell, result)

View file

@ -112,6 +112,12 @@ type
## nodes that are post-processed after parsing ## nodes that are post-processed after parsing
of rnNimdocRef: of rnNimdocRef:
tooltip*: string tooltip*: string
of rnTable, rnGridTable, rnMarkdownTable:
colCount*: int ## Number of (not-united) cells in the table
of rnTableRow:
endsHeader*: bool ## Is last row in the header of table?
of rnTableHeaderCell, rnTableDataCell:
span*: int ## Number of table columns that the cell occupies
else: else:
discard discard
anchor*: string ## anchor, internal link target anchor*: string ## anchor, internal link target
@ -416,6 +422,13 @@ proc treeRepr*(node: PRstNode, indent=0): string =
result.add (if node.order == 0: "" else: " order=" & $node.order) result.add (if node.order == 0: "" else: " order=" & $node.order)
of rnMarkdownBlockQuoteItem: of rnMarkdownBlockQuoteItem:
result.add " quotationDepth=" & $node.quotationDepth result.add " quotationDepth=" & $node.quotationDepth
of rnTable, rnGridTable, rnMarkdownTable:
result.add " colCount=" & $node.colCount
of rnTableHeaderCell, rnTableDataCell:
if node.span > 0:
result.add " span=" & $node.span
of rnTableRow:
if node.endsHeader: result.add " endsHeader"
else: else:
discard discard
result.add (if node.anchor == "": "" else: " anchor='" & node.anchor & "'") result.add (if node.anchor == "": "" else: " anchor='" & node.anchor & "'")

View file

@ -1091,10 +1091,6 @@ proc renderContainer(d: PDoc, n: PRstNode, result: var string) =
else: else:
dispA(d.target, result, "<div class=\"$1\">$2</div>", "$2", [arg, tmp]) dispA(d.target, result, "<div class=\"$1\">$2</div>", "$2", [arg, tmp])
proc texColumns(n: PRstNode): string =
let nColumns = if n.sons.len > 0: len(n.sons[0]) else: 1
result = "L".repeat(nColumns)
proc renderField(d: PDoc, n: PRstNode, result: var string) = proc renderField(d: PDoc, n: PRstNode, result: var string) =
var b = false var b = false
if d.target == outLatex: if d.target == outLatex:
@ -1323,24 +1319,49 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
renderAux(d, n, renderAux(d, n,
"<table$2 border=\"1\" class=\"docutils\">$1</table>", "<table$2 border=\"1\" class=\"docutils\">$1</table>",
"\n$2\n\\begin{rsttab}{" & "\n$2\n\\begin{rsttab}{" &
texColumns(n) & "}\n\\hline\n$1\\end{rsttab}", result) "L".repeat(n.colCount) & "}\n\\toprule\n$1" &
"\\addlinespace[0.1em]\\bottomrule\n\\end{rsttab}", result)
of rnTableRow: of rnTableRow:
if len(n) >= 1: if len(n) >= 1:
if d.target == outLatex: case d.target
#var tmp = "" of outHtml:
renderRstToOut(d, n.sons[0], result)
for i in countup(1, len(n) - 1):
result.add(" & ")
renderRstToOut(d, n.sons[i], result)
result.add("\\\\\n\\hline\n")
else:
result.add("<tr>") result.add("<tr>")
renderAux(d, n, result) renderAux(d, n, result)
result.add("</tr>\n") result.add("</tr>\n")
of rnTableDataCell: of outLatex:
renderAux(d, n, "<td>$1</td>", "$1", result) if n.sons[0].kind == rnTableHeaderCell:
of rnTableHeaderCell: result.add "\\rowcolor{gray!15} "
renderAux(d, n, "<th>$1</th>", "\\textbf{$1}", result) var spanLines: seq[(int, int)]
var nCell = 0
for uCell in 0 .. n.len - 1:
renderRstToOut(d, n.sons[uCell], result)
if n.sons[uCell].span > 0:
spanLines.add (nCell + 1, nCell + n.sons[uCell].span)
nCell += n.sons[uCell].span
else:
nCell += 1
if uCell != n.len - 1:
result.add(" & ")
result.add("\\\\")
if n.endsHeader: result.add("\\midrule\n")
for (start, stop) in spanLines:
result.add("\\cmidrule(lr){$1-$2}" % [$start, $stop])
result.add("\n")
of rnTableHeaderCell, rnTableDataCell:
case d.target
of outHtml:
let tag = if n.kind == rnTableHeaderCell: "th" else: "td"
var spanSpec: string
if n.span <= 1: spanSpec = ""
else:
spanSpec = " colspan=\"" & $n.span & "\" style=\"text-align: center\""
renderAux(d, n, "<$1$2>$$1</$1>" % [tag, spanSpec], "", result)
of outLatex:
let text = if n.kind == rnTableHeaderCell: "\\textbf{$1}" else: "$1"
var latexStr: string
if n.span <= 1: latexStr = text
else: latexStr = "\\multicolumn{" & $n.span & "}{c}{" & text & "}"
renderAux(d, n, "", latexStr, result)
of rnFootnoteGroup: of rnFootnoteGroup:
renderAux(d, n, renderAux(d, n,
"<hr class=\"footnote\">" & "<hr class=\"footnote\">" &

View file

@ -8,11 +8,11 @@
# #
## Nim coroutines implementation, supports several context switching methods: ## Nim coroutines implementation, supports several context switching methods:
## -------- ------------ ## ======== ============
## ucontext available on unix and alike (default) ## ucontext available on unix and alike (default)
## setjmp available on unix and alike (x86/64 only) ## setjmp available on unix and alike (x86/64 only)
## fibers available and required on windows. ## fibers available and required on windows.
## -------- ------------ ## ======== ============
## ##
## -d:nimCoroutines Required to build this module. ## -d:nimCoroutines Required to build this module.
## -d:nimCoroutinesUcontext Use ucontext backend. ## -d:nimCoroutinesUcontext Use ucontext backend.

View file

@ -2383,21 +2383,21 @@ iterator walkDirRec*(dir: string,
## ##
## Walking is recursive. `followFilter` controls the behaviour of the iterator: ## Walking is recursive. `followFilter` controls the behaviour of the iterator:
## ##
## --------------------- --------------------------------------------- ## ===================== =============================================
## yieldFilter meaning ## yieldFilter meaning
## --------------------- --------------------------------------------- ## ===================== =============================================
## ``pcFile`` yield real files (default) ## ``pcFile`` yield real files (default)
## ``pcLinkToFile`` yield symbolic links to files ## ``pcLinkToFile`` yield symbolic links to files
## ``pcDir`` yield real directories ## ``pcDir`` yield real directories
## ``pcLinkToDir`` yield symbolic links to directories ## ``pcLinkToDir`` yield symbolic links to directories
## --------------------- --------------------------------------------- ## ===================== =============================================
## ##
## --------------------- --------------------------------------------- ## ===================== =============================================
## followFilter meaning ## followFilter meaning
## --------------------- --------------------------------------------- ## ===================== =============================================
## ``pcDir`` follow real directories (default) ## ``pcDir`` follow real directories (default)
## ``pcLinkToDir`` follow symbolic links to directories ## ``pcLinkToDir`` follow symbolic links to directories
## --------------------- --------------------------------------------- ## ===================== =============================================
## ##
## ##
## See also: ## See also:

View file

@ -552,15 +552,15 @@ proc escape*(s: string): string =
## ##
## Escapes these characters: ## Escapes these characters:
## ##
## ------------ ------------------- ## ============ ===================
## char is converted to ## char is converted to
## ------------ ------------------- ## ============ ===================
## ``<`` ``&lt;`` ## ``<`` ``&lt;``
## ``>`` ``&gt;`` ## ``>`` ``&gt;``
## ``&`` ``&amp;`` ## ``&`` ``&amp;``
## ``"`` ``&quot;`` ## ``"`` ``&quot;``
## ``'`` ``&apos;`` ## ``'`` ``&apos;``
## ------------ ------------------- ## ============ ===================
## ##
## You can also use `addEscaped proc <#addEscaped,string,string>`_. ## You can also use `addEscaped proc <#addEscaped,string,string>`_.
result = newStringOfCap(s.len) result = newStringOfCap(s.len)

View file

@ -3,6 +3,8 @@ discard """
[Suite] RST parsing [Suite] RST parsing
[Suite] RST tables
[Suite] RST indentation [Suite] RST indentation
[Suite] Warnings [Suite] Warnings
@ -618,6 +620,233 @@ suite "RST parsing":
rnLeaf 'code' 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": suite "RST indentation":
test "nested bullet lists": test "nested bullet lists":
let input = dedent """ let input = dedent """