the documentation generator now supports system.runnableExamples
This commit is contained in:
parent
c1782fac21
commit
8d1a5dc8e7
9 changed files with 132 additions and 58 deletions
|
|
@ -94,3 +94,7 @@ This now needs to be written as:
|
||||||
- [``poly``](https://github.com/lcrees/polynumeric)
|
- [``poly``](https://github.com/lcrees/polynumeric)
|
||||||
- [``pdcurses``](https://github.com/lcrees/pdcurses)
|
- [``pdcurses``](https://github.com/lcrees/pdcurses)
|
||||||
- [``romans``](https://github.com/lcrees/romans)
|
- [``romans``](https://github.com/lcrees/romans)
|
||||||
|
|
||||||
|
- Added ``system.runnableExamples`` to make examples in Nim's documentation easier
|
||||||
|
to write and test. The examples are tested as the last step of
|
||||||
|
``nim doc``.
|
||||||
|
|
|
||||||
|
|
@ -639,7 +639,7 @@ type
|
||||||
mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl,
|
mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl,
|
||||||
mNHint, mNWarning, mNError,
|
mNHint, mNWarning, mNError,
|
||||||
mInstantiationInfo, mGetTypeInfo, mNGenSym,
|
mInstantiationInfo, mGetTypeInfo, mNGenSym,
|
||||||
mNimvm, mIntDefine, mStrDefine
|
mNimvm, mIntDefine, mStrDefine, mRunnableExamples
|
||||||
|
|
||||||
# things that we can evaluate safely at compile time, even if not asked for it:
|
# things that we can evaluate safely at compile time, even if not asked for it:
|
||||||
const
|
const
|
||||||
|
|
|
||||||
|
|
@ -110,3 +110,4 @@ proc initDefines*() =
|
||||||
when false: defineSymbol("nimHasOpt")
|
when false: defineSymbol("nimHasOpt")
|
||||||
defineSymbol("nimNoArrayToCstringConversion")
|
defineSymbol("nimNoArrayToCstringConversion")
|
||||||
defineSymbol("nimNewRoof")
|
defineSymbol("nimNewRoof")
|
||||||
|
defineSymbol("nimHasRunnableExamples")
|
||||||
|
|
|
||||||
|
|
@ -204,11 +204,86 @@ proc getPlainDocstring(n: PNode): string =
|
||||||
if n.comment != nil and startsWith(n.comment, "##"):
|
if n.comment != nil and startsWith(n.comment, "##"):
|
||||||
result = n.comment
|
result = n.comment
|
||||||
if result.len < 1:
|
if result.len < 1:
|
||||||
if n.kind notin {nkEmpty..nkNilLit}:
|
for i in countup(0, safeLen(n)-1):
|
||||||
for i in countup(0, len(n)-1):
|
|
||||||
result = getPlainDocstring(n.sons[i])
|
result = getPlainDocstring(n.sons[i])
|
||||||
if result.len > 0: return
|
if result.len > 0: return
|
||||||
|
|
||||||
|
proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}) =
|
||||||
|
var r: TSrcGen
|
||||||
|
var literal = ""
|
||||||
|
initTokRender(r, n, renderFlags)
|
||||||
|
var kind = tkEof
|
||||||
|
while true:
|
||||||
|
getNextTok(r, kind, literal)
|
||||||
|
case kind
|
||||||
|
of tkEof:
|
||||||
|
break
|
||||||
|
of tkComment:
|
||||||
|
dispA(result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
|
||||||
|
[rope(esc(d.target, literal))])
|
||||||
|
of tokKeywordLow..tokKeywordHigh:
|
||||||
|
dispA(result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
|
||||||
|
[rope(literal)])
|
||||||
|
of tkOpr:
|
||||||
|
dispA(result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}",
|
||||||
|
[rope(esc(d.target, literal))])
|
||||||
|
of tkStrLit..tkTripleStrLit:
|
||||||
|
dispA(result, "<span class=\"StringLit\">$1</span>",
|
||||||
|
"\\spanStringLit{$1}", [rope(esc(d.target, literal))])
|
||||||
|
of tkCharLit:
|
||||||
|
dispA(result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}",
|
||||||
|
[rope(esc(d.target, literal))])
|
||||||
|
of tkIntLit..tkUInt64Lit:
|
||||||
|
dispA(result, "<span class=\"DecNumber\">$1</span>",
|
||||||
|
"\\spanDecNumber{$1}", [rope(esc(d.target, literal))])
|
||||||
|
of tkFloatLit..tkFloat128Lit:
|
||||||
|
dispA(result, "<span class=\"FloatNumber\">$1</span>",
|
||||||
|
"\\spanFloatNumber{$1}", [rope(esc(d.target, literal))])
|
||||||
|
of tkSymbol:
|
||||||
|
dispA(result, "<span class=\"Identifier\">$1</span>",
|
||||||
|
"\\spanIdentifier{$1}", [rope(esc(d.target, literal))])
|
||||||
|
of tkSpaces, tkInvalid:
|
||||||
|
add(result, literal)
|
||||||
|
of tkCurlyDotLe:
|
||||||
|
dispA(result, """<span class="Other pragmabegin">$1</span><div class="pragma">""",
|
||||||
|
"\\spanOther{$1}",
|
||||||
|
[rope(esc(d.target, literal))])
|
||||||
|
of tkCurlyDotRi:
|
||||||
|
dispA(result, "</div><span class=\"Other pragmaend\">$1</span>",
|
||||||
|
"\\spanOther{$1}",
|
||||||
|
[rope(esc(d.target, literal))])
|
||||||
|
of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
|
||||||
|
tkBracketDotLe, tkBracketDotRi, tkParDotLe,
|
||||||
|
tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot,
|
||||||
|
tkAccent, tkColonColon,
|
||||||
|
tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr:
|
||||||
|
dispA(result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
|
||||||
|
[rope(esc(d.target, literal))])
|
||||||
|
|
||||||
|
proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) =
|
||||||
|
case n.kind
|
||||||
|
of nkCallKinds:
|
||||||
|
if n[0].kind == nkSym and n[0].sym.magic == mRunnableExamples and
|
||||||
|
n.len >= 2 and n.lastSon.kind == nkStmtList:
|
||||||
|
dispA(dest, "\n<strong class=\"examples_text\">$1</strong>\n",
|
||||||
|
"\n\\textbf{$1}\n", [rope"Examples:"])
|
||||||
|
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: body = body.lastSon
|
||||||
|
for b in body:
|
||||||
|
if i > 0: dest.add "\n"
|
||||||
|
inc i
|
||||||
|
nodeToHighlightedHtml(d, b, dest, {})
|
||||||
|
dest.add(d.config.getOrDefault"doc.listing_end" % id)
|
||||||
|
else: discard
|
||||||
|
for i in 0 ..< n.safeLen:
|
||||||
|
getAllRunnableExamples(d, n[i], dest)
|
||||||
|
|
||||||
when false:
|
when false:
|
||||||
proc findDocComment(n: PNode): PNode =
|
proc findDocComment(n: PNode): PNode =
|
||||||
if n == nil: return nil
|
if n == nil: return nil
|
||||||
|
|
@ -379,11 +454,12 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
|
||||||
let
|
let
|
||||||
name = getName(d, nameNode)
|
name = getName(d, nameNode)
|
||||||
nameRope = name.rope
|
nameRope = name.rope
|
||||||
plainDocstring = getPlainDocstring(n) # call here before genRecComment!
|
var plainDocstring = getPlainDocstring(n) # call here before genRecComment!
|
||||||
var result: Rope = nil
|
var result: Rope = nil
|
||||||
var literal, plainName = ""
|
var literal, plainName = ""
|
||||||
var kind = tkEof
|
var kind = tkEof
|
||||||
var comm = genRecComment(d, n) # call this here for the side-effect!
|
var comm = genRecComment(d, n) # call this here for the side-effect!
|
||||||
|
getAllRunnableExamples(d, n, comm)
|
||||||
var r: TSrcGen
|
var r: TSrcGen
|
||||||
# Obtain the plain rendered string for hyperlink titles.
|
# Obtain the plain rendered string for hyperlink titles.
|
||||||
initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments,
|
initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments,
|
||||||
|
|
@ -395,53 +471,7 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
|
||||||
plainName.add(literal)
|
plainName.add(literal)
|
||||||
|
|
||||||
# Render the HTML hyperlink.
|
# Render the HTML hyperlink.
|
||||||
initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments})
|
nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, renderDocComments})
|
||||||
while true:
|
|
||||||
getNextTok(r, kind, literal)
|
|
||||||
case kind
|
|
||||||
of tkEof:
|
|
||||||
break
|
|
||||||
of tkComment:
|
|
||||||
dispA(result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
|
|
||||||
[rope(esc(d.target, literal))])
|
|
||||||
of tokKeywordLow..tokKeywordHigh:
|
|
||||||
dispA(result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
|
|
||||||
[rope(literal)])
|
|
||||||
of tkOpr:
|
|
||||||
dispA(result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}",
|
|
||||||
[rope(esc(d.target, literal))])
|
|
||||||
of tkStrLit..tkTripleStrLit:
|
|
||||||
dispA(result, "<span class=\"StringLit\">$1</span>",
|
|
||||||
"\\spanStringLit{$1}", [rope(esc(d.target, literal))])
|
|
||||||
of tkCharLit:
|
|
||||||
dispA(result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}",
|
|
||||||
[rope(esc(d.target, literal))])
|
|
||||||
of tkIntLit..tkUInt64Lit:
|
|
||||||
dispA(result, "<span class=\"DecNumber\">$1</span>",
|
|
||||||
"\\spanDecNumber{$1}", [rope(esc(d.target, literal))])
|
|
||||||
of tkFloatLit..tkFloat128Lit:
|
|
||||||
dispA(result, "<span class=\"FloatNumber\">$1</span>",
|
|
||||||
"\\spanFloatNumber{$1}", [rope(esc(d.target, literal))])
|
|
||||||
of tkSymbol:
|
|
||||||
dispA(result, "<span class=\"Identifier\">$1</span>",
|
|
||||||
"\\spanIdentifier{$1}", [rope(esc(d.target, literal))])
|
|
||||||
of tkSpaces, tkInvalid:
|
|
||||||
add(result, literal)
|
|
||||||
of tkCurlyDotLe:
|
|
||||||
dispA(result, """<span class="Other pragmabegin">$1</span><div class="pragma">""",
|
|
||||||
"\\spanOther{$1}",
|
|
||||||
[rope(esc(d.target, literal))])
|
|
||||||
of tkCurlyDotRi:
|
|
||||||
dispA(result, "</div><span class=\"Other pragmaend\">$1</span>",
|
|
||||||
"\\spanOther{$1}",
|
|
||||||
[rope(esc(d.target, literal))])
|
|
||||||
of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
|
|
||||||
tkBracketDotLe, tkBracketDotRi, tkParDotLe,
|
|
||||||
tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot,
|
|
||||||
tkAccent, tkColonColon,
|
|
||||||
tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr:
|
|
||||||
dispA(result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
|
|
||||||
[rope(esc(d.target, literal))])
|
|
||||||
|
|
||||||
inc(d.id)
|
inc(d.id)
|
||||||
let
|
let
|
||||||
|
|
@ -609,10 +639,7 @@ proc generateJson*(d: PDoc, n: PNode) =
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string =
|
proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string =
|
||||||
var
|
result = getName(d, nameNode) & "\n"
|
||||||
name = getName(d, nameNode)
|
|
||||||
|
|
||||||
result = name & "\n"
|
|
||||||
|
|
||||||
proc generateTags*(d: PDoc, n: PNode, r: var Rope) =
|
proc generateTags*(d: PDoc, n: PNode, r: var Rope) =
|
||||||
case n.kind
|
case n.kind
|
||||||
|
|
|
||||||
|
|
@ -570,6 +570,18 @@ proc myProcess(context: PPassContext, n: PNode): PNode =
|
||||||
result = ast.emptyNode
|
result = ast.emptyNode
|
||||||
#if gCmd == cmdIdeTools: findSuggest(c, n)
|
#if gCmd == cmdIdeTools: findSuggest(c, n)
|
||||||
|
|
||||||
|
proc testExamples(c: PContext) =
|
||||||
|
let inp = toFullPath(c.module.info)
|
||||||
|
let outp = inp.changeFileExt"" & "_examples.nim"
|
||||||
|
renderModule(c.runnableExamples, inp, outp)
|
||||||
|
let backend = if isDefined("js"): "js"
|
||||||
|
elif isDefined("cpp"): "cpp"
|
||||||
|
elif isDefined("objc"): "objc"
|
||||||
|
else: "c"
|
||||||
|
if os.execShellCmd("nim " & backend & " -r " & outp) != 0:
|
||||||
|
quit "[Examples] failed"
|
||||||
|
removeFile(outp)
|
||||||
|
|
||||||
proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode =
|
proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode =
|
||||||
var c = PContext(context)
|
var c = PContext(context)
|
||||||
if gCmd == cmdIdeTools and not c.suggestionsMade:
|
if gCmd == cmdIdeTools and not c.suggestionsMade:
|
||||||
|
|
@ -584,5 +596,6 @@ proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode =
|
||||||
result.add(c.module.ast)
|
result.add(c.module.ast)
|
||||||
popOwner(c)
|
popOwner(c)
|
||||||
popProcCon(c)
|
popProcCon(c)
|
||||||
|
if c.runnableExamples != nil: testExamples(c)
|
||||||
|
|
||||||
const semPass* = makePass(myOpen, myOpenCached, myProcess, myClose)
|
const semPass* = makePass(myOpen, myOpenCached, myProcess, myClose)
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ type
|
||||||
# the generic type has been constructed completely. See
|
# the generic type has been constructed completely. See
|
||||||
# tests/destructor/topttree.nim for an example that
|
# tests/destructor/topttree.nim for an example that
|
||||||
# would otherwise fail.
|
# would otherwise fail.
|
||||||
|
runnableExamples*: PNode
|
||||||
|
|
||||||
proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair =
|
proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair =
|
||||||
result.genericSym = s
|
result.genericSym = s
|
||||||
|
|
|
||||||
|
|
@ -1847,6 +1847,17 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
|
||||||
analyseIfAddressTakenInCall(c, result)
|
analyseIfAddressTakenInCall(c, result)
|
||||||
if callee.magic != mNone:
|
if callee.magic != mNone:
|
||||||
result = magicsAfterOverloadResolution(c, result, flags)
|
result = magicsAfterOverloadResolution(c, result, flags)
|
||||||
|
of mRunnableExamples:
|
||||||
|
if gCmd == cmdDoc and n.len >= 2 and n.lastSon.kind == nkStmtList:
|
||||||
|
if sfMainModule in c.module.flags:
|
||||||
|
let inp = toFullPath(c.module.info)
|
||||||
|
if c.runnableExamples == nil:
|
||||||
|
c.runnableExamples = newTree(nkStmtList,
|
||||||
|
newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp))))
|
||||||
|
c.runnableExamples.add newTree(nkBlockStmt, emptyNode, n.lastSon)
|
||||||
|
result = n
|
||||||
|
else:
|
||||||
|
result = emptyNode
|
||||||
else:
|
else:
|
||||||
result = semDirectOp(c, n, flags)
|
result = semDirectOp(c, n, flags)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ type
|
||||||
target*: OutputTarget
|
target*: OutputTarget
|
||||||
config*: StringTableRef
|
config*: StringTableRef
|
||||||
splitAfter*: int # split too long entries in the TOC
|
splitAfter*: int # split too long entries in the TOC
|
||||||
listingCounter: int
|
listingCounter*: int
|
||||||
tocPart*: seq[TocEntry]
|
tocPart*: seq[TocEntry]
|
||||||
hasToc*: bool
|
hasToc*: bool
|
||||||
theIndex: string # Contents of the index file to be dumped at the end.
|
theIndex: string # Contents of the index file to be dumped at the end.
|
||||||
|
|
|
||||||
|
|
@ -3992,3 +3992,20 @@ when defined(windows) and appType == "console" and defined(nimSetUtf8CodePage):
|
||||||
proc setConsoleOutputCP(codepage: cint): cint {.stdcall, dynlib: "kernel32",
|
proc setConsoleOutputCP(codepage: cint): cint {.stdcall, dynlib: "kernel32",
|
||||||
importc: "SetConsoleOutputCP".}
|
importc: "SetConsoleOutputCP".}
|
||||||
discard setConsoleOutputCP(65001) # 65001 - utf-8 codepage
|
discard setConsoleOutputCP(65001) # 65001 - utf-8 codepage
|
||||||
|
|
||||||
|
|
||||||
|
when defined(nimHasRunnableExamples):
|
||||||
|
proc runnableExamples*(body: untyped) {.magic: "RunnableExamples".}
|
||||||
|
## A section you should use to mark `runnable example`:idx: code with.
|
||||||
|
##
|
||||||
|
## - In normal debug and release builds code within
|
||||||
|
## a ``runnableExamples`` section is ignored.
|
||||||
|
## - The documentation generator is aware of these examples and considers them
|
||||||
|
## part of the ``##`` doc comment. As the last step of documentation
|
||||||
|
## generation the examples are put into an ``$file_example.nim`` file,
|
||||||
|
## compiled and tested. The collected examples are
|
||||||
|
## put into their own module to ensure the examples do not refer to
|
||||||
|
## non-exported symbols.
|
||||||
|
else:
|
||||||
|
template runnableExamples*(body: untyped) =
|
||||||
|
discard
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue