docgen: get rid of ropes (#17939)

This commit is contained in:
Andrey Makarov 2021-05-05 10:10:05 +03:00 • committed by GitHub
commit 706562f661
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -11,7 +11,7 @@
# by knowing how the anchors are going to be named. # by knowing how the anchors are going to be named.
import import
ast, strutils, strtabs, options, msgs, os, ropes, idents, ast, strutils, strtabs, options, msgs, os, idents,
wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast, wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast,
packages/docutils/rst, packages/docutils/rstgen, packages/docutils/rst, packages/docutils/rstgen,
json, xmltree, trees, types, json, xmltree, trees, types,
@ -28,7 +28,7 @@ const
DocColOffset = "## ".len # assuming that a space was added after ## DocColOffset = "## ".len # assuming that a space was added after ##
type type
TSections = array[TSymKind, Rope] TSections = array[TSymKind, string]
ExampleGroup = ref object ExampleGroup = ref object
## a group of runnableExamples with same rdoccmd ## a group of runnableExamples with same rdoccmd
rdoccmd: string ## from 1st arg in `runnableExamples(rdoccmd): body` rdoccmd: string ## from 1st arg in `runnableExamples(rdoccmd): body`
@ -36,11 +36,11 @@ type
code: string ## contains imports; each import contains `body` code: string ## contains imports; each import contains `body`
index: int ## group index index: int ## group index
TDocumentor = object of rstgen.RstGenerator TDocumentor = object of rstgen.RstGenerator
modDesc: Rope # module description modDesc: string # module description
module: PSym module: PSym
modDeprecationMsg: Rope modDeprecationMsg: string
toc, toc2, section: TSections toc, toc2, section: TSections
tocTable: array[TSymKind, Table[string, Rope]] tocTable: array[TSymKind, Table[string, string]]
indexValFilename: string indexValFilename: string
analytics: string # Google Analytics javascript, "" if doesn't exist analytics: string # Google Analytics javascript, "" if doesn't exist
seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML. seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML.
@ -247,7 +247,8 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef,
result.destFile = getOutFile2(conf, presentationPath(conf, filename), outExt, false).string result.destFile = getOutFile2(conf, presentationPath(conf, filename), outExt, false).string
result.thisDir = result.destFile.AbsoluteFile.splitFile.dir result.thisDir = result.destFile.AbsoluteFile.splitFile.dir
template dispA(conf: ConfigRef; dest: var Rope, xml, tex: string, args: openArray[Rope]) = template dispA(conf: ConfigRef; dest: var string, xml, tex: string,
args: openArray[string]) =
if conf.cmd != cmdRst2tex: dest.addf(xml, args) if conf.cmd != cmdRst2tex: dest.addf(xml, args)
else: dest.addf(tex, args) else: dest.addf(tex, args)
@ -257,64 +258,6 @@ proc getVarIdx(varnames: openArray[string], id: string): int =
return i return i
result = -1 result = -1
proc ropeFormatNamedVars(conf: ConfigRef; frmt: FormatStr,
varnames: openArray[string],
varvalues: openArray[Rope]): Rope =
var i = 0
result = nil
var num = 0
while i < frmt.len:
if frmt[i] == '$':
inc(i) # skip '$'
case frmt[i]
of '#':
result.add(varvalues[num])
inc(num)
inc(i)
of '$':
result.add("$")
inc(i)
of '0'..'9':
var j = 0
while true:
j = (j * 10) + ord(frmt[i]) - ord('0')
inc(i)
if (i > frmt.len + 0 - 1) or not (frmt[i] in {'0'..'9'}): break
if j > high(varvalues) + 1:
rawMessage(conf, errGenerated, "Invalid format string; too many $s: " & frmt)
num = j
result.add(varvalues[j - 1])
of 'A'..'Z', 'a'..'z', '\x80'..'\xFF':
var id = ""
while true:
id.add(frmt[i])
inc(i)
if not (frmt[i] in {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}): break
var idx = getVarIdx(varnames, id)
if idx >= 0: result.add(varvalues[idx])
else: rawMessage(conf, errGenerated, "unknown substition variable: " & id)
of '{':
var id = ""
inc(i)
while i < frmt.len and frmt[i] != '}':
id.add(frmt[i])
inc(i)
if i >= frmt.len:
rawMessage(conf, errGenerated, "expected closing '}'")
else:
inc(i) # skip }
# search for the variable:
let idx = getVarIdx(varnames, id)
if idx >= 0: result.add(varvalues[idx])
else: rawMessage(conf, errGenerated, "unknown substition variable: " & id)
else:
result.add("$")
var start = i
while i < frmt.len:
if frmt[i] != '$': inc(i)
else: break
if i - 1 >= start: result.add(substr(frmt, start, i - 1))
proc genComment(d: PDoc, n: PNode): string = proc genComment(d: PDoc, n: PNode): string =
result = "" result = ""
if n.comment.len > 0: if n.comment.len > 0:
@ -329,23 +272,23 @@ proc genComment(d: PDoc, n: PNode): string =
(var dummy: bool; dummy), d.options, d.conf), (var dummy: bool; dummy), d.options, d.conf),
result) result)
proc genRecCommentAux(d: PDoc, n: PNode): Rope = proc genRecCommentAux(d: PDoc, n: PNode): string =
if n == nil: return nil if n == nil: return ""
result = genComment(d, n).rope result = genComment(d, n)
if result == nil: if result == "":
if n.kind in {nkStmtList, nkStmtListExpr, nkTypeDef, nkConstDef, if n.kind in {nkStmtList, nkStmtListExpr, nkTypeDef, nkConstDef,
nkObjectTy, nkRefTy, nkPtrTy, nkAsgn, nkFastAsgn, nkHiddenStdConv}: nkObjectTy, nkRefTy, nkPtrTy, nkAsgn, nkFastAsgn, nkHiddenStdConv}:
# notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}: # notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}:
for i in 0..<n.len: for i in 0..<n.len:
result = genRecCommentAux(d, n[i]) result = genRecCommentAux(d, n[i])
if result != nil: return if result != "": return
else: else:
n.comment = "" n.comment = ""
proc genRecComment(d: PDoc, n: PNode): Rope = proc genRecComment(d: PDoc, n: PNode): string =
if n == nil: return nil if n == nil: return ""
result = genComment(d, n).rope result = genComment(d, n)
if result == nil: if result == "":
if n.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, if n.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef,
nkMacroDef, nkTemplateDef, nkConverterDef}: nkMacroDef, nkTemplateDef, nkConverterDef}:
result = genRecCommentAux(d, n[bodyPos]) result = genRecCommentAux(d, n[bodyPos])
@ -377,15 +320,16 @@ proc externalDep(d: PDoc; module: PSym): string =
else: else:
result = extractFilename toFullPath(d.conf, FileIndex module.position) result = extractFilename toFullPath(d.conf, FileIndex module.position)
proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}; proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var string;
procLink: Rope) = renderFlags: TRenderFlags = {};
procLink: string) =
var r: TSrcGen var r: TSrcGen
var literal = "" var literal = ""
initTokRender(r, n, renderFlags) initTokRender(r, n, renderFlags)
var kind = tkEof var kind = tkEof
var tokenPos = 0 var tokenPos = 0
var procTokenPos = 0 var procTokenPos = 0
template escLit(): untyped = rope(esc(d.target, literal)) template escLit(): untyped = esc(d.target, literal)
while true: while true:
getNextTok(r, kind, literal) getNextTok(r, kind, literal)
inc tokenPos inc tokenPos
@ -399,7 +343,7 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe
if kind in {tkProc, tkMethod, tkIterator, tkMacro, tkTemplate, tkFunc, tkConverter}: if kind in {tkProc, tkMethod, tkIterator, tkMacro, tkTemplate, tkFunc, tkConverter}:
procTokenPos = tokenPos procTokenPos = tokenPos
dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}", dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
[rope(literal)]) [literal])
of tkOpr: of tkOpr:
dispA(d.conf, result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}", dispA(d.conf, result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}",
[escLit]) [escLit])
@ -418,7 +362,7 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe
of tkSymbol: of tkSymbol:
let s = getTokSym(r) let s = getTokSym(r)
# -2 because of the whitespace in between: # -2 because of the whitespace in between:
if procTokenPos == tokenPos-2 and procLink != nil: if procTokenPos == tokenPos-2 and procLink != "":
dispA(d.conf, result, "<a href=\"#$2\"><span class=\"Identifier\">$1</span></a>", dispA(d.conf, result, "<a href=\"#$2\"><span class=\"Identifier\">$1</span></a>",
"\\spanIdentifier{$1}", [escLit, procLink]) "\\spanIdentifier{$1}", [escLit, procLink])
elif s != nil and s.kind in {skType, skVar, skLet, skConst} and elif s != nil and s.kind in {skType, skVar, skLet, skConst} and
@ -426,7 +370,7 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe
belongsToPackage(d.conf, s.owner) and d.target == outHtml: belongsToPackage(d.conf, s.owner) and d.target == outHtml:
let external = externalDep(d, s.owner) let external = externalDep(d, s.owner)
result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>", result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>",
[rope changeFileExt(external, "html"), rope literal, [changeFileExt(external, "html"), literal,
escLit] escLit]
else: else:
dispA(d.conf, result, "<span class=\"Identifier\">$1</span>", dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
@ -570,7 +514,9 @@ type RunnableState = enum
rsRunnable rsRunnable
rsDone rsDone
proc getAllRunnableExamplesImpl(d: PDoc; n: PNode, dest: var Rope, state: RunnableState, topLevel: bool): RunnableState = proc getAllRunnableExamplesImpl(d: PDoc; n: PNode, dest: var string,
state: RunnableState, topLevel: bool):
RunnableState =
##[ ##[
Simple state machine to tell whether we render runnableExamples and doc comments. Simple state machine to tell whether we render runnableExamples and doc comments.
This is to ensure that we can interleave runnableExamples and doc comments freely; This is to ensure that we can interleave runnableExamples and doc comments freely;
@ -600,7 +546,7 @@ proc getAllRunnableExamplesImpl(d: PDoc; n: PNode, dest: var Rope, state: Runnab
var msg = "Example:" var msg = "Example:"
if rdoccmd.len > 0: msg.add " cmd: " & rdoccmd 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", [msg.rope]) "\n\\textbf{$1}\n", [msg])
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", ""])
@ -646,12 +592,12 @@ proc getRoutineBody(n: PNode): PNode =
doAssert result.len == 2 doAssert result.len == 2
result = result[1] result = result[1]
proc getAllRunnableExamples(d: PDoc, n: PNode, dest: var Rope) = proc getAllRunnableExamples(d: PDoc, n: PNode, dest: var string) =
var n = n var n = n
var state = rsStart var state = rsStart
template fn(n2, topLevel) = template fn(n2, topLevel) =
state = getAllRunnableExamplesImpl(d, n2, dest, state, topLevel) state = getAllRunnableExamplesImpl(d, n2, dest, state, topLevel)
dest.add genComment(d, n).rope dest.add genComment(d, n)
case n.kind case n.kind
of routineDefs: of routineDefs:
n = n.getRoutineBody n = n.getRoutineBody
@ -806,20 +752,18 @@ proc docstringSummary(rstText: string): string =
result.delete(pos, last) result.delete(pos, last)
result.add("…") result.add("…")
proc genDeprecationMsg(d: PDoc, n: PNode): Rope = proc genDeprecationMsg(d: PDoc, n: PNode): string =
## Given a nkPragma wDeprecated node output a well-formatted section ## Given a nkPragma wDeprecated node output a well-formatted section
if n == nil: return if n == nil: return
case n.safeLen: case n.safeLen:
of 0: # Deprecated w/o any message of 0: # Deprecated w/o any message
result = ropeFormatNamedVars(d.conf, result = getConfigVar(d.conf, "doc.deprecationmsg") % [
getConfigVar(d.conf, "doc.deprecationmsg"), ["label", "message"], "label" , "Deprecated", "message", ""]
[~"Deprecated", nil])
of 2: # Deprecated w/ a message of 2: # Deprecated w/ a message
if n[1].kind in {nkStrLit..nkTripleStrLit}: if n[1].kind in {nkStrLit..nkTripleStrLit}:
result = ropeFormatNamedVars(d.conf, result = getConfigVar(d.conf, "doc.deprecationmsg") % [
getConfigVar(d.conf, "doc.deprecationmsg"), ["label", "message"], "label", "Deprecated:", "message", xmltree.escape(n[1].strVal)]
[~"Deprecated:", rope(xmltree.escape(n[1].strVal))])
else: else:
doAssert false doAssert false
@ -827,7 +771,7 @@ type DocFlags = enum
kDefault kDefault
kForceExport kForceExport
proc genSeeSrcRope(d: PDoc, path: string, line: int): Rope = proc genSeeSrc(d: PDoc, path: string, line: int): string =
let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc") let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc")
if docItemSeeSrc.len > 0: if docItemSeeSrc.len > 0:
let path = relativeTo(AbsoluteFile path, AbsoluteDir getCurrentDir(), '/') let path = relativeTo(AbsoluteFile path, AbsoluteDir getCurrentDir(), '/')
@ -843,20 +787,19 @@ proc genSeeSrcRope(d: PDoc, path: string, line: int): Rope =
else: "version-$1-$2" % [$NimMajor, $NimMinor] else: "version-$1-$2" % [$NimMajor, $NimMinor]
let commit = getConfigVar(d.conf, "git.commit", defaultBranch) let commit = getConfigVar(d.conf, "git.commit", defaultBranch)
let develBranch = getConfigVar(d.conf, "git.devel", "devel") let develBranch = getConfigVar(d.conf, "git.devel", "devel")
dispA(d.conf, result, "$1", "", [ropeFormatNamedVars(d.conf, docItemSeeSrc, dispA(d.conf, result, "$1", "", [docItemSeeSrc % [
["path", "line", "url", "commit", "devel"], [rope path.string, "path", path.string, "line", $line, "url", gitUrl,
rope($line), rope gitUrl, rope commit, rope develBranch])]) "commit", commit, "devel", develBranch]])
proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind, docFlags: DocFlags) = proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind, docFlags: DocFlags) =
if (docFlags != kForceExport) and not isVisible(d, nameNode): return if (docFlags != kForceExport) and not isVisible(d, nameNode): return
let let
name = getName(d, nameNode) name = getName(d, nameNode)
nameRope = name.rope
var plainDocstring = getPlainDocstring(n) # call here before genRecComment! var plainDocstring = getPlainDocstring(n) # call here before genRecComment!
var result: Rope = nil var result = ""
var literal, plainName = "" var literal, plainName = ""
var kind = tkEof var kind = tkEof
var comm: Rope = nil var comm = ""
if n.kind in routineDefs: if n.kind in routineDefs:
getAllRunnableExamples(d, n, comm) getAllRunnableExamples(d, n, comm)
else: else:
@ -878,35 +821,33 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind, docFlags: DocFlags) =
inc(d.id) inc(d.id)
let let
plainNameRope = rope(xmltree.escape(plainName.strip)) plainNameEsc = xmltree.escape(plainName.strip)
cleanPlainSymbol = renderPlainSymbolName(nameNode) cleanPlainSymbol = renderPlainSymbolName(nameNode)
complexSymbol = complexName(k, n, cleanPlainSymbol) complexSymbol = complexName(k, n, cleanPlainSymbol)
plainSymbolRope = rope(cleanPlainSymbol) plainSymbolEnc = encodeUrl(cleanPlainSymbol)
plainSymbolEncRope = rope(encodeUrl(cleanPlainSymbol))
itemIDRope = rope(d.id)
symbolOrId = d.newUniquePlainSymbol(complexSymbol) symbolOrId = d.newUniquePlainSymbol(complexSymbol)
symbolOrIdRope = symbolOrId.rope symbolOrIdEnc = encodeUrl(symbolOrId)
symbolOrIdEncRope = encodeUrl(symbolOrId).rope deprecationMsg = genDeprecationMsg(d, pragmaNode)
deprecationMsgRope = genDeprecationMsg(d, pragmaNode)
nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments,
renderDocComments, renderSyms}, symbolOrIdEncRope) renderDocComments, renderSyms}, symbolOrIdEnc)
let seeSrcRope = genSeeSrcRope(d, toFullPath(d.conf, n.info), n.info.line.int) let seeSrc = genSeeSrc(d, toFullPath(d.conf, n.info), n.info.line.int)
d.section[k].add(ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item"),
["name", "header", "desc", "itemID", "header_plain", "itemSym", d.section[k].add(getConfigVar(d.conf, "doc.item") %
"itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "seeSrc", "deprecationMsg"], ["name", name, "header", result, "desc", comm, "itemID", $d.id,
[nameRope, result, comm, itemIDRope, plainNameRope, plainSymbolRope, "header_plain", plainNameEsc, "itemSym", cleanPlainSymbol,
symbolOrIdRope, plainSymbolEncRope, symbolOrIdEncRope, seeSrcRope, "itemSymOrID", symbolOrId, "itemSymEnc", plainSymbolEnc,
deprecationMsgRope])) "itemSymOrIDEnc", symbolOrIdEnc, "seeSrc", seeSrc,
"deprecationMsg", deprecationMsg])
let external = d.destFile.AbsoluteFile.relativeTo(d.conf.outDir, '/').changeFileExt(HtmlExt).string let external = d.destFile.AbsoluteFile.relativeTo(d.conf.outDir, '/').changeFileExt(HtmlExt).string
var attype: Rope var attype = ""
if k in routineKinds and nameNode.kind == nkSym: if k in routineKinds and nameNode.kind == nkSym:
let att = attachToType(d, nameNode.sym) let att = attachToType(d, nameNode.sym)
if att != nil: if att != nil:
attype = rope esc(d.target, att.name.s) attype = esc(d.target, att.name.s)
elif k == skType and nameNode.kind == nkSym and nameNode.sym.typ.kind in {tyEnum, tyBool}: elif k == skType and nameNode.kind == nkSym and nameNode.sym.typ.kind in {tyEnum, tyBool}:
let etyp = nameNode.sym.typ let etyp = nameNode.sym.typ
for e in etyp.n: for e in etyp.n:
@ -916,14 +857,15 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind, docFlags: DocFlags) =
setIndexTerm(d[], external, symbolOrId, plain, nameNode.sym.name.s & '.' & plain, setIndexTerm(d[], external, symbolOrId, plain, nameNode.sym.name.s & '.' & plain,
xmltree.escape(getPlainDocstring(e).docstringSummary)) xmltree.escape(getPlainDocstring(e).docstringSummary))
d.toc[k].add(ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item.toc"), d.toc[k].add(getConfigVar(d.conf, "doc.item.toc") % [
["name", "header_plain", "itemSymOrIDEnc"], "name", name, "header_plain", plainNameEsc,
[nameRope, plainNameRope, symbolOrIdEncRope])) "itemSymOrIDEnc", symbolOrIdEnc])
d.tocTable[k].mgetOrPut(cleanPlainSymbol, nil).add(ropeFormatNamedVars( d.tocTable[k].mgetOrPut(cleanPlainSymbol, "").add(
d.conf, getConfigVar(d.conf, "doc.item.tocTable"), getConfigVar(d.conf, "doc.item.tocTable") % [
["name", "header_plain", "itemSymOrID", "itemSymOrIDEnc"], "name", name, "header_plain", plainNameEsc,
[nameRope, plainNameRope, rope(symbolOrId.replace(",", ",<wbr>")), symbolOrIdEncRope])) "itemSymOrID", symbolOrId.replace(",", ",<wbr>"),
"itemSymOrIDEnc", symbolOrIdEnc])
# Ironically for types the complexSymbol is *cleaner* than the plainName # Ironically for types the complexSymbol is *cleaner* than the plainName
# because it doesn't include object fields or documentation comments. So we # because it doesn't include object fields or documentation comments. So we
@ -941,7 +883,7 @@ proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode =
if not isVisible(d, nameNode): return if not isVisible(d, nameNode): return
var var
name = getName(d, nameNode) name = getName(d, nameNode)
comm = $genRecComment(d, n) comm = genRecComment(d, n)
r: TSrcGen r: TSrcGen
initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments}) initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments})
result = %{ "name": %name, "type": %($k), "line": %n.info.line.int, result = %{ "name": %name, "type": %($k), "line": %n.info.line.int,
@ -997,35 +939,35 @@ proc traceDeps(d: PDoc, it: PNode) =
traceDeps(d, a) traceDeps(d, a)
elif it.kind == nkSym and belongsToPackage(d.conf, it.sym): elif it.kind == nkSym and belongsToPackage(d.conf, it.sym):
let external = externalDep(d, it.sym) let external = externalDep(d, it.sym)
if d.section[k] != nil: d.section[k].add(", ") if d.section[k] != "": d.section[k].add(", ")
dispA(d.conf, d.section[k], dispA(d.conf, d.section[k],
"<a class=\"reference external\" href=\"$2\">$1</a>", "<a class=\"reference external\" href=\"$2\">$1</a>",
"$1", [rope esc(d.target, external.prettyLink), "$1", [esc(d.target, external.prettyLink),
rope changeFileExt(external, "html")]) changeFileExt(external, "html")])
proc exportSym(d: PDoc; s: PSym) = proc exportSym(d: PDoc; s: PSym) =
const k = exportSection const k = exportSection
if s.kind == skModule and belongsToPackage(d.conf, s): if s.kind == skModule and belongsToPackage(d.conf, s):
let external = externalDep(d, s) let external = externalDep(d, s)
if d.section[k] != nil: d.section[k].add(", ") if d.section[k] != "": d.section[k].add(", ")
dispA(d.conf, d.section[k], dispA(d.conf, d.section[k],
"<a class=\"reference external\" href=\"$2\">$1</a>", "<a class=\"reference external\" href=\"$2\">$1</a>",
"$1", [rope esc(d.target, external.prettyLink), "$1", [esc(d.target, external.prettyLink),
rope changeFileExt(external, "html")]) changeFileExt(external, "html")])
elif s.kind != skModule and s.owner != nil: elif s.kind != skModule and s.owner != nil:
let module = originatingModule(s) let module = originatingModule(s)
if belongsToPackage(d.conf, module): if belongsToPackage(d.conf, module):
let let
complexSymbol = complexName(s.kind, s.ast, s.name.s) complexSymbol = complexName(s.kind, s.ast, s.name.s)
symbolOrIdRope = rope(d.newUniquePlainSymbol(complexSymbol)) symbolOrId = d.newUniquePlainSymbol(complexSymbol)
external = externalDep(d, module) external = externalDep(d, module)
if d.section[k] != nil: d.section[k].add(", ") if d.section[k] != "": d.section[k].add(", ")
# XXX proper anchor generation here # XXX proper anchor generation here
dispA(d.conf, d.section[k], dispA(d.conf, d.section[k],
"<a href=\"$2#$3\"><span class=\"Identifier\">$1</span></a>", "<a href=\"$2#$3\"><span class=\"Identifier\">$1</span></a>",
"$1", [rope esc(d.target, s.name.s), "$1", [esc(d.target, s.name.s),
rope changeFileExt(external, "html"), changeFileExt(external, "html"),
symbolOrIdRope]) symbolOrId])
proc documentNewEffect(cache: IdentCache; n: PNode): PNode = proc documentNewEffect(cache: IdentCache; n: PNode): PNode =
let s = n[namePos].sym let s = n[namePos].sym
@ -1130,9 +1072,9 @@ proc generateDoc*(d: PDoc, n, orig: PNode, docFlags: DocFlags = kDefault) =
of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept" of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept"
of nkFromStmt, nkImportExceptStmt: traceDeps(d, n[0]) of nkFromStmt, nkImportExceptStmt: traceDeps(d, n[0])
of nkCallKinds: of nkCallKinds:
var comm: Rope = nil var comm = ""
getAllRunnableExamples(d, n, comm) getAllRunnableExamples(d, n, comm)
if comm != nil: d.modDesc.add(comm) if comm != "": d.modDesc.add(comm)
else: discard else: discard
proc add(d: PDoc; j: JsonNode) = proc add(d: PDoc; j: JsonNode) =
@ -1179,7 +1121,7 @@ proc generateJson*(d: PDoc, n: PNode, includeComments: bool = true) =
proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string = proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string =
result = getName(d, nameNode) & "\n" result = getName(d, nameNode) & "\n"
proc generateTags*(d: PDoc, n: PNode, r: var Rope) = proc generateTags*(d: PDoc, n: PNode, r: var string) =
case n.kind case n.kind
of nkCommentStmt: of nkCommentStmt:
if startsWith(n.comment, "##"): if startsWith(n.comment, "##"):
@ -1224,41 +1166,41 @@ proc genSection(d: PDoc, kind: TSymKind, groupedToc = false) =
"Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs", "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs",
"Methods", "Iterators", "Converters", "Macros", "Templates", "Exports" "Methods", "Iterators", "Converters", "Macros", "Templates", "Exports"
] ]
if d.section[kind] == nil: return if d.section[kind] == "": return
var title = sectionNames[kind].rope var title = sectionNames[kind]
d.section[kind] = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section"), [ d.section[kind] = getConfigVar(d.conf, "doc.section") % [
"sectionid", "sectionTitle", "sectionTitleID", "content"], [ "sectionid", $ord(kind), "sectionTitle", title,
ord(kind).rope, title, rope(ord(kind) + 50), d.section[kind]]) "sectionTitleID", $(ord(kind) + 50), "content", d.section[kind]]
var tocSource = d.toc var tocSource = d.toc
if groupedToc: if groupedToc:
for p in d.tocTable[kind].keys: for p in d.tocTable[kind].keys:
d.toc2[kind].add ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section.toc2"), [ d.toc2[kind].add getConfigVar(d.conf, "doc.section.toc2") % [
"sectionid", "sectionTitle", "sectionTitleID", "content", "plainName"], [ "sectionid", $ord(kind), "sectionTitle", title,
ord(kind).rope, title, rope(ord(kind) + 50), d.tocTable[kind][p], p.rope]) "sectionTitleID", $(ord(kind) + 50),
"content", d.tocTable[kind][p], "plainName", p]
tocSource = d.toc2 tocSource = d.toc2
d.toc[kind] = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section.toc"), [ d.toc[kind] = getConfigVar(d.conf, "doc.section.toc") % [
"sectionid", "sectionTitle", "sectionTitleID", "content"], [ "sectionid", $ord(kind), "sectionTitle", title,
ord(kind).rope, title, rope(ord(kind) + 50), tocSource[kind]]) "sectionTitleID", $(ord(kind) + 50), "content", tocSource[kind]]
proc relLink(outDir: AbsoluteDir, destFile: AbsoluteFile, linkto: RelativeFile): Rope = proc relLink(outDir: AbsoluteDir, destFile: AbsoluteFile, linkto: RelativeFile): string =
rope($relativeTo(outDir / linkto, destFile.splitFile().dir, '/')) $relativeTo(outDir / linkto, destFile.splitFile().dir, '/')
proc genOutFile(d: PDoc, groupedToc = false): Rope = proc genOutFile(d: PDoc, groupedToc = false): string =
var var
code, content: Rope code, content: string
title = "" title = ""
var j = 0 var j = 0
var tmp = "" var toc = ""
renderTocEntries(d[], j, 1, tmp) renderTocEntries(d[], j, 1, toc)
var toc = tmp.rope
for i in TSymKind: for i in TSymKind:
var shouldSort = i in routineKinds and groupedToc var shouldSort = i in routineKinds and groupedToc
genSection(d, i, shouldSort) genSection(d, i, shouldSort)
toc.add(d.toc[i]) toc.add(d.toc[i])
if toc != nil: if toc != "":
toc = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.toc"), ["content"], [toc]) toc = getConfigVar(d.conf, "doc.toc") % ["content", toc]
for i in TSymKind: code.add(d.section[i]) for i in TSymKind: code.add(d.section[i])
# Extract the title. Non API modules generate an entry in the index table. # Extract the title. Non API modules generate an entry in the index table.
@ -1269,10 +1211,10 @@ proc genOutFile(d: PDoc, groupedToc = false): Rope =
else: else:
# Modules get an automatic title for the HTML, but no entry in the index. # Modules get an automatic title for the HTML, but no entry in the index.
title = canonicalImport(d.conf, AbsoluteFile d.filename) title = canonicalImport(d.conf, AbsoluteFile d.filename)
var subtitle = "".rope var subtitle = ""
if d.meta[metaSubtitle] != "": if d.meta[metaSubtitle] != "":
dispA(d.conf, subtitle, "<h2 class=\"subtitle\">$1</h2>", dispA(d.conf, subtitle, "<h2 class=\"subtitle\">$1</h2>",
"\\\\\\vspace{0.5em}\\large $1", [d.meta[metaSubtitle].rope]) "\\\\\\vspace{0.5em}\\large $1", [d.meta[metaSubtitle]])
var groupsection = getConfigVar(d.conf, "doc.body_toc_groupsection") var groupsection = getConfigVar(d.conf, "doc.body_toc_groupsection")
let bodyname = if d.hasToc and not d.isPureRst: let bodyname = if d.hasToc and not d.isPureRst:
@ -1280,20 +1222,27 @@ proc genOutFile(d: PDoc, groupedToc = false): Rope =
"doc.body_toc_group" "doc.body_toc_group"
elif d.hasToc: "doc.body_toc" elif d.hasToc: "doc.body_toc"
else: "doc.body_no_toc" else: "doc.body_no_toc"
let seeSrcRope = genSeeSrcRope(d, d.filename, 1) let seeSrc = genSeeSrc(d, d.filename, 1)
content = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, bodyname), ["title", "subtitle", content = getConfigVar(d.conf, bodyname) % [
"tableofcontents", "moduledesc", "date", "time", "content", "deprecationMsg", "theindexhref", "body_toc_groupsection", "seeSrc"], "title", title, "subtitle", subtitle,
[title.rope, subtitle, toc, d.modDesc, rope(getDateStr()), "tableofcontents", toc, "moduledesc", d.modDesc, "date", getDateStr(),
rope(getClockStr()), code, d.modDeprecationMsg, relLink(d.conf.outDir, d.destFile.AbsoluteFile, theindexFname.RelativeFile), groupsection.rope, seeSrcRope]) "time", getClockStr(), "content", code,
"deprecationMsg", d.modDeprecationMsg,
"theindexhref", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
theindexFname.RelativeFile),
"body_toc_groupsection", groupsection, "seeSrc", seeSrc]
if optCompileOnly notin d.conf.globalOptions: if optCompileOnly notin d.conf.globalOptions:
# XXX what is this hack doing here? 'optCompileOnly' means raw output!? # XXX what is this hack doing here? 'optCompileOnly' means raw output!?
code = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.file"), [ code = getConfigVar(d.conf, "doc.file") % [
"nimdoccss", "dochackjs", "title", "subtitle", "tableofcontents", "moduledesc", "date", "time", "nimdoccss", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
"content", "author", "version", "analytics", "deprecationMsg"], nimdocOutCss.RelativeFile),
[relLink(d.conf.outDir, d.destFile.AbsoluteFile, nimdocOutCss.RelativeFile), "dochackjs", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
relLink(d.conf.outDir, d.destFile.AbsoluteFile, docHackJsFname.RelativeFile), docHackJsFname.RelativeFile),
title.rope, subtitle, toc, d.modDesc, rope(getDateStr()), rope(getClockStr()), "title", title, "subtitle", subtitle, "tableofcontents", toc,
content, d.meta[metaAuthor].rope, d.meta[metaVersion].rope, d.analytics.rope, d.modDeprecationMsg]) "moduledesc", d.modDesc, "date", getDateStr(), "time", getClockStr(),
"content", content, "author", d.meta[metaAuthor],
"version", d.meta[metaVersion], "analytics", d.analytics,
"deprecationMsg", d.modDeprecationMsg]
else: else:
code = content code = content
result = code result = code
@ -1316,17 +1265,19 @@ proc writeOutput*(d: PDoc, useWarning = false, groupedToc = false) =
runAllExamples(d) runAllExamples(d)
var content = genOutFile(d, groupedToc) var content = genOutFile(d, groupedToc)
if optStdout in d.conf.globalOptions: if optStdout in d.conf.globalOptions:
writeRope(stdout, content) write(stdout, content)
else: else:
template outfile: untyped = d.destFile.AbsoluteFile template outfile: untyped = d.destFile.AbsoluteFile
#let outfile = getOutFile2(d.conf, shortenDir(d.conf, filename), outExt) #let outfile = getOutFile2(d.conf, shortenDir(d.conf, filename), outExt)
let dir = outfile.splitFile.dir let dir = outfile.splitFile.dir
createDir(dir) createDir(dir)
updateOutfile(d, outfile) updateOutfile(d, outfile)
if not writeRope(content, outfile): try:
writeFile(outfile, content)
except IOError:
rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile, rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile,
outfile.string) outfile.string)
elif not d.wroteSupportFiles: # nimdoc.css + dochack.js if not d.wroteSupportFiles: # nimdoc.css + dochack.js
let nimr = $d.conf.getPrefixDir() let nimr = $d.conf.getPrefixDir()
copyFile(docCss.interp(nimr = nimr), $d.conf.outDir / nimdocOutCss) copyFile(docCss.interp(nimr = nimr), $d.conf.outDir / nimdocOutCss)
if optGenIndex in d.conf.globalOptions: if optGenIndex in d.conf.globalOptions:
@ -1386,7 +1337,7 @@ proc commandRstAux(cache: IdentCache, conf: ConfigRef;
conf) conf)
var modDesc = newStringOfCap(30_000) var modDesc = newStringOfCap(30_000)
renderRstToOut(d[], rst, modDesc) renderRstToOut(d[], rst, modDesc)
d.modDesc = rope(modDesc) d.modDesc = modDesc
writeOutput(d) writeOutput(d)
generateIndex(d) generateIndex(d)
@ -1407,14 +1358,16 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) =
d.hasToc = true d.hasToc = true
generateJson(d, ast) generateJson(d, ast)
let json = d.jArray let json = d.jArray
let content = rope(pretty(json)) let content = pretty(json)
if optStdout in d.conf.globalOptions: if optStdout in d.conf.globalOptions:
writeRope(stdout, content) write(stdout, content)
else: else:
#echo getOutFile(gProjectFull, JsonExt) #echo getOutFile(gProjectFull, JsonExt)
let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt) let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt)
if not writeRope(content, filename): try:
writeFile(filename, content)
except:
rawMessage(conf, errCannotOpenFile, filename.string) rawMessage(conf, errCannotOpenFile, filename.string)
proc commandTags*(cache: IdentCache, conf: ConfigRef) = proc commandTags*(cache: IdentCache, conf: ConfigRef) =
@ -1427,33 +1380,36 @@ proc commandTags*(cache: IdentCache, conf: ConfigRef) =
warnUser, "the ':test:' attribute is not supported by this backend") warnUser, "the ':test:' attribute is not supported by this backend")
d.hasToc = true d.hasToc = true
var var
content: Rope content = ""
generateTags(d, ast, content) generateTags(d, ast, content)
if optStdout in d.conf.globalOptions: if optStdout in d.conf.globalOptions:
writeRope(stdout, content) write(stdout, content)
else: else:
#echo getOutFile(gProjectFull, TagsExt) #echo getOutFile(gProjectFull, TagsExt)
let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt) let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt)
if not writeRope(content, filename): try:
writeFile(filename, content)
except:
rawMessage(conf, errCannotOpenFile, filename.string) rawMessage(conf, errCannotOpenFile, filename.string)
proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"") = proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"") =
var content = mergeIndexes(dir).rope var content = mergeIndexes(dir)
var outFile = outFile var outFile = outFile
if outFile.isEmpty: outFile = theindexFname.RelativeFile.changeFileExt("") if outFile.isEmpty: outFile = theindexFname.RelativeFile.changeFileExt("")
let filename = getOutFile(conf, outFile, HtmlExt) let filename = getOutFile(conf, outFile, HtmlExt)
let code = ropeFormatNamedVars(conf, getConfigVar(conf, "doc.file"), [ let code = getConfigVar(conf, "doc.file") % [
"nimdoccss", "dochackjs", "nimdoccss", relLink(conf.outDir, filename, nimdocOutCss.RelativeFile),
"title", "subtitle", "tableofcontents", "moduledesc", "date", "time", "dochackjs", relLink(conf.outDir, filename, docHackJsFname.RelativeFile),
"content", "author", "version", "analytics"], "title", "Index",
[relLink(conf.outDir, filename, nimdocOutCss.RelativeFile), "subtitle", "", "tableofcontents", "", "moduledesc", "",
relLink(conf.outDir, filename, docHackJsFname.RelativeFile), "date", getDateStr(), "time", getClockStr(),
rope"Index", rope"", nil, nil, rope(getDateStr()), "content", content, "author", "", "version", "", "analytics", ""]
rope(getClockStr()), content, nil, nil, nil])
# no analytics because context is not available # no analytics because context is not available
if not writeRope(code, filename): try:
writeFile(filename, code)
except:
rawMessage(conf, errCannotOpenFile, filename.string) rawMessage(conf, errCannotOpenFile, filename.string)