* runnableExamples "-b:cpp -r:off": code
This commit is contained in:
parent
4ae341353d
commit
3d20f14193
6 changed files with 104 additions and 37 deletions
|
|
@ -185,6 +185,8 @@ proc mydiv(a, b): int {.raises: [].} =
|
||||||
use `--doccmd:skip` to skip runnableExamples and rst test snippets.
|
use `--doccmd:skip` to skip runnableExamples and rst test snippets.
|
||||||
- new flag `--usenimcache` to output to nimcache (whatever it resolves to after all commands are processed)
|
- new flag `--usenimcache` to output to nimcache (whatever it resolves to after all commands are processed)
|
||||||
and avoids polluting both $pwd and $projectdir. It can be used with any command.
|
and avoids polluting both $pwd and $projectdir. It can be used with any command.
|
||||||
|
- `runnableExamples "-b:cpp -r:off": code` is now supported, allowing to override how an example is compiled and run,
|
||||||
|
for example to change backend or compile only.
|
||||||
|
|
||||||
## Tool changes
|
## Tool changes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import
|
||||||
packages/docutils/rst, packages/docutils/rstgen,
|
packages/docutils/rst, packages/docutils/rstgen,
|
||||||
json, xmltree, cgi, trees, types,
|
json, xmltree, cgi, trees, types,
|
||||||
typesrenderer, astalgo, lineinfos, intsets,
|
typesrenderer, astalgo, lineinfos, intsets,
|
||||||
pathutils, trees
|
pathutils, trees, tables
|
||||||
|
|
||||||
const
|
const
|
||||||
exportSection = skField
|
exportSection = skField
|
||||||
|
|
@ -26,6 +26,12 @@ const
|
||||||
|
|
||||||
type
|
type
|
||||||
TSections = array[TSymKind, Rope]
|
TSections = array[TSymKind, Rope]
|
||||||
|
ExampleGroup = ref object
|
||||||
|
## a group of runnableExamples with same rdoccmd
|
||||||
|
rdoccmd: string ## from 1st arg in `runnableExamples(rdoccmd): body`
|
||||||
|
docCmd: string ## from user config, eg --doccmd:-d:foo
|
||||||
|
code: string ## contains imports; each import contains `body`
|
||||||
|
index: int ## group index
|
||||||
TDocumentor = object of rstgen.RstGenerator
|
TDocumentor = object of rstgen.RstGenerator
|
||||||
modDesc: Rope # module description
|
modDesc: Rope # module description
|
||||||
module: PSym
|
module: PSym
|
||||||
|
|
@ -44,7 +50,7 @@ type
|
||||||
# already. See bug #3655
|
# already. See bug #3655
|
||||||
destFile*: AbsoluteFile
|
destFile*: AbsoluteFile
|
||||||
thisDir*: AbsoluteDir
|
thisDir*: AbsoluteDir
|
||||||
examples: string
|
exampleGroups: OrderedTable[string, ExampleGroup]
|
||||||
wroteCss*: bool
|
wroteCss*: bool
|
||||||
|
|
||||||
PDoc* = ref TDocumentor ## Alias to type less.
|
PDoc* = ref TDocumentor ## Alias to type less.
|
||||||
|
|
@ -443,54 +449,72 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe
|
||||||
dispA(d.conf, result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
|
dispA(d.conf, result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
|
||||||
[escLit])
|
[escLit])
|
||||||
|
|
||||||
proc testExample(d: PDoc; ex: PNode) =
|
proc exampleOutputDir(d: PDoc): AbsoluteDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
|
||||||
|
|
||||||
|
proc writeExample(d: PDoc; ex: PNode, rdoccmd: string) =
|
||||||
if d.conf.errorCounter > 0: return
|
if d.conf.errorCounter > 0: return
|
||||||
let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
|
let outputDir = d.exampleOutputDir
|
||||||
createDir(outputDir)
|
createDir(outputDir)
|
||||||
inc d.exampleCounter
|
inc d.exampleCounter
|
||||||
|
# PRTEMP
|
||||||
let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" &
|
let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" &
|
||||||
"_examples" & $d.exampleCounter & ".nim"))
|
"_examples" & $d.exampleCounter & ".nim"))
|
||||||
#let nimcache = outp.changeFileExt"" & "_nimcache"
|
#let nimcache = outp.changeFileExt"" & "_nimcache"
|
||||||
renderModule(ex, d.filename, outp.string, conf = d.conf)
|
renderModule(ex, d.filename, outp.string, conf = d.conf)
|
||||||
d.examples.add "import r\"" & outp.string & "\"\n"
|
if rdoccmd notin d.exampleGroups: d.exampleGroups[rdoccmd] = ExampleGroup(rdoccmd: rdoccmd, docCmd: d.conf.docCmd, index: d.exampleGroups.len)
|
||||||
|
d.exampleGroups[rdoccmd].code.add "import r\"$1\"\n" % outp.string
|
||||||
|
|
||||||
proc runAllExamples(d: PDoc) =
|
proc runAllExamples(d: PDoc) =
|
||||||
let docCmd = d.conf.docCmd
|
|
||||||
let backend = d.conf.backend
|
let backend = d.conf.backend
|
||||||
# This used to be: `let backend = if isDefined(d.conf, "js"): "js"` (etc), however
|
# This used to be: `let backend = if isDefined(d.conf, "js"): "js"` (etc), however
|
||||||
# using `-d:js` (etc) cannot work properly, eg would fail with `importjs`
|
# using `-d:js` (etc) cannot work properly, eg would fail with `importjs`
|
||||||
# since semantics are affected by `config.backend`, not by isDefined(d.conf, "js")
|
# since semantics are affected by `config.backend`, not by isDefined(d.conf, "js")
|
||||||
if d.examples.len == 0 or docCmd == docCmdSkip: return
|
let outputDir = d.exampleOutputDir
|
||||||
let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
|
for _, group in d.exampleGroups:
|
||||||
let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" &
|
if group.docCmd == docCmdSkip: continue
|
||||||
"_examples.nim"))
|
let outp = outputDir / RelativeFile("$1_group$2_examples.nim" % [d.filename.splitFile.name, $group.index])
|
||||||
writeFile(outp, d.examples)
|
group.code = "# autogenerated by docgen\n# source: $1\n# rdoccmd: $2\n$3" % [d.filename, group.rdoccmd, group.code]
|
||||||
let cmd = "$nim $backend -r --warning:UnusedImport:off --path:$path --nimcache:$nimcache $docCmd $file" % [
|
writeFile(outp, group.code)
|
||||||
"nim", os.getAppFilename(),
|
# most useful semantics is that `docCmd` comes after `rdoccmd`, so that we can (temporarily) override
|
||||||
"backend", $d.conf.backend,
|
# via command line
|
||||||
"path", quoteShell(d.conf.projectPath),
|
let cmd = "$nim $backend -r --warning:UnusedImport:off --path:$path --nimcache:$nimcache $rdoccmd $docCmd $file" % [
|
||||||
"nimcache", quoteShell(outputDir),
|
"nim", os.getAppFilename(),
|
||||||
"file", quoteShell(outp),
|
"backend", $d.conf.backend,
|
||||||
"docCmd", docCmd,
|
"path", quoteShell(d.conf.projectPath),
|
||||||
]
|
"nimcache", quoteShell(outputDir),
|
||||||
if os.execShellCmd(cmd) != 0:
|
"file", quoteShell(outp),
|
||||||
quit "[runnableExamples] failed: generated file: '$1' cmd: $2" % [outp.string, cmd]
|
"rdoccmd", group.rdoccmd,
|
||||||
else:
|
"docCmd", group.docCmd,
|
||||||
# keep generated source file `outp` to allow inspection.
|
]
|
||||||
rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string])
|
if os.execShellCmd(cmd) != 0:
|
||||||
removeFile(outp.changeFileExt(ExeExt))
|
quit "[runnableExamples] failed: generated file: '$1' group: '$2' cmd: $3" % [outp.string, $group[], cmd]
|
||||||
|
else:
|
||||||
|
# keep generated source file `outp` to allow inspection.
|
||||||
|
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
|
||||||
|
var rdoccmd = ""
|
||||||
|
if n.len < 2 or n.len > 3: globalError(d.conf, n.info, "runnableExamples invalid")
|
||||||
|
if n.len == 3:
|
||||||
|
let n1 = n[1]
|
||||||
|
# xxx this should be evaluated during sempass
|
||||||
|
if n1.kind notin nkStrKinds: globalError(d.conf, n1.info, "string litteral expected")
|
||||||
|
rdoccmd = n1.strVal
|
||||||
|
|
||||||
proc prepareExamples(d: PDoc; n: PNode) =
|
|
||||||
var docComment = newTree(nkCommentStmt)
|
var docComment = newTree(nkCommentStmt)
|
||||||
let loc = d.conf.toFileLineCol(n.info)
|
let loc = d.conf.toFileLineCol(n.info)
|
||||||
docComment.comment = "autogenerated by docgen from " & loc
|
|
||||||
|
docComment.comment = "autogenerated by docgen\nloc: $1\nrdoccmd: $2" % [loc, rdoccmd]
|
||||||
var runnableExamples = newTree(nkStmtList,
|
var runnableExamples = newTree(nkStmtList,
|
||||||
docComment,
|
docComment,
|
||||||
newTree(nkImportStmt, newStrNode(nkStrLit, d.filename)))
|
newTree(nkImportStmt, newStrNode(nkStrLit, d.filename)))
|
||||||
runnableExamples.info = n.info
|
runnableExamples.info = n.info
|
||||||
|
|
||||||
for a in n.lastSon: runnableExamples.add a
|
for a in n.lastSon: runnableExamples.add a
|
||||||
testExample(d, runnableExamples)
|
writeExample(d, runnableExamples, rdoccmd)
|
||||||
|
result = rdoccmd
|
||||||
when false:
|
when false:
|
||||||
proc extractImports(n: PNode; result: PNode) =
|
proc extractImports(n: PNode; result: PNode) =
|
||||||
if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}:
|
if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}:
|
||||||
|
|
@ -510,9 +534,11 @@ proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) =
|
||||||
of nkCallKinds:
|
of nkCallKinds:
|
||||||
if isRunnableExamples(n[0]) and
|
if isRunnableExamples(n[0]) and
|
||||||
n.len >= 2 and n.lastSon.kind == nkStmtList:
|
n.len >= 2 and n.lastSon.kind == nkStmtList:
|
||||||
prepareExamples(d, n)
|
let rdoccmd = prepareExample(d, n)
|
||||||
|
var msg = "Example:"
|
||||||
|
if rdoccmd.len > 0: msg.add " cmd: " & rdoccmd
|
||||||
dispA(d.conf, dest, "\n<p><strong class=\"examples_text\">$1</strong></p>\n",
|
dispA(d.conf, dest, "\n<p><strong class=\"examples_text\">$1</strong></p>\n",
|
||||||
"\n\\textbf{$1}\n", [rope"Examples:"])
|
"\n\\textbf{$1}\n", [msg.rope])
|
||||||
inc d.listingCounter
|
inc d.listingCounter
|
||||||
let id = $d.listingCounter
|
let id = $d.listingCounter
|
||||||
dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim"])
|
dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim"])
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ else:
|
||||||
Ordinal* = OrdinalImpl | uint | uint64
|
Ordinal* = OrdinalImpl | uint | uint64
|
||||||
|
|
||||||
when defined(nimHasRunnableExamples):
|
when defined(nimHasRunnableExamples):
|
||||||
proc runnableExamples*(body: untyped) {.magic: "RunnableExamples".}
|
proc runnableExamples*(rdoccmd = "", body: untyped) {.magic: "RunnableExamples".}
|
||||||
## A section you should use to mark `runnable example`:idx: code with.
|
## A section you should use to mark `runnable example`:idx: code with.
|
||||||
##
|
##
|
||||||
## - In normal debug and release builds code within
|
## - In normal debug and release builds code within
|
||||||
|
|
@ -139,10 +139,15 @@ when defined(nimHasRunnableExamples):
|
||||||
## assert double(5) == 10
|
## assert double(5) == 10
|
||||||
## block: ## at block scope
|
## block: ## at block scope
|
||||||
## defer: echo "done"
|
## defer: echo "done"
|
||||||
##
|
|
||||||
## result = 2 * x
|
## result = 2 * x
|
||||||
|
## runnableExamples "-d:foo -b:cpp":
|
||||||
|
## import std/compilesettings
|
||||||
|
## doAssert querySetting(backend) == "cpp"
|
||||||
|
## runnableExamples "-r:off": ## this one is only compiled
|
||||||
|
## import std/browsers
|
||||||
|
## openDefaultBrowser "https://forum.nim-lang.org/"
|
||||||
else:
|
else:
|
||||||
template runnableExamples*(body: untyped) =
|
template runnableExamples*(doccmd = "", body: untyped) =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc declared*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.}
|
proc declared*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,13 @@ proc testNimDoc(prjDir, docsDir: string; switches: NimSwitches; fixup = false) =
|
||||||
nimBuildIndexSwitches = switches.buildIndex.join(" ")
|
nimBuildIndexSwitches = switches.buildIndex.join(" ")
|
||||||
|
|
||||||
putEnv("SOURCE_DATE_EPOCH", "100000")
|
putEnv("SOURCE_DATE_EPOCH", "100000")
|
||||||
|
const nimExe = getCurrentCompilerExe() # so that `bin/nim_temp r nimdoc/tester.nim` works
|
||||||
|
|
||||||
if nimDocSwitches != "":
|
if nimDocSwitches != "":
|
||||||
exec("nim doc $1" % [nimDocSwitches])
|
exec("$1 doc $2" % [nimExe, nimDocSwitches])
|
||||||
|
|
||||||
if nimBuildIndexSwitches != "":
|
if nimBuildIndexSwitches != "":
|
||||||
exec("nim buildIndex $1" % [nimBuildIndexSwitches])
|
exec("$1 buildIndex $2" % [nimExe, nimBuildIndexSwitches])
|
||||||
|
|
||||||
for expected in walkDirRec(prjDir / "expected/"):
|
for expected in walkDirRec(prjDir / "expected/"):
|
||||||
let produced = expected.replace('\\', '/').replace("/expected/", "/$1/" % [docsDir])
|
let produced = expected.replace('\\', '/').replace("/expected/", "/$1/" % [docsDir])
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ function main() {
|
||||||
<div id="tocRoot"></div>
|
<div id="tocRoot"></div>
|
||||||
|
|
||||||
<p class="module-desc">This is the top level module.
|
<p class="module-desc">This is the top level module.
|
||||||
<p><strong class="examples_text">Examples:</strong></p>
|
<p><strong class="examples_text">Example:</strong></p>
|
||||||
<pre class="listing"><span class="Keyword">import</span>
|
<pre class="listing"><span class="Keyword">import</span>
|
||||||
<span class="Identifier">subdir</span> <span class="Operator">/</span> <span class="Identifier">subdir_b</span> <span class="Operator">/</span> <span class="Identifier">utils</span>
|
<span class="Identifier">subdir</span> <span class="Operator">/</span> <span class="Identifier">subdir_b</span> <span class="Operator">/</span> <span class="Identifier">utils</span>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
discard """
|
discard """
|
||||||
cmd: "nim doc $file"
|
cmd: "nim doc --doccmd:-d:testFooExternal --hints:off $file"
|
||||||
action: "compile"
|
action: "compile"
|
||||||
nimout: '''
|
nimout: '''
|
||||||
foo1
|
foo1
|
||||||
|
|
@ -66,6 +66,39 @@ when true: # issue #12746
|
||||||
# specifying Error is culprit
|
# specifying Error is culprit
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
when true: # runnableExamples with rdoccmd
|
||||||
|
runnableExamples "-d:testFoo -d:testBar":
|
||||||
|
doAssert defined(testFoo) and defined(testBar)
|
||||||
|
doAssert defined(testFooExternal)
|
||||||
|
runnableExamples "-d:testFoo2":
|
||||||
|
doAssert defined(testFoo2)
|
||||||
|
doAssert not defined(testFoo) # doesn't get confused by other examples
|
||||||
|
|
||||||
|
## all these syntaxes work too
|
||||||
|
runnableExamples("-d:testFoo2"): discard
|
||||||
|
runnableExamples(): discard
|
||||||
|
runnableExamples: discard
|
||||||
|
runnableExamples "-r:off": # issue #10731
|
||||||
|
doAssert false ## we compile only (-r:off), so this won't be run
|
||||||
|
runnableExamples "-b:js":
|
||||||
|
import std/compilesettings
|
||||||
|
proc startsWith*(s, prefix: cstring): bool {.noSideEffect, importjs: "#.startsWith(#)".}
|
||||||
|
doAssert querySetting(backend) == "js"
|
||||||
|
runnableExamples "-b:cpp":
|
||||||
|
static: doAssert defined(cpp)
|
||||||
|
type std_exception {.importcpp: "std::exception", header: "<exception>".} = object
|
||||||
|
|
||||||
|
proc fun2*() =
|
||||||
|
runnableExamples "-d:foo": discard # checks that it also works inside procs
|
||||||
|
|
||||||
|
when false: # future work
|
||||||
|
# passing non-string-litterals (for reuse)
|
||||||
|
const a = "-b:cpp"
|
||||||
|
runnableExamples(a): discard
|
||||||
|
|
||||||
|
# passing seq (to run with multiple compilation options)
|
||||||
|
runnableExamples(@["-b:cpp", "-b:js"]): discard
|
||||||
|
|
||||||
# also check for runnableExamples at module scope
|
# also check for runnableExamples at module scope
|
||||||
runnableExamples:
|
runnableExamples:
|
||||||
block:
|
block:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue