RST: implement admonitions (#16438)

This commit is contained in:
Andrey Makarov
2020-12-27 13:16:12 +03:00
committed by GitHub
parent 626c2bc658
commit 2bdc479622
6 changed files with 239 additions and 54 deletions

View File

@@ -52,6 +52,15 @@ doc.file = """
\usepackage{hyperref}
\usepackage{enumitem}
\usepackage{xcolor}
\usepackage[tikz]{mdframed}
\usetikzlibrary{shadows}
\mdfsetup{%
linewidth=3,
topline=false,
rightline=false,
bottomline=false}
\begin{document}
\title{$title $version}
\author{$author}

View File

@@ -14,6 +14,9 @@ Modified by Boyd Greenfield and narimiran
--primary-background: #fff;
--secondary-background: ghostwhite;
--third-background: #e8e8e8;
--info-background: #50c050;
--warning-background: #c0a000;
--error-background: #e04040;
--border: #dde;
--text: #222;
--anchor: #07b;
@@ -39,6 +42,9 @@ Modified by Boyd Greenfield and narimiran
--primary-background: #171921;
--secondary-background: #1e202a;
--third-background: #2b2e3b;
--info-background: #008000;
--warning-background: #807000;
--error-background: #c03000;
--border: #0e1014;
--text: #fff;
--anchor: #8be9fd;
@@ -609,6 +615,34 @@ table.borderless td, table.borderless th {
The right padding separates the table cells. */
padding: 0 0.5em 0 0 !important; }
.admonition {
padding: 0.3em;
background-color: var(--secondary-background);
border-left: 0.4em solid #7f7f84;
margin-bottom: 0.5em;
-webkit-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2);
-moz-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2);
box-shadow: 0 5px 8px -6px rgba(0,0,0,.2);
}
.admonition-info {
border-color: var(--info-background);
}
.admonition-info-text {
color: var(--info-background);
}
.admonition-warning {
border-color: var(--warning-background);
}
.admonition-warning-text {
color: var(--warning-background);
}
.admonition-error {
border-color: var(--error-background);
}
.admonition-error-text {
color: var(--error-background);
}
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 !important; }

View File

@@ -7,11 +7,18 @@
# distribution, for details about the copyright.
#
## This module implements a `reStructuredText`:idx: parser. A large
## subset is implemented. Some features of the `markdown`:idx: wiki syntax are
## also supported.
## This module implements a `reStructuredText`:idx: (RST) parser. A large
## subset is implemented. Some features of the `markdown`:idx: syntax are
## also supported. Nim can output the result to HTML (command ``rst2html``)
## or Latex (command ``rst2tex``).
##
## Supported RST features:
## If you are new to RST please consider reading the following:
##
## 1) a short `quick introduction`_
## 2) an `RST reference`_: a comprehensive cheatsheet for RST
## 3) a more formal 50-page `RST specification`_.
##
## Supported standard RST features:
##
## * body elements
## + sections
@@ -25,20 +32,29 @@
## + option lists
## + indented literal blocks
## + simple tables
## + directives
## - image, figure
## - code-block
## - substitution definitions: replace and image
## - ... a few more
## + directives (see official documentation in `RST directives list`_):
## - ``image``, ``figure`` for including images and videos
## - ``code``
## - ``contents`` (table of contents), ``container``, ``raw``
## - ``include``
## - admonitions: "attention", "caution", "danger", "error", "hint",
## "important", "note", "tip", "warning", "admonition"
## - substitution definitions: `replace` and `image`
## + comments
## * inline markup
## + *emphasis*, **strong emphasis**, `interpreted text`,
## + *emphasis*, **strong emphasis**,
## ``inline literals``, hyperlink references, substitution references,
## standalone hyperlinks
## + \`interpreted text\` with roles ``:literal:``, ``:strong:``,
## ``emphasis``, ``:sub:``/``:subscript:``, ``:sup:``/``:supscript:``
## (see `RST roles list`_ for description).
##
## Additional features:
##
## * directives: ``code-block``, ``title``, ``index``
## * ***triple emphasis*** (bold and italic) using \*\*\*
## * ``:idx:`` role for \`interpreted text\` to include the link to this
## text into an index (example: `Nim index`_).
##
## Optional additional features, turned on by ``options: RstParseOption`` in
## `rstParse proc <#rstParse,string,string,int,int,bool,RstParseOptions,FindFileHandler,MsgHandler>`_:
@@ -51,7 +67,11 @@
## * using ``1`` as auto-enumerator in enumerated lists like RST ``#``
## (auto-enumerator ``1`` can not be used with ``#`` in the same list)
##
## **Note:** By default nim has ``roSupportMarkdown`` turned **on**.
## .. Note:: By default Nim has ``roSupportMarkdown`` and
## ``roSupportRawDirective`` turned **on**.
##
## .. warning:: Using Nim-specific features can cause other RST implementations
## to fail on your document.
##
## Limitations:
##
@@ -61,14 +81,32 @@
## - no quoted literal blocks
## - no doctest blocks
## - no grid tables
## - directives: no support for admonitions (notes, caution)
## - some directives are missing (check official `RST directives list`_):
## ``parsed-literal``, ``sidebar``, ``topic``, ``math``, ``rubric``,
## ``epigraph``, ``highlights``, ``pull-quote``, ``compound``,
## ``table``, ``csv-table``, ``list-table``, ``section-numbering``,
## ``header``, ``footer``, ``meta``, ``class``
## - no ``role`` directives and no custom interpreted text roles
## - some standard roles are not supported (check `RST roles list`_)
## - no footnotes & citations support
## - no inline internal targets
## * inline markup
## - no simple-inline-markup
## - no embedded URI and aliases
##
## **Note:** Import ``packages/docutils/rst`` to use this module
## .. _quick introduction: https://docutils.sourceforge.io/docs/user/rst/quickstart.html
## .. _RST reference: https://docutils.sourceforge.io/docs/user/rst/quickref.html
## .. _RST specification: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html
## .. _RST directives list: https://docutils.sourceforge.io/docs/ref/rst/directives.html
## .. _RST roles list: https://docutils.sourceforge.io/docs/ref/rst/roles.html
## .. _Nim index: https://nim-lang.org/docs/theindex.html
##
## See `Nim DocGen Tools Guide <docgen.html>`_ for the details about
## ``nim doc``, ``nim rst2html`` and ``nim rst2tex`` commands.
##
## .. note:: Import ``packages/docutils/rst`` to use this module.
##
## See also `packages/docutils/rstgen module <rstgen.html>`_.
import
os, strutils, rstast
@@ -947,6 +985,7 @@ proc getDirective(p: var RstParser): string =
result = "" # error
else:
result = ""
result = result.toLowerAscii()
proc parseComment(p: var RstParser): PRstNode =
case currentTok(p).kind
@@ -968,21 +1007,6 @@ proc parseComment(p: var RstParser): PRstNode =
while currentTok(p).kind notin {tkIndent, tkEof}: inc p.idx
result = nil
type
DirKind = enum # must be ordered alphabetically!
dkNone, dkAuthor, dkAuthors, dkCode, dkCodeBlock, dkContainer, dkContents,
dkFigure, dkImage, dkInclude, dkIndex, dkRaw, dkTitle
const
DirIds: array[0..12, string] = ["", "author", "authors", "code",
"code-block", "container", "contents", "figure", "image", "include",
"index", "raw", "title"]
proc getDirKind(s: string): DirKind =
let i = find(DirIds, s)
if i >= 0: result = DirKind(i)
else: result = dkNone
proc parseLine(p: var RstParser, father: PRstNode) =
while true:
case currentTok(p).kind
@@ -1191,7 +1215,8 @@ proc whichSection(p: RstParser): RstNodeKind =
result = rnMarkdownTable
elif currentTok(p).symbol == "|" and isLineBlock(p):
result = rnLineBlock
elif match(p, tokenAfterNewline(p), "ai"):
elif match(p, tokenAfterNewline(p), "ai") and
isAdornmentHeadline(p, tokenAfterNewline(p)):
result = rnHeadline
elif predNL(p) and
currentTok(p).symbol in ["+", "*", "-"] and nextTok(p).kind == tkWhite:
@@ -1664,8 +1689,8 @@ proc parseDirective(p: var RstParser, flags: DirFlags): PRstNode =
## Parses arguments and options for a directive block.
##
## A directive block will always have three sons: the arguments for the
## directive (rnDirArg), the options (rnFieldList) and the block
## (rnLineBlock). This proc parses the two first nodes, the block is left to
## directive (rnDirArg), the options (rnFieldList) and the directive
## content block. This proc parses the two first nodes, the 3rd is left to
## the outer `parseDirective` call.
##
## Both rnDirArg and rnFieldList children nodes might be nil, so you need to
@@ -1703,12 +1728,20 @@ proc indFollows(p: RstParser): bool =
proc parseDirective(p: var RstParser, flags: DirFlags,
contentParser: SectionParser): PRstNode =
## Returns a generic rnDirective tree.
## A helper proc that does main work for specific directive procs.
## Always returns a generic rnDirective tree with these 3 children:
##
## The children are rnDirArg, rnFieldList and rnLineBlock. Any might be nil.
## 1) rnDirArg
## 2) rnFieldList
## 3) a node returned by `contentParser`.
##
## .. warning:: Any of the 3 children may be nil.
result = parseDirective(p, flags)
if not isNil(contentParser) and indFollows(p):
pushInd(p, currentTok(p).ival)
if not isNil(contentParser):
var nextIndent = p.tok[tokenAfterNewline(p)-1].ival
if nextIndent <= currInd(p): # parse only this line
nextIndent = currentTok(p).col
pushInd(p, nextIndent)
var content = contentParser(p)
popInd(p)
result.add(content)
@@ -1814,7 +1847,7 @@ proc dirCodeBlock(p: var RstParser, nimExtension = false): PRstNode =
n.add(newRstNode(rnLeaf, readFile(path)))
result.sons[2] = n
# Extend the field block if we are using our custom extension.
# Extend the field block if we are using our custom Nim extension.
if nimExtension:
# Create a field block if the input block didn't have any.
if result.sons[1].isNil: result.sons[1] = newRstNode(rnFieldList)
@@ -1856,6 +1889,11 @@ proc dirIndex(p: var RstParser): PRstNode =
result = parseDirective(p, {}, parseSectionWrapper)
result.kind = rnIndex
proc dirAdmonition(p: var RstParser, d: string): PRstNode =
result = parseDirective(p, {}, parseSectionWrapper)
result.kind = rnAdmonition
result.text = d
proc dirRawAux(p: var RstParser, result: var PRstNode, kind: RstNodeKind,
contentParser: SectionParser) =
var filename = getFieldValue(result, "file")
@@ -1891,29 +1929,42 @@ proc dirRaw(p: var RstParser): PRstNode =
else:
dirRawAux(p, result, rnRaw, parseSectionWrapper)
proc selectDir(p: var RstParser, d: string): PRstNode =
result = nil
case d
of "admonition", "attention", "caution": result = dirAdmonition(p, d)
of "code": result = dirCodeBlock(p)
of "code-block": result = dirCodeBlock(p, nimExtension = true)
of "container": result = dirContainer(p)
of "contents": result = dirContents(p)
of "danger", "error": result = dirAdmonition(p, d)
of "figure": result = dirFigure(p)
of "hint": result = dirAdmonition(p, d)
of "image": result = dirImage(p)
of "important": result = dirAdmonition(p, d)
of "include": result = dirInclude(p)
of "index": result = dirIndex(p)
of "note": result = dirAdmonition(p, d)
of "raw":
if roSupportRawDirective in p.s.options:
result = dirRaw(p)
else:
rstMessage(p, meInvalidDirective, d)
of "tip": result = dirAdmonition(p, d)
of "title": result = dirTitle(p)
of "warning": result = dirAdmonition(p, d)
else:
rstMessage(p, meInvalidDirective, d)
proc parseDotDot(p: var RstParser): PRstNode =
# parse "explicit markup blocks"
result = nil
var col = currentTok(p).col
inc p.idx
var d = getDirective(p)
if d != "":
pushInd(p, col)
case getDirKind(d)
of dkInclude: result = dirInclude(p)
of dkImage: result = dirImage(p)
of dkFigure: result = dirFigure(p)
of dkTitle: result = dirTitle(p)
of dkContainer: result = dirContainer(p)
of dkContents: result = dirContents(p)
of dkRaw:
if roSupportRawDirective in p.s.options:
result = dirRaw(p)
else:
rstMessage(p, meInvalidDirective, d)
of dkCode: result = dirCodeBlock(p)
of dkCodeBlock: result = dirCodeBlock(p, nimExtension = true)
of dkIndex: result = dirIndex(p)
else: rstMessage(p, meInvalidDirective, d)
result = selectDir(p, d)
popInd(p)
elif match(p, p.idx, " _"):
# hyperlink target:

View File

@@ -41,8 +41,11 @@ type
rnLabel, # used for footnotes and other things
rnFootnote, # a footnote
rnCitation, # similar to footnote
rnStandaloneHyperlink, rnHyperlink, rnRef, rnDirective, # a directive
rnDirArg, rnRaw, rnTitle, rnContents, rnImage, rnFigure, rnCodeBlock,
rnStandaloneHyperlink, rnHyperlink, rnRef,
rnDirective, # a general directive
rnDirArg, # a directive argument (for some directives).
# here are directives that are not rnDirective:
rnRaw, rnTitle, rnContents, rnImage, rnFigure, rnCodeBlock, rnAdmonition,
rnRawHtml, rnRawLatex,
rnContainer, # ``container`` directive
rnIndex, # index directve:
@@ -70,6 +73,7 @@ type
kind*: RstNodeKind ## the node's kind
text*: string ## valid for leafs in the AST; and the title of
## the document or the section; and rnEnumList
## and rnAdmonition
level*: int ## valid for some node kinds
sons*: RstNodeSeq ## the node's sons
@@ -316,3 +320,17 @@ proc renderRstToJson*(node: PRstNode): string =
## "sons":optional node array
## }
renderRstToJsonNode(node).pretty
proc renderRstToStr*(node: PRstNode, indent=0): string =
## Writes the parsed RST `node` into a compact string
## representation in the format (one line per every sub-node):
## ``indent - kind - text - level (if non-zero)``
## (suitable for debugging of RST parsing).
if node == nil:
result.add " ".repeat(indent) & "[nil]\n"
return
result.add " ".repeat(indent) & $node.kind & "\t" &
(if node.text == "": "" else: "'" & node.text & "'") &
(if node.level == 0: "" else: "\tlevel=" & $node.level) & "\n"
for son in node.sons:
result.add renderRstToStr(son, indent=indent+2)

View File

@@ -1079,6 +1079,29 @@ proc renderEnumList(d: PDoc, n: PRstNode, result: var string) =
"\\begin{enumerate}" & specifier & "$1\\end{enumerate}\n",
result)
proc renderAdmonition(d: PDoc, n: PRstNode, result: var string) =
var
htmlCls = "admonition_warning"
texSz = "\\large"
texColor = "orange"
case n.text
of "hint", "note", "tip":
htmlCls = "admonition-info"; texSz = "\\normalsize"; texColor = "green"
of "attention", "admonition", "important", "warning":
htmlCls = "admonition-warning"; texSz = "\\large"; texColor = "orange"
of "danger", "error":
htmlCls = "admonition-error"; texSz = "\\Large"; texColor = "red"
else: discard
let txt = n.text.capitalizeAscii()
let htmlHead = "<div class=\"admonition " & htmlCls & "\">"
renderAux(d, n,
htmlHead & "<span class=\"" & htmlCls & "-text\"><b>" & txt &
":</b></span>\n" & "$1</div>\n",
"\n\n\\begin{mdframed}[linecolor=" & texColor & "]\n" &
"{" & texSz & "\\color{" & texColor & "}{\\textbf{" & txt & ":}}} " &
"$1\n\\end{mdframed}\n",
result)
proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
if n == nil: return
case n.kind
@@ -1143,6 +1166,7 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
of rnBlockQuote:
renderAux(d, n, "<blockquote><p>$1</p></blockquote>\n",
"\\begin{quote}$1\\end{quote}\n", result)
of rnAdmonition: renderAdmonition(d, n, result)
of rnTable, rnGridTable, rnMarkdownTable:
renderAux(d, n,
"<table border=\"1\" class=\"docutils\">$1</table>",

View File

@@ -535,6 +535,55 @@ Test1
assert count(output1, "<ul ") == 1
assert count(output1, "</ul>") == 1
test "RST admonitions":
# check that all admonitions are implemented
let input0 = dedent """
.. admonition:: endOf admonition
.. attention:: endOf attention
.. caution:: endOf caution
.. danger:: endOf danger
.. error:: endOf error
.. hint:: endOf hint
.. important:: endOf important
.. note:: endOf note
.. tip:: endOf tip
.. warning:: endOf warning
"""
let output0 = rstToHtml(input0, {roSupportMarkdown}, defaultConfig())
for a in ["admonition", "attention", "caution", "danger", "error", "hint",
"important", "note", "tip", "warning" ]:
assert "endOf " & a & "</div>" in output0
# Test that admonition does not swallow up the next paragraph.
let input1 = dedent """
.. error:: endOfError
Test paragraph.
"""
let output1 = rstToHtml(input1, {roSupportMarkdown}, defaultConfig())
assert "endOfError</div>" in output1
assert "<p>Test paragraph. </p>" in output1
assert "class=\"admonition admonition-error\"" in output1
# Test that second line is parsed as continuation of the first line.
let input2 = dedent """
.. error:: endOfError
Test2p.
Test paragraph.
"""
let output2 = rstToHtml(input2, {roSupportMarkdown}, defaultConfig())
assert "endOfError Test2p.</div>" in output2
assert "<p>Test paragraph. </p>" in output2
assert "class=\"admonition admonition-error\"" in output2
let input3 = dedent """
.. note:: endOfNote
"""
let output3 = rstToHtml(input3, {roSupportMarkdown}, defaultConfig())
assert "endOfNote</div>" in output3
assert "class=\"admonition admonition-info\"" in output3
suite "RST/Code highlight":
test "Basic Python code highlight":
let pythonCode = """