docgen.nim 41 KB

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