From d182094b0cef14f7af82a636d92426ae8bd3169b Mon Sep 17 00:00:00 2001
From: Timothee Cour
Date: Sat, 29 Feb 2020 16:30:31 -0800
Subject: [PATCH] fix #8871 runnableExamples now preserves source code
comments, litterals, and all formatting
---
compiler/docgen.nim | 40 +++++++-----
compiler/msgs.nim | 19 +++---
compiler/renderverbatim.nim | 65 ++++++++++++++++++++
nimdoc/testproject/expected/testproject.html | 36 ++++++++---
nimdoc/testproject/expected/theindex.html | 4 ++
nimdoc/testproject/testproject.nim | 16 +++++
6 files changed, 147 insertions(+), 33 deletions(-)
create mode 100644 compiler/renderverbatim.nim
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index 2072945973ce7..ddfbeb549847a 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -17,7 +17,7 @@ import
packages/docutils/rst, packages/docutils/rstgen,
json, xmltree, cgi, trees, types,
typesrenderer, astalgo, lineinfos, intsets,
- pathutils, trees, tables, nimpaths
+ pathutils, trees, tables, nimpaths, renderverbatim
const
exportSection = skField
@@ -494,8 +494,8 @@ proc runAllExamples(d: PDoc) =
rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string])
# removeFile(outp.changeFileExt(ExeExt)) # it's in nimcache, no need to remove
-proc prepareExample(d: PDoc; n: PNode): string =
- ## returns `rdoccmd` for this runnableExamples
+proc prepareExample(d: PDoc; n: PNode): tuple[rdoccmd: string, code: string] =
+ ## returns `rdoccmd` and source code for this runnableExamples
var rdoccmd = ""
if n.len < 2 or n.len > 3: globalError(d.conf, n.info, "runnableExamples invalid")
if n.len == 3:
@@ -512,10 +512,11 @@ proc prepareExample(d: PDoc; n: PNode): string =
docComment,
newTree(nkImportStmt, newStrNode(nkStrLit, d.filename)))
runnableExamples.info = n.info
-
+ let ret = extractRunnableExamplesSource(d.conf, n)
for a in n.lastSon: runnableExamples.add a
+ # we could also use `ret` instead here, to keep sources verbatim
writeExample(d, runnableExamples, rdoccmd)
- result = rdoccmd
+ result = (rdoccmd, ret)
when false:
proc extractImports(n: PNode; result: PNode) =
if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}:
@@ -561,7 +562,7 @@ proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope, previous
if isRunnableExamples(n[0]) and
n.len >= 2 and n.lastSon.kind == nkStmtList:
previousIsRunnable = true
- let rdoccmd = prepareExample(d, n)
+ let (rdoccmd, code) = prepareExample(d, n)
var msg = "Example:"
if rdoccmd.len > 0: msg.add " cmd: " & rdoccmd
dispA(d.conf, dest, "\n$1
\n",
@@ -569,17 +570,22 @@ proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope, previous
inc d.listingCounter
let id = $d.listingCounter
dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim"])
- # this is a rather hacky way to get rid of the initial indentation
- # that the renderer currently produces:
- var i = 0
- var body = n.lastSon
- if body.len == 1 and body.kind == nkStmtList and
- body.lastSon.kind == nkStmtList:
- body = body.lastSon
- for b in body:
- if i > 0: dest.add "\n"
- inc i
- nodeToHighlightedHtml(d, b, dest, {renderRunnableExamples}, nil)
+ when true:
+ var dest2 = ""
+ renderNimCode(dest2, code, isLatex = d.conf.cmd == cmdRst2tex)
+ dest.add dest2
+ else:
+ # this is a rather hacky way to get rid of the initial indentation
+ # that the renderer currently produces:
+ var i = 0
+ var body = n.lastSon
+ if body.len == 1 and body.kind == nkStmtList and
+ body.lastSon.kind == nkStmtList:
+ body = body.lastSon
+ for b in body:
+ if i > 0: dest.add "\n"
+ inc i
+ nodeToHighlightedHtml(d, b, dest, {renderRunnableExamples}, nil)
dest.add(d.config.getOrDefault"doc.listing_end" % id)
else: previousIsRunnable = false
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index a26aa0a3cef67..00efff3aab0a6 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -435,18 +435,21 @@ proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
conf.m.fileInfos[fileIdx.int32].lines.add line
-proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
- if i.fileIndex.int32 < 0: return ""
-
- if conf.m.fileInfos[i.fileIndex.int32].lines.len == 0:
+proc numLines*(conf: ConfigRef, fileIdx: FileIndex): int =
+ result = conf.m.fileInfos[fileIdx.int32].lines.len
+ if result == 0:
try:
- for line in lines(toFullPathConsiderDirty(conf, i)):
- addSourceLine conf, i.fileIndex, line.string
+ for line in lines(toFullPathConsiderDirty(conf, fileIdx).string):
+ addSourceLine conf, fileIdx, line.string
except IOError:
discard
- assert i.fileIndex.int32 < conf.m.fileInfos.len
+ result = conf.m.fileInfos[fileIdx.int32].lines.len
+
+proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
+ if i.fileIndex.int32 < 0: return ""
+ let num = numLines(conf, i.fileIndex)
# can happen if the error points to EOF:
- if i.line.int > conf.m.fileInfos[i.fileIndex.int32].lines.len: return ""
+ if i.line.int > num: return ""
result = conf.m.fileInfos[i.fileIndex.int32].lines[i.line.int-1]
diff --git a/compiler/renderverbatim.nim b/compiler/renderverbatim.nim
new file mode 100644
index 0000000000000..9513353cd92ac
--- /dev/null
+++ b/compiler/renderverbatim.nim
@@ -0,0 +1,65 @@
+import strutils
+from xmltree import addEscaped
+
+import ast, options, msgs
+import packages/docutils/highlite
+
+proc lastNodeRec(n: PNode): PNode =
+ result = n
+ while result.safeLen > 0: result = result[^1]
+
+proc isInIndentationBlock(src: string, indent: int): bool =
+ #[
+ we stop at the first de-indentation; there's an inherent ambiguity with non
+ doc comments since they can have arbitrary indentation, so we just take the
+ practical route and require a runnableExamples to keep its code (including non
+ doc comments) to its indentation level.
+ ]#
+ for j in 0.. last.line and not isInIndentationBlock(src, indent):
+ break
+ if line > first.line: ret.add "\n"
+ if src.len > indent:
+ ret.add src[indent..^1]
+ lastNonemptyPos = ret.len
+ ret = ret[0..$2" % [class, buf]
+
+ while true:
+ getNextToken(toknizr, langNim)
+ case toknizr.kind
+ of gtEof: break # End Of File (or string)
+ else:
+ # TODO: avoid alloc; maybe toOpenArray
+ append(toknizr.kind, substr(code, toknizr.start, toknizr.length + toknizr.start - 1))
diff --git a/nimdoc/testproject/expected/testproject.html b/nimdoc/testproject/expected/testproject.html
index aa29c0c0dcbd4..257cb581b06b7 100644
--- a/nimdoc/testproject/expected/testproject.html
+++ b/nimdoc/testproject/expected/testproject.html
@@ -173,7 +173,9 @@ testproject
Templates
@@ -187,13 +189,11 @@ testproject
This is the top level module.
Example:
-import
- subdir / subdir_b / utils
-
-doAssert bar(3, 4) == 7
-foo(enumValueA, enumValueB)
-for x in "xx":
- discard
+import subdir / subdir_b / utils
+doAssert bar(3, 4) == 7
+foo(enumValueA, enumValueB)
+
+for x in "xx": discard
@@ -331,6 +331,26 @@
+
+template myfn()
+-
+
+
+
Example:
+import std/strutils
+
+
+doAssert "'foo" == "'foo"
+
+doAssert: not "foo".startsWith "ba"
+block:
+ discard 0xff
+
+
+
template foo(a, b: SomeType)
-
diff --git a/nimdoc/testproject/expected/theindex.html b/nimdoc/testproject/expected/theindex.html
index a76f5961f6b88..f78be11eeef16 100644
--- a/nimdoc/testproject/expected/theindex.html
+++ b/nimdoc/testproject/expected/theindex.html
@@ -143,6 +143,10 @@
Index
- testproject: isValid[T](x: T): bool
+- myfn:
- someFunc:
- testproject: someFunc()
diff --git a/nimdoc/testproject/testproject.nim b/nimdoc/testproject/testproject.nim
index d1fcf58cd83e4..b2976fe9a5495 100644
--- a/nimdoc/testproject/testproject.nim
+++ b/nimdoc/testproject/testproject.nim
@@ -9,6 +9,22 @@ runnableExamples:
# bug #11078
for x in "xx": discard
+template myfn*() =
+ runnableExamples:
+ import std/strutils
+ ## line doc comment
+ # bar
+ doAssert "'foo" == "'foo"
+ ##[
+ foo
+ bar
+ ]##
+ doAssert: not "foo".startsWith "ba"
+ block:
+ discard 0xff # elu par cette crapule
+ # should be in
+ # should be out
+
const
C_A* = 0x7FF0000000000000'f64
C_B* = 0o377'i8