docgen.nim 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2012 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # This is the documentation generator. It is currently pretty simple: No
  10. # semantic checking is done for the code. Cross-references are generated
  11. # by knowing how the anchors are going to be named.
  12. import
  13. ast, strutils, strtabs, options, msgs, os, ropes, idents,
  14. wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast,
  15. packages/docutils/rst, packages/docutils/rstgen, times,
  16. packages/docutils/highlite, importer, sempass2, json, xmltree, cgi,
  17. typesrenderer, astalgo
  18. type
  19. TSections = array[TSymKind, Rope]
  20. TDocumentor = object of rstgen.RstGenerator
  21. modDesc: Rope # module description
  22. id: int # for generating IDs
  23. toc, section: TSections
  24. indexValFilename: string
  25. analytics: string # Google Analytics javascript, "" if doesn't exist
  26. seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML.
  27. jArray: JsonNode
  28. types: TStrTable
  29. isPureRst: bool
  30. PDoc* = ref TDocumentor ## Alias to type less.
  31. proc whichType(d: PDoc; n: PNode): PSym =
  32. if n.kind == nkSym:
  33. if d.types.strTableContains(n.sym):
  34. result = n.sym
  35. else:
  36. for i in 0..<safeLen(n):
  37. let x = whichType(d, n[i])
  38. if x != nil: return x
  39. proc attachToType(d: PDoc; p: PSym): PSym =
  40. let params = p.ast.sons[paramsPos]
  41. # first check the first parameter, then the return type,
  42. # then the other parameter:
  43. template check(i) =
  44. result = whichType(d, params[i])
  45. if result != nil: return result
  46. if params.len > 1: check(1)
  47. if params.len > 0: check(0)
  48. for i in 2..<params.len: check(i)
  49. proc compilerMsgHandler(filename: string, line, col: int,
  50. msgKind: rst.MsgKind, arg: string) {.procvar.} =
  51. # translate msg kind:
  52. var k: msgs.TMsgKind
  53. case msgKind
  54. of meCannotOpenFile: k = errCannotOpenFile
  55. of meExpected: k = errXExpected
  56. of meGridTableNotImplemented: k = errGridTableNotImplemented
  57. of meNewSectionExpected: k = errNewSectionExpected
  58. of meGeneralParseError: k = errGeneralParseError
  59. of meInvalidDirective: k = errInvalidDirectiveX
  60. of mwRedefinitionOfLabel: k = warnRedefinitionOfLabel
  61. of mwUnknownSubstitution: k = warnUnknownSubstitutionX
  62. of mwUnsupportedLanguage: k = warnLanguageXNotSupported
  63. of mwUnsupportedField: k = warnFieldXNotSupported
  64. globalError(newLineInfo(filename, line, col), k, arg)
  65. proc docgenFindFile(s: string): string {.procvar.} =
  66. result = options.findFile(s)
  67. if result.len == 0:
  68. result = getCurrentDir() / s
  69. if not existsFile(result): result = ""
  70. proc parseRst(text, filename: string,
  71. line, column: int, hasToc: var bool,
  72. rstOptions: RstParseOptions): PRstNode =
  73. result = rstParse(text, filename, line, column, hasToc, rstOptions,
  74. docgenFindFile, compilerMsgHandler)
  75. proc newDocumentor*(filename: string, config: StringTableRef): PDoc =
  76. new(result)
  77. initRstGenerator(result[], (if gCmd != cmdRst2tex: outHtml else: outLatex),
  78. options.gConfigVars, filename, {roSupportRawDirective},
  79. docgenFindFile, compilerMsgHandler)
  80. if config.hasKey("doc.googleAnalytics"):
  81. result.analytics = """
  82. <script>
  83. (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  84. (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  85. m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  86. })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
  87. ga('create', '$1', 'auto');
  88. ga('send', 'pageview');
  89. </script>
  90. """ % [config.getOrDefault"doc.googleAnalytics"]
  91. else:
  92. result.analytics = ""
  93. result.seenSymbols = newStringTable(modeCaseInsensitive)
  94. result.id = 100
  95. result.jArray = newJArray()
  96. initStrTable result.types
  97. proc dispA(dest: var Rope, xml, tex: string, args: openArray[Rope]) =
  98. if gCmd != cmdRst2tex: addf(dest, xml, args)
  99. else: addf(dest, tex, args)
  100. proc getVarIdx(varnames: openArray[string], id: string): int =
  101. for i in countup(0, high(varnames)):
  102. if cmpIgnoreStyle(varnames[i], id) == 0:
  103. return i
  104. result = -1
  105. proc ropeFormatNamedVars(frmt: FormatStr, varnames: openArray[string],
  106. varvalues: openArray[Rope]): Rope =
  107. var i = 0
  108. var L = len(frmt)
  109. result = nil
  110. var num = 0
  111. while i < L:
  112. if frmt[i] == '$':
  113. inc(i) # skip '$'
  114. case frmt[i]
  115. of '#':
  116. add(result, varvalues[num])
  117. inc(num)
  118. inc(i)
  119. of '$':
  120. add(result, "$")
  121. inc(i)
  122. of '0'..'9':
  123. var j = 0
  124. while true:
  125. j = (j * 10) + ord(frmt[i]) - ord('0')
  126. inc(i)
  127. if (i > L + 0 - 1) or not (frmt[i] in {'0'..'9'}): break
  128. if j > high(varvalues) + 1: internalError("ropeFormatNamedVars")
  129. num = j
  130. add(result, varvalues[j - 1])
  131. of 'A'..'Z', 'a'..'z', '\x80'..'\xFF':
  132. var id = ""
  133. while true:
  134. add(id, frmt[i])
  135. inc(i)
  136. if not (frmt[i] in {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}): break
  137. var idx = getVarIdx(varnames, id)
  138. if idx >= 0: add(result, varvalues[idx])
  139. else: rawMessage(errUnknownSubstitionVar, id)
  140. of '{':
  141. var id = ""
  142. inc(i)
  143. while frmt[i] != '}':
  144. if frmt[i] == '\0': rawMessage(errTokenExpected, "}")
  145. add(id, frmt[i])
  146. inc(i)
  147. inc(i) # skip }
  148. # search for the variable:
  149. var idx = getVarIdx(varnames, id)
  150. if idx >= 0: add(result, varvalues[idx])
  151. else: rawMessage(errUnknownSubstitionVar, id)
  152. else: internalError("ropeFormatNamedVars")
  153. var start = i
  154. while i < L:
  155. if frmt[i] != '$': inc(i)
  156. else: break
  157. if i - 1 >= start: add(result, substr(frmt, start, i - 1))
  158. proc genComment(d: PDoc, n: PNode): string =
  159. result = ""
  160. var dummyHasToc: bool
  161. if n.comment != nil:
  162. renderRstToOut(d[], parseRst(n.comment, toFilename(n.info),
  163. toLinenumber(n.info), toColumn(n.info),
  164. dummyHasToc, d.options), result)
  165. proc genRecComment(d: PDoc, n: PNode): Rope =
  166. if n == nil: return nil
  167. result = genComment(d, n).rope
  168. if result == nil:
  169. if n.kind notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}:
  170. for i in countup(0, len(n)-1):
  171. result = genRecComment(d, n.sons[i])
  172. if result != nil: return
  173. else:
  174. n.comment = nil
  175. proc getPlainDocstring(n: PNode): string =
  176. ## Gets the plain text docstring of a node non destructively.
  177. ##
  178. ## You need to call this before genRecComment, whose side effects are removal
  179. ## of comments from the tree. The proc will recursively scan and return all
  180. ## the concatenated ``##`` comments of the node.
  181. result = ""
  182. if n == nil: return
  183. if n.comment != nil and startsWith(n.comment, "##"):
  184. result = n.comment
  185. if result.len < 1:
  186. if n.kind notin {nkEmpty..nkNilLit}:
  187. for i in countup(0, len(n)-1):
  188. result = getPlainDocstring(n.sons[i])
  189. if result.len > 0: return
  190. when false:
  191. proc findDocComment(n: PNode): PNode =
  192. if n == nil: return nil
  193. if not isNil(n.comment) and startsWith(n.comment, "##"): return n
  194. for i in countup(0, safeLen(n)-1):
  195. result = findDocComment(n.sons[i])
  196. if result != nil: return
  197. proc extractDocComment*(s: PSym, d: PDoc = nil): string =
  198. let n = findDocComment(s.ast)
  199. result = ""
  200. if not n.isNil:
  201. if not d.isNil:
  202. var dummyHasToc: bool
  203. renderRstToOut(d[], parseRst(n.comment, toFilename(n.info),
  204. toLinenumber(n.info), toColumn(n.info),
  205. dummyHasToc, d.options + {roSkipPounds}),
  206. result)
  207. else:
  208. result = n.comment.substr(2).replace("\n##", "\n").strip
  209. proc isVisible(n: PNode): bool =
  210. result = false
  211. if n.kind == nkPostfix:
  212. if n.len == 2 and n.sons[0].kind == nkIdent:
  213. var v = n.sons[0].ident
  214. result = v.id == ord(wStar) or v.id == ord(wMinus)
  215. elif n.kind == nkSym:
  216. # we cannot generate code for forwarded symbols here as we have no
  217. # exception tracking information here. Instead we copy over the comment
  218. # from the proc header.
  219. result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported}
  220. elif n.kind == nkPragmaExpr:
  221. result = isVisible(n.sons[0])
  222. proc getName(d: PDoc, n: PNode, splitAfter = -1): string =
  223. case n.kind
  224. of nkPostfix: result = getName(d, n.sons[1], splitAfter)
  225. of nkPragmaExpr: result = getName(d, n.sons[0], splitAfter)
  226. of nkSym: result = esc(d.target, n.sym.renderDefinitionName, splitAfter)
  227. of nkIdent: result = esc(d.target, n.ident.s, splitAfter)
  228. of nkAccQuoted:
  229. result = esc(d.target, "`")
  230. for i in 0.. <n.len: result.add(getName(d, n[i], splitAfter))
  231. result.add esc(d.target, "`")
  232. of nkOpenSymChoice, nkClosedSymChoice:
  233. result = getName(d, n[0], splitAfter)
  234. else:
  235. internalError(n.info, "getName()")
  236. result = ""
  237. proc getNameIdent(n: PNode): PIdent =
  238. case n.kind
  239. of nkPostfix: result = getNameIdent(n.sons[1])
  240. of nkPragmaExpr: result = getNameIdent(n.sons[0])
  241. of nkSym: result = n.sym.name
  242. of nkIdent: result = n.ident
  243. of nkAccQuoted:
  244. var r = ""
  245. for i in 0.. <n.len: r.add(getNameIdent(n[i]).s)
  246. result = getIdent(r)
  247. of nkOpenSymChoice, nkClosedSymChoice:
  248. result = getNameIdent(n[0])
  249. else:
  250. result = nil
  251. proc getRstName(n: PNode): PRstNode =
  252. case n.kind
  253. of nkPostfix: result = getRstName(n.sons[1])
  254. of nkPragmaExpr: result = getRstName(n.sons[0])
  255. of nkSym: result = newRstNode(rnLeaf, n.sym.renderDefinitionName)
  256. of nkIdent: result = newRstNode(rnLeaf, n.ident.s)
  257. of nkAccQuoted:
  258. result = getRstName(n.sons[0])
  259. for i in 1 .. <n.len: result.text.add(getRstName(n[i]).text)
  260. of nkOpenSymChoice, nkClosedSymChoice:
  261. result = getRstName(n[0])
  262. else:
  263. internalError(n.info, "getRstName()")
  264. result = nil
  265. proc newUniquePlainSymbol(d: PDoc, original: string): string =
  266. ## Returns a new unique plain symbol made up from the original.
  267. ##
  268. ## When a collision is found in the seenSymbols table, new numerical variants
  269. ## with underscore + number will be generated.
  270. if not d.seenSymbols.hasKey(original):
  271. result = original
  272. d.seenSymbols[original] = ""
  273. return
  274. # Iterate over possible numeric variants of the original name.
  275. var count = 2
  276. while true:
  277. result = original & "_" & $count
  278. if not d.seenSymbols.hasKey(result):
  279. d.seenSymbols[result] = ""
  280. break
  281. count += 1
  282. proc complexName(k: TSymKind, n: PNode, baseName: string): string =
  283. ## Builds a complex unique href name for the node.
  284. ##
  285. ## Pass as ``baseName`` the plain symbol obtained from the nodeName. The
  286. ## format of the returned symbol will be ``baseName(.callable type)?,(param
  287. ## type)?(,param type)*``. The callable type part will be added only if the
  288. ## node is not a proc, as those are the common ones. The suffix will be a dot
  289. ## and a single letter representing the type of the callable. The parameter
  290. ## types will be added with a preceding dash. Return types won't be added.
  291. ##
  292. ## If you modify the output of this proc, please update the anchor generation
  293. ## section of ``doc/docgen.txt``.
  294. result = baseName
  295. case k:
  296. of skProc, skFunc: result.add(defaultParamSeparator)
  297. of skMacro: result.add(".m" & defaultParamSeparator)
  298. of skMethod: result.add(".e" & defaultParamSeparator)
  299. of skIterator: result.add(".i" & defaultParamSeparator)
  300. of skTemplate: result.add(".t" & defaultParamSeparator)
  301. of skConverter: result.add(".c" & defaultParamSeparator)
  302. else: discard
  303. if len(n) > paramsPos and n[paramsPos].kind == nkFormalParams:
  304. result.add(renderParamTypes(n[paramsPos]))
  305. proc isCallable(n: PNode): bool =
  306. ## Returns true if `n` contains a callable node.
  307. case n.kind
  308. of nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef,
  309. nkConverterDef, nkFuncDef: result = true
  310. else:
  311. result = false
  312. proc docstringSummary(rstText: string): string =
  313. ## Returns just the first line or a brief chunk of text from a rst string.
  314. ##
  315. ## Most docstrings will contain a one liner summary, so stripping at the
  316. ## first newline is usually fine. If after that the content is still too big,
  317. ## it is stripped at the first comma, colon or dot, usual english sentence
  318. ## separators.
  319. ##
  320. ## No guarantees are made on the size of the output, but it should be small.
  321. ## Also, we hope to not break the rst, but maybe we do. If there is any
  322. ## trimming done, an ellipsis unicode char is added.
  323. const maxDocstringChars = 100
  324. assert(rstText.len < 2 or (rstText[0] == '#' and rstText[1] == '#'))
  325. result = rstText.substr(2).strip
  326. var pos = result.find('\L')
  327. if pos > 0:
  328. result.delete(pos, result.len - 1)
  329. result.add("…")
  330. if pos < maxDocstringChars:
  331. return
  332. # Try to keep trimming at other natural boundaries.
  333. pos = result.find({'.', ',', ':'})
  334. let last = result.len - 1
  335. if pos > 0 and pos < last:
  336. result.delete(pos, last)
  337. result.add("…")
  338. proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
  339. if not isVisible(nameNode): return
  340. let
  341. name = getName(d, nameNode)
  342. nameRope = name.rope
  343. plainDocstring = getPlainDocstring(n) # call here before genRecComment!
  344. var result: Rope = nil
  345. var literal, plainName = ""
  346. var kind = tkEof
  347. var comm = genRecComment(d, n) # call this here for the side-effect!
  348. var r: TSrcGen
  349. # Obtain the plain rendered string for hyperlink titles.
  350. initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments,
  351. renderNoPragmas, renderNoProcDefs})
  352. while true:
  353. getNextTok(r, kind, literal)
  354. if kind == tkEof:
  355. break
  356. plainName.add(literal)
  357. # Render the HTML hyperlink.
  358. initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments})
  359. while true:
  360. getNextTok(r, kind, literal)
  361. case kind
  362. of tkEof:
  363. break
  364. of tkComment:
  365. dispA(result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
  366. [rope(esc(d.target, literal))])
  367. of tokKeywordLow..tokKeywordHigh:
  368. dispA(result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
  369. [rope(literal)])
  370. of tkOpr:
  371. dispA(result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}",
  372. [rope(esc(d.target, literal))])
  373. of tkStrLit..tkTripleStrLit:
  374. dispA(result, "<span class=\"StringLit\">$1</span>",
  375. "\\spanStringLit{$1}", [rope(esc(d.target, literal))])
  376. of tkCharLit:
  377. dispA(result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}",
  378. [rope(esc(d.target, literal))])
  379. of tkIntLit..tkUInt64Lit:
  380. dispA(result, "<span class=\"DecNumber\">$1</span>",
  381. "\\spanDecNumber{$1}", [rope(esc(d.target, literal))])
  382. of tkFloatLit..tkFloat128Lit:
  383. dispA(result, "<span class=\"FloatNumber\">$1</span>",
  384. "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))])
  385. of tkSymbol:
  386. dispA(result, "<span class=\"Identifier\">$1</span>",
  387. "\\spanIdentifier{$1}", [rope(esc(d.target, literal))])
  388. of tkSpaces, tkInvalid:
  389. add(result, literal)
  390. of tkCurlyDotLe:
  391. dispA(result, """<span class="Other pragmabegin">$1</span><div class="pragma">""",
  392. "\\spanOther{$1}",
  393. [rope(esc(d.target, literal))])
  394. of tkCurlyDotRi:
  395. dispA(result, "</div><span class=\"Other pragmaend\">$1</span>",
  396. "\\spanOther{$1}",
  397. [rope(esc(d.target, literal))])
  398. of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
  399. tkBracketDotLe, tkBracketDotRi, tkParDotLe,
  400. tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot,
  401. tkAccent, tkColonColon,
  402. tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr:
  403. dispA(result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
  404. [rope(esc(d.target, literal))])
  405. inc(d.id)
  406. let
  407. plainNameRope = rope(xmltree.escape(plainName.strip))
  408. cleanPlainSymbol = renderPlainSymbolName(nameNode)
  409. complexSymbol = complexName(k, n, cleanPlainSymbol)
  410. plainSymbolRope = rope(cleanPlainSymbol)
  411. plainSymbolEncRope = rope(encodeUrl(cleanPlainSymbol))
  412. itemIDRope = rope(d.id)
  413. symbolOrId = d.newUniquePlainSymbol(complexSymbol)
  414. symbolOrIdRope = symbolOrId.rope
  415. symbolOrIdEncRope = encodeUrl(symbolOrId).rope
  416. var seeSrcRope: Rope = nil
  417. let docItemSeeSrc = getConfigVar("doc.item.seesrc")
  418. if docItemSeeSrc.len > 0:
  419. let cwd = getCurrentDir().canonicalizePath()
  420. var path = n.info.toFullPath
  421. if path.startsWith(cwd):
  422. path = path[cwd.len+1 .. ^1].replace('\\', '/')
  423. let gitUrl = getConfigVar("git.url")
  424. if gitUrl.len > 0:
  425. var commit = getConfigVar("git.commit")
  426. if commit.len == 0: commit = "master"
  427. dispA(seeSrcRope, "$1", "", [ropeFormatNamedVars(docItemSeeSrc,
  428. ["path", "line", "url", "commit"], [rope path,
  429. rope($n.info.line), rope gitUrl,
  430. rope commit])])
  431. add(d.section[k], ropeFormatNamedVars(getConfigVar("doc.item"),
  432. ["name", "header", "desc", "itemID", "header_plain", "itemSym",
  433. "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "seeSrc"],
  434. [nameRope, result, comm, itemIDRope, plainNameRope, plainSymbolRope,
  435. symbolOrIdRope, plainSymbolEncRope, symbolOrIdEncRope, seeSrcRope]))
  436. var attype: Rope
  437. if k in routineKinds and nameNode.kind == nkSym:
  438. let att = attachToType(d, nameNode.sym)
  439. if att != nil:
  440. attype = rope esc(d.target, att.name.s)
  441. add(d.toc[k], ropeFormatNamedVars(getConfigVar("doc.item.toc"),
  442. ["name", "header", "desc", "itemID", "header_plain", "itemSym",
  443. "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "attype"],
  444. [rope(getName(d, nameNode, d.splitAfter)), result, comm,
  445. itemIDRope, plainNameRope, plainSymbolRope, symbolOrIdRope,
  446. plainSymbolEncRope, symbolOrIdEncRope, attype]))
  447. # Ironically for types the complexSymbol is *cleaner* than the plainName
  448. # because it doesn't include object fields or documentation comments. So we
  449. # use the plain one for callable elements, and the complex for the rest.
  450. var linkTitle = changeFileExt(extractFilename(d.filename), "") & " : "
  451. if n.isCallable: linkTitle.add(xmltree.escape(plainName.strip))
  452. else: linkTitle.add(xmltree.escape(complexSymbol.strip))
  453. setIndexTerm(d[], symbolOrId, name, linkTitle,
  454. xmltree.escape(plainDocstring.docstringSummary))
  455. if k == skType and nameNode.kind == nkSym:
  456. d.types.strTableAdd nameNode.sym
  457. proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode =
  458. if not isVisible(nameNode): return
  459. var
  460. name = getName(d, nameNode)
  461. comm = $genRecComment(d, n)
  462. r: TSrcGen
  463. initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments})
  464. result = %{ "name": %name, "type": %($k), "line": %n.info.line,
  465. "col": %n.info.col}
  466. if comm != nil and comm != "":
  467. result["description"] = %comm
  468. if r.buf != nil:
  469. result["code"] = %r.buf
  470. proc checkForFalse(n: PNode): bool =
  471. result = n.kind == nkIdent and cmpIgnoreStyle(n.ident.s, "false") == 0
  472. proc traceDeps(d: PDoc, n: PNode) =
  473. const k = skModule
  474. if d.section[k] != nil: add(d.section[k], ", ")
  475. dispA(d.section[k],
  476. "<a class=\"reference external\" href=\"$1.html\">$1</a>",
  477. "$1", [rope(getModuleName(n))])
  478. proc generateDoc*(d: PDoc, n: PNode) =
  479. case n.kind
  480. of nkCommentStmt: add(d.modDesc, genComment(d, n))
  481. of nkProcDef:
  482. when useEffectSystem: documentRaises(n)
  483. genItem(d, n, n.sons[namePos], skProc)
  484. of nkFuncDef:
  485. when useEffectSystem: documentRaises(n)
  486. genItem(d, n, n.sons[namePos], skFunc)
  487. of nkMethodDef:
  488. when useEffectSystem: documentRaises(n)
  489. genItem(d, n, n.sons[namePos], skMethod)
  490. of nkIteratorDef:
  491. when useEffectSystem: documentRaises(n)
  492. genItem(d, n, n.sons[namePos], skIterator)
  493. of nkMacroDef: genItem(d, n, n.sons[namePos], skMacro)
  494. of nkTemplateDef: genItem(d, n, n.sons[namePos], skTemplate)
  495. of nkConverterDef:
  496. when useEffectSystem: documentRaises(n)
  497. genItem(d, n, n.sons[namePos], skConverter)
  498. of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
  499. for i in countup(0, sonsLen(n) - 1):
  500. if n.sons[i].kind != nkCommentStmt:
  501. # order is always 'type var let const':
  502. genItem(d, n.sons[i], n.sons[i].sons[0],
  503. succ(skType, ord(n.kind)-ord(nkTypeSection)))
  504. of nkStmtList:
  505. for i in countup(0, sonsLen(n) - 1): generateDoc(d, n.sons[i])
  506. of nkWhenStmt:
  507. # generate documentation for the first branch only:
  508. if not checkForFalse(n.sons[0].sons[0]):
  509. generateDoc(d, lastSon(n.sons[0]))
  510. of nkImportStmt:
  511. for i in 0 .. sonsLen(n)-1: traceDeps(d, n.sons[i])
  512. of nkFromStmt, nkImportExceptStmt: traceDeps(d, n.sons[0])
  513. else: discard
  514. proc add(d: PDoc; j: JsonNode) =
  515. if j != nil: d.jArray.add j
  516. proc generateJson*(d: PDoc, n: PNode) =
  517. case n.kind
  518. of nkCommentStmt:
  519. if n.comment != nil and startsWith(n.comment, "##"):
  520. let stripped = n.comment.substr(2).strip
  521. d.add %{ "comment": %stripped, "line": %n.info.line,
  522. "col": %n.info.col }
  523. of nkProcDef:
  524. when useEffectSystem: documentRaises(n)
  525. d.add genJsonItem(d, n, n.sons[namePos], skProc)
  526. of nkFuncDef:
  527. when useEffectSystem: documentRaises(n)
  528. d.add genJsonItem(d, n, n.sons[namePos], skFunc)
  529. of nkMethodDef:
  530. when useEffectSystem: documentRaises(n)
  531. d.add genJsonItem(d, n, n.sons[namePos], skMethod)
  532. of nkIteratorDef:
  533. when useEffectSystem: documentRaises(n)
  534. d.add genJsonItem(d, n, n.sons[namePos], skIterator)
  535. of nkMacroDef:
  536. d.add genJsonItem(d, n, n.sons[namePos], skMacro)
  537. of nkTemplateDef:
  538. d.add genJsonItem(d, n, n.sons[namePos], skTemplate)
  539. of nkConverterDef:
  540. when useEffectSystem: documentRaises(n)
  541. d.add genJsonItem(d, n, n.sons[namePos], skConverter)
  542. of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
  543. for i in countup(0, sonsLen(n) - 1):
  544. if n.sons[i].kind != nkCommentStmt:
  545. # order is always 'type var let const':
  546. d.add genJsonItem(d, n.sons[i], n.sons[i].sons[0],
  547. succ(skType, ord(n.kind)-ord(nkTypeSection)))
  548. of nkStmtList:
  549. for i in countup(0, sonsLen(n) - 1):
  550. generateJson(d, n.sons[i])
  551. of nkWhenStmt:
  552. # generate documentation for the first branch only:
  553. if not checkForFalse(n.sons[0].sons[0]):
  554. generateJson(d, lastSon(n.sons[0]))
  555. else: discard
  556. proc genSection(d: PDoc, kind: TSymKind) =
  557. const sectionNames: array[skModule..skTemplate, string] = [
  558. "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs",
  559. "Methods", "Iterators", "Converters", "Macros", "Templates"
  560. ]
  561. if d.section[kind] == nil: return
  562. var title = sectionNames[kind].rope
  563. d.section[kind] = ropeFormatNamedVars(getConfigVar("doc.section"), [
  564. "sectionid", "sectionTitle", "sectionTitleID", "content"], [
  565. ord(kind).rope, title, rope(ord(kind) + 50), d.section[kind]])
  566. d.toc[kind] = ropeFormatNamedVars(getConfigVar("doc.section.toc"), [
  567. "sectionid", "sectionTitle", "sectionTitleID", "content"], [
  568. ord(kind).rope, title, rope(ord(kind) + 50), d.toc[kind]])
  569. proc genOutFile(d: PDoc): Rope =
  570. var
  571. code, content: Rope
  572. title = ""
  573. var j = 0
  574. var tmp = ""
  575. renderTocEntries(d[], j, 1, tmp)
  576. var toc = tmp.rope
  577. for i in countup(low(TSymKind), high(TSymKind)):
  578. genSection(d, i)
  579. add(toc, d.toc[i])
  580. if toc != nil:
  581. toc = ropeFormatNamedVars(getConfigVar("doc.toc"), ["content"], [toc])
  582. for i in countup(low(TSymKind), high(TSymKind)): add(code, d.section[i])
  583. # Extract the title. Non API modules generate an entry in the index table.
  584. if d.meta[metaTitle].len != 0:
  585. title = d.meta[metaTitle]
  586. setIndexTerm(d[], "", title)
  587. else:
  588. # Modules get an automatic title for the HTML, but no entry in the index.
  589. title = "Module " & extractFilename(changeFileExt(d.filename, ""))
  590. let bodyname = if d.hasToc and not d.isPureRst: "doc.body_toc_group"
  591. elif d.hasToc: "doc.body_toc"
  592. else: "doc.body_no_toc"
  593. content = ropeFormatNamedVars(getConfigVar(bodyname), ["title",
  594. "tableofcontents", "moduledesc", "date", "time", "content"],
  595. [title.rope, toc, d.modDesc, rope(getDateStr()),
  596. rope(getClockStr()), code])
  597. if optCompileOnly notin gGlobalOptions:
  598. # XXX what is this hack doing here? 'optCompileOnly' means raw output!?
  599. code = ropeFormatNamedVars(getConfigVar("doc.file"), ["title",
  600. "tableofcontents", "moduledesc", "date", "time",
  601. "content", "author", "version", "analytics"],
  602. [title.rope, toc, d.modDesc, rope(getDateStr()),
  603. rope(getClockStr()), content, d.meta[metaAuthor].rope,
  604. d.meta[metaVersion].rope, d.analytics.rope])
  605. else:
  606. code = content
  607. result = code
  608. proc generateIndex*(d: PDoc) =
  609. if optGenIndex in gGlobalOptions:
  610. writeIndexFile(d[], splitFile(options.outFile).dir /
  611. splitFile(d.filename).name & IndexExt)
  612. proc getOutFile2(filename, ext, dir: string): string =
  613. if gWholeProject:
  614. let d = if options.outFile != "": options.outFile else: dir
  615. createDir(d)
  616. result = d / changeFileExt(filename, ext)
  617. else:
  618. result = getOutFile(filename, ext)
  619. proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) =
  620. var content = genOutFile(d)
  621. if optStdout in gGlobalOptions:
  622. writeRope(stdout, content)
  623. else:
  624. writeRope(content, getOutFile2(filename, outExt, "htmldocs"), useWarning)
  625. proc writeOutputJson*(d: PDoc, filename, outExt: string,
  626. useWarning = false) =
  627. let content = %*{"orig": d.filename,
  628. "nimble": getPackageName(d.filename),
  629. "entries": d.jArray}
  630. if optStdout in gGlobalOptions:
  631. write(stdout, $content)
  632. else:
  633. var f: File
  634. if open(f, getOutFile2(splitFile(filename).name,
  635. outExt, "jsondocs"), fmWrite):
  636. write(f, $content)
  637. close(f)
  638. else:
  639. discard "fixme: error report"
  640. proc commandDoc*() =
  641. var ast = parseFile(gProjectMainIdx, newIdentCache())
  642. if ast == nil: return
  643. var d = newDocumentor(gProjectFull, options.gConfigVars)
  644. d.hasToc = true
  645. generateDoc(d, ast)
  646. writeOutput(d, gProjectFull, HtmlExt)
  647. generateIndex(d)
  648. proc commandRstAux(filename, outExt: string) =
  649. var filen = addFileExt(filename, "txt")
  650. var d = newDocumentor(filen, options.gConfigVars)
  651. d.isPureRst = true
  652. var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc,
  653. {roSupportRawDirective})
  654. var modDesc = newStringOfCap(30_000)
  655. #d.modDesc = newMutableRope(30_000)
  656. renderRstToOut(d[], rst, modDesc)
  657. #freezeMutableRope(d.modDesc)
  658. d.modDesc = rope(modDesc)
  659. writeOutput(d, filename, outExt)
  660. generateIndex(d)
  661. proc commandRst2Html*() =
  662. commandRstAux(gProjectFull, HtmlExt)
  663. proc commandRst2TeX*() =
  664. splitter = "\\-"
  665. commandRstAux(gProjectFull, TexExt)
  666. proc commandJson*() =
  667. var ast = parseFile(gProjectMainIdx, newIdentCache())
  668. if ast == nil: return
  669. var d = newDocumentor(gProjectFull, options.gConfigVars)
  670. d.hasToc = true
  671. generateJson(d, ast)
  672. let json = d.jArray
  673. let content = rope(pretty(json))
  674. if optStdout in gGlobalOptions:
  675. writeRope(stdout, content)
  676. else:
  677. #echo getOutFile(gProjectFull, JsonExt)
  678. writeRope(content, getOutFile(gProjectFull, JsonExt), useWarning = false)
  679. proc commandBuildIndex*() =
  680. var content = mergeIndexes(gProjectFull).rope
  681. let code = ropeFormatNamedVars(getConfigVar("doc.file"), ["title",
  682. "tableofcontents", "moduledesc", "date", "time",
  683. "content", "author", "version", "analytics"],
  684. ["Index".rope, nil, nil, rope(getDateStr()),
  685. rope(getClockStr()), content, nil, nil, nil])
  686. # no analytics because context is not available
  687. writeRope(code, getOutFile("theindex", HtmlExt))