docgen.nim 41 KB

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