docgen.nim 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  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,
  16. json, xmltree, cgi, trees, types,
  17. typesrenderer, astalgo, lineinfos, intsets,
  18. pathutils, trees
  19. const
  20. exportSection = skField
  21. type
  22. TSections = array[TSymKind, Rope]
  23. TDocumentor = object of rstgen.RstGenerator
  24. modDesc: Rope # module description
  25. modDeprecationMsg: Rope
  26. toc, section: TSections
  27. indexValFilename: string
  28. analytics: string # Google Analytics javascript, "" if doesn't exist
  29. seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML.
  30. jArray: JsonNode
  31. types: TStrTable
  32. isPureRst: bool
  33. conf*: ConfigRef
  34. cache*: IdentCache
  35. exampleCounter: int
  36. emitted: IntSet # we need to track which symbols have been emitted
  37. # already. See bug #3655
  38. destFile*: AbsoluteFile
  39. thisDir*: AbsoluteDir
  40. examples: string
  41. PDoc* = ref TDocumentor ## Alias to type less.
  42. proc whichType(d: PDoc; n: PNode): PSym =
  43. if n.kind == nkSym:
  44. if d.types.strTableContains(n.sym):
  45. result = n.sym
  46. else:
  47. for i in 0..<safeLen(n):
  48. let x = whichType(d, n[i])
  49. if x != nil: return x
  50. proc attachToType(d: PDoc; p: PSym): PSym =
  51. let params = p.ast.sons[paramsPos]
  52. template check(i) =
  53. result = whichType(d, params[i])
  54. if result != nil: return result
  55. # first check the first parameter, then the return type,
  56. # then the other parameter:
  57. if params.len > 1: check(1)
  58. if params.len > 0: check(0)
  59. for i in 2..<params.len: check(i)
  60. template declareClosures =
  61. proc compilerMsgHandler(filename: string, line, col: int,
  62. msgKind: rst.MsgKind, arg: string) {.procvar, gcsafe.} =
  63. # translate msg kind:
  64. var k: TMsgKind
  65. case msgKind
  66. of meCannotOpenFile: k = errCannotOpenFile
  67. of meExpected: k = errXExpected
  68. of meGridTableNotImplemented: k = errGridTableNotImplemented
  69. of meNewSectionExpected: k = errNewSectionExpected
  70. of meGeneralParseError: k = errGeneralParseError
  71. of meInvalidDirective: k = errInvalidDirectiveX
  72. of mwRedefinitionOfLabel: k = warnRedefinitionOfLabel
  73. of mwUnknownSubstitution: k = warnUnknownSubstitutionX
  74. of mwUnsupportedLanguage: k = warnLanguageXNotSupported
  75. of mwUnsupportedField: k = warnFieldXNotSupported
  76. {.gcsafe.}:
  77. globalError(conf, newLineInfo(conf, AbsoluteFile filename, line, col), k, arg)
  78. proc docgenFindFile(s: string): string {.procvar, gcsafe.} =
  79. result = options.findFile(conf, s).string
  80. if result.len == 0:
  81. result = getCurrentDir() / s
  82. if not existsFile(result): result = ""
  83. proc parseRst(text, filename: string,
  84. line, column: int, hasToc: var bool,
  85. rstOptions: RstParseOptions;
  86. conf: ConfigRef): PRstNode =
  87. declareClosures()
  88. result = rstParse(text, filename, line, column, hasToc, rstOptions,
  89. docgenFindFile, compilerMsgHandler)
  90. proc getOutFile2(conf: ConfigRef; filename: RelativeFile,
  91. ext: string, dir: RelativeDir; guessTarget: bool): AbsoluteFile =
  92. if optWholeProject in conf.globalOptions:
  93. let d = if conf.outDir.isEmpty: conf.projectPath / dir else: conf.outDir
  94. createDir(d)
  95. result = d / changeFileExt(filename, ext)
  96. elif guessTarget:
  97. let d = if not conf.outDir.isEmpty: conf.outDir
  98. else: conf.projectPath
  99. createDir(d)
  100. result = d / changeFileExt(filename, ext)
  101. elif not conf.outFile.isEmpty:
  102. result = absOutFile(conf)
  103. else:
  104. result = getOutFile(conf, filename, ext)
  105. proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, outExt: string = HtmlExt): PDoc =
  106. declareClosures()
  107. new(result)
  108. result.conf = conf
  109. result.cache = cache
  110. initRstGenerator(result[], (if conf.cmd != cmdRst2tex: outHtml else: outLatex),
  111. conf.configVars, filename.string, {roSupportRawDirective, roSupportMarkdown},
  112. docgenFindFile, compilerMsgHandler)
  113. if conf.configVars.hasKey("doc.googleAnalytics"):
  114. result.analytics = """
  115. <script>
  116. (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  117. (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  118. m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  119. })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
  120. ga('create', '$1', 'auto');
  121. ga('send', 'pageview');
  122. </script>
  123. """ % [conf.configVars.getOrDefault"doc.googleAnalytics"]
  124. else:
  125. result.analytics = ""
  126. result.seenSymbols = newStringTable(modeCaseInsensitive)
  127. result.id = 100
  128. result.jArray = newJArray()
  129. initStrTable result.types
  130. result.onTestSnippet =
  131. proc (gen: var RstGenerator; filename, cmd: string; status: int; content: string) =
  132. var d = TDocumentor(gen)
  133. var outp: AbsoluteFile
  134. if filename.len == 0:
  135. inc(d.id)
  136. let nameOnly = splitFile(d.filename).name
  137. outp = getNimcacheDir(conf) / RelativeDir(nameOnly) /
  138. RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim")
  139. elif isAbsolute(filename):
  140. outp = AbsoluteFile(filename)
  141. else:
  142. # Nim's convention: every path is relative to the file it was written in:
  143. let nameOnly = splitFile(d.filename).name
  144. outp = AbsoluteDir(nameOnly) / RelativeFile(filename)
  145. # Make sure the destination directory exists
  146. createDir(outp.splitFile.dir)
  147. # Include the current file if we're parsing a nim file
  148. let importStmt = if d.isPureRst: "" else: "import \"$1\"\n" % [d.filename.replace("\\", "/")]
  149. writeFile(outp, importStmt & content)
  150. let c = if cmd.startsWith("nim "): os.getAppFilename() & cmd.substr(3)
  151. else: cmd
  152. let c2 = c % quoteShell(outp)
  153. rawMessage(conf, hintExecuting, c2)
  154. if execShellCmd(c2) != status:
  155. rawMessage(conf, errGenerated, "executing of external program failed: " & c2)
  156. result.emitted = initIntSet()
  157. result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath),
  158. outExt, RelativeDir"htmldocs", false)
  159. result.thisDir = result.destFile.splitFile.dir
  160. template dispA(conf: ConfigRef; dest: var Rope, xml, tex: string, args: openArray[Rope]) =
  161. if conf.cmd != cmdRst2tex: addf(dest, xml, args)
  162. else: addf(dest, tex, args)
  163. proc getVarIdx(varnames: openArray[string], id: string): int =
  164. for i in 0 .. high(varnames):
  165. if cmpIgnoreStyle(varnames[i], id) == 0:
  166. return i
  167. result = -1
  168. proc ropeFormatNamedVars(conf: ConfigRef; frmt: FormatStr,
  169. varnames: openArray[string],
  170. varvalues: openArray[Rope]): Rope =
  171. var i = 0
  172. var L = len(frmt)
  173. result = nil
  174. var num = 0
  175. while i < L:
  176. if frmt[i] == '$':
  177. inc(i) # skip '$'
  178. case frmt[i]
  179. of '#':
  180. add(result, varvalues[num])
  181. inc(num)
  182. inc(i)
  183. of '$':
  184. add(result, "$")
  185. inc(i)
  186. of '0'..'9':
  187. var j = 0
  188. while true:
  189. j = (j * 10) + ord(frmt[i]) - ord('0')
  190. inc(i)
  191. if (i > L + 0 - 1) or not (frmt[i] in {'0'..'9'}): break
  192. if j > high(varvalues) + 1:
  193. rawMessage(conf, errGenerated, "Invalid format string; too many $s: " & frmt)
  194. num = j
  195. add(result, varvalues[j - 1])
  196. of 'A'..'Z', 'a'..'z', '\x80'..'\xFF':
  197. var id = ""
  198. while true:
  199. add(id, frmt[i])
  200. inc(i)
  201. if not (frmt[i] in {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}): break
  202. var idx = getVarIdx(varnames, id)
  203. if idx >= 0: add(result, varvalues[idx])
  204. else: rawMessage(conf, errGenerated, "unknown substition variable: " & id)
  205. of '{':
  206. var id = ""
  207. inc(i)
  208. while i < frmt.len and frmt[i] != '}':
  209. add(id, frmt[i])
  210. inc(i)
  211. if i >= frmt.len:
  212. rawMessage(conf, errGenerated, "expected closing '}'")
  213. else:
  214. inc(i) # skip }
  215. # search for the variable:
  216. let idx = getVarIdx(varnames, id)
  217. if idx >= 0: add(result, varvalues[idx])
  218. else: rawMessage(conf, errGenerated, "unknown substition variable: " & id)
  219. else:
  220. add(result, "$")
  221. var start = i
  222. while i < L:
  223. if frmt[i] != '$': inc(i)
  224. else: break
  225. if i - 1 >= start: add(result, substr(frmt, start, i - 1))
  226. proc genComment(d: PDoc, n: PNode): string =
  227. result = ""
  228. var dummyHasToc: bool
  229. if n.comment.len > 0:
  230. renderRstToOut(d[], parseRst(n.comment, toFullPath(d.conf, n.info),
  231. toLinenumber(n.info), toColumn(n.info),
  232. dummyHasToc, d.options, d.conf), result)
  233. proc genRecCommentAux(d: PDoc, n: PNode): Rope =
  234. if n == nil: return nil
  235. result = genComment(d, n).rope
  236. if result == nil:
  237. if n.kind in {nkStmtList, nkStmtListExpr, nkTypeDef, nkConstDef,
  238. nkObjectTy, nkRefTy, nkPtrTy, nkAsgn, nkFastAsgn, nkHiddenStdConv}:
  239. # notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}:
  240. for i in 0 ..< len(n):
  241. result = genRecCommentAux(d, n.sons[i])
  242. if result != nil: return
  243. else:
  244. when defined(nimNoNilSeqs): n.comment = ""
  245. else: n.comment = nil
  246. proc genRecComment(d: PDoc, n: PNode): Rope =
  247. if n == nil: return nil
  248. result = genComment(d, n).rope
  249. if result == nil:
  250. if n.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef,
  251. nkMacroDef, nkTemplateDef, nkConverterDef}:
  252. result = genRecCommentAux(d, n[bodyPos])
  253. else:
  254. result = genRecCommentAux(d, n)
  255. proc getPlainDocstring(n: PNode): string =
  256. ## Gets the plain text docstring of a node non destructively.
  257. ##
  258. ## You need to call this before genRecComment, whose side effects are removal
  259. ## of comments from the tree. The proc will recursively scan and return all
  260. ## the concatenated ``##`` comments of the node.
  261. result = ""
  262. if n == nil: return
  263. if startsWith(n.comment, "##"):
  264. result = n.comment
  265. if result.len < 1:
  266. for i in 0 ..< safeLen(n):
  267. result = getPlainDocstring(n.sons[i])
  268. if result.len > 0: return
  269. proc belongsToPackage(conf: ConfigRef; module: PSym): bool =
  270. result = module.kind == skModule and module.owner != nil and
  271. module.owner.id == conf.mainPackageId
  272. proc externalDep(d: PDoc; module: PSym): string =
  273. if optWholeProject in d.conf.globalOptions:
  274. let full = AbsoluteFile toFullPath(d.conf, FileIndex module.position)
  275. let tmp = getOutFile2(d.conf, full.relativeTo(d.conf.projectPath), HtmlExt,
  276. RelativeDir"htmldocs", sfMainModule notin module.flags)
  277. result = relativeTo(tmp, d.thisDir, '/').string
  278. else:
  279. result = extractFilename toFullPath(d.conf, FileIndex module.position)
  280. proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {};
  281. procLink: Rope) =
  282. var r: TSrcGen
  283. var literal = ""
  284. initTokRender(r, n, renderFlags)
  285. var kind = tkEof
  286. var tokenPos = 0
  287. var procTokenPos = 0
  288. while true:
  289. getNextTok(r, kind, literal)
  290. inc tokenPos
  291. case kind
  292. of tkEof:
  293. break
  294. of tkComment:
  295. dispA(d.conf, result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
  296. [rope(esc(d.target, literal))])
  297. of tokKeywordLow..tokKeywordHigh:
  298. if kind in {tkProc, tkMethod, tkIterator, tkMacro, tkTemplate, tkFunc, tkConverter}:
  299. procTokenPos = tokenPos
  300. dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
  301. [rope(literal)])
  302. of tkOpr:
  303. dispA(d.conf, result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}",
  304. [rope(esc(d.target, literal))])
  305. of tkStrLit..tkTripleStrLit:
  306. dispA(d.conf, result, "<span class=\"StringLit\">$1</span>",
  307. "\\spanStringLit{$1}", [rope(esc(d.target, literal))])
  308. of tkCharLit:
  309. dispA(d.conf, result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}",
  310. [rope(esc(d.target, literal))])
  311. of tkIntLit..tkUInt64Lit:
  312. dispA(d.conf, result, "<span class=\"DecNumber\">$1</span>",
  313. "\\spanDecNumber{$1}", [rope(esc(d.target, literal))])
  314. of tkFloatLit..tkFloat128Lit:
  315. dispA(d.conf, result, "<span class=\"FloatNumber\">$1</span>",
  316. "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))])
  317. of tkSymbol:
  318. let s = getTokSym(r)
  319. # -2 because of the whitespace in between:
  320. if procTokenPos == tokenPos-2 and procLink != nil:
  321. dispA(d.conf, result, "<a href=\"#$2\"><span class=\"Identifier\">$1</span></a>",
  322. "\\spanIdentifier{$1}", [rope(esc(d.target, literal)), procLink])
  323. elif s != nil and s.kind in {skType, skVar, skLet, skConst} and
  324. sfExported in s.flags and s.owner != nil and
  325. belongsToPackage(d.conf, s.owner) and d.target == outHtml:
  326. let external = externalDep(d, s.owner)
  327. result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>",
  328. [rope changeFileExt(external, "html"), rope literal,
  329. rope(esc(d.target, literal))]
  330. else:
  331. dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
  332. "\\spanIdentifier{$1}", [rope(esc(d.target, literal))])
  333. of tkSpaces, tkInvalid:
  334. add(result, literal)
  335. of tkCurlyDotLe:
  336. dispA(d.conf, result, "<span>" & # This span is required for the JS to work properly
  337. """<span class="Other">{</span><span class="Other pragmadots">...</span><span class="Other">}</span>
  338. </span>
  339. <span class="pragmawrap">
  340. <span class="Other">$1</span>
  341. <span class="pragma">""".replace("\n", ""), # Must remove newlines because wrapped in a <pre>
  342. "\\spanOther{$1}",
  343. [rope(esc(d.target, literal))])
  344. of tkCurlyDotRi:
  345. dispA(d.conf, result, """
  346. </span>
  347. <span class="Other">$1</span>
  348. </span>""".replace("\n", ""),
  349. "\\spanOther{$1}",
  350. [rope(esc(d.target, literal))])
  351. of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
  352. tkBracketDotLe, tkBracketDotRi, tkParDotLe,
  353. tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot,
  354. tkAccent, tkColonColon,
  355. tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr,
  356. tkBracketLeColon:
  357. dispA(d.conf, result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
  358. [rope(esc(d.target, literal))])
  359. proc testExample(d: PDoc; ex: PNode) =
  360. if d.conf.errorCounter > 0: return
  361. let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
  362. createDir(outputDir)
  363. inc d.exampleCounter
  364. let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" &
  365. "_examples" & $d.exampleCounter & ".nim"))
  366. #let nimcache = outp.changeFileExt"" & "_nimcache"
  367. renderModule(ex, d.filename, outp.string, conf = d.conf)
  368. d.examples.add "import r\"" & outp.string & "\"\n"
  369. proc runAllExamples(d: PDoc) =
  370. if d.examples.len == 0: return
  371. let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
  372. let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" &
  373. "_examples.nim"))
  374. writeFile(outp, d.examples)
  375. let backend = if isDefined(d.conf, "js"): "js"
  376. elif isDefined(d.conf, "cpp"): "cpp"
  377. elif isDefined(d.conf, "objc"): "objc"
  378. else: "c"
  379. if os.execShellCmd(os.getAppFilename() & " " & backend &
  380. " --path:" & quoteShell(d.conf.projectPath) &
  381. " --nimcache:" & quoteShell(outputDir) &
  382. " -r " & quoteShell(outp)) != 0:
  383. quit "[Examples] failed: see " & outp.string
  384. else:
  385. # keep generated source file `outp` to allow inspection.
  386. rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string])
  387. removeFile(outp.changeFileExt(ExeExt))
  388. proc extractImports(n: PNode; result: PNode) =
  389. if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}:
  390. result.add copyTree(n)
  391. n.kind = nkEmpty
  392. return
  393. for i in 0..<n.safeLen: extractImports(n[i], result)
  394. proc prepareExamples(d: PDoc; n: PNode) =
  395. var docComment = newTree(nkCommentStmt)
  396. let loc = d.conf.toFileLineCol(n.info)
  397. docComment.comment = "autogenerated by docgen from " & loc
  398. var runnableExamples = newTree(nkStmtList,
  399. docComment,
  400. newTree(nkImportStmt, newStrNode(nkStrLit, d.filename)))
  401. runnableExamples.info = n.info
  402. let imports = newTree(nkStmtList)
  403. var savedLastSon = copyTree n.lastSon
  404. extractImports(savedLastSon, imports)
  405. for imp in imports: runnableExamples.add imp
  406. runnableExamples.add newTree(nkBlockStmt, newNode(nkEmpty), copyTree savedLastSon)
  407. testExample(d, runnableExamples)
  408. proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) =
  409. if n.info.fileIndex != orig.info.fileIndex: return
  410. case n.kind
  411. of nkCallKinds:
  412. if isRunnableExamples(n[0]) and
  413. n.len >= 2 and n.lastSon.kind == nkStmtList:
  414. prepareExamples(d, n)
  415. dispA(d.conf, dest, "\n<p><strong class=\"examples_text\">$1</strong></p>\n",
  416. "\n\\textbf{$1}\n", [rope"Examples:"])
  417. inc d.listingCounter
  418. let id = $d.listingCounter
  419. dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim"])
  420. # this is a rather hacky way to get rid of the initial indentation
  421. # that the renderer currently produces:
  422. var i = 0
  423. var body = n.lastSon
  424. if body.len == 1 and body.kind == nkStmtList and
  425. body.lastSon.kind == nkStmtList:
  426. body = body.lastSon
  427. for b in body:
  428. if i > 0: dest.add "\n"
  429. inc i
  430. nodeToHighlightedHtml(d, b, dest, {}, nil)
  431. dest.add(d.config.getOrDefault"doc.listing_end" % id)
  432. else: discard
  433. for i in 0 ..< n.safeLen:
  434. getAllRunnableExamplesRec(d, n[i], orig, dest)
  435. proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) =
  436. getAllRunnableExamplesRec(d, n, n, dest)
  437. proc isVisible(d: PDoc; n: PNode): bool =
  438. result = false
  439. if n.kind == nkPostfix:
  440. if n.len == 2 and n.sons[0].kind == nkIdent:
  441. var v = n.sons[0].ident
  442. result = v.id == ord(wStar) or v.id == ord(wMinus)
  443. elif n.kind == nkSym:
  444. # we cannot generate code for forwarded symbols here as we have no
  445. # exception tracking information here. Instead we copy over the comment
  446. # from the proc header.
  447. if optDocInternal in d.conf.globalOptions:
  448. result = {sfFromGeneric, sfForward}*n.sym.flags == {}
  449. else:
  450. result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported}
  451. if result and containsOrIncl(d.emitted, n.sym.id):
  452. result = false
  453. elif n.kind == nkPragmaExpr:
  454. result = isVisible(d, n.sons[0])
  455. proc getName(d: PDoc, n: PNode, splitAfter = -1): string =
  456. case n.kind
  457. of nkPostfix: result = getName(d, n.sons[1], splitAfter)
  458. of nkPragmaExpr: result = getName(d, n.sons[0], splitAfter)
  459. of nkSym: result = esc(d.target, n.sym.renderDefinitionName, splitAfter)
  460. of nkIdent: result = esc(d.target, n.ident.s, splitAfter)
  461. of nkAccQuoted:
  462. result = esc(d.target, "`")
  463. for i in 0..<n.len: result.add(getName(d, n[i], splitAfter))
  464. result.add esc(d.target, "`")
  465. of nkOpenSymChoice, nkClosedSymChoice:
  466. result = getName(d, n[0], splitAfter)
  467. else:
  468. result = ""
  469. proc getNameIdent(cache: IdentCache; n: PNode): PIdent =
  470. case n.kind
  471. of nkPostfix: result = getNameIdent(cache, n.sons[1])
  472. of nkPragmaExpr: result = getNameIdent(cache, n.sons[0])
  473. of nkSym: result = n.sym.name
  474. of nkIdent: result = n.ident
  475. of nkAccQuoted:
  476. var r = ""
  477. for i in 0..<n.len: r.add(getNameIdent(cache, n[i]).s)
  478. result = getIdent(cache, r)
  479. of nkOpenSymChoice, nkClosedSymChoice:
  480. result = getNameIdent(cache, n[0])
  481. else:
  482. result = nil
  483. proc getRstName(n: PNode): PRstNode =
  484. case n.kind
  485. of nkPostfix: result = getRstName(n.sons[1])
  486. of nkPragmaExpr: result = getRstName(n.sons[0])
  487. of nkSym: result = newRstNode(rnLeaf, n.sym.renderDefinitionName)
  488. of nkIdent: result = newRstNode(rnLeaf, n.ident.s)
  489. of nkAccQuoted:
  490. result = getRstName(n.sons[0])
  491. for i in 1 ..< n.len: result.text.add(getRstName(n[i]).text)
  492. of nkOpenSymChoice, nkClosedSymChoice:
  493. result = getRstName(n[0])
  494. else:
  495. result = nil
  496. proc newUniquePlainSymbol(d: PDoc, original: string): string =
  497. ## Returns a new unique plain symbol made up from the original.
  498. ##
  499. ## When a collision is found in the seenSymbols table, new numerical variants
  500. ## with underscore + number will be generated.
  501. if not d.seenSymbols.hasKey(original):
  502. result = original
  503. d.seenSymbols[original] = ""
  504. return
  505. # Iterate over possible numeric variants of the original name.
  506. var count = 2
  507. while true:
  508. result = original & "_" & $count
  509. if not d.seenSymbols.hasKey(result):
  510. d.seenSymbols[result] = ""
  511. break
  512. count += 1
  513. proc complexName(k: TSymKind, n: PNode, baseName: string): string =
  514. ## Builds a complex unique href name for the node.
  515. ##
  516. ## Pass as ``baseName`` the plain symbol obtained from the nodeName. The
  517. ## format of the returned symbol will be ``baseName(.callable type)?,(param
  518. ## type)?(,param type)*``. The callable type part will be added only if the
  519. ## node is not a proc, as those are the common ones. The suffix will be a dot
  520. ## and a single letter representing the type of the callable. The parameter
  521. ## types will be added with a preceding dash. Return types won't be added.
  522. ##
  523. ## If you modify the output of this proc, please update the anchor generation
  524. ## section of ``doc/docgen.txt``.
  525. result = baseName
  526. case k
  527. of skProc, skFunc: discard
  528. of skMacro: result.add(".m")
  529. of skMethod: result.add(".e")
  530. of skIterator: result.add(".i")
  531. of skTemplate: result.add(".t")
  532. of skConverter: result.add(".c")
  533. else: discard
  534. if len(n) > paramsPos and n[paramsPos].kind == nkFormalParams:
  535. let params = renderParamTypes(n[paramsPos])
  536. if params.len > 0:
  537. result.add(defaultParamSeparator)
  538. result.add(params)
  539. proc isCallable(n: PNode): bool =
  540. ## Returns true if `n` contains a callable node.
  541. case n.kind
  542. of nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef,
  543. nkConverterDef, nkFuncDef: result = true
  544. else:
  545. result = false
  546. proc docstringSummary(rstText: string): string =
  547. ## Returns just the first line or a brief chunk of text from a rst string.
  548. ##
  549. ## Most docstrings will contain a one liner summary, so stripping at the
  550. ## first newline is usually fine. If after that the content is still too big,
  551. ## it is stripped at the first comma, colon or dot, usual english sentence
  552. ## separators.
  553. ##
  554. ## No guarantees are made on the size of the output, but it should be small.
  555. ## Also, we hope to not break the rst, but maybe we do. If there is any
  556. ## trimming done, an ellipsis unicode char is added.
  557. const maxDocstringChars = 100
  558. assert(rstText.len < 2 or (rstText[0] == '#' and rstText[1] == '#'))
  559. result = rstText.substr(2).strip
  560. var pos = result.find('\L')
  561. if pos > 0:
  562. result.delete(pos, result.len - 1)
  563. result.add("…")
  564. if pos < maxDocstringChars:
  565. return
  566. # Try to keep trimming at other natural boundaries.
  567. pos = result.find({'.', ',', ':'})
  568. let last = result.len - 1
  569. if pos > 0 and pos < last:
  570. result.delete(pos, last)
  571. result.add("…")
  572. proc genDeprecationMsg(d: PDoc, n: PNode): Rope =
  573. ## Given a nkPragma wDeprecated node output a well-formatted section
  574. if n == nil: return
  575. case n.safeLen:
  576. of 0: # Deprecated w/o any message
  577. result = ropeFormatNamedVars(d.conf,
  578. getConfigVar(d.conf, "doc.deprecationmsg"), ["label", "message"],
  579. [~"Deprecated", nil])
  580. of 2: # Deprecated w/ a message
  581. if n[1].kind in {nkStrLit..nkTripleStrLit}:
  582. result = ropeFormatNamedVars(d.conf,
  583. getConfigVar(d.conf, "doc.deprecationmsg"), ["label", "message"],
  584. [~"Deprecated:", rope(xmltree.escape(n[1].strVal))])
  585. else:
  586. doAssert false
  587. proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
  588. if not isVisible(d, nameNode): return
  589. let
  590. name = getName(d, nameNode)
  591. nameRope = name.rope
  592. var plainDocstring = getPlainDocstring(n) # call here before genRecComment!
  593. var result: Rope = nil
  594. var literal, plainName = ""
  595. var kind = tkEof
  596. var comm = genRecComment(d, n) # call this here for the side-effect!
  597. getAllRunnableExamples(d, n, comm)
  598. var r: TSrcGen
  599. # Obtain the plain rendered string for hyperlink titles.
  600. initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments,
  601. renderNoPragmas, renderNoProcDefs})
  602. while true:
  603. getNextTok(r, kind, literal)
  604. if kind == tkEof:
  605. break
  606. plainName.add(literal)
  607. var pragmaNode: PNode = nil
  608. if n.isCallable and n.sons[pragmasPos].kind != nkEmpty:
  609. pragmaNode = findPragma(n.sons[pragmasPos], wDeprecated)
  610. inc(d.id)
  611. let
  612. plainNameRope = rope(xmltree.escape(plainName.strip))
  613. cleanPlainSymbol = renderPlainSymbolName(nameNode)
  614. complexSymbol = complexName(k, n, cleanPlainSymbol)
  615. plainSymbolRope = rope(cleanPlainSymbol)
  616. plainSymbolEncRope = rope(encodeUrl(cleanPlainSymbol))
  617. itemIDRope = rope(d.id)
  618. symbolOrId = d.newUniquePlainSymbol(complexSymbol)
  619. symbolOrIdRope = symbolOrId.rope
  620. symbolOrIdEncRope = encodeUrl(symbolOrId).rope
  621. deprecationMsgRope = genDeprecationMsg(d, pragmaNode)
  622. nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments,
  623. renderDocComments, renderSyms}, symbolOrIdEncRope)
  624. var seeSrcRope: Rope = nil
  625. let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc")
  626. if docItemSeeSrc.len > 0:
  627. let path = relativeTo(AbsoluteFile toFullPath(d.conf, n.info), AbsoluteDir getCurrentDir(), '/')
  628. when false:
  629. let cwd = canonicalizePath(d.conf, getCurrentDir())
  630. var path = toFullPath(d.conf, n.info)
  631. if path.startsWith(cwd):
  632. path = path[cwd.len+1 .. ^1].replace('\\', '/')
  633. let gitUrl = getConfigVar(d.conf, "git.url")
  634. if gitUrl.len > 0:
  635. let defaultBranch =
  636. if NimPatch mod 2 == 1: "devel"
  637. else: "version-$1-$2" % [$NimMajor, $NimMinor]
  638. let commit = getConfigVar(d.conf, "git.commit", defaultBranch)
  639. let develBranch = getConfigVar(d.conf, "git.devel", "devel")
  640. dispA(d.conf, seeSrcRope, "$1", "", [ropeFormatNamedVars(d.conf, docItemSeeSrc,
  641. ["path", "line", "url", "commit", "devel"], [rope path.string,
  642. rope($n.info.line), rope gitUrl, rope commit, rope develBranch])])
  643. add(d.section[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item"),
  644. ["name", "header", "desc", "itemID", "header_plain", "itemSym",
  645. "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "seeSrc", "deprecationMsg"],
  646. [nameRope, result, comm, itemIDRope, plainNameRope, plainSymbolRope,
  647. symbolOrIdRope, plainSymbolEncRope, symbolOrIdEncRope, seeSrcRope,
  648. deprecationMsgRope]))
  649. let external = d.destFile.relativeTo(d.conf.outDir, '/').changeFileExt(HtmlExt).string
  650. var attype: Rope
  651. if k in routineKinds and nameNode.kind == nkSym:
  652. let att = attachToType(d, nameNode.sym)
  653. if att != nil:
  654. attype = rope esc(d.target, att.name.s)
  655. elif k == skType and nameNode.kind == nkSym and nameNode.sym.typ.kind in {tyEnum, tyBool}:
  656. let etyp = nameNode.sym.typ
  657. for e in etyp.n:
  658. if e.sym.kind != skEnumField: continue
  659. let plain = renderPlainSymbolName(e)
  660. let symbolOrId = d.newUniquePlainSymbol(plain)
  661. setIndexTerm(d[], external, symbolOrId, plain, nameNode.sym.name.s & '.' & plain,
  662. xmltree.escape(getPlainDocstring(e).docstringSummary))
  663. add(d.toc[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item.toc"),
  664. ["name", "header", "desc", "itemID", "header_plain", "itemSym",
  665. "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "attype"],
  666. [rope(getName(d, nameNode, d.splitAfter)), result, comm,
  667. itemIDRope, plainNameRope, plainSymbolRope, symbolOrIdRope,
  668. plainSymbolEncRope, symbolOrIdEncRope, attype]))
  669. # Ironically for types the complexSymbol is *cleaner* than the plainName
  670. # because it doesn't include object fields or documentation comments. So we
  671. # use the plain one for callable elements, and the complex for the rest.
  672. var linkTitle = changeFileExt(extractFilename(d.filename), "") & ": "
  673. if n.isCallable: linkTitle.add(xmltree.escape(plainName.strip))
  674. else: linkTitle.add(xmltree.escape(complexSymbol.strip))
  675. setIndexTerm(d[], external, symbolOrId, name, linkTitle,
  676. xmltree.escape(plainDocstring.docstringSummary))
  677. if k == skType and nameNode.kind == nkSym:
  678. d.types.strTableAdd nameNode.sym
  679. proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode =
  680. if not isVisible(d, nameNode): return
  681. var
  682. name = getName(d, nameNode)
  683. comm = $genRecComment(d, n)
  684. r: TSrcGen
  685. initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments})
  686. result = %{ "name": %name, "type": %($k), "line": %n.info.line.int,
  687. "col": %n.info.col}
  688. if comm.len > 0:
  689. result["description"] = %comm
  690. if r.buf.len > 0:
  691. result["code"] = %r.buf
  692. proc checkForFalse(n: PNode): bool =
  693. result = n.kind == nkIdent and cmpIgnoreStyle(n.ident.s, "false") == 0
  694. proc traceDeps(d: PDoc, it: PNode) =
  695. const k = skModule
  696. if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
  697. let sep = it[0]
  698. let dir = it[1]
  699. let a = newNodeI(nkInfix, it.info)
  700. a.add sep
  701. a.add dir
  702. a.add sep # dummy entry, replaced in the loop
  703. for x in it[2]:
  704. a.sons[2] = x
  705. traceDeps(d, a)
  706. elif it.kind == nkSym and belongsToPackage(d.conf, it.sym):
  707. let external = externalDep(d, it.sym)
  708. if d.section[k] != nil: add(d.section[k], ", ")
  709. dispA(d.conf, d.section[k],
  710. "<a class=\"reference external\" href=\"$2\">$1</a>",
  711. "$1", [rope esc(d.target, changeFileExt(external, "")),
  712. rope changeFileExt(external, "html")])
  713. proc exportSym(d: PDoc; s: PSym) =
  714. const k = exportSection
  715. if s.kind == skModule and belongsToPackage(d.conf, s):
  716. let external = externalDep(d, s)
  717. if d.section[k] != nil: add(d.section[k], ", ")
  718. dispA(d.conf, d.section[k],
  719. "<a class=\"reference external\" href=\"$2\">$1</a>",
  720. "$1", [rope esc(d.target, changeFileExt(external, "")),
  721. rope changeFileExt(external, "html")])
  722. elif s.kind != skModule and s.owner != nil:
  723. let module = originatingModule(s)
  724. if belongsToPackage(d.conf, module):
  725. let external = externalDep(d, module)
  726. if d.section[k] != nil: add(d.section[k], ", ")
  727. # XXX proper anchor generation here
  728. dispA(d.conf, d.section[k],
  729. "<a href=\"$2#$1\"><span class=\"Identifier\">$1</span></a>",
  730. "$1", [rope esc(d.target, s.name.s),
  731. rope changeFileExt(external, "html")])
  732. proc documentNewEffect(cache: IdentCache; n: PNode): PNode =
  733. let s = n.sons[namePos].sym
  734. if tfReturnsNew in s.typ.flags:
  735. result = newIdentNode(getIdent(cache, "new"), n.info)
  736. proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, idx: int): PNode =
  737. let spec = effectSpec(x, effectType)
  738. if isNil(spec):
  739. let s = n.sons[namePos].sym
  740. let actual = s.typ.n.sons[0]
  741. if actual.len != effectListLen: return
  742. let real = actual.sons[idx]
  743. # warning: hack ahead:
  744. var effects = newNodeI(nkBracket, n.info, real.len)
  745. for i in 0 ..< real.len:
  746. var t = typeToString(real[i].typ)
  747. if t.startsWith("ref "): t = substr(t, 4)
  748. effects.sons[i] = newIdentNode(getIdent(cache, t), n.info)
  749. # set the type so that the following analysis doesn't screw up:
  750. effects.sons[i].typ = real[i].typ
  751. result = newNode(nkExprColonExpr, n.info, @[
  752. newIdentNode(getIdent(cache, specialWords[effectType]), n.info), effects])
  753. proc documentWriteEffect(cache: IdentCache; n: PNode; flag: TSymFlag; pragmaName: string): PNode =
  754. let s = n.sons[namePos].sym
  755. let params = s.typ.n
  756. var effects = newNodeI(nkBracket, n.info)
  757. for i in 1 ..< params.len:
  758. if params[i].kind == nkSym and flag in params[i].sym.flags:
  759. effects.add params[i]
  760. if effects.len > 0:
  761. result = newNode(nkExprColonExpr, n.info, @[
  762. newIdentNode(getIdent(cache, pragmaName), n.info), effects])
  763. proc documentRaises*(cache: IdentCache; n: PNode) =
  764. if n.sons[namePos].kind != nkSym: return
  765. let pragmas = n.sons[pragmasPos]
  766. let p1 = documentEffect(cache, n, pragmas, wRaises, exceptionEffects)
  767. let p2 = documentEffect(cache, n, pragmas, wTags, tagEffects)
  768. let p3 = documentWriteEffect(cache, n, sfWrittenTo, "writes")
  769. let p4 = documentNewEffect(cache, n)
  770. let p5 = documentWriteEffect(cache, n, sfEscapes, "escapes")
  771. if p1 != nil or p2 != nil or p3 != nil or p4 != nil or p5 != nil:
  772. if pragmas.kind == nkEmpty:
  773. n.sons[pragmasPos] = newNodeI(nkPragma, n.info)
  774. if p1 != nil: n.sons[pragmasPos].add p1
  775. if p2 != nil: n.sons[pragmasPos].add p2
  776. if p3 != nil: n.sons[pragmasPos].add p3
  777. if p4 != nil: n.sons[pragmasPos].add p4
  778. if p5 != nil: n.sons[pragmasPos].add p5
  779. proc generateDoc*(d: PDoc, n, orig: PNode) =
  780. case n.kind
  781. of nkPragma:
  782. let pragmaNode = findPragma(n, wDeprecated)
  783. add(d.modDeprecationMsg, genDeprecationMsg(d, pragmaNode))
  784. of nkCommentStmt: add(d.modDesc, genComment(d, n))
  785. of nkProcDef:
  786. when useEffectSystem: documentRaises(d.cache, n)
  787. genItem(d, n, n.sons[namePos], skProc)
  788. of nkFuncDef:
  789. when useEffectSystem: documentRaises(d.cache, n)
  790. genItem(d, n, n.sons[namePos], skFunc)
  791. of nkMethodDef:
  792. when useEffectSystem: documentRaises(d.cache, n)
  793. genItem(d, n, n.sons[namePos], skMethod)
  794. of nkIteratorDef:
  795. when useEffectSystem: documentRaises(d.cache, n)
  796. genItem(d, n, n.sons[namePos], skIterator)
  797. of nkMacroDef: genItem(d, n, n.sons[namePos], skMacro)
  798. of nkTemplateDef: genItem(d, n, n.sons[namePos], skTemplate)
  799. of nkConverterDef:
  800. when useEffectSystem: documentRaises(d.cache, n)
  801. genItem(d, n, n.sons[namePos], skConverter)
  802. of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
  803. for i in 0 ..< sonsLen(n):
  804. if n.sons[i].kind != nkCommentStmt:
  805. # order is always 'type var let const':
  806. genItem(d, n.sons[i], n.sons[i].sons[0],
  807. succ(skType, ord(n.kind)-ord(nkTypeSection)))
  808. of nkStmtList:
  809. for i in 0 ..< sonsLen(n): generateDoc(d, n.sons[i], orig)
  810. of nkWhenStmt:
  811. # generate documentation for the first branch only:
  812. if not checkForFalse(n.sons[0].sons[0]):
  813. generateDoc(d, lastSon(n.sons[0]), orig)
  814. of nkImportStmt:
  815. for it in n: traceDeps(d, it)
  816. of nkExportStmt:
  817. for it in n:
  818. if it.kind == nkSym: exportSym(d, it.sym)
  819. of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept"
  820. of nkFromStmt, nkImportExceptStmt: traceDeps(d, n.sons[0])
  821. of nkCallKinds:
  822. var comm: Rope = nil
  823. getAllRunnableExamples(d, n, comm)
  824. if comm != nil: add(d.modDesc, comm)
  825. else: discard
  826. proc add(d: PDoc; j: JsonNode) =
  827. if j != nil: d.jArray.add j
  828. proc generateJson*(d: PDoc, n: PNode, includeComments: bool = true) =
  829. case n.kind
  830. of nkCommentStmt:
  831. if includeComments:
  832. d.add %*{"comment": genComment(d, n)}
  833. else:
  834. add(d.modDesc, genComment(d, n))
  835. of nkProcDef:
  836. when useEffectSystem: documentRaises(d.cache, n)
  837. d.add genJsonItem(d, n, n.sons[namePos], skProc)
  838. of nkFuncDef:
  839. when useEffectSystem: documentRaises(d.cache, n)
  840. d.add genJsonItem(d, n, n.sons[namePos], skFunc)
  841. of nkMethodDef:
  842. when useEffectSystem: documentRaises(d.cache, n)
  843. d.add genJsonItem(d, n, n.sons[namePos], skMethod)
  844. of nkIteratorDef:
  845. when useEffectSystem: documentRaises(d.cache, n)
  846. d.add genJsonItem(d, n, n.sons[namePos], skIterator)
  847. of nkMacroDef:
  848. d.add genJsonItem(d, n, n.sons[namePos], skMacro)
  849. of nkTemplateDef:
  850. d.add genJsonItem(d, n, n.sons[namePos], skTemplate)
  851. of nkConverterDef:
  852. when useEffectSystem: documentRaises(d.cache, n)
  853. d.add genJsonItem(d, n, n.sons[namePos], skConverter)
  854. of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
  855. for i in 0 ..< sonsLen(n):
  856. if n.sons[i].kind != nkCommentStmt:
  857. # order is always 'type var let const':
  858. d.add genJsonItem(d, n.sons[i], n.sons[i].sons[0],
  859. succ(skType, ord(n.kind)-ord(nkTypeSection)))
  860. of nkStmtList:
  861. for i in 0 ..< sonsLen(n):
  862. generateJson(d, n.sons[i], includeComments)
  863. of nkWhenStmt:
  864. # generate documentation for the first branch only:
  865. if not checkForFalse(n.sons[0].sons[0]):
  866. generateJson(d, lastSon(n.sons[0]), includeComments)
  867. else: discard
  868. proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string =
  869. result = getName(d, nameNode) & "\n"
  870. proc generateTags*(d: PDoc, n: PNode, r: var Rope) =
  871. case n.kind
  872. of nkCommentStmt:
  873. if startsWith(n.comment, "##"):
  874. let stripped = n.comment.substr(2).strip
  875. r.add stripped
  876. of nkProcDef:
  877. when useEffectSystem: documentRaises(d.cache, n)
  878. r.add genTagsItem(d, n, n.sons[namePos], skProc)
  879. of nkFuncDef:
  880. when useEffectSystem: documentRaises(d.cache, n)
  881. r.add genTagsItem(d, n, n.sons[namePos], skFunc)
  882. of nkMethodDef:
  883. when useEffectSystem: documentRaises(d.cache, n)
  884. r.add genTagsItem(d, n, n.sons[namePos], skMethod)
  885. of nkIteratorDef:
  886. when useEffectSystem: documentRaises(d.cache, n)
  887. r.add genTagsItem(d, n, n.sons[namePos], skIterator)
  888. of nkMacroDef:
  889. r.add genTagsItem(d, n, n.sons[namePos], skMacro)
  890. of nkTemplateDef:
  891. r.add genTagsItem(d, n, n.sons[namePos], skTemplate)
  892. of nkConverterDef:
  893. when useEffectSystem: documentRaises(d.cache, n)
  894. r.add genTagsItem(d, n, n.sons[namePos], skConverter)
  895. of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
  896. for i in 0 ..< sonsLen(n):
  897. if n.sons[i].kind != nkCommentStmt:
  898. # order is always 'type var let const':
  899. r.add genTagsItem(d, n.sons[i], n.sons[i].sons[0],
  900. succ(skType, ord(n.kind)-ord(nkTypeSection)))
  901. of nkStmtList:
  902. for i in 0 ..< sonsLen(n):
  903. generateTags(d, n.sons[i], r)
  904. of nkWhenStmt:
  905. # generate documentation for the first branch only:
  906. if not checkForFalse(n.sons[0].sons[0]):
  907. generateTags(d, lastSon(n.sons[0]), r)
  908. else: discard
  909. proc genSection(d: PDoc, kind: TSymKind) =
  910. const sectionNames: array[skModule..skField, string] = [
  911. "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs",
  912. "Methods", "Iterators", "Converters", "Macros", "Templates", "Exports"
  913. ]
  914. if d.section[kind] == nil: return
  915. var title = sectionNames[kind].rope
  916. d.section[kind] = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section"), [
  917. "sectionid", "sectionTitle", "sectionTitleID", "content"], [
  918. ord(kind).rope, title, rope(ord(kind) + 50), d.section[kind]])
  919. d.toc[kind] = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section.toc"), [
  920. "sectionid", "sectionTitle", "sectionTitleID", "content"], [
  921. ord(kind).rope, title, rope(ord(kind) + 50), d.toc[kind]])
  922. proc genOutFile(d: PDoc): Rope =
  923. var
  924. code, content: Rope
  925. title = ""
  926. var j = 0
  927. var tmp = ""
  928. renderTocEntries(d[], j, 1, tmp)
  929. var toc = tmp.rope
  930. for i in low(TSymKind) .. high(TSymKind):
  931. genSection(d, i)
  932. add(toc, d.toc[i])
  933. if toc != nil:
  934. toc = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.toc"), ["content"], [toc])
  935. for i in low(TSymKind) .. high(TSymKind): add(code, d.section[i])
  936. # Extract the title. Non API modules generate an entry in the index table.
  937. if d.meta[metaTitle].len != 0:
  938. title = d.meta[metaTitle]
  939. let external = AbsoluteFile(d.filename).relativeTo(d.conf.projectPath, '/').changeFileExt(HtmlExt).string
  940. setIndexTerm(d[], external, "", title)
  941. else:
  942. # Modules get an automatic title for the HTML, but no entry in the index.
  943. title = extractFilename(changeFileExt(d.filename, ""))
  944. let bodyname = if d.hasToc and not d.isPureRst: "doc.body_toc_group"
  945. elif d.hasToc: "doc.body_toc"
  946. else: "doc.body_no_toc"
  947. content = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, bodyname), ["title",
  948. "tableofcontents", "moduledesc", "date", "time", "content", "deprecationMsg"],
  949. [title.rope, toc, d.modDesc, rope(getDateStr()),
  950. rope(getClockStr()), code, d.modDeprecationMsg])
  951. if optCompileOnly notin d.conf.globalOptions:
  952. # XXX what is this hack doing here? 'optCompileOnly' means raw output!?
  953. code = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.file"), ["title",
  954. "tableofcontents", "moduledesc", "date", "time",
  955. "content", "author", "version", "analytics", "deprecationMsg"],
  956. [title.rope, toc, d.modDesc, rope(getDateStr()),
  957. rope(getClockStr()), content, d.meta[metaAuthor].rope,
  958. d.meta[metaVersion].rope, d.analytics.rope, d.modDeprecationMsg])
  959. else:
  960. code = content
  961. result = code
  962. proc generateIndex*(d: PDoc) =
  963. if optGenIndex in d.conf.globalOptions:
  964. let dir = if not d.conf.outDir.isEmpty: d.conf.outDir
  965. else: d.conf.projectPath / RelativeDir"htmldocs"
  966. createDir(dir)
  967. let dest = dir / changeFileExt(relativeTo(AbsoluteFile d.filename,
  968. d.conf.projectPath), IndexExt)
  969. writeIndexFile(d[], dest.string)
  970. proc writeOutput*(d: PDoc, useWarning = false) =
  971. runAllExamples(d)
  972. var content = genOutFile(d)
  973. if optStdout in d.conf.globalOptions:
  974. writeRope(stdout, content)
  975. else:
  976. template outfile: untyped = d.destFile
  977. #let outfile = getOutFile2(d.conf, shortenDir(d.conf, filename), outExt, "htmldocs")
  978. createDir(outfile.splitFile.dir)
  979. if not writeRope(content, outfile):
  980. rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile,
  981. outfile.string)
  982. proc writeOutputJson*(d: PDoc, useWarning = false) =
  983. runAllExamples(d)
  984. var modDesc: string
  985. for desc in d.modDesc:
  986. modDesc &= desc
  987. let content = %*{"orig": d.filename,
  988. "nimble": getPackageName(d.conf, d.filename),
  989. "moduleDescription": modDesc,
  990. "entries": d.jArray}
  991. if optStdout in d.conf.globalOptions:
  992. write(stdout, $content)
  993. else:
  994. var f: File
  995. if open(f, d.destFile.string, fmWrite):
  996. write(f, $content)
  997. close(f)
  998. else:
  999. localError(d.conf, newLineInfo(d.conf, AbsoluteFile d.filename, -1, -1),
  1000. warnUser, "unable to open file \"" & d.destFile.string &
  1001. "\" for writing")
  1002. proc handleDocOutputOptions*(conf: ConfigRef) =
  1003. if optWholeProject in conf.globalOptions:
  1004. # Backward compatibility with previous versions
  1005. conf.outDir = AbsoluteDir(conf.outDir / conf.outFile)
  1006. proc commandDoc*(cache: IdentCache, conf: ConfigRef) =
  1007. handleDocOutputOptions conf
  1008. var ast = parseFile(conf.projectMainIdx, cache, conf)
  1009. if ast == nil: return
  1010. var d = newDocumentor(conf.projectFull, cache, conf)
  1011. d.hasToc = true
  1012. generateDoc(d, ast, ast)
  1013. writeOutput(d)
  1014. generateIndex(d)
  1015. proc commandRstAux(cache: IdentCache, conf: ConfigRef;
  1016. filename: AbsoluteFile, outExt: string) =
  1017. var filen = addFileExt(filename, "txt")
  1018. var d = newDocumentor(filen, cache, conf, outExt)
  1019. d.isPureRst = true
  1020. var rst = parseRst(readFile(filen.string), filen.string, 0, 1, d.hasToc,
  1021. {roSupportRawDirective, roSupportMarkdown}, conf)
  1022. var modDesc = newStringOfCap(30_000)
  1023. renderRstToOut(d[], rst, modDesc)
  1024. d.modDesc = rope(modDesc)
  1025. writeOutput(d)
  1026. generateIndex(d)
  1027. proc commandRst2Html*(cache: IdentCache, conf: ConfigRef) =
  1028. commandRstAux(cache, conf, conf.projectFull, HtmlExt)
  1029. proc commandRst2TeX*(cache: IdentCache, conf: ConfigRef) =
  1030. commandRstAux(cache, conf, conf.projectFull, TexExt)
  1031. proc commandJson*(cache: IdentCache, conf: ConfigRef) =
  1032. var ast = parseFile(conf.projectMainIdx, cache, conf)
  1033. if ast == nil: return
  1034. var d = newDocumentor(conf.projectFull, cache, conf)
  1035. d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string;
  1036. status: int; content: string) =
  1037. localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1),
  1038. warnUser, "the ':test:' attribute is not supported by this backend")
  1039. d.hasToc = true
  1040. generateJson(d, ast)
  1041. let json = d.jArray
  1042. let content = rope(pretty(json))
  1043. if optStdout in d.conf.globalOptions:
  1044. writeRope(stdout, content)
  1045. else:
  1046. #echo getOutFile(gProjectFull, JsonExt)
  1047. let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt)
  1048. if not writeRope(content, filename):
  1049. rawMessage(conf, errCannotOpenFile, filename.string)
  1050. proc commandTags*(cache: IdentCache, conf: ConfigRef) =
  1051. var ast = parseFile(conf.projectMainIdx, cache, conf)
  1052. if ast == nil: return
  1053. var d = newDocumentor(conf.projectFull, cache, conf)
  1054. d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string;
  1055. status: int; content: string) =
  1056. localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1),
  1057. warnUser, "the ':test:' attribute is not supported by this backend")
  1058. d.hasToc = true
  1059. var
  1060. content: Rope
  1061. generateTags(d, ast, content)
  1062. if optStdout in d.conf.globalOptions:
  1063. writeRope(stdout, content)
  1064. else:
  1065. #echo getOutFile(gProjectFull, TagsExt)
  1066. let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt)
  1067. if not writeRope(content, filename):
  1068. rawMessage(conf, errCannotOpenFile, filename.string)
  1069. proc commandBuildIndex*(cache: IdentCache, conf: ConfigRef) =
  1070. var content = mergeIndexes(conf.projectFull.string).rope
  1071. let code = ropeFormatNamedVars(conf, getConfigVar(conf, "doc.file"), ["title",
  1072. "tableofcontents", "moduledesc", "date", "time",
  1073. "content", "author", "version", "analytics"],
  1074. ["Index".rope, nil, nil, rope(getDateStr()),
  1075. rope(getClockStr()), content, nil, nil, nil])
  1076. # no analytics because context is not available
  1077. let filename = getOutFile(conf, RelativeFile"theindex", HtmlExt)
  1078. if not writeRope(code, filename):
  1079. rawMessage(conf, errCannotOpenFile, filename.string)