summary refs log tree commit diff stats
path: root/compiler/docgen.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/docgen.nim')
-rw-r--r--compiler/docgen.nim140
1 files changed, 112 insertions, 28 deletions
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index d954b897b..bbbec081a 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -14,7 +14,7 @@
 import
   ast, strutils, strtabs, options, msgs, os, ropes, idents,
   wordrecg, syntaxes, renderer, lexer, rstast, rst, rstgen, times, highlite,
-  importer, sempass2, json, xmltree, cgi, typesrenderer
+  importer, sempass2, json, xmltree, cgi, typesrenderer, astalgo
 
 type
   TSections = array[TSymKind, Rope]
@@ -25,9 +25,32 @@ type
     indexValFilename: string
     analytics: string  # Google Analytics javascript, "" if doesn't exist
     seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML.
+    jArray: JsonNode
+    types: TStrTable
 
   PDoc* = ref TDocumentor ## Alias to type less.
 
+proc whichType(d: PDoc; n: PNode): PSym =
+  if n.kind == nkSym:
+    if d.types.strTableContains(n.sym):
+      result = n.sym
+  else:
+    for i in 0..<safeLen(n):
+      let x = whichType(d, n[i])
+      if x != nil: return x
+
+proc attachToType(d: PDoc; p: PSym): PSym =
+  let params = p.ast.sons[paramsPos]
+  # first check the first parameter, then the return type,
+  # then the other parameter:
+  template check(i) =
+    result = whichType(d, params[i])
+    if result != nil: return result
+
+  if params.len > 1: check(1)
+  if params.len > 0: check(0)
+  for i in 2..<params.len: check(i)
+
 proc compilerMsgHandler(filename: string, line, col: int,
                         msgKind: rst.MsgKind, arg: string) {.procvar.} =
   # translate msg kind:
@@ -81,6 +104,8 @@ proc newDocumentor*(filename: string, config: StringTableRef): PDoc =
 
   result.seenSymbols = newStringTable(modeCaseInsensitive)
   result.id = 100
+  result.jArray = newJArray()
+  initStrTable result.types
 
 proc dispA(dest: var Rope, xml, tex: string, args: openArray[Rope]) =
   if gCmd != cmdRst2tex: addf(dest, xml, args)
@@ -226,10 +251,27 @@ proc getName(d: PDoc, n: PNode, splitAfter = -1): string =
     result = esc(d.target, "`")
     for i in 0.. <n.len: result.add(getName(d, n[i], splitAfter))
     result.add esc(d.target, "`")
+  of nkOpenSymChoice, nkClosedSymChoice:
+    result = getName(d, n[0], splitAfter)
   else:
     internalError(n.info, "getName()")
     result = ""
 
+proc getNameIdent(n: PNode): PIdent =
+  case n.kind
+  of nkPostfix: result = getNameIdent(n.sons[1])
+  of nkPragmaExpr: result = getNameIdent(n.sons[0])
+  of nkSym: result = n.sym.name
+  of nkIdent: result = n.ident
+  of nkAccQuoted:
+    var r = ""
+    for i in 0.. <n.len: r.add(getNameIdent(n[i]).s)
+    result = getIdent(r)
+  of nkOpenSymChoice, nkClosedSymChoice:
+    result = getNameIdent(n[0])
+  else:
+    result = nil
+
 proc getRstName(n: PNode): PRstNode =
   case n.kind
   of nkPostfix: result = getRstName(n.sons[1])
@@ -239,6 +281,8 @@ proc getRstName(n: PNode): PRstNode =
   of nkAccQuoted:
     result = getRstName(n.sons[0])
     for i in 1 .. <n.len: result.text.add(getRstName(n[i]).text)
+  of nkOpenSymChoice, nkClosedSymChoice:
+    result = getRstName(n[0])
   else:
     internalError(n.info, "getRstName()")
     result = nil
@@ -311,7 +355,7 @@ proc docstringSummary(rstText: string): string =
   ## Also, we hope to not break the rst, but maybe we do. If there is any
   ## trimming done, an ellipsis unicode char is added.
   const maxDocstringChars = 100
-  assert (rstText.len < 2 or (rstText[0] == '#' and rstText[1] == '#'))
+  assert(rstText.len < 2 or (rstText[0] == '#' and rstText[1] == '#'))
   result = rstText.substr(2).strip
   var pos = result.find('\L')
   if pos > 0:
@@ -380,13 +424,27 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
             "\\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, tkCurlyDotLe, tkCurlyDotRi, tkParDotLe,
+       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))])
+
+  if k in routineKinds and nameNode.kind == nkSym:
+    let att = attachToType(d, nameNode.sym)
+    if att != nil:
+      dispA(result, """<span class="attachedType" style="visibility:hidden">$1</span>""", "",
+        [rope esc(d.target, att.name.s)])
   inc(d.id)
   let
     plainNameRope = rope(xmltree.escape(plainName.strip))
@@ -430,8 +488,10 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
 
   setIndexTerm(d[], symbolOrId, name, linkTitle,
     xmltree.escape(plainDocstring.docstringSummary))
+  if k == skType and nameNode.kind == nkSym:
+    d.types.strTableAdd nameNode.sym
 
-proc genJSONItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode =
+proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode =
   if not isVisible(nameNode): return
   var
     name = getName(d, nameNode)
@@ -440,8 +500,8 @@ proc genJSONItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode =
 
   initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments})
 
-  result = %{ "name": %name, "type": %($k) }
-
+  result = %{ "name": %name, "type": %($k), "line": %n.info.line,
+                 "col": %n.info.col}
   if comm != nil and comm != "":
     result["description"] = %comm
   if r.buf != nil:
@@ -491,46 +551,45 @@ proc generateDoc*(d: PDoc, n: PNode) =
   of nkFromStmt, nkImportExceptStmt: traceDeps(d, n.sons[0])
   else: discard
 
-proc generateJson(d: PDoc, n: PNode, jArray: JsonNode = nil): JsonNode =
+proc add(d: PDoc; j: JsonNode) =
+  if j != nil: d.jArray.add j
+
+proc generateJson*(d: PDoc, n: PNode) =
   case n.kind
   of nkCommentStmt:
     if n.comment != nil and startsWith(n.comment, "##"):
       let stripped = n.comment.substr(2).strip
-      result = %{ "comment": %stripped }
+      d.add %{ "comment": %stripped, "line": %n.info.line,
+               "col": %n.info.col }
   of nkProcDef:
     when useEffectSystem: documentRaises(n)
-    result = genJSONItem(d, n, n.sons[namePos], skProc)
+    d.add genJsonItem(d, n, n.sons[namePos], skProc)
   of nkMethodDef:
     when useEffectSystem: documentRaises(n)
-    result = genJSONItem(d, n, n.sons[namePos], skMethod)
+    d.add genJsonItem(d, n, n.sons[namePos], skMethod)
   of nkIteratorDef:
     when useEffectSystem: documentRaises(n)
-    result = genJSONItem(d, n, n.sons[namePos], skIterator)
+    d.add genJsonItem(d, n, n.sons[namePos], skIterator)
   of nkMacroDef:
-    result = genJSONItem(d, n, n.sons[namePos], skMacro)
+    d.add genJsonItem(d, n, n.sons[namePos], skMacro)
   of nkTemplateDef:
-    result = genJSONItem(d, n, n.sons[namePos], skTemplate)
+    d.add genJsonItem(d, n, n.sons[namePos], skTemplate)
   of nkConverterDef:
     when useEffectSystem: documentRaises(n)
-    result = genJSONItem(d, n, n.sons[namePos], skConverter)
+    d.add genJsonItem(d, n, n.sons[namePos], skConverter)
   of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
     for i in countup(0, sonsLen(n) - 1):
       if n.sons[i].kind != nkCommentStmt:
         # order is always 'type var let const':
-        result = genJSONItem(d, n.sons[i], n.sons[i].sons[0],
+        d.add genJsonItem(d, n.sons[i], n.sons[i].sons[0],
                 succ(skType, ord(n.kind)-ord(nkTypeSection)))
   of nkStmtList:
-    result = if jArray != nil: jArray else: newJArray()
-
     for i in countup(0, sonsLen(n) - 1):
-      var r = generateJson(d, n.sons[i], result)
-      if r != nil:
-        result.add(r)
-
+      generateJson(d, n.sons[i])
   of nkWhenStmt:
     # generate documentation for the first branch only:
-    if not checkForFalse(n.sons[0].sons[0]) and jArray != nil:
-      discard generateJson(d, lastSon(n.sons[0]), jArray)
+    if not checkForFalse(n.sons[0].sons[0]):
+      generateJson(d, lastSon(n.sons[0]))
   else: discard
 
 proc genSection(d: PDoc, kind: TSymKind) =
@@ -592,12 +651,36 @@ proc generateIndex*(d: PDoc) =
     writeIndexFile(d[], splitFile(options.outFile).dir /
                         splitFile(d.filename).name & IndexExt)
 
+proc getOutFile2(filename, ext, dir: string): string =
+  if gWholeProject:
+    let d = if options.outFile != "": options.outFile else: dir
+    createDir(d)
+    result = d / changeFileExt(filename, ext)
+  else:
+    result = getOutFile(filename, ext)
+
 proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) =
   var content = genOutFile(d)
   if optStdout in gGlobalOptions:
     writeRope(stdout, content)
   else:
-    writeRope(content, getOutFile(filename, outExt), useWarning)
+    writeRope(content, getOutFile2(filename, outExt, "htmldocs"), useWarning)
+
+proc writeOutputJson*(d: PDoc, filename, outExt: string,
+                      useWarning = false) =
+  let content = %*{"orig": d.filename,
+    "nimble": getPackageName(d.filename),
+    "entries": d.jArray}
+  if optStdout in gGlobalOptions:
+    write(stdout, $content)
+  else:
+    var f: File
+    if open(f, getOutFile2(splitFile(filename).name,
+            outExt, "jsondocs"), fmWrite):
+      write(f, $content)
+      close(f)
+    else:
+      discard "fixme: error report"
 
 proc commandDoc*() =
   var ast = parseFile(gProjectMainIdx)
@@ -628,18 +711,19 @@ proc commandRst2TeX*() =
   splitter = "\\-"
   commandRstAux(gProjectFull, TexExt)
 
-proc commandJSON*() =
+proc commandJson*() =
   var ast = parseFile(gProjectMainIdx)
   if ast == nil: return
   var d = newDocumentor(gProjectFull, options.gConfigVars)
   d.hasToc = true
-  var json = generateJson(d, ast)
-  var content = rope(pretty(json))
+  generateJson(d, ast)
+  let json = d.jArray
+  let content = rope(pretty(json))
 
   if optStdout in gGlobalOptions:
     writeRope(stdout, content)
   else:
-    echo getOutFile(gProjectFull, JsonExt)
+    #echo getOutFile(gProjectFull, JsonExt)
     writeRope(content, getOutFile(gProjectFull, JsonExt), useWarning = false)
 
 proc commandBuildIndex*() =