Skip to content

Commit

Permalink
Docgen: math formulas using RST/LaTeX syntax (nim-lang#22657)
Browse files Browse the repository at this point in the history
Allows to type LaTeX math formulas using RST syntax like this:

    ## Solves the quadratic equation :math:`ax^2+bx+c = 0` using the formula:
    ##
    ## .. math:: x = -b ± \frac{\sqrt{b^2 - 4ac}}{2a}
  • Loading branch information
a-mr committed Sep 30, 2023
1 parent b2ca6be commit eb29a98
Show file tree
Hide file tree
Showing 18 changed files with 84 additions and 2 deletions.
5 changes: 5 additions & 0 deletions compiler/docgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1730,9 +1730,13 @@ proc genOutFile(d: PDoc, groupedToc = false): string =
"body_toc_groupsection", groupsection, "seeSrc", seeSrc]
if optCompileOnly notin d.conf.globalOptions:
# XXX what is this hack doing here? 'optCompileOnly' means raw output!?
let mathHeader =
if d.sharedState.hasMath: getConfigVar(d.conf, "doc.mathHeader")
else: ""
code = getConfigVar(d.conf, "doc.file") % [
"nimdoccss", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
nimdocOutCss.RelativeFile),
"optionalMathHeader", mathHeader,
"dochackjs", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
docHackJsFname.RelativeFile),
"title", title, "subtitle", subtitle, "tableofcontents", toc,
Expand Down Expand Up @@ -1920,6 +1924,7 @@ proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"")
let code = getConfigVar(conf, "doc.file") % [
"nimdoccss", relLink(conf.outDir, filename, nimdocOutCss.RelativeFile),
"dochackjs", relLink(conf.outDir, filename, docHackJsFname.RelativeFile),
"optionalMathHeader", "",
"title", "Index",
"subtitle", "", "tableofcontents", "", "moduledesc", "",
"date", getDateStr(), "time", getClockStr(),
Expand Down
24 changes: 24 additions & 0 deletions config/nimdoc.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,29 @@ $content
doc.listing_start = "<pre$3 class=\"listing\">"
doc.listing_end = "</pre>"
# Version & hash are from official KaTeX page https://github.com/KaTeX/KaTeX
# (updated 28 September 2023). Only referrerpolicy="no-referrer" is added.
# One may want to use a locally installed version of KaTeX (instead of https),
# which e.g. on Debian-based systems (package `libjs-katex`) is:
# /usr/share/javascript/katex/{katex.js,katex.min.css}
doc.mathHeader = """
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous" referrerpolicy="no-referrer">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
var formulas = document.getElementsByClassName("math");
for (var i = 0; i < formulas.length; i++) {
if (formulas[i].tagName == "SPAN") {
katex.render(formulas[i].firstChild.data, formulas[i], {
displayMode: formulas[i].classList.contains('display'),
throwOnError: false
})
}
}
})
</script>
"""
# * $analytics: Google analytics location, includes <script> tags
doc.file = """<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Expand All @@ -241,6 +264,7 @@ doc.file = """<?xml version="1.0" encoding="utf-8" ?>

<!-- JS -->
<script type="text/javascript" src="${dochackjs}?v=$nimVersion"></script>
$optionalMathHeader
</head>
<body>
<div class="document" id="documentId">
Expand Down
10 changes: 9 additions & 1 deletion lib/packages/docutils/rst.nim
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ const
SandboxDirAllowlist = [
"image", "code", "code-block", "admonition", "attention", "caution",
"container", "contents", "danger", "default-role", "error", "figure",
"hint", "important", "index", "note", "role", "tip", "title", "warning"]
"hint", "important", "index", "math", "note", "role", "tip", "title",
"warning"]

type
TokType = enum
Expand Down Expand Up @@ -419,6 +420,7 @@ type
currFileIdx*: FileIndex # current index in `filenames`
tocPart*: seq[PRstNode] # all the headings of a document
hasToc*: bool
hasMath*: bool
idxImports*: Table[string, ImportdocInfo]
# map `importdoc`ed filename -> it's info
nimFileImported*: bool # Was any ``.nim`` module `importdoc`ed ?
Expand Down Expand Up @@ -494,6 +496,7 @@ proc whichRoleAux(sym: string): RstNodeKind =
# literal and code are the same in our implementation
of "code": result = rnInlineLiteral
of "program", "option", "tok": result = rnCodeFragment
of "math": result = rnInlineMath
# c++ currently can be spelled only as cpp, c# only as csharp
elif getSourceLanguage(r) != langNone:
result = rnInlineCode
Expand Down Expand Up @@ -3402,6 +3405,10 @@ proc dirAdmonition(p: var RstParser, d: string): PRstNode =
result = parseDirective(p, rnAdmonition, {}, parseSectionWrapper)
result.adType = d

proc dirMath(p: var RstParser): PRstNode =
result = parseDirective(p, rnMath, {}, parseLiteralBlock)
p.s.hasMath = true

proc dirDefaultRole(p: var RstParser): PRstNode =
result = parseDirective(p, rnDefaultRole, {hasArg}, nil)
if result.sons[0].len == 0: p.s.currRole = defaultRole(p.s.options)
Expand Down Expand Up @@ -3484,6 +3491,7 @@ proc selectDir(p: var RstParser, d: string): PRstNode =
of "importdoc": result = dirImportdoc(p)
of "include": result = dirInclude(p)
of "index": result = dirIndex(p)
of "math": result = dirMath(p)
of "note": result = dirAdmonition(p, d)
of "raw":
if roSupportRawDirective in p.s.options:
Expand Down
2 changes: 2 additions & 0 deletions lib/packages/docutils/rstast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type
rnDirArg, # a directive argument (for some directives).
# here are directives that are not rnDirective:
rnRaw, rnTitle, rnContents, rnImage, rnFigure, rnCodeBlock, rnAdmonition,
rnMath,
rnRawHtml, rnRawLatex,
rnContainer, # ``container`` directive
rnIndex, # index directve:
Expand All @@ -70,6 +71,7 @@ type
rnInlineCode, # interpreted text with code in a known language
rnCodeFragment, # inline code for highlighting with the specified
# class (which cannot be inferred from context)
rnInlineMath,
rnUnknownRole, # interpreted text with an unknown role
rnSub, rnSup, rnIdx,
rnEmphasis, # "*"
Expand Down
22 changes: 21 additions & 1 deletion lib/packages/docutils/rstgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type

EscapeMode* = enum # in Latex text inside options [] and URLs is
# escaped slightly differently than in normal text
emText, emOption, emUrl # emText is currently used for code also
emText, emOption, emUrl, emMath # emText is currently used for code also

RstGenerator* = object of RootObj
target*: OutputTarget
Expand Down Expand Up @@ -206,6 +206,9 @@ proc addTexChar(dest: var string, c: char, escMode: EscapeMode) =
## TODO: @ is always a normal symbol (besides the header), am I wrong?
## All escapes that need to work in text and code blocks (`emText` mode)
## should start from \ (to be compatible with fancyvrb/fvextra).
if escMode == emMath:
add(dest, c)
return
case c
of '_', '&', '#', '%': add(dest, "\\" & c)
# commands \label and \pageref don't accept \$ by some reason but OK with $:
Expand Down Expand Up @@ -1105,6 +1108,21 @@ proc renderHyperlink(d: PDoc, text, link: PRstNode, result: var string,
"\\hyperlink{$2}{$1} (p.~\\pageref{$2})",
[textStr, linkStr, nimDocStr, tooltipStr])

proc renderMath(d: PDoc, n: PRstNode, result: var string, inline: bool) =
let saveEscMode = d.escMode
d.escMode = emMath # treat \ literally in Latex
if inline:
renderAux(d, n,
"""<span class="math inline">$1</span>""",
"$$$1$$",
result)
else:
renderAux(d, n.sons[2],
"""<span class="math display">$1</span>""",
"\n\\[$1\\]\n",
result)
d.escMode = saveEscMode

proc traverseForIndex*(d: PDoc, n: PRstNode) =
## A version of [renderRstToOut] that only fills entries for ``.idx`` files.
var discarded: string
Expand Down Expand Up @@ -1322,6 +1340,8 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =

of rnImage, rnFigure: renderImage(d, n, result)
of rnCodeBlock, rnInlineCode: renderCode(d, n, result)
of rnMath: renderMath(d, n, result, inline=false)
of rnInlineMath: renderMath(d, n, result, inline=true)
of rnContainer: renderContainer(d, n, result)
of rnSubstitutionReferences, rnSubstitutionDef:
renderAux(d, n, "|$1|", "|$1|", result)
Expand Down
1 change: 1 addition & 0 deletions nimdoc/extlinks/project/expected/_._/util.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="../dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/extlinks/project/expected/doc/manual.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="../dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/extlinks/project/expected/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/extlinks/project/expected/sub/submodule.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="../dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/extlinks/project/expected/theindex.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/rst2html/expected/rst_examples.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/test_doctype/expected/test_doctype.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/test_out_index_dot_html/expected/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/test_out_index_dot_html/expected/theindex.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/testproject/expected/subdir/subdir_b/utils.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="../../dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/testproject/expected/testproject.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
1 change: 1 addition & 0 deletions nimdoc/testproject/expected/theindex.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<!-- JS -->
<script type="text/javascript" src="dochack.js"></script>

</head>
<body>
<div class="document" id="documentId">
Expand Down
11 changes: 11 additions & 0 deletions tests/stdlib/trstgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ not in table"""
doAssert output2 == """<table border="1" class="docutils"><tr><th>A1 header</th><th>A2</th></tr>
</table>"""

test "Math formulas":
check("""Inline :math:`\sqrt{3x-1}+(1+x)^2`""".toHtml ==
"""Inline <span class="math inline">\sqrt{3x-1}+(1+x)^2</span>""")

check(dedent"""
.. math::
\sqrt{3x-1}+(1+x)^2
""".toHtml ==
"""<span class="math display">\sqrt{3x-1}+(1+x)^2</span>""")

test "RST tables":
let input1 = """
Test 2 column/4 rows table:
Expand Down

0 comments on commit eb29a98

Please sign in to comment.