docgen.nim 41 KB

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