diff options
-rw-r--r-- | changelog.md | 2 | ||||
-rw-r--r-- | compiler/docgen.nim | 88 | ||||
-rw-r--r-- | lib/system.nim | 11 | ||||
-rw-r--r-- | nimdoc/tester.nim | 5 | ||||
-rw-r--r-- | nimdoc/testproject/expected/testproject.html | 2 | ||||
-rw-r--r-- | tests/nimdoc/trunnableexamples.nim | 35 |
6 files changed, 105 insertions, 38 deletions
diff --git a/changelog.md b/changelog.md index 88619b1c2..0e1af73c1 100644 --- a/changelog.md +++ b/changelog.md @@ -185,6 +185,8 @@ proc mydiv(a, b): int {.raises: [].} = 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) 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 diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 6123d49cf..8b5201029 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 + pathutils, trees, tables const exportSection = skField @@ -26,6 +26,12 @@ const type 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 modDesc: Rope # module description module: PSym @@ -44,7 +50,7 @@ type # already. See bug #3655 destFile*: AbsoluteFile thisDir*: AbsoluteDir - examples: string + exampleGroups: OrderedTable[string, ExampleGroup] wroteCss*: bool 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}", [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 - let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples" + let outputDir = d.exampleOutputDir createDir(outputDir) inc d.exampleCounter + # PRTEMP let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" & "_examples" & $d.exampleCounter & ".nim")) #let nimcache = outp.changeFileExt"" & "_nimcache" 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) = - let docCmd = d.conf.docCmd let backend = d.conf.backend # 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` # 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.conf.getNimcacheDir / RelativeDir"runnableExamples" - let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" & - "_examples.nim")) - writeFile(outp, d.examples) - let cmd = "$nim $backend -r --warning:UnusedImport:off --path:$path --nimcache:$nimcache $docCmd $file" % [ - "nim", os.getAppFilename(), - "backend", $d.conf.backend, - "path", quoteShell(d.conf.projectPath), - "nimcache", quoteShell(outputDir), - "file", quoteShell(outp), - "docCmd", docCmd, - ] - if os.execShellCmd(cmd) != 0: - quit "[runnableExamples] failed: generated file: '$1' cmd: $2" % [outp.string, cmd] - else: - # keep generated source file `outp` to allow inspection. - rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string]) - removeFile(outp.changeFileExt(ExeExt)) - + let outputDir = d.exampleOutputDir + for _, group in d.exampleGroups: + if group.docCmd == docCmdSkip: continue + let outp = outputDir / RelativeFile("$1_group$2_examples.nim" % [d.filename.splitFile.name, $group.index]) + group.code = "# autogenerated by docgen\n# source: $1\n# rdoccmd: $2\n$3" % [d.filename, group.rdoccmd, group.code] + writeFile(outp, group.code) + # most useful semantics is that `docCmd` comes after `rdoccmd`, so that we can (temporarily) override + # via command line + let cmd = "$nim $backend -r --warning:UnusedImport:off --path:$path --nimcache:$nimcache $rdoccmd $docCmd $file" % [ + "nim", os.getAppFilename(), + "backend", $d.conf.backend, + "path", quoteShell(d.conf.projectPath), + "nimcache", quoteShell(outputDir), + "file", quoteShell(outp), + "rdoccmd", group.rdoccmd, + "docCmd", group.docCmd, + ] + if os.execShellCmd(cmd) != 0: + 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) 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, docComment, newTree(nkImportStmt, newStrNode(nkStrLit, d.filename))) runnableExamples.info = n.info + for a in n.lastSon: runnableExamples.add a - testExample(d, runnableExamples) + writeExample(d, runnableExamples, rdoccmd) + result = rdoccmd when false: proc extractImports(n: PNode; result: PNode) = if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}: @@ -510,9 +534,11 @@ proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) = of nkCallKinds: if isRunnableExamples(n[0]) and 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", - "\n\\textbf{$1}\n", [rope"Examples:"]) + "\n\\textbf{$1}\n", [msg.rope]) inc d.listingCounter let id = $d.listingCounter dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim"]) diff --git a/lib/system.nim b/lib/system.nim index e5b359c70..85be43cba 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -117,7 +117,7 @@ else: Ordinal* = OrdinalImpl | uint | uint64 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. ## ## - In normal debug and release builds code within @@ -139,10 +139,15 @@ when defined(nimHasRunnableExamples): ## assert double(5) == 10 ## block: ## at block scope ## defer: echo "done" - ## ## 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: - template runnableExamples*(body: untyped) = + template runnableExamples*(doccmd = "", body: untyped) = discard proc declared*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} diff --git a/nimdoc/tester.nim b/nimdoc/tester.nim index a1500455e..58272a9b6 100644 --- a/nimdoc/tester.nim +++ b/nimdoc/tester.nim @@ -24,12 +24,13 @@ proc testNimDoc(prjDir, docsDir: string; switches: NimSwitches; fixup = false) = nimBuildIndexSwitches = switches.buildIndex.join(" ") putEnv("SOURCE_DATE_EPOCH", "100000") + const nimExe = getCurrentCompilerExe() # so that `bin/nim_temp r nimdoc/tester.nim` works if nimDocSwitches != "": - exec("nim doc $1" % [nimDocSwitches]) + exec("$1 doc $2" % [nimExe, nimDocSwitches]) if nimBuildIndexSwitches != "": - exec("nim buildIndex $1" % [nimBuildIndexSwitches]) + exec("$1 buildIndex $2" % [nimExe, nimBuildIndexSwitches]) for expected in walkDirRec(prjDir / "expected/"): let produced = expected.replace('\\', '/').replace("/expected/", "/$1/" % [docsDir]) diff --git a/nimdoc/testproject/expected/testproject.html b/nimdoc/testproject/expected/testproject.html index 77f178265..4eb8ed44c 100644 --- a/nimdoc/testproject/expected/testproject.html +++ b/nimdoc/testproject/expected/testproject.html @@ -183,7 +183,7 @@ function main() { <div id="tocRoot"></div> <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> <span class="Identifier">subdir</span> <span class="Operator">/</span> <span class="Identifier">subdir_b</span> <span class="Operator">/</span> <span class="Identifier">utils</span> diff --git a/tests/nimdoc/trunnableexamples.nim b/tests/nimdoc/trunnableexamples.nim index e3ae6b9cb..73cfacd43 100644 --- a/tests/nimdoc/trunnableexamples.nim +++ b/tests/nimdoc/trunnableexamples.nim @@ -1,5 +1,5 @@ discard """ -cmd: "nim doc $file" +cmd: "nim doc --doccmd:-d:testFooExternal --hints:off $file" action: "compile" nimout: ''' foo1 @@ -66,6 +66,39 @@ when true: # issue #12746 # specifying Error is culprit 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 runnableExamples: block: |