mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-03 03:32:32 +00:00
RST: implement admonitions (#16438)
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>",
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
Reference in New Issue
Block a user