docgen.nim 76 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955
  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 Nim documentation generator. Cross-references are generated
  10. ## by knowing how the anchors are going to be named.
  11. ##
  12. ## .. importdoc:: ../docgen.md
  13. ##
  14. ## For corresponding users' documentation see [Nim DocGen Tools Guide].
  15. import
  16. ast, options, msgs, idents,
  17. wordrecg, syntaxes, renderer, lexer,
  18. packages/docutils/[rst, rstidx, rstgen, dochelpers],
  19. trees, types,
  20. typesrenderer, astalgo, lineinfos,
  21. pathutils, nimpaths, renderverbatim, packages
  22. import packages/docutils/rstast except FileIndex, TLineInfo
  23. import std/[os, strutils, strtabs, algorithm, json, osproc, tables, intsets, xmltree, sequtils]
  24. from std/uri import encodeUrl
  25. from nodejs import findNodeJs
  26. when defined(nimPreviewSlimSystem):
  27. import std/[assertions, syncio]
  28. const
  29. exportSection = skField
  30. docCmdSkip = "skip"
  31. DocColOffset = "## ".len # assuming that a space was added after ##
  32. type
  33. ItemFragment = object ## A fragment from each item will be eventually
  34. ## constructed by converting `rst` fields to strings.
  35. case isRst: bool
  36. of true:
  37. rst: PRstNode
  38. of false: ## contains ready markup e.g. from runnableExamples
  39. str: string
  40. ItemPre = seq[ItemFragment] ## A pre-processed item.
  41. Item = object ## Any item in documentation, e.g. symbol
  42. ## entry. Configuration variable ``doc.item``
  43. ## is used for its HTML rendering.
  44. descRst: ItemPre ## Description of the item (may contain
  45. ## runnableExamples).
  46. substitutions: seq[string] ## Variable names in `doc.item`...
  47. sortName: string ## The string used for sorting in output
  48. info: rstast.TLineInfo ## place where symbol was defined (for messages)
  49. anchor: string ## e.g. HTML anchor
  50. name: string ## short name of the symbol, not unique
  51. ## (includes backticks ` if present)
  52. detailedName: string ## longer name like `proc search(x: int): int`
  53. ModSection = object ## Section like Procs, Types, etc.
  54. secItems: Table[string, seq[Item]]
  55. ## Map basic name -> pre-processed items.
  56. finalMarkup: string ## The items, after RST pass 2 and rendering.
  57. ModSections = array[TSymKind, ModSection]
  58. TocItem = object ## HTML TOC item
  59. content: string
  60. sortName: string
  61. TocSectionsFinal = array[TSymKind, string]
  62. ExampleGroup = ref object
  63. ## a group of runnableExamples with same rdoccmd
  64. rdoccmd: string ## from 1st arg in `runnableExamples(rdoccmd): body`
  65. docCmd: string ## from user config, e.g. --doccmd:-d:foo
  66. code: string ## contains imports; each import contains `body`
  67. index: int ## group index
  68. JsonItem = object # pre-processed item: `rst` should be finalized
  69. json: JsonNode
  70. rst: PRstNode
  71. rstField: string
  72. TDocumentor = object of rstgen.RstGenerator
  73. modDescPre: ItemPre # module description, not finalized
  74. modDescFinal: string # module description, after RST pass 2 and rendering
  75. module: PSym
  76. modDeprecationMsg: string
  77. section: ModSections # entries of ``.nim`` file (for `proc`s, etc)
  78. tocSimple: array[TSymKind, seq[TocItem]]
  79. # TOC entries for non-overloadable symbols (e.g. types, constants)...
  80. tocTable: array[TSymKind, Table[string, seq[TocItem]]]
  81. # ...otherwise (e.g. procs)
  82. toc2: TocSectionsFinal # TOC `content`, which is probably wrapped
  83. # in `doc.section.toc2`
  84. toc: TocSectionsFinal # final TOC (wrapped in `doc.section.toc`)
  85. indexValFilename: string
  86. analytics: string # Google Analytics javascript, "" if doesn't exist
  87. seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML.
  88. jEntriesPre: seq[JsonItem] # pre-processed RST + JSON content
  89. jEntriesFinal: JsonNode # final JSON after RST pass 2 and rendering
  90. types: TStrTable
  91. sharedState: PRstSharedState
  92. standaloneDoc: bool # is markup (.rst/.md) document?
  93. conf*: ConfigRef
  94. cache*: IdentCache
  95. exampleCounter: int
  96. emitted: IntSet # we need to track which symbols have been emitted
  97. # already. See bug #3655
  98. thisDir*: AbsoluteDir
  99. exampleGroups: OrderedTable[string, ExampleGroup]
  100. wroteSupportFiles*: bool
  101. nimToRstFid: Table[lineinfos.FileIndex, rstast.FileIndex]
  102. ## map Nim FileIndex -> RST one, it's needed because we keep them separate
  103. PDoc* = ref TDocumentor ## Alias to type less.
  104. proc add(dest: var ItemPre, rst: PRstNode) = dest.add ItemFragment(isRst: true, rst: rst)
  105. proc add(dest: var ItemPre, str: string) = dest.add ItemFragment(isRst: false, str: str)
  106. proc addRstFileIndex(d: PDoc, fileIndex: lineinfos.FileIndex): rstast.FileIndex =
  107. let invalid = rstast.FileIndex(-1)
  108. result = d.nimToRstFid.getOrDefault(fileIndex, default = invalid)
  109. if result == invalid:
  110. let fname = toFullPath(d.conf, fileIndex)
  111. result = addFilename(d.sharedState, fname)
  112. d.nimToRstFid[fileIndex] = result
  113. proc addRstFileIndex(d: PDoc, info: lineinfos.TLineInfo): rstast.FileIndex =
  114. addRstFileIndex(d, info.fileIndex)
  115. proc cmpDecimalsIgnoreCase(a, b: string): int =
  116. ## For sorting with correct handling of cases like 'uint8' and 'uint16'.
  117. ## Also handles leading zeros well (however note that leading zeros are
  118. ## significant when lengths of numbers mismatch, e.g. 'bar08' > 'bar8' !).
  119. runnableExamples:
  120. doAssert cmpDecimalsIgnoreCase("uint8", "uint16") < 0
  121. doAssert cmpDecimalsIgnoreCase("val00032", "val16suffix") > 0
  122. doAssert cmpDecimalsIgnoreCase("val16suffix", "val16") > 0
  123. doAssert cmpDecimalsIgnoreCase("val_08_32", "val_08_8") > 0
  124. doAssert cmpDecimalsIgnoreCase("val_07_32", "val_08_8") < 0
  125. doAssert cmpDecimalsIgnoreCase("ab8", "ab08") < 0
  126. doAssert cmpDecimalsIgnoreCase("ab8de", "ab08c") < 0 # sanity check
  127. let aLen = a.len
  128. let bLen = b.len
  129. var
  130. iA = 0
  131. iB = 0
  132. while iA < aLen and iB < bLen:
  133. if isDigit(a[iA]) and isDigit(b[iB]):
  134. var
  135. limitA = iA # index after the last (least significant) digit
  136. limitB = iB
  137. while limitA < aLen and isDigit(a[limitA]): inc limitA
  138. while limitB < bLen and isDigit(b[limitB]): inc limitB
  139. var pos = max(limitA-iA, limitB-iA)
  140. while pos > 0:
  141. if limitA-pos < iA: # digit in `a` is 0 effectively
  142. result = ord('0') - ord(b[limitB-pos])
  143. elif limitB-pos < iB: # digit in `b` is 0 effectively
  144. result = ord(a[limitA-pos]) - ord('0')
  145. else:
  146. result = ord(a[limitA-pos]) - ord(b[limitB-pos])
  147. if result != 0: return
  148. dec pos
  149. result = (limitA - iA) - (limitB - iB) # consider 'bar08' > 'bar8'
  150. if result != 0: return
  151. iA = limitA
  152. iB = limitB
  153. else:
  154. result = ord(toLowerAscii(a[iA])) - ord(toLowerAscii(b[iB]))
  155. if result != 0: return
  156. inc iA
  157. inc iB
  158. result = (aLen - iA) - (bLen - iB)
  159. proc prettyString(a: object): string =
  160. # xxx pending std/prettyprint refs https://github.com/nim-lang/RFCs/issues/203#issuecomment-602534906
  161. result = ""
  162. for k, v in fieldPairs(a):
  163. result.add k & ": " & $v & "\n"
  164. proc presentationPath*(conf: ConfigRef, file: AbsoluteFile): RelativeFile =
  165. ## returns a relative file that will be appended to outDir
  166. let file2 = $file
  167. template bail() =
  168. result = relativeTo(file, conf.projectPath)
  169. proc nimbleDir(): AbsoluteDir =
  170. getNimbleFile(conf, file2).parentDir.AbsoluteDir
  171. case conf.docRoot:
  172. of docRootDefault:
  173. result = getRelativePathFromConfigPath(conf, file)
  174. let dir = nimbleDir()
  175. if not dir.isEmpty:
  176. let result2 = relativeTo(file, dir)
  177. if not result2.isEmpty and (result.isEmpty or result2.string.len < result.string.len):
  178. result = result2
  179. if result.isEmpty: bail()
  180. of "@pkg":
  181. let dir = nimbleDir()
  182. if dir.isEmpty: bail()
  183. else: result = relativeTo(file, dir)
  184. of "@path":
  185. result = getRelativePathFromConfigPath(conf, file)
  186. if result.isEmpty: bail()
  187. elif conf.docRoot.len > 0:
  188. # we're (currently) requiring `isAbsolute` to avoid confusion when passing
  189. # a relative path (would it be relative with regard to $PWD or to projectfile)
  190. conf.globalAssert conf.docRoot.isAbsolute, arg=conf.docRoot
  191. conf.globalAssert conf.docRoot.dirExists, arg=conf.docRoot
  192. # needed because `canonicalizePath` called on `file`
  193. result = file.relativeTo conf.docRoot.expandFilename.AbsoluteDir
  194. else:
  195. bail()
  196. if isAbsolute(result.string):
  197. result = file.string.splitPath()[1].RelativeFile
  198. result = result.string.replace("..", dotdotMangle).RelativeFile
  199. doAssert not result.isEmpty
  200. doAssert not isAbsolute(result.string)
  201. proc whichType(d: PDoc; n: PNode): PSym =
  202. if n.kind == nkSym:
  203. if d.types.strTableContains(n.sym):
  204. result = n.sym
  205. else:
  206. result = nil
  207. else:
  208. result = nil
  209. for i in 0..<n.safeLen:
  210. let x = whichType(d, n[i])
  211. if x != nil: return x
  212. proc attachToType(d: PDoc; p: PSym): PSym =
  213. result = nil
  214. let params = p.ast[paramsPos]
  215. template check(i) =
  216. result = whichType(d, params[i])
  217. if result != nil: return result
  218. # first check the first parameter, then the return type,
  219. # then the other parameter:
  220. if params.len > 1: check(1)
  221. if params.len > 0: check(0)
  222. for i in 2..<params.len: check(i)
  223. template declareClosures(currentFilename: AbsoluteFile, destFile: string) =
  224. proc compilerMsgHandler(filename: string, line, col: int,
  225. msgKind: rst.MsgKind, arg: string) {.gcsafe.} =
  226. # translate msg kind:
  227. var k: TMsgKind
  228. case msgKind
  229. of meCannotOpenFile: k = errCannotOpenFile
  230. of meExpected: k = errXExpected
  231. of meMissingClosing: k = errRstMissingClosing
  232. of meGridTableNotImplemented: k = errRstGridTableNotImplemented
  233. of meMarkdownIllformedTable: k = errRstMarkdownIllformedTable
  234. of meIllformedTable: k = errRstIllformedTable
  235. of meNewSectionExpected: k = errRstNewSectionExpected
  236. of meGeneralParseError: k = errRstGeneralParseError
  237. of meInvalidDirective: k = errRstInvalidDirectiveX
  238. of meInvalidField: k = errRstInvalidField
  239. of meFootnoteMismatch: k = errRstFootnoteMismatch
  240. of meSandboxedDirective: k = errRstSandboxedDirective
  241. of mwRedefinitionOfLabel: k = warnRstRedefinitionOfLabel
  242. of mwUnknownSubstitution: k = warnRstUnknownSubstitutionX
  243. of mwAmbiguousLink: k = warnRstAmbiguousLink
  244. of mwBrokenLink: k = warnRstBrokenLink
  245. of mwUnsupportedLanguage: k = warnRstLanguageXNotSupported
  246. of mwUnsupportedField: k = warnRstFieldXNotSupported
  247. of mwUnusedImportdoc: k = warnRstUnusedImportdoc
  248. of mwRstStyle: k = warnRstStyle
  249. {.gcsafe.}:
  250. let errorsAsWarnings = (roPreferMarkdown in d.sharedState.options) and
  251. not d.standaloneDoc # not tolerate errors in .rst/.md files
  252. if whichMsgClass(msgKind) == mcError and errorsAsWarnings:
  253. liMessage(conf, newLineInfo(conf, AbsoluteFile filename, line, col),
  254. k, arg, doNothing, instLoc(), ignoreError=true)
  255. # when our Markdown parser fails, we currently can only terminate the
  256. # parsing (and then we will return monospaced text instead of markup):
  257. raiseRecoverableError("")
  258. else:
  259. globalError(conf, newLineInfo(conf, AbsoluteFile filename, line, col), k, arg)
  260. proc docgenFindFile(s: string): string {.gcsafe.} =
  261. result = options.findFile(conf, s).string
  262. if result.len == 0:
  263. result = getCurrentDir() / s
  264. if not fileExists(result): result = ""
  265. proc docgenFindRefFile(targetRelPath: string):
  266. tuple[targetPath: string, linkRelPath: string] {.gcsafe.} =
  267. let fromDir = splitFile(destFile).dir # dir where we reference from
  268. let basedir = os.splitFile(currentFilename.string).dir
  269. let outDirPath: RelativeFile =
  270. presentationPath(conf, AbsoluteFile(basedir / targetRelPath))
  271. # use presentationPath because `..` path can be be mangled to `_._`
  272. result = (string(conf.outDir / outDirPath), "")
  273. if not fileExists(result.targetPath):
  274. # this can happen if targetRelPath goes to parent directory `OUTDIR/..`.
  275. # Trying it, this may cause ambiguities, but allows us to insert
  276. # "packages" into each other, which is actually used in Nim repo itself.
  277. let destPath = fromDir / targetRelPath
  278. if destPath != result.targetPath and fileExists(destPath):
  279. result.targetPath = destPath
  280. result.linkRelPath = relativePath(result.targetPath.splitFile.dir,
  281. fromDir).replace('\\', '/')
  282. proc parseRst(text: string,
  283. line, column: int,
  284. conf: ConfigRef, sharedState: PRstSharedState): PRstNode =
  285. result = rstParsePass1(text, line, column, sharedState)
  286. proc getOutFile2(conf: ConfigRef; filename: RelativeFile,
  287. ext: string, guessTarget: bool): AbsoluteFile =
  288. if optWholeProject in conf.globalOptions or guessTarget:
  289. let d = conf.outDir
  290. createDir(d)
  291. result = d / changeFileExt(filename, ext)
  292. elif not conf.outFile.isEmpty:
  293. result = absOutFile(conf)
  294. else:
  295. result = getOutFile(conf, filename, ext)
  296. proc isLatexCmd(conf: ConfigRef): bool =
  297. conf.cmd in {cmdRst2tex, cmdMd2tex, cmdDoc2tex}
  298. proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef,
  299. outExt: string = HtmlExt, module: PSym = nil,
  300. standaloneDoc = false, preferMarkdown = true,
  301. hasToc = true): PDoc =
  302. let destFile = getOutFile2(conf, presentationPath(conf, filename), outExt, false).string
  303. new(result)
  304. let d = result # pass `d` to `declareClosures`:
  305. declareClosures(currentFilename = filename, destFile = destFile)
  306. result.module = module
  307. result.conf = conf
  308. result.cache = cache
  309. result.outDir = conf.outDir.string
  310. result.standaloneDoc = standaloneDoc
  311. var options= {roSupportRawDirective, roSupportMarkdown, roSandboxDisabled}
  312. if preferMarkdown:
  313. options.incl roPreferMarkdown
  314. if not standaloneDoc: options.incl roNimFile
  315. # (options can be changed dynamically in `setDoctype` by `{.doctype.}`)
  316. result.hasToc = hasToc
  317. result.sharedState = newRstSharedState(
  318. options, filename.string,
  319. docgenFindFile, docgenFindRefFile, compilerMsgHandler, hasToc)
  320. initRstGenerator(result[], (if conf.isLatexCmd: outLatex else: outHtml),
  321. conf.configVars, filename.string,
  322. docgenFindFile, compilerMsgHandler)
  323. if conf.configVars.hasKey("doc.googleAnalytics") and
  324. conf.configVars.hasKey("doc.plausibleAnalytics"):
  325. raiseAssert "Either use googleAnalytics or plausibleAnalytics"
  326. if conf.configVars.hasKey("doc.googleAnalytics"):
  327. result.analytics = """
  328. <script>
  329. (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  330. (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  331. m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  332. })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
  333. ga('create', '$1', 'auto');
  334. ga('send', 'pageview');
  335. </script>
  336. """ % [conf.configVars.getOrDefault"doc.googleAnalytics"]
  337. elif conf.configVars.hasKey("doc.plausibleAnalytics"):
  338. result.analytics = """
  339. <script defer data-domain="$1" src="https://plausible.io/js/plausible.js"></script>
  340. """ % [conf.configVars.getOrDefault"doc.plausibleAnalytics"]
  341. else:
  342. result.analytics = ""
  343. result.seenSymbols = newStringTable(modeCaseInsensitive)
  344. result.id = 100
  345. result.jEntriesFinal = newJArray()
  346. result.types = initStrTable()
  347. result.onTestSnippet =
  348. proc (gen: var RstGenerator; filename, cmd: string; status: int; content: string) {.gcsafe.} =
  349. if conf.docCmd == docCmdSkip: return
  350. inc(gen.id)
  351. var d = (ptr TDocumentor)(addr gen)
  352. var outp: AbsoluteFile
  353. if filename.len == 0:
  354. let nameOnly = splitFile(d.filename).name
  355. # "snippets" needed, refs bug #17183
  356. outp = getNimcacheDir(conf) / "snippets".RelativeDir / RelativeDir(nameOnly) /
  357. RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim")
  358. elif isAbsolute(filename):
  359. outp = AbsoluteFile(filename)
  360. else:
  361. # Nim's convention: every path is relative to the file it was written in:
  362. let nameOnly = splitFile(d.filename).name
  363. outp = AbsoluteDir(nameOnly) / RelativeFile(filename)
  364. # Make sure the destination directory exists
  365. createDir(outp.splitFile.dir)
  366. # Include the current file if we're parsing a nim file
  367. let importStmt = if d.standaloneDoc: "" else: "import \"$1\"\n" % [d.filename.replace("\\", "/")]
  368. writeFile(outp, importStmt & content)
  369. proc interpSnippetCmd(cmd: string): string =
  370. # backward compatibility hacks; interpolation commands should explicitly use `$`
  371. if cmd.startsWith "nim ": result = "$nim " & cmd[4..^1]
  372. else: result = cmd
  373. # factor with D20210224T221756
  374. result = result.replace("$1", "$options") % [
  375. "nim", os.getAppFilename().quoteShell,
  376. "libpath", quoteShell(d.conf.libpath),
  377. "docCmd", d.conf.docCmd,
  378. "backend", $d.conf.backend,
  379. "options", outp.quoteShell,
  380. # xxx `quoteShell` seems buggy if user passes options = "-d:foo somefile.nim"
  381. ]
  382. let cmd = cmd.interpSnippetCmd
  383. rawMessage(conf, hintExecuting, cmd)
  384. let (output, gotten) = execCmdEx(cmd)
  385. if gotten != status:
  386. rawMessage(conf, errGenerated, "snippet failed: cmd: '$1' status: $2 expected: $3 output: $4" % [cmd, $gotten, $status, output])
  387. result.emitted = initIntSet()
  388. result.destFile = destFile
  389. result.thisDir = result.destFile.AbsoluteFile.splitFile.dir
  390. template dispA(conf: ConfigRef; dest: var string, xml, tex: string,
  391. args: openArray[string]) =
  392. if not conf.isLatexCmd: dest.addf(xml, args)
  393. else: dest.addf(tex, args)
  394. proc getVarIdx(varnames: openArray[string], id: string): int =
  395. for i in 0..high(varnames):
  396. if cmpIgnoreStyle(varnames[i], id) == 0:
  397. return i
  398. result = -1
  399. proc genComment(d: PDoc, n: PNode): PRstNode =
  400. if n.comment.len > 0:
  401. d.sharedState.currFileIdx = addRstFileIndex(d, n.info)
  402. try:
  403. result = parseRst(n.comment,
  404. toLinenumber(n.info),
  405. toColumn(n.info) + DocColOffset,
  406. d.conf, d.sharedState)
  407. except ERecoverableError:
  408. result = newRstNode(rnLiteralBlock, @[newRstLeaf(n.comment)])
  409. else:
  410. result = nil
  411. proc genRecCommentAux(d: PDoc, n: PNode): PRstNode =
  412. if n == nil: return nil
  413. result = genComment(d, n)
  414. if result == nil:
  415. if n.kind in {nkStmtList, nkStmtListExpr, nkTypeDef, nkConstDef, nkTypeClassTy,
  416. nkObjectTy, nkRefTy, nkPtrTy, nkAsgn, nkFastAsgn, nkSinkAsgn, nkHiddenStdConv}:
  417. # notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}:
  418. for i in 0..<n.len:
  419. result = genRecCommentAux(d, n[i])
  420. if result != nil: return
  421. else:
  422. n.comment = ""
  423. proc genRecComment(d: PDoc, n: PNode): PRstNode =
  424. if n == nil: return nil
  425. result = genComment(d, n)
  426. if result == nil:
  427. if n.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef,
  428. nkMacroDef, nkTemplateDef, nkConverterDef}:
  429. result = genRecCommentAux(d, n[bodyPos])
  430. else:
  431. result = genRecCommentAux(d, n)
  432. proc getPlainDocstring(n: PNode): string =
  433. ## Gets the plain text docstring of a node non destructively.
  434. ##
  435. ## You need to call this before genRecComment, whose side effects are removal
  436. ## of comments from the tree. The proc will recursively scan and return all
  437. ## the concatenated ``##`` comments of the node.
  438. if n == nil: result = ""
  439. elif startsWith(n.comment, "##"):
  440. result = n.comment
  441. else:
  442. result = ""
  443. for i in 0..<n.safeLen:
  444. result = getPlainDocstring(n[i])
  445. if result.len > 0: return
  446. proc externalDep(d: PDoc; module: PSym): string =
  447. if optWholeProject in d.conf.globalOptions or d.conf.docRoot.len > 0:
  448. let full = AbsoluteFile toFullPath(d.conf, FileIndex module.position)
  449. let tmp = getOutFile2(d.conf, presentationPath(d.conf, full), HtmlExt, sfMainModule notin module.flags)
  450. result = relativeTo(tmp, d.thisDir, '/').string
  451. else:
  452. result = extractFilename toFullPath(d.conf, FileIndex module.position)
  453. proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var string;
  454. renderFlags: TRenderFlags = {};
  455. procLink: string) =
  456. var r: TSrcGen = initTokRender(n, renderFlags)
  457. var literal = ""
  458. var kind = tkEof
  459. var tokenPos = 0
  460. var procTokenPos = 0
  461. template escLit(): untyped = esc(d.target, literal)
  462. while true:
  463. getNextTok(r, kind, literal)
  464. inc tokenPos
  465. case kind
  466. of tkEof:
  467. break
  468. of tkComment:
  469. dispA(d.conf, result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
  470. [escLit])
  471. of tokKeywordLow..tokKeywordHigh:
  472. if kind in {tkProc, tkMethod, tkIterator, tkMacro, tkTemplate, tkFunc, tkConverter}:
  473. procTokenPos = tokenPos
  474. dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
  475. [literal])
  476. of tkOpr:
  477. dispA(d.conf, result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}",
  478. [escLit])
  479. of tkStrLit..tkTripleStrLit, tkCustomLit:
  480. dispA(d.conf, result, "<span class=\"StringLit\">$1</span>",
  481. "\\spanStringLit{$1}", [escLit])
  482. of tkCharLit:
  483. dispA(d.conf, result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}",
  484. [escLit])
  485. of tkIntLit..tkUInt64Lit:
  486. dispA(d.conf, result, "<span class=\"DecNumber\">$1</span>",
  487. "\\spanDecNumber{$1}", [escLit])
  488. of tkFloatLit..tkFloat128Lit:
  489. dispA(d.conf, result, "<span class=\"FloatNumber\">$1</span>",
  490. "\\spanFloatNumber{$1}", [escLit])
  491. of tkSymbol:
  492. let s = getTokSym(r)
  493. # -2 because of the whitespace in between:
  494. if procTokenPos == tokenPos-2 and procLink != "":
  495. dispA(d.conf, result, "<a href=\"#$2\"><span class=\"Identifier\">$1</span></a>",
  496. "\\spanIdentifier{$1}", [escLit, procLink])
  497. elif s != nil and s.kind in {skType, skVar, skLet, skConst} and
  498. sfExported in s.flags and s.owner != nil and
  499. belongsToProjectPackage(d.conf, s.owner) and d.target == outHtml:
  500. let external = externalDep(d, s.owner)
  501. result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>",
  502. [changeFileExt(external, "html"), literal,
  503. escLit]
  504. else:
  505. dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
  506. "\\spanIdentifier{$1}", [escLit])
  507. of tkSpaces, tkInvalid:
  508. result.add(literal)
  509. of tkHideableStart:
  510. template fun(s) = dispA(d.conf, result, s, "\\spanOther{$1}", [escLit])
  511. if renderRunnableExamples in renderFlags: fun "$1"
  512. else:
  513. # 1st span is required for the JS to work properly
  514. fun """
  515. <span>
  516. <span class="Other pragmadots">...</span>
  517. </span>
  518. <span class="pragmawrap">""".replace("\n", "") # Must remove newlines because wrapped in a <pre>
  519. of tkHideableEnd:
  520. template fun(s) = dispA(d.conf, result, s, "\\spanOther{$1}", [escLit])
  521. if renderRunnableExamples in renderFlags: fun "$1"
  522. else: fun "</span>"
  523. of tkCurlyDotLe: dispA(d.conf, result, "$1", "\\spanOther{$1}", [escLit])
  524. of tkCurlyDotRi: dispA(d.conf, result, "$1", "\\spanOther{$1}", [escLit])
  525. of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
  526. tkBracketDotLe, tkBracketDotRi, tkParDotLe,
  527. tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot,
  528. tkAccent, tkColonColon,
  529. tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr,
  530. tkBracketLeColon:
  531. dispA(d.conf, result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
  532. [escLit])
  533. proc exampleOutputDir(d: PDoc): AbsoluteDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
  534. proc runAllExamples(d: PDoc) =
  535. # This used to be: `let backend = if isDefined(d.conf, "js"): "js"` (etc), however
  536. # using `-d:js` (etc) cannot work properly, e.g. would fail with `importjs`
  537. # since semantics are affected by `config.backend`, not by isDefined(d.conf, "js")
  538. let outputDir = d.exampleOutputDir
  539. for _, group in d.exampleGroups:
  540. if group.docCmd == docCmdSkip: continue
  541. let outp = outputDir / RelativeFile("$1_group$2_examples.nim" % [d.filename.splitFile.name, $group.index])
  542. group.code = "# autogenerated by docgen\n# source: $1\n# rdoccmd: $2\n$3" % [d.filename, group.rdoccmd, group.code]
  543. writeFile(outp, group.code)
  544. # most useful semantics is that `docCmd` comes after `rdoccmd`, so that we can (temporarily) override
  545. # via command line
  546. # D20210224T221756:here
  547. var pathArgs = "--path:$path" % [ "path", quoteShell(d.conf.projectPath) ]
  548. for p in d.conf.searchPaths:
  549. pathArgs = "$args --path:$path" % [ "args", pathArgs, "path", quoteShell(p) ]
  550. let cmd = "$nim $backend -r --lib:$libpath --warning:UnusedImport:off $pathArgs --nimcache:$nimcache $rdoccmd $docCmd $file" % [
  551. "nim", quoteShell(os.getAppFilename()),
  552. "backend", $d.conf.backend,
  553. "pathArgs", pathArgs,
  554. "libpath", quoteShell(d.conf.libpath),
  555. "nimcache", quoteShell(outputDir),
  556. "file", quoteShell(outp),
  557. "rdoccmd", group.rdoccmd,
  558. "docCmd", group.docCmd,
  559. ]
  560. if d.conf.backend == backendJs and findNodeJs() == "":
  561. discard "ignore JS runnableExample"
  562. elif os.execShellCmd(cmd) != 0:
  563. d.conf.quitOrRaise "[runnableExamples] failed: generated file: '$1' group: '$2' cmd: $3" % [outp.string, group[].prettyString, cmd]
  564. else:
  565. # keep generated source file `outp` to allow inspection.
  566. rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string])
  567. # removeFile(outp.changeFileExt(ExeExt)) # it's in nimcache, no need to remove
  568. proc quoted(a: string): string =
  569. result = ""
  570. result.addQuoted(a)
  571. proc toInstantiationInfo(conf: ConfigRef, info: TLineInfo): (string, int, int) =
  572. # xxx expose in compiler/lineinfos.nim
  573. (conf.toMsgFilename(info), info.line.int, info.col.int + ColOffset)
  574. proc prepareExample(d: PDoc; n: PNode, topLevel: bool): tuple[rdoccmd: string, code: string] =
  575. ## returns `rdoccmd` and source code for this runnableExamples
  576. var rdoccmd = ""
  577. if n.len < 2 or n.len > 3: globalError(d.conf, n.info, "runnableExamples invalid")
  578. if n.len == 3:
  579. let n1 = n[1]
  580. # xxx this should be evaluated during sempass
  581. if n1.kind notin nkStrKinds: globalError(d.conf, n1.info, "string litteral expected")
  582. rdoccmd = n1.strVal
  583. let useRenderModule = false
  584. let loc = d.conf.toFileLineCol(n.info)
  585. let code = extractRunnableExamplesSource(d.conf, n)
  586. if d.conf.errorCounter > 0:
  587. return (rdoccmd, code)
  588. let comment = "autogenerated by docgen\nloc: $1\nrdoccmd: $2" % [loc, rdoccmd]
  589. let outputDir = d.exampleOutputDir
  590. createDir(outputDir)
  591. inc d.exampleCounter
  592. let outp = outputDir / RelativeFile("$#_examples_$#.nim" % [d.filename.extractFilename.changeFileExt"", $d.exampleCounter])
  593. if useRenderModule:
  594. var docComment = newTree(nkCommentStmt)
  595. docComment.comment = comment
  596. var runnableExamples = newTree(nkStmtList,
  597. docComment,
  598. newTree(nkImportStmt, newStrNode(nkStrLit, "std/assertions")),
  599. newTree(nkImportStmt, newStrNode(nkStrLit, d.filename)))
  600. runnableExamples.info = n.info
  601. for a in n.lastSon: runnableExamples.add a
  602. # buggy, refs bug #17292
  603. # still worth fixing as it can affect other code relying on `renderModule`,
  604. # so we keep this code path here for now, which could still be useful in some
  605. # other situations.
  606. renderModule(runnableExamples, outp.string, conf = d.conf)
  607. else:
  608. var code2 = code
  609. if code.len > 0 and "codeReordering" notin code:
  610. let codeIndent = extractRunnableExamplesSource(d.conf, n, indent = 2)
  611. # hacky but simplest solution, until we devise a way to make `{.line.}`
  612. # work without introducing a scope
  613. code2 = """
  614. {.line: $#.}:
  615. $#
  616. """ % [$toInstantiationInfo(d.conf, n.info), codeIndent]
  617. code2 = """
  618. #[
  619. $#
  620. ]#
  621. import std/assertions
  622. import $#
  623. $#
  624. """ % [comment, d.filename.quoted, code2]
  625. writeFile(outp.string, code2)
  626. if rdoccmd notin d.exampleGroups:
  627. d.exampleGroups[rdoccmd] = ExampleGroup(rdoccmd: rdoccmd, docCmd: d.conf.docCmd, index: d.exampleGroups.len)
  628. d.exampleGroups[rdoccmd].code.add "import $1\n" % outp.string.quoted
  629. var codeShown: string
  630. if topLevel: # refs https://github.com/nim-lang/RFCs/issues/352
  631. let title = canonicalImport(d.conf, AbsoluteFile d.filename)
  632. codeShown = "import $#\n$#" % [title, code]
  633. else:
  634. codeShown = code
  635. result = (rdoccmd, codeShown)
  636. when false:
  637. proc extractImports(n: PNode; result: PNode) =
  638. if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}:
  639. result.add copyTree(n)
  640. n.kind = nkEmpty
  641. return
  642. for i in 0..<n.safeLen: extractImports(n[i], result)
  643. let imports = newTree(nkStmtList)
  644. var savedLastSon = copyTree n.lastSon
  645. extractImports(savedLastSon, imports)
  646. for imp in imports: runnableExamples.add imp
  647. runnableExamples.add newTree(nkBlockStmt, newNode(nkEmpty), copyTree savedLastSon)
  648. type RunnableState = enum
  649. rsStart
  650. rsComment
  651. rsRunnable
  652. rsDone
  653. proc getAllRunnableExamplesImpl(d: PDoc; n: PNode, dest: var ItemPre,
  654. state: RunnableState, topLevel: bool):
  655. RunnableState =
  656. ##[
  657. Simple state machine to tell whether we render runnableExamples and doc comments.
  658. This is to ensure that we can interleave runnableExamples and doc comments freely;
  659. the logic is easy to change but currently a doc comment following another doc comment
  660. will not render, to avoid rendering in following case:
  661. proc fn* =
  662. runnableExamples: discard
  663. ## d1
  664. runnableExamples: discard
  665. ## d2
  666. ## internal explanation # <- this one should be out; it's part of rest of function body and would likey not make sense in doc comment
  667. discard # some code
  668. ]##
  669. case n.kind
  670. of nkCommentStmt:
  671. if state in {rsStart, rsRunnable}:
  672. dest.add genRecComment(d, n)
  673. return rsComment
  674. of nkCallKinds:
  675. if isRunnableExamples(n[0]) and
  676. n.len >= 2 and n.lastSon.kind == nkStmtList:
  677. if state in {rsStart, rsComment, rsRunnable}:
  678. let (rdoccmd, code) = prepareExample(d, n, topLevel)
  679. var msg = "Example:"
  680. if rdoccmd.len > 0: msg.add " cmd: " & rdoccmd
  681. var s: string = ""
  682. dispA(d.conf, s, "\n<p><strong class=\"examples_text\">$1</strong></p>\n",
  683. "\n\n\\textbf{$1}\n", [msg])
  684. dest.add s
  685. inc d.listingCounter
  686. let id = $d.listingCounter
  687. dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim", ""])
  688. var dest2 = ""
  689. renderNimCode(dest2, code, d.target)
  690. dest.add dest2
  691. dest.add(d.config.getOrDefault"doc.listing_end" % id)
  692. return rsRunnable
  693. else:
  694. localError(d.conf, n.info, errUser, "runnableExamples must appear before the first non-comment statement")
  695. else: discard
  696. return rsDone
  697. # change this to `rsStart` if you want to keep generating doc comments
  698. # and runnableExamples that occur after some code in routine
  699. proc getRoutineBody(n: PNode): PNode =
  700. ##[
  701. nim transforms these quite differently:
  702. proc someType*(): int =
  703. ## foo
  704. result = 3
  705. =>
  706. result =
  707. ## foo
  708. 3;
  709. proc someType*(): int =
  710. ## foo
  711. 3
  712. =>
  713. ## foo
  714. result = 3;
  715. so we normalize the results to get to the statement list containing the
  716. (0 or more) doc comments and runnableExamples.
  717. ]##
  718. result = n[bodyPos]
  719. # This won't be transformed: result.id = 10. Namely result[0].kind != nkSym.
  720. if result.kind == nkAsgn and result[0].kind == nkSym and
  721. n.len > bodyPos+1 and n[bodyPos+1].kind == nkSym:
  722. doAssert result.len == 2
  723. result = result[1]
  724. proc getAllRunnableExamples(d: PDoc, n: PNode, dest: var ItemPre) =
  725. var n = n
  726. var state = rsStart
  727. template fn(n2, topLevel) =
  728. state = getAllRunnableExamplesImpl(d, n2, dest, state, topLevel)
  729. dest.add genComment(d, n)
  730. case n.kind
  731. of routineDefs:
  732. n = n.getRoutineBody
  733. case n.kind
  734. of nkCommentStmt, nkCallKinds: fn(n, topLevel = false)
  735. else:
  736. for i in 0..<n.safeLen:
  737. fn(n[i], topLevel = false)
  738. if state == rsDone: discard # check all sons
  739. else: fn(n, topLevel = true)
  740. proc isVisible(d: PDoc; n: PNode): bool =
  741. result = false
  742. if n.kind == nkPostfix:
  743. if n.len == 2 and n[0].kind == nkIdent:
  744. var v = n[0].ident
  745. result = v.id == ord(wStar) or v.id == ord(wMinus)
  746. elif n.kind == nkSym:
  747. # we cannot generate code for forwarded symbols here as we have no
  748. # exception tracking information here. Instead we copy over the comment
  749. # from the proc header.
  750. if optDocInternal in d.conf.globalOptions:
  751. result = {sfFromGeneric, sfForward}*n.sym.flags == {}
  752. else:
  753. result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported}
  754. if result and containsOrIncl(d.emitted, n.sym.id):
  755. result = false
  756. elif n.kind == nkPragmaExpr:
  757. result = isVisible(d, n[0])
  758. proc getName(n: PNode): string =
  759. case n.kind
  760. of nkPostfix: result = getName(n[1])
  761. of nkPragmaExpr: result = getName(n[0])
  762. of nkSym: result = n.sym.renderDefinitionName
  763. of nkIdent: result = n.ident.s
  764. of nkAccQuoted:
  765. result = "`"
  766. for i in 0..<n.len: result.add(getName(n[i]))
  767. result = "`"
  768. of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym:
  769. result = getName(n[0])
  770. else:
  771. result = ""
  772. proc getNameEsc(d: PDoc, n: PNode): string =
  773. esc(d.target, getName(n))
  774. proc getNameIdent(cache: IdentCache; n: PNode): PIdent =
  775. case n.kind
  776. of nkPostfix: result = getNameIdent(cache, n[1])
  777. of nkPragmaExpr: result = getNameIdent(cache, n[0])
  778. of nkSym: result = n.sym.name
  779. of nkIdent: result = n.ident
  780. of nkAccQuoted:
  781. var r = ""
  782. for i in 0..<n.len: r.add(getNameIdent(cache, n[i]).s)
  783. result = getIdent(cache, r)
  784. of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym:
  785. result = getNameIdent(cache, n[0])
  786. else:
  787. result = nil
  788. proc getRstName(n: PNode): PRstNode =
  789. case n.kind
  790. of nkPostfix: result = getRstName(n[1])
  791. of nkPragmaExpr: result = getRstName(n[0])
  792. of nkSym: result = newRstLeaf(n.sym.renderDefinitionName)
  793. of nkIdent: result = newRstLeaf(n.ident.s)
  794. of nkAccQuoted:
  795. result = getRstName(n[0])
  796. for i in 1..<n.len: result.text.add(getRstName(n[i]).text)
  797. of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym:
  798. result = getRstName(n[0])
  799. else:
  800. result = nil
  801. proc newUniquePlainSymbol(d: PDoc, original: string): string =
  802. ## Returns a new unique plain symbol made up from the original.
  803. ##
  804. ## When a collision is found in the seenSymbols table, new numerical variants
  805. ## with underscore + number will be generated.
  806. if not d.seenSymbols.hasKey(original):
  807. result = original
  808. d.seenSymbols[original] = ""
  809. return
  810. # Iterate over possible numeric variants of the original name.
  811. var count = 2
  812. while true:
  813. result = original & "_" & $count
  814. if not d.seenSymbols.hasKey(result):
  815. d.seenSymbols[result] = ""
  816. break
  817. count += 1
  818. proc complexName(k: TSymKind, n: PNode, baseName: string): string =
  819. ## Builds a complex unique href name for the node.
  820. ##
  821. ## Pass as ``baseName`` the plain symbol obtained from the nodeName. The
  822. ## format of the returned symbol will be ``baseName(.callable type)?,(param
  823. ## type)?(,param type)*``. The callable type part will be added only if the
  824. ## node is not a proc, as those are the common ones. The suffix will be a dot
  825. ## and a single letter representing the type of the callable. The parameter
  826. ## types will be added with a preceding dash. Return types won't be added.
  827. ##
  828. ## If you modify the output of this proc, please update the anchor generation
  829. ## section of ``doc/docgen.rst``.
  830. result = baseName
  831. case k
  832. of skProc, skFunc: discard
  833. of skMacro: result.add(".m")
  834. of skMethod: result.add(".e")
  835. of skIterator: result.add(".i")
  836. of skTemplate: result.add(".t")
  837. of skConverter: result.add(".c")
  838. else: discard
  839. if n.safeLen > paramsPos and n[paramsPos].kind == nkFormalParams:
  840. let params = renderParamTypes(n[paramsPos])
  841. if params.len > 0:
  842. result.add(defaultParamSeparator)
  843. result.add(params)
  844. proc docstringSummary(rstText: string): string =
  845. ## Returns just the first line or a brief chunk of text from a rst string.
  846. ##
  847. ## Most docstrings will contain a one liner summary, so stripping at the
  848. ## first newline is usually fine. If after that the content is still too big,
  849. ## it is stripped at the first comma, colon or dot, usual English sentence
  850. ## separators.
  851. ##
  852. ## No guarantees are made on the size of the output, but it should be small.
  853. ## Also, we hope to not break the rst, but maybe we do. If there is any
  854. ## trimming done, an ellipsis unicode char is added.
  855. const maxDocstringChars = 100
  856. assert(rstText.len < 2 or (rstText[0] == '#' and rstText[1] == '#'))
  857. result = rstText.substr(2).strip
  858. var pos = result.find('\L')
  859. if pos > 0:
  860. result.setLen(pos - 1)
  861. result.add("…")
  862. if pos < maxDocstringChars:
  863. return
  864. # Try to keep trimming at other natural boundaries.
  865. pos = result.find({'.', ',', ':'})
  866. let last = result.len - 1
  867. if pos > 0 and pos < last:
  868. result.setLen(pos - 1)
  869. result.add("…")
  870. proc genDeprecationMsg(d: PDoc, n: PNode): string =
  871. ## Given a nkPragma wDeprecated node output a well-formatted section
  872. if n == nil: return
  873. case n.safeLen:
  874. of 0: # Deprecated w/o any message
  875. result = getConfigVar(d.conf, "doc.deprecationmsg") % [
  876. "label" , "Deprecated", "message", ""]
  877. of 2: # Deprecated w/ a message
  878. if n[1].kind in {nkStrLit..nkTripleStrLit}:
  879. result = getConfigVar(d.conf, "doc.deprecationmsg") % [
  880. "label", "Deprecated:", "message", xmltree.escape(n[1].strVal)]
  881. else:
  882. result = ""
  883. else:
  884. raiseAssert "unreachable"
  885. type DocFlags = enum
  886. kDefault
  887. kForceExport
  888. proc genSeeSrc(d: PDoc, path: string, line: int): string =
  889. result = ""
  890. let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc")
  891. if docItemSeeSrc.len > 0:
  892. let path = relativeTo(AbsoluteFile path, AbsoluteDir getCurrentDir(), '/')
  893. when false:
  894. let cwd = canonicalizePath(d.conf, getCurrentDir())
  895. var path = path
  896. if path.startsWith(cwd):
  897. path = path[cwd.len+1..^1].replace('\\', '/')
  898. let gitUrl = getConfigVar(d.conf, "git.url")
  899. if gitUrl.len > 0:
  900. let defaultBranch =
  901. if NimPatch mod 2 == 1: "devel"
  902. else: "version-$1-$2" % [$NimMajor, $NimMinor]
  903. let commit = getConfigVar(d.conf, "git.commit", defaultBranch)
  904. let develBranch = getConfigVar(d.conf, "git.devel", "devel")
  905. dispA(d.conf, result, "$1", "", [docItemSeeSrc % [
  906. "path", path.string, "line", $line, "url", gitUrl,
  907. "commit", commit, "devel", develBranch]])
  908. proc symbolPriority(k: TSymKind): int =
  909. result = case k
  910. of skMacro: -3
  911. of skTemplate: -2
  912. of skIterator: -1
  913. else: 0 # including skProc which have higher priority
  914. # documentation itself has even higher priority 1
  915. proc getTypeKind(n: PNode): string =
  916. case n[2].kind
  917. of nkEnumTy: "enum"
  918. of nkObjectTy: "object"
  919. of nkTupleTy: "tuple"
  920. else: ""
  921. proc toLangSymbol(k: TSymKind, n: PNode, baseName: string): LangSymbol =
  922. ## Converts symbol info (names/types/parameters) in `n` into format
  923. ## `LangSymbol` convenient for ``rst.nim``/``dochelpers.nim``.
  924. result = LangSymbol(name: baseName.nimIdentNormalize,
  925. symKind: k.toHumanStr
  926. )
  927. if k in routineKinds:
  928. var
  929. paramTypes: seq[string] = @[]
  930. renderParamTypes(paramTypes, n[paramsPos], toNormalize=true)
  931. let paramNames = renderParamNames(n[paramsPos], toNormalize=true)
  932. # In some rare cases (system.typeof) parameter type is not set for default:
  933. doAssert paramTypes.len <= paramNames.len
  934. for i in 0 ..< paramNames.len:
  935. if i < paramTypes.len:
  936. result.parameters.add (paramNames[i], paramTypes[i])
  937. else:
  938. result.parameters.add (paramNames[i], "")
  939. result.parametersProvided = true
  940. result.outType = renderOutType(n[paramsPos], toNormalize=true)
  941. if k in {skProc, skFunc, skType, skIterator}:
  942. # Obtain `result.generics`
  943. # Use `n[miscPos]` since n[genericParamsPos] does not contain constraints
  944. var genNode: PNode = nil
  945. if k == skType:
  946. genNode = n[1] # FIXME: what is index 1?
  947. else:
  948. if n[miscPos].kind != nkEmpty:
  949. genNode = n[miscPos][1] # FIXME: what is index 1?
  950. if genNode != nil:
  951. var literal = ""
  952. var r: TSrcGen = initTokRender(genNode, {renderNoBody, renderNoComments,
  953. renderNoPragmas, renderNoProcDefs, renderExpandUsing, renderNoPostfix})
  954. var kind = tkEof
  955. while true:
  956. getNextTok(r, kind, literal)
  957. if kind == tkEof:
  958. break
  959. if kind != tkSpaces:
  960. result.generics.add(literal.nimIdentNormalize)
  961. if k == skType: result.symTypeKind = getTypeKind(n)
  962. proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind, docFlags: DocFlags, nonExports: bool = false) =
  963. if (docFlags != kForceExport) and not isVisible(d, nameNode): return
  964. let
  965. name = getName(nameNode)
  966. nameEsc = esc(d.target, name)
  967. var plainDocstring = getPlainDocstring(n) # call here before genRecComment!
  968. var result = ""
  969. var literal, plainName = ""
  970. var kind = tkEof
  971. var comm: ItemPre = default(ItemPre)
  972. if n.kind in routineDefs:
  973. getAllRunnableExamples(d, n, comm)
  974. else:
  975. comm.add genRecComment(d, n)
  976. # Obtain the plain rendered string for hyperlink titles.
  977. var r: TSrcGen = initTokRender(n, {renderNoBody, renderNoComments, renderDocComments,
  978. renderNoPragmas, renderNoProcDefs, renderExpandUsing, renderNoPostfix})
  979. while true:
  980. getNextTok(r, kind, literal)
  981. if kind == tkEof:
  982. break
  983. plainName.add(literal)
  984. var pragmaNode = getDeclPragma(n)
  985. if pragmaNode != nil: pragmaNode = findPragma(pragmaNode, wDeprecated)
  986. inc(d.id)
  987. let
  988. plainNameEsc = esc(d.target, plainName.strip)
  989. typeDescr =
  990. if k == skType and getTypeKind(n) != "": getTypeKind(n)
  991. else: k.toHumanStr
  992. detailedName = typeDescr & " " & (
  993. if k in routineKinds: plainName else: name)
  994. uniqueName = if k in routineKinds: plainNameEsc else: nameEsc
  995. sortName = if k in routineKinds: plainName.strip else: name
  996. cleanPlainSymbol = renderPlainSymbolName(nameNode)
  997. complexSymbol = complexName(k, n, cleanPlainSymbol)
  998. plainSymbolEnc = encodeUrl(cleanPlainSymbol, usePlus = false)
  999. symbolOrId = d.newUniquePlainSymbol(complexSymbol)
  1000. symbolOrIdEnc = encodeUrl(symbolOrId, usePlus = false)
  1001. deprecationMsg = genDeprecationMsg(d, pragmaNode)
  1002. rstLangSymbol = toLangSymbol(k, n, cleanPlainSymbol)
  1003. symNameNode =
  1004. if nameNode.kind == nkPostfix: nameNode[1]
  1005. else: nameNode
  1006. # we generate anchors automatically for subsequent use in doc comments
  1007. let lineinfo = rstast.TLineInfo(
  1008. line: nameNode.info.line, col: nameNode.info.col,
  1009. fileIndex: addRstFileIndex(d, nameNode.info))
  1010. addAnchorNim(d.sharedState, external = false, refn = symbolOrId,
  1011. tooltip = detailedName, langSym = rstLangSymbol,
  1012. priority = symbolPriority(k), info = lineinfo,
  1013. module = addRstFileIndex(d, FileIndex d.module.position))
  1014. var renderFlags = {renderNoBody, renderNoComments, renderDocComments,
  1015. renderSyms, renderExpandUsing, renderNoPostfix}
  1016. if nonExports:
  1017. renderFlags.incl renderNonExportedFields
  1018. nodeToHighlightedHtml(d, n, result, renderFlags, symbolOrIdEnc)
  1019. let seeSrc = genSeeSrc(d, toFullPath(d.conf, n.info), n.info.line.int)
  1020. d.section[k].secItems.mgetOrPut(cleanPlainSymbol, newSeq[Item]()).add Item(
  1021. descRst: comm,
  1022. sortName: sortName,
  1023. info: lineinfo,
  1024. anchor: symbolOrId,
  1025. detailedName: detailedName,
  1026. name: name,
  1027. substitutions: @[
  1028. "uniqueName", uniqueName,
  1029. "header", result, "itemID", $d.id,
  1030. "header_plain", plainNameEsc, "itemSym", cleanPlainSymbol,
  1031. "itemSymEnc", plainSymbolEnc,
  1032. "itemSymOrIDEnc", symbolOrIdEnc, "seeSrc", seeSrc,
  1033. "deprecationMsg", deprecationMsg])
  1034. let external = d.destFile.AbsoluteFile.relativeTo(d.conf.outDir, '/').changeFileExt(HtmlExt).string
  1035. var attype = ""
  1036. if k in routineKinds and symNameNode.kind == nkSym:
  1037. let att = attachToType(d, nameNode.sym)
  1038. if att != nil:
  1039. attype = esc(d.target, att.name.s)
  1040. elif k == skType and symNameNode.kind == nkSym and
  1041. symNameNode.sym.typ.kind in {tyEnum, tyBool}:
  1042. let etyp = symNameNode.sym.typ
  1043. for e in etyp.n:
  1044. if e.sym.kind != skEnumField: continue
  1045. let plain = renderPlainSymbolName(e)
  1046. let symbolOrId = d.newUniquePlainSymbol(plain)
  1047. setIndexTerm(d[], ieNim, htmlFile = external, id = symbolOrId,
  1048. term = plain, linkTitle = symNameNode.sym.name.s & '.' & plain,
  1049. linkDesc = xmltree.escape(getPlainDocstring(e).docstringSummary),
  1050. line = n.info.line.int)
  1051. d.tocSimple[k].add TocItem(
  1052. sortName: sortName,
  1053. content: getConfigVar(d.conf, "doc.item.toc") % [
  1054. "name", name, "header_plain", plainNameEsc,
  1055. "itemSymOrIDEnc", symbolOrIdEnc])
  1056. d.tocTable[k].mgetOrPut(cleanPlainSymbol, newSeq[TocItem]()).add TocItem(
  1057. sortName: sortName,
  1058. content: getConfigVar(d.conf, "doc.item.tocTable") % [
  1059. "name", name, "header_plain", plainNameEsc,
  1060. "itemSymOrID", symbolOrId.replace(",", ",<wbr>"),
  1061. "itemSymOrIDEnc", symbolOrIdEnc])
  1062. setIndexTerm(d[], ieNim, htmlFile = external, id = symbolOrId, term = name,
  1063. linkTitle = detailedName,
  1064. linkDesc = xmltree.escape(plainDocstring.docstringSummary),
  1065. line = n.info.line.int)
  1066. if k == skType and symNameNode.kind == nkSym:
  1067. d.types.strTableAdd symNameNode.sym
  1068. proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind, nonExports = false): JsonItem =
  1069. if not isVisible(d, nameNode): return
  1070. var
  1071. name = getNameEsc(d, nameNode)
  1072. comm = genRecComment(d, n)
  1073. r: TSrcGen
  1074. renderFlags = {renderNoBody, renderNoComments, renderDocComments,
  1075. renderExpandUsing, renderNoPostfix}
  1076. if nonExports:
  1077. renderFlags.incl renderNonExportedFields
  1078. r = initTokRender(n, renderFlags)
  1079. result = JsonItem(json: %{ "name": %name, "type": %($k), "line": %n.info.line.int,
  1080. "col": %n.info.col}
  1081. )
  1082. if comm != nil:
  1083. result.rst = comm
  1084. result.rstField = "description"
  1085. if r.buf.len > 0:
  1086. result.json["code"] = %r.buf
  1087. if k in routineKinds:
  1088. result.json["signature"] = newJObject()
  1089. if n[paramsPos][0].kind != nkEmpty:
  1090. result.json["signature"]["return"] = %($n[paramsPos][0])
  1091. if n[paramsPos].len > 1:
  1092. result.json["signature"]["arguments"] = newJArray()
  1093. for paramIdx in 1 ..< n[paramsPos].len:
  1094. for identIdx in 0 ..< n[paramsPos][paramIdx].len - 2:
  1095. let
  1096. paramName = $n[paramsPos][paramIdx][identIdx]
  1097. paramType = $n[paramsPos][paramIdx][^2]
  1098. if n[paramsPos][paramIdx][^1].kind != nkEmpty:
  1099. let paramDefault = $n[paramsPos][paramIdx][^1]
  1100. result.json["signature"]["arguments"].add %{"name": %paramName, "type": %paramType, "default": %paramDefault}
  1101. else:
  1102. result.json["signature"]["arguments"].add %{"name": %paramName, "type": %paramType}
  1103. if n[pragmasPos].kind != nkEmpty:
  1104. result.json["signature"]["pragmas"] = newJArray()
  1105. for pragma in n[pragmasPos]:
  1106. result.json["signature"]["pragmas"].add %($pragma)
  1107. if n[genericParamsPos].kind != nkEmpty:
  1108. result.json["signature"]["genericParams"] = newJArray()
  1109. for genericParam in n[genericParamsPos]:
  1110. var param = %{"name": %($genericParam)}
  1111. if genericParam.sym.typ.len > 0:
  1112. param["types"] = newJArray()
  1113. param["types"] = %($genericParam.sym.typ.elementType)
  1114. result.json["signature"]["genericParams"].add param
  1115. if optGenIndex in d.conf.globalOptions:
  1116. genItem(d, n, nameNode, k, kForceExport)
  1117. proc setDoctype(d: PDoc, n: PNode) =
  1118. ## Processes `{.doctype.}` pragma changing Markdown/RST parsing options.
  1119. if n == nil:
  1120. return
  1121. if n.len != 2:
  1122. localError(d.conf, n.info, errUser,
  1123. "doctype pragma takes exactly 1 argument"
  1124. )
  1125. return
  1126. var dt = ""
  1127. case n[1].kind
  1128. of nkStrLit:
  1129. dt = toLowerAscii(n[1].strVal)
  1130. of nkIdent:
  1131. dt = toLowerAscii(n[1].ident.s)
  1132. else:
  1133. localError(d.conf, n.info, errUser,
  1134. "unknown argument type $1 provided to doctype" % [$n[1].kind]
  1135. )
  1136. return
  1137. case dt
  1138. of "markdown":
  1139. d.sharedState.options.incl roSupportMarkdown
  1140. d.sharedState.options.incl roPreferMarkdown
  1141. of "rstmarkdown":
  1142. d.sharedState.options.incl roSupportMarkdown
  1143. d.sharedState.options.excl roPreferMarkdown
  1144. of "rst":
  1145. d.sharedState.options.excl roSupportMarkdown
  1146. d.sharedState.options.excl roPreferMarkdown
  1147. else:
  1148. localError(d.conf, n.info, errUser,
  1149. (
  1150. "unknown doctype value \"$1\", should be from " &
  1151. "\"RST\", \"Markdown\", \"RstMarkdown\""
  1152. ) % [dt]
  1153. )
  1154. proc checkForFalse(n: PNode): bool =
  1155. result = n.kind == nkIdent and cmpIgnoreStyle(n.ident.s, "false") == 0
  1156. proc traceDeps(d: PDoc, it: PNode) =
  1157. const k = skModule
  1158. if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
  1159. let sep = it[0]
  1160. let dir = it[1]
  1161. let a = newNodeI(nkInfix, it.info)
  1162. a.add sep
  1163. a.add dir
  1164. a.add sep # dummy entry, replaced in the loop
  1165. for x in it[2]:
  1166. a[2] = x
  1167. traceDeps(d, a)
  1168. elif it.kind == nkSym and belongsToProjectPackage(d.conf, it.sym):
  1169. let external = externalDep(d, it.sym)
  1170. if d.section[k].finalMarkup != "": d.section[k].finalMarkup.add(", ")
  1171. dispA(d.conf, d.section[k].finalMarkup,
  1172. "<a class=\"reference external\" href=\"$2\">$1</a>",
  1173. "$1", [esc(d.target, external.prettyLink),
  1174. changeFileExt(external, "html")])
  1175. proc exportSym(d: PDoc; s: PSym) =
  1176. const k = exportSection
  1177. if s.kind == skModule and belongsToProjectPackage(d.conf, s):
  1178. let external = externalDep(d, s)
  1179. if d.section[k].finalMarkup != "": d.section[k].finalMarkup.add(", ")
  1180. dispA(d.conf, d.section[k].finalMarkup,
  1181. "<a class=\"reference external\" href=\"$2\">$1</a>",
  1182. "$1", [esc(d.target, external.prettyLink),
  1183. changeFileExt(external, "html")])
  1184. elif s.kind != skModule and s.owner != nil:
  1185. let module = originatingModule(s)
  1186. if belongsToProjectPackage(d.conf, module):
  1187. let
  1188. complexSymbol = complexName(s.kind, s.ast, s.name.s)
  1189. symbolOrId = d.newUniquePlainSymbol(complexSymbol)
  1190. external = externalDep(d, module)
  1191. if d.section[k].finalMarkup != "": d.section[k].finalMarkup.add(", ")
  1192. # XXX proper anchor generation here
  1193. dispA(d.conf, d.section[k].finalMarkup,
  1194. "<a href=\"$2#$3\"><span class=\"Identifier\">$1</span></a>",
  1195. "$1", [esc(d.target, s.name.s),
  1196. changeFileExt(external, "html"),
  1197. symbolOrId])
  1198. proc documentNewEffect(cache: IdentCache; n: PNode): PNode =
  1199. let s = n[namePos].sym
  1200. if tfReturnsNew in s.typ.flags:
  1201. result = newIdentNode(getIdent(cache, "new"), n.info)
  1202. else:
  1203. result = nil
  1204. proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, idx: int): PNode =
  1205. let spec = effectSpec(x, effectType)
  1206. if isNil(spec):
  1207. let s = n[namePos].sym
  1208. let actual = s.typ.n[0]
  1209. if actual.len != effectListLen: return
  1210. let real = actual[idx]
  1211. if real == nil: return
  1212. let realLen = real.len
  1213. # warning: hack ahead:
  1214. var effects = newNodeI(nkBracket, n.info, realLen)
  1215. for i in 0..<realLen:
  1216. var t = typeToString(real[i].typ)
  1217. if t.startsWith("ref "): t = substr(t, 4)
  1218. effects[i] = newIdentNode(getIdent(cache, t), n.info)
  1219. # set the type so that the following analysis doesn't screw up:
  1220. effects[i].typ = real[i].typ
  1221. result = newTreeI(nkExprColonExpr, n.info,
  1222. newIdentNode(getIdent(cache, $effectType), n.info), effects)
  1223. else:
  1224. result = nil
  1225. proc documentWriteEffect(cache: IdentCache; n: PNode; flag: TSymFlag; pragmaName: string): PNode =
  1226. let s = n[namePos].sym
  1227. let params = s.typ.n
  1228. var effects = newNodeI(nkBracket, n.info)
  1229. for i in 1..<params.len:
  1230. if params[i].kind == nkSym and flag in params[i].sym.flags:
  1231. effects.add params[i]
  1232. if effects.len > 0:
  1233. result = newTreeI(nkExprColonExpr, n.info,
  1234. newIdentNode(getIdent(cache, pragmaName), n.info), effects)
  1235. else:
  1236. result = nil
  1237. proc documentRaises*(cache: IdentCache; n: PNode) =
  1238. if n[namePos].kind != nkSym: return
  1239. let pragmas = n[pragmasPos]
  1240. let p1 = documentEffect(cache, n, pragmas, wRaises, exceptionEffects)
  1241. let p2 = documentEffect(cache, n, pragmas, wTags, tagEffects)
  1242. let p3 = documentWriteEffect(cache, n, sfWrittenTo, "writes")
  1243. let p4 = documentNewEffect(cache, n)
  1244. let p5 = documentWriteEffect(cache, n, sfEscapes, "escapes")
  1245. let p6 = documentEffect(cache, n, pragmas, wForbids, forbiddenEffects)
  1246. if p1 != nil or p2 != nil or p3 != nil or p4 != nil or p5 != nil or p6 != nil:
  1247. if pragmas.kind == nkEmpty:
  1248. n[pragmasPos] = newNodeI(nkPragma, n.info)
  1249. if p1 != nil: n[pragmasPos].add p1
  1250. if p2 != nil: n[pragmasPos].add p2
  1251. if p3 != nil: n[pragmasPos].add p3
  1252. if p4 != nil: n[pragmasPos].add p4
  1253. if p5 != nil: n[pragmasPos].add p5
  1254. if p6 != nil: n[pragmasPos].add p6
  1255. proc generateDoc*(d: PDoc, n, orig: PNode, config: ConfigRef, docFlags: DocFlags = kDefault) =
  1256. ## Goes through nim nodes recursively and collects doc comments.
  1257. ## Main function for `doc`:option: command,
  1258. ## which is implemented in ``docgen2.nim``.
  1259. template genItemAux(skind) =
  1260. genItem(d, n, n[namePos], skind, docFlags)
  1261. let showNonExports = optShowNonExportedFields in config.globalOptions
  1262. case n.kind
  1263. of nkPragma:
  1264. let pragmaNode = findPragma(n, wDeprecated)
  1265. d.modDeprecationMsg.add(genDeprecationMsg(d, pragmaNode))
  1266. let doctypeNode = findPragma(n, wDoctype)
  1267. setDoctype(d, doctypeNode)
  1268. of nkCommentStmt: d.modDescPre.add(genComment(d, n))
  1269. of nkProcDef, nkFuncDef:
  1270. when useEffectSystem: documentRaises(d.cache, n)
  1271. genItemAux(skProc)
  1272. of nkMethodDef:
  1273. when useEffectSystem: documentRaises(d.cache, n)
  1274. genItemAux(skMethod)
  1275. of nkIteratorDef:
  1276. when useEffectSystem: documentRaises(d.cache, n)
  1277. genItemAux(skIterator)
  1278. of nkMacroDef: genItemAux(skMacro)
  1279. of nkTemplateDef: genItemAux(skTemplate)
  1280. of nkConverterDef:
  1281. when useEffectSystem: documentRaises(d.cache, n)
  1282. genItemAux(skConverter)
  1283. of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
  1284. for i in 0..<n.len:
  1285. if n[i].kind != nkCommentStmt:
  1286. # order is always 'type var let const':
  1287. genItem(d, n[i], n[i][0],
  1288. succ(skType, ord(n.kind)-ord(nkTypeSection)), docFlags, showNonExports)
  1289. of nkStmtList:
  1290. for i in 0..<n.len: generateDoc(d, n[i], orig, config)
  1291. of nkWhenStmt:
  1292. # generate documentation for the first branch only:
  1293. if not checkForFalse(n[0][0]):
  1294. generateDoc(d, lastSon(n[0]), orig, config)
  1295. of nkImportStmt:
  1296. for it in n: traceDeps(d, it)
  1297. of nkExportStmt:
  1298. for it in n:
  1299. # bug #23051; don't generate documentation for exported symbols again
  1300. if it.kind == nkSym and sfExported notin it.sym.flags:
  1301. if d.module != nil and d.module == it.sym.owner:
  1302. generateDoc(d, it.sym.ast, orig, config, kForceExport)
  1303. elif it.sym.ast != nil:
  1304. exportSym(d, it.sym)
  1305. of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept"
  1306. of nkFromStmt, nkImportExceptStmt: traceDeps(d, n[0])
  1307. of nkCallKinds:
  1308. var comm: ItemPre = default(ItemPre)
  1309. getAllRunnableExamples(d, n, comm)
  1310. if comm.len != 0: d.modDescPre.add(comm)
  1311. else: discard
  1312. proc overloadGroupName(s: string, k: TSymKind): string =
  1313. ## Turns a name like `f` into anchor `f-procs-all`
  1314. s & "-" & k.toHumanStr & "s-all"
  1315. proc setIndexTitle(d: PDoc, useMetaTitle: bool) =
  1316. let titleKind = if d.standaloneDoc: ieMarkupTitle else: ieNimTitle
  1317. let external = AbsoluteFile(d.destFile)
  1318. .relativeTo(d.conf.outDir, '/')
  1319. .changeFileExt(HtmlExt)
  1320. .string
  1321. var term, linkTitle: string
  1322. if useMetaTitle and d.meta[metaTitle].len != 0:
  1323. term = d.meta[metaTitleRaw]
  1324. linkTitle = d.meta[metaTitleRaw]
  1325. else:
  1326. let filename = extractFilename(d.filename)
  1327. term =
  1328. if d.standaloneDoc: filename # keep .rst/.md extension
  1329. else: changeFileExt(filename, "") # rm .nim extension
  1330. linkTitle =
  1331. if d.standaloneDoc: term # keep .rst/.md extension
  1332. else: canonicalImport(d.conf, AbsoluteFile d.filename)
  1333. if not d.standaloneDoc:
  1334. linkTitle = "module " & linkTitle
  1335. setIndexTerm(d[], titleKind, htmlFile = external, id = "",
  1336. term = term, linkTitle = linkTitle)
  1337. proc finishGenerateDoc*(d: var PDoc) =
  1338. ## Perform 2nd RST pass for resolution of links/footnotes/headings...
  1339. # copy file map `filenames` to ``rstgen.nim`` for its warnings
  1340. d.filenames = d.sharedState.filenames
  1341. # Main title/subtitle are allowed only in the first RST fragment of document
  1342. var firstRst = PRstNode(nil)
  1343. for fragment in d.modDescPre:
  1344. if fragment.isRst:
  1345. firstRst = fragment.rst
  1346. break
  1347. d.hasToc = d.hasToc or d.sharedState.hasToc
  1348. # in --index:only mode we do NOT want to load other .idx, only write ours:
  1349. let importdoc = optGenIndexOnly notin d.conf.globalOptions and
  1350. optNoImportdoc notin d.conf.globalOptions
  1351. preparePass2(d.sharedState, firstRst, importdoc)
  1352. if optGenIndexOnly in d.conf.globalOptions:
  1353. # Top-level doc.comments may contain titles and :idx: statements:
  1354. for fragment in d.modDescPre:
  1355. if fragment.isRst:
  1356. traverseForIndex(d[], fragment.rst)
  1357. setIndexTitle(d, useMetaTitle = d.standaloneDoc)
  1358. # Symbol-associated doc.comments may contain :idx: statements:
  1359. for k in TSymKind:
  1360. for _, overloadChoices in d.section[k].secItems:
  1361. for item in overloadChoices:
  1362. for fragment in item.descRst:
  1363. if fragment.isRst:
  1364. traverseForIndex(d[], fragment.rst)
  1365. # add anchors to overload groups before RST resolution
  1366. for k in TSymKind:
  1367. if k in routineKinds:
  1368. for plainName, overloadChoices in d.section[k].secItems:
  1369. if overloadChoices.len > 1:
  1370. let refn = overloadGroupName(plainName, k)
  1371. let tooltip = "$1 ($2 overloads)" % [
  1372. k.toHumanStr & " " & plainName, $overloadChoices.len]
  1373. let name = nimIdentBackticksNormalize(plainName)
  1374. # save overload group to ``.idx``
  1375. let external = d.destFile.AbsoluteFile.relativeTo(d.conf.outDir, '/').
  1376. changeFileExt(HtmlExt).string
  1377. setIndexTerm(d[], ieNimGroup, htmlFile = external, id = refn,
  1378. term = name, linkTitle = k.toHumanStr,
  1379. linkDesc = "", line = overloadChoices[0].info.line.int)
  1380. if optGenIndexOnly in d.conf.globalOptions: continue
  1381. addAnchorNim(d.sharedState, external=false, refn, tooltip,
  1382. LangSymbol(symKind: k.toHumanStr,
  1383. name: name,
  1384. isGroup: true),
  1385. priority = symbolPriority(k),
  1386. # select index `0` just to have any meaningful warning:
  1387. info = overloadChoices[0].info,
  1388. module = addRstFileIndex(d, FileIndex d.module.position))
  1389. if optGenIndexOnly in d.conf.globalOptions:
  1390. return
  1391. # Finalize fragments of ``.nim`` or ``.rst`` file
  1392. proc renderItemPre(d: PDoc, fragments: ItemPre, result: var string) =
  1393. for f in fragments:
  1394. case f.isRst:
  1395. of true:
  1396. var resolved = resolveSubs(d.sharedState, f.rst)
  1397. renderRstToOut(d[], resolved, result)
  1398. of false: result &= f.str
  1399. proc cmp(x, y: Item): int = cmpDecimalsIgnoreCase(x.sortName, y.sortName)
  1400. for k in TSymKind:
  1401. # add symbols to section for each `k`, while optionally wrapping
  1402. # overloadable items with the same basic name by ``doc.item2``
  1403. let overloadableNames = toSeq(keys(d.section[k].secItems))
  1404. for plainName in overloadableNames.sorted(cmpDecimalsIgnoreCase):
  1405. var overloadChoices = d.section[k].secItems[plainName]
  1406. overloadChoices.sort(cmp)
  1407. var nameContent = ""
  1408. for item in overloadChoices:
  1409. var itemDesc: string = ""
  1410. renderItemPre(d, item.descRst, itemDesc)
  1411. nameContent.add(
  1412. getConfigVar(d.conf, "doc.item") % (
  1413. item.substitutions & @[
  1414. "desc", itemDesc,
  1415. "name", item.name,
  1416. "itemSymOrID", item.anchor]))
  1417. if k in routineKinds:
  1418. let plainNameEsc1 = esc(d.target, plainName.strip)
  1419. let plainNameEsc2 = esc(d.target, plainName.strip, escMode=emUrl)
  1420. d.section[k].finalMarkup.add(
  1421. getConfigVar(d.conf, "doc.item2") % (
  1422. @["header_plain", plainNameEsc1,
  1423. "overloadGroupName", overloadGroupName(plainNameEsc2, k),
  1424. "content", nameContent]))
  1425. else:
  1426. d.section[k].finalMarkup.add(nameContent)
  1427. d.section[k].secItems.clear
  1428. renderItemPre(d, d.modDescPre, d.modDescFinal)
  1429. d.modDescPre.setLen 0
  1430. # Finalize fragments of ``.json`` file
  1431. for i, entry in d.jEntriesPre:
  1432. if entry.rst != nil:
  1433. let resolved = resolveSubs(d.sharedState, entry.rst)
  1434. var str: string = ""
  1435. renderRstToOut(d[], resolved, str)
  1436. entry.json[entry.rstField] = %str
  1437. d.jEntriesPre[i].rst = nil
  1438. d.jEntriesFinal.add entry.json # generates docs
  1439. setIndexTitle(d, useMetaTitle = d.standaloneDoc)
  1440. completePass2(d.sharedState)
  1441. proc add(d: PDoc; j: JsonItem) =
  1442. if j.json != nil or j.rst != nil: d.jEntriesPre.add j
  1443. proc generateJson*(d: PDoc, n: PNode, config: ConfigRef, includeComments: bool = true) =
  1444. case n.kind
  1445. of nkPragma:
  1446. let doctypeNode = findPragma(n, wDoctype)
  1447. setDoctype(d, doctypeNode)
  1448. of nkCommentStmt:
  1449. if includeComments:
  1450. d.add JsonItem(rst: genComment(d, n), rstField: "comment",
  1451. json: %Table[string, string]())
  1452. else:
  1453. d.modDescPre.add(genComment(d, n))
  1454. of nkProcDef, nkFuncDef:
  1455. when useEffectSystem: documentRaises(d.cache, n)
  1456. d.add genJsonItem(d, n, n[namePos], skProc)
  1457. of nkMethodDef:
  1458. when useEffectSystem: documentRaises(d.cache, n)
  1459. d.add genJsonItem(d, n, n[namePos], skMethod)
  1460. of nkIteratorDef:
  1461. when useEffectSystem: documentRaises(d.cache, n)
  1462. d.add genJsonItem(d, n, n[namePos], skIterator)
  1463. of nkMacroDef:
  1464. d.add genJsonItem(d, n, n[namePos], skMacro)
  1465. of nkTemplateDef:
  1466. d.add genJsonItem(d, n, n[namePos], skTemplate)
  1467. of nkConverterDef:
  1468. when useEffectSystem: documentRaises(d.cache, n)
  1469. d.add genJsonItem(d, n, n[namePos], skConverter)
  1470. of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
  1471. for i in 0..<n.len:
  1472. if n[i].kind != nkCommentStmt:
  1473. # order is always 'type var let const':
  1474. d.add genJsonItem(d, n[i], n[i][0],
  1475. succ(skType, ord(n.kind)-ord(nkTypeSection)), optShowNonExportedFields in config.globalOptions)
  1476. of nkStmtList:
  1477. for i in 0..<n.len:
  1478. generateJson(d, n[i], config, includeComments)
  1479. of nkWhenStmt:
  1480. # generate documentation for the first branch only:
  1481. if not checkForFalse(n[0][0]):
  1482. generateJson(d, lastSon(n[0]), config, includeComments)
  1483. else: discard
  1484. proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string =
  1485. result = getNameEsc(d, nameNode) & "\n"
  1486. proc generateTags*(d: PDoc, n: PNode, r: var string) =
  1487. case n.kind
  1488. of nkCommentStmt:
  1489. if startsWith(n.comment, "##"):
  1490. let stripped = n.comment.substr(2).strip
  1491. r.add stripped
  1492. of nkProcDef:
  1493. when useEffectSystem: documentRaises(d.cache, n)
  1494. r.add genTagsItem(d, n, n[namePos], skProc)
  1495. of nkFuncDef:
  1496. when useEffectSystem: documentRaises(d.cache, n)
  1497. r.add genTagsItem(d, n, n[namePos], skFunc)
  1498. of nkMethodDef:
  1499. when useEffectSystem: documentRaises(d.cache, n)
  1500. r.add genTagsItem(d, n, n[namePos], skMethod)
  1501. of nkIteratorDef:
  1502. when useEffectSystem: documentRaises(d.cache, n)
  1503. r.add genTagsItem(d, n, n[namePos], skIterator)
  1504. of nkMacroDef:
  1505. r.add genTagsItem(d, n, n[namePos], skMacro)
  1506. of nkTemplateDef:
  1507. r.add genTagsItem(d, n, n[namePos], skTemplate)
  1508. of nkConverterDef:
  1509. when useEffectSystem: documentRaises(d.cache, n)
  1510. r.add genTagsItem(d, n, n[namePos], skConverter)
  1511. of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
  1512. for i in 0..<n.len:
  1513. if n[i].kind != nkCommentStmt:
  1514. # order is always 'type var let const':
  1515. r.add genTagsItem(d, n[i], n[i][0],
  1516. succ(skType, ord(n.kind)-ord(nkTypeSection)))
  1517. of nkStmtList:
  1518. for i in 0..<n.len:
  1519. generateTags(d, n[i], r)
  1520. of nkWhenStmt:
  1521. # generate documentation for the first branch only:
  1522. if not checkForFalse(n[0][0]):
  1523. generateTags(d, lastSon(n[0]), r)
  1524. else: discard
  1525. proc genSection(d: PDoc, kind: TSymKind, groupedToc = false) =
  1526. const sectionNames: array[skModule..skField, string] = [
  1527. "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs",
  1528. "Methods", "Iterators", "Converters", "Macros", "Templates", "Exports"
  1529. ]
  1530. if d.section[kind].finalMarkup == "": return
  1531. var title = sectionNames[kind]
  1532. d.section[kind].finalMarkup = getConfigVar(d.conf, "doc.section") % [
  1533. "sectionid", $ord(kind), "sectionTitle", title,
  1534. "sectionTitleID", $(ord(kind) + 50), "content", d.section[kind].finalMarkup]
  1535. proc cmp(x, y: TocItem): int = cmpDecimalsIgnoreCase(x.sortName, y.sortName)
  1536. if groupedToc:
  1537. let overloadableNames = toSeq(keys(d.tocTable[kind]))
  1538. for plainName in overloadableNames.sorted(cmpDecimalsIgnoreCase):
  1539. var overloadChoices = d.tocTable[kind][plainName]
  1540. overloadChoices.sort(cmp)
  1541. var content: string = ""
  1542. for item in overloadChoices:
  1543. content.add item.content
  1544. d.toc2[kind].add getConfigVar(d.conf, "doc.section.toc2") % [
  1545. "sectionid", $ord(kind), "sectionTitle", title,
  1546. "sectionTitleID", $(ord(kind) + 50),
  1547. "content", content, "plainName", plainName]
  1548. else:
  1549. for item in d.tocSimple[kind].sorted(cmp):
  1550. d.toc2[kind].add item.content
  1551. let sectionValues = @[
  1552. "sectionID", $ord(kind), "sectionTitleID", $(ord(kind) + 50),
  1553. "sectionTitle", title
  1554. ]
  1555. # Check if the toc has any children
  1556. if d.toc2[kind] != "":
  1557. # Use the dropdown version instead and store the children in the dropdown
  1558. d.toc[kind] = getConfigVar(d.conf, "doc.section.toc") % (sectionValues & @[
  1559. "content", d.toc2[kind]
  1560. ])
  1561. else:
  1562. # Just have the link
  1563. d.toc[kind] = getConfigVar(d.conf, "doc.section.toc_item") % sectionValues
  1564. proc relLink(outDir: AbsoluteDir, destFile: AbsoluteFile, linkto: RelativeFile): string =
  1565. $relativeTo(outDir / linkto, destFile.splitFile().dir, '/')
  1566. proc genOutFile(d: PDoc, groupedToc = false): string =
  1567. var
  1568. code, content: string = ""
  1569. title = ""
  1570. var j = 0
  1571. var toc = ""
  1572. renderTocEntries(d[], j, 1, toc)
  1573. for i in TSymKind:
  1574. var shouldSort = i in routineKinds and groupedToc
  1575. genSection(d, i, shouldSort)
  1576. toc.add(d.toc[i])
  1577. if toc != "" or d.target == outLatex:
  1578. # for Latex $doc.toc will automatically generate TOC if `d.hasToc` is set
  1579. toc = getConfigVar(d.conf, "doc.toc") % ["content", toc]
  1580. for i in TSymKind: code.add(d.section[i].finalMarkup)
  1581. # Extract the title. Non API modules generate an entry in the index table.
  1582. if d.meta[metaTitle].len != 0:
  1583. title = d.meta[metaTitle]
  1584. else:
  1585. title = canonicalImport(d.conf, AbsoluteFile d.filename)
  1586. title = esc(d.target, title)
  1587. var subtitle = ""
  1588. if d.meta[metaSubtitle] != "":
  1589. dispA(d.conf, subtitle, "<h2 class=\"subtitle\">$1</h2>",
  1590. "\\\\\\vspace{0.5em}\\large $1", [esc(d.target, d.meta[metaSubtitle])])
  1591. var groupsection = getConfigVar(d.conf, "doc.body_toc_groupsection")
  1592. let bodyname = if d.hasToc and not d.standaloneDoc and not d.conf.isLatexCmd:
  1593. groupsection.setLen 0
  1594. "doc.body_toc_group"
  1595. elif d.hasToc: "doc.body_toc"
  1596. else: "doc.body_no_toc"
  1597. let seeSrc = genSeeSrc(d, d.filename, 1)
  1598. content = getConfigVar(d.conf, bodyname) % [
  1599. "title", title, "subtitle", subtitle,
  1600. "tableofcontents", toc, "moduledesc", d.modDescFinal, "date", getDateStr(),
  1601. "time", getClockStr(), "content", code,
  1602. "deprecationMsg", d.modDeprecationMsg,
  1603. "theindexhref", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
  1604. theindexFname.RelativeFile),
  1605. "body_toc_groupsection", groupsection, "seeSrc", seeSrc]
  1606. if optCompileOnly notin d.conf.globalOptions:
  1607. # XXX what is this hack doing here? 'optCompileOnly' means raw output!?
  1608. code = getConfigVar(d.conf, "doc.file") % [
  1609. "nimdoccss", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
  1610. nimdocOutCss.RelativeFile),
  1611. "dochackjs", relLink(d.conf.outDir, d.destFile.AbsoluteFile,
  1612. docHackJsFname.RelativeFile),
  1613. "title", title, "subtitle", subtitle, "tableofcontents", toc,
  1614. "moduledesc", d.modDescFinal, "date", getDateStr(), "time", getClockStr(),
  1615. "content", content, "author", d.meta[metaAuthor],
  1616. "version", esc(d.target, d.meta[metaVersion]), "analytics", d.analytics,
  1617. "deprecationMsg", d.modDeprecationMsg, "nimVersion", $NimMajor & "." & $NimMinor & "." & $NimPatch]
  1618. else:
  1619. code = content
  1620. result = code
  1621. proc indexFile(d: PDoc): AbsoluteFile =
  1622. let dir = d.conf.outDir
  1623. result = dir / changeFileExt(presentationPath(d.conf,
  1624. AbsoluteFile d.filename),
  1625. IndexExt)
  1626. let (finalDir, _, _) = result.string.splitFile
  1627. createDir(finalDir)
  1628. proc generateIndex*(d: PDoc) =
  1629. if optGenIndex in d.conf.globalOptions:
  1630. let dest = indexFile(d)
  1631. writeIndexFile(d[], dest.string)
  1632. proc updateOutfile(d: PDoc, outfile: AbsoluteFile) =
  1633. if d.module == nil or sfMainModule in d.module.flags: # nil for e.g. for commandRst2Html
  1634. if d.conf.outFile.isEmpty:
  1635. d.conf.outFile = outfile.relativeTo(d.conf.outDir)
  1636. if isAbsolute(d.conf.outFile.string):
  1637. d.conf.outFile = splitPath(d.conf.outFile.string)[1].RelativeFile
  1638. proc writeOutput*(d: PDoc, useWarning = false, groupedToc = false) =
  1639. if optGenIndexOnly in d.conf.globalOptions:
  1640. d.conf.outFile = indexFile(d).relativeTo(d.conf.outDir) # just for display
  1641. return
  1642. runAllExamples(d)
  1643. var content = genOutFile(d, groupedToc)
  1644. if optStdout in d.conf.globalOptions:
  1645. write(stdout, content)
  1646. else:
  1647. template outfile: untyped = d.destFile.AbsoluteFile
  1648. #let outfile = getOutFile2(d.conf, shortenDir(d.conf, filename), outExt)
  1649. let dir = outfile.splitFile.dir
  1650. createDir(dir)
  1651. updateOutfile(d, outfile)
  1652. try:
  1653. writeFile(outfile, content)
  1654. except IOError:
  1655. rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile,
  1656. outfile.string)
  1657. if not d.wroteSupportFiles: # nimdoc.css + dochack.js
  1658. let nimr = $d.conf.getPrefixDir()
  1659. case d.target
  1660. of outHtml:
  1661. copyFile(docCss.interp(nimr = nimr), $d.conf.outDir / nimdocOutCss)
  1662. of outLatex:
  1663. copyFile(docCls.interp(nimr = nimr), $d.conf.outDir / nimdocOutCls)
  1664. if optGenIndex in d.conf.globalOptions:
  1665. let docHackJs2 = getDocHacksJs(nimr, nim = getAppFilename())
  1666. copyFile(docHackJs2, $d.conf.outDir / docHackJs2.lastPathPart)
  1667. d.wroteSupportFiles = true
  1668. proc writeOutputJson*(d: PDoc, useWarning = false) =
  1669. runAllExamples(d)
  1670. var modDesc: string = ""
  1671. for desc in d.modDescFinal:
  1672. modDesc &= desc
  1673. let content = %*{"orig": d.filename,
  1674. "nimble": getPackageName(d.conf, d.filename),
  1675. "moduleDescription": modDesc,
  1676. "entries": d.jEntriesFinal}
  1677. if optStdout in d.conf.globalOptions:
  1678. writeLine(stdout, $content)
  1679. else:
  1680. let dir = d.destFile.splitFile.dir
  1681. createDir(dir)
  1682. var f: File = default(File)
  1683. if open(f, d.destFile, fmWrite):
  1684. write(f, $content)
  1685. close(f)
  1686. updateOutfile(d, d.destFile.AbsoluteFile)
  1687. else:
  1688. localError(d.conf, newLineInfo(d.conf, AbsoluteFile d.filename, -1, -1),
  1689. warnUser, "unable to open file \"" & d.destFile &
  1690. "\" for writing")
  1691. proc handleDocOutputOptions*(conf: ConfigRef) =
  1692. if optWholeProject in conf.globalOptions:
  1693. # Backward compatibility with previous versions
  1694. # xxx this is buggy when user provides `nim doc --project -o:sub/bar.html main`,
  1695. # it'd write to `sub/bar.html/main.html`
  1696. conf.outDir = AbsoluteDir(conf.outDir / conf.outFile)
  1697. proc commandDoc*(cache: IdentCache, conf: ConfigRef) =
  1698. ## implementation of deprecated ``doc0`` command (without semantic checking)
  1699. handleDocOutputOptions conf
  1700. var ast = parseFile(conf.projectMainIdx, cache, conf)
  1701. if ast == nil: return
  1702. var d = newDocumentor(conf.projectFull, cache, conf, hasToc = true)
  1703. generateDoc(d, ast, ast, conf)
  1704. finishGenerateDoc(d)
  1705. writeOutput(d)
  1706. generateIndex(d)
  1707. proc commandRstAux(cache: IdentCache, conf: ConfigRef;
  1708. filename: AbsoluteFile, outExt: string,
  1709. preferMarkdown: bool) =
  1710. var filen = addFileExt(filename, "txt")
  1711. var d = newDocumentor(filen, cache, conf, outExt, standaloneDoc = true,
  1712. preferMarkdown = preferMarkdown, hasToc = false)
  1713. try:
  1714. let rst = parseRst(readFile(filen.string),
  1715. line=LineRstInit, column=ColRstInit,
  1716. conf, d.sharedState)
  1717. d.modDescPre = @[ItemFragment(isRst: true, rst: rst)]
  1718. finishGenerateDoc(d)
  1719. writeOutput(d)
  1720. generateIndex(d)
  1721. except ERecoverableError:
  1722. discard "already reported the error"
  1723. proc commandRst2Html*(cache: IdentCache, conf: ConfigRef,
  1724. preferMarkdown=false) =
  1725. commandRstAux(cache, conf, conf.projectFull, HtmlExt, preferMarkdown)
  1726. proc commandRst2TeX*(cache: IdentCache, conf: ConfigRef,
  1727. preferMarkdown=false) =
  1728. commandRstAux(cache, conf, conf.projectFull, TexExt, preferMarkdown)
  1729. proc commandJson*(cache: IdentCache, conf: ConfigRef) =
  1730. ## implementation of a deprecated jsondoc0 command
  1731. var ast = parseFile(conf.projectMainIdx, cache, conf)
  1732. if ast == nil: return
  1733. var d = newDocumentor(conf.projectFull, cache, conf, hasToc = true)
  1734. d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string;
  1735. status: int; content: string) {.gcsafe.} =
  1736. localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1),
  1737. warnUser, "the ':test:' attribute is not supported by this backend")
  1738. generateJson(d, ast, conf)
  1739. finishGenerateDoc(d)
  1740. let json = d.jEntriesFinal
  1741. let content = pretty(json)
  1742. if optStdout in d.conf.globalOptions:
  1743. write(stdout, content)
  1744. else:
  1745. #echo getOutFile(gProjectFull, JsonExt)
  1746. let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt)
  1747. try:
  1748. writeFile(filename, content)
  1749. except IOError:
  1750. rawMessage(conf, errCannotOpenFile, filename.string)
  1751. proc commandTags*(cache: IdentCache, conf: ConfigRef) =
  1752. var ast = parseFile(conf.projectMainIdx, cache, conf)
  1753. if ast == nil: return
  1754. var d = newDocumentor(conf.projectFull, cache, conf, hasToc = true)
  1755. d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string;
  1756. status: int; content: string) {.gcsafe.} =
  1757. localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1),
  1758. warnUser, "the ':test:' attribute is not supported by this backend")
  1759. var
  1760. content = ""
  1761. generateTags(d, ast, content)
  1762. if optStdout in d.conf.globalOptions:
  1763. write(stdout, content)
  1764. else:
  1765. #echo getOutFile(gProjectFull, TagsExt)
  1766. let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt)
  1767. try:
  1768. writeFile(filename, content)
  1769. except IOError:
  1770. rawMessage(conf, errCannotOpenFile, filename.string)
  1771. proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"") =
  1772. if optGenIndexOnly in conf.globalOptions:
  1773. return
  1774. var content = mergeIndexes(dir)
  1775. var outFile = outFile
  1776. if outFile.isEmpty: outFile = theindexFname.RelativeFile.changeFileExt("")
  1777. let filename = getOutFile(conf, outFile, HtmlExt)
  1778. let code = getConfigVar(conf, "doc.file") % [
  1779. "nimdoccss", relLink(conf.outDir, filename, nimdocOutCss.RelativeFile),
  1780. "dochackjs", relLink(conf.outDir, filename, docHackJsFname.RelativeFile),
  1781. "title", "Index",
  1782. "subtitle", "", "tableofcontents", "", "moduledesc", "",
  1783. "date", getDateStr(), "time", getClockStr(),
  1784. "content", content, "author", "", "version", "", "analytics", "", "nimVersion", $NimMajor & "." & $NimMinor & "." & $NimPatch]
  1785. # no analytics because context is not available
  1786. try:
  1787. writeFile(filename, code)
  1788. except IOError:
  1789. rawMessage(conf, errCannotOpenFile, filename.string)
  1790. proc commandBuildIndexJson*(conf: ConfigRef, dir: string, outFile = RelativeFile"") =
  1791. var (modules, symbols, docs) = readIndexDir(dir)
  1792. let documents = toSeq(keys(Table[IndexEntry, seq[IndexEntry]](docs)))
  1793. let body = %*({"documents": documents, "modules": modules, "symbols": symbols})
  1794. var outFile = outFile
  1795. if outFile.isEmpty: outFile = theindexFname.RelativeFile.changeFileExt("")
  1796. let filename = getOutFile(conf, outFile, JsonExt)
  1797. try:
  1798. writeFile(filename, $body)
  1799. except IOError:
  1800. rawMessage(conf, errCannotOpenFile, filename.string)