rstgen.nim 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589
  1. #
  2. #
  3. # Nim's Runtime Library
  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 module implements a generator of HTML/Latex from
  10. ## `reStructuredText`:idx: (see http://docutils.sourceforge.net/rst.html for
  11. ## information on this markup syntax) and is used by the compiler's `docgen
  12. ## tools <docgen.html>`_.
  13. ##
  14. ## You can generate HTML output through the convenience proc ``rstToHtml``,
  15. ## which provided an input string with rst markup returns a string with the
  16. ## generated HTML. The final output is meant to be embedded inside a full
  17. ## document you provide yourself, so it won't contain the usual ``<header>`` or
  18. ## ``<body>`` parts.
  19. ##
  20. ## You can also create a ``RstGenerator`` structure and populate it with the
  21. ## other lower level methods to finally build complete documents. This requires
  22. ## many options and tweaking, but you are not limited to snippets and can
  23. ## generate `LaTeX documents <https://en.wikipedia.org/wiki/LaTeX>`_ too.
  24. ##
  25. ## `Docutils configuration files`_ are not supported. Instead HTML generation
  26. ## can be tweaked by editing file ``config/nimdoc.cfg``.
  27. ##
  28. ## .. _Docutils configuration files: https://docutils.sourceforge.io/docs/user/config.htm
  29. ##
  30. ## There are stylistic difference between how this module renders some elements
  31. ## and how original Python Docutils does:
  32. ##
  33. ## * Backreferences to TOC in section headings are not generated.
  34. ## In HTML each section is also a link that points to the section itself:
  35. ## this is done for user to be able to copy the link into clipboard.
  36. ##
  37. ## * The same goes for footnotes/citations links: they point to themselves.
  38. ## No backreferences are generated since finding all references of a footnote
  39. ## can be done by simply searching for [footnoteName].
  40. import strutils, os, hashes, strtabs, rstast, rst, highlite, tables, sequtils,
  41. algorithm, parseutils, std/strbasics
  42. import ../../std/private/since
  43. const
  44. HtmlExt = "html"
  45. IndexExt* = ".idx"
  46. type
  47. OutputTarget* = enum ## which document type to generate
  48. outHtml, # output is HTML
  49. outLatex # output is Latex
  50. TocEntry = object
  51. n*: PRstNode
  52. refname*, header*: string
  53. MetaEnum* = enum
  54. metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion
  55. EscapeMode = enum # in Latex text inside options [] and URLs is
  56. # escaped slightly differently than in normal text
  57. emText, emOption, emUrl # emText is currently used for code also
  58. RstGenerator* = object of RootObj
  59. target*: OutputTarget
  60. config*: StringTableRef
  61. splitAfter*: int # split too long entries in the TOC
  62. listingCounter*: int
  63. tocPart*: seq[TocEntry]
  64. hasToc*: bool
  65. theIndex: string # Contents of the index file to be dumped at the end.
  66. findFile*: FindFileHandler
  67. msgHandler*: MsgHandler
  68. outDir*: string ## output directory, initialized by docgen.nim
  69. destFile*: string ## output (HTML) file, initialized by docgen.nim
  70. filenames*: RstFileTable
  71. filename*: string ## source Nim or Rst file
  72. meta*: array[MetaEnum, string]
  73. currentSection: string ## \
  74. ## Stores the empty string or the last headline/overline found in the rst
  75. ## document, so it can be used as a prettier name for term index generation.
  76. seenIndexTerms: Table[string, int] ## \
  77. ## Keeps count of same text index terms to generate different identifiers
  78. ## for hyperlinks. See renderIndexTerm proc for details.
  79. id*: int ## A counter useful for generating IDs.
  80. onTestSnippet*: proc (d: var RstGenerator; filename, cmd: string; status: int;
  81. content: string)
  82. escMode*: EscapeMode
  83. PDoc = var RstGenerator ## Alias to type less.
  84. CodeBlockParams = object ## Stores code block params.
  85. numberLines: bool ## True if the renderer has to show line numbers.
  86. startLine: int ## The starting line of the code block, by default 1.
  87. langStr: string ## Input string used to specify the language.
  88. lang: SourceLanguage ## Type of highlighting, by default none.
  89. filename: string
  90. testCmd: string
  91. status: int
  92. proc prettyLink*(file: string): string =
  93. changeFileExt(file, "").replace("_._", "..")
  94. proc init(p: var CodeBlockParams) =
  95. ## Default initialisation of CodeBlockParams to sane values.
  96. p.startLine = 1
  97. p.lang = langNone
  98. p.langStr = ""
  99. proc initRstGenerator*(g: var RstGenerator, target: OutputTarget,
  100. config: StringTableRef, filename: string,
  101. findFile: FindFileHandler = nil,
  102. msgHandler: MsgHandler = nil,
  103. filenames = default(RstFileTable)) =
  104. ## Initializes a ``RstGenerator``.
  105. ##
  106. ## You need to call this before using a ``RstGenerator`` with any other
  107. ## procs in this module. Pass a non ``nil`` ``StringTableRef`` value as
  108. ## `config` with parameters used by the HTML output generator. If you don't
  109. ## know what to use, pass the results of the `defaultConfig()
  110. ## <#defaultConfig>_` proc.
  111. ##
  112. ## The `filename` parameter will be used for error reporting and creating
  113. ## index hyperlinks to the file, but you can pass an empty string here if you
  114. ## are parsing a stream in memory. If `filename` ends with the ``.nim``
  115. ## extension, the title for the document will be set by default to ``Module
  116. ## filename``. This default title can be overridden by the embedded rst, but
  117. ## it helps to prettify the generated index if no title is found.
  118. ##
  119. ## The ``RstParseOptions``, ``FindFileHandler`` and ``MsgHandler`` types
  120. ## are defined in the `packages/docutils/rst module <rst.html>`_.
  121. ## ``options`` selects the behaviour of the rst parser.
  122. ##
  123. ## ``findFile`` is a proc used by the rst ``include`` directive among others.
  124. ## The purpose of this proc is to mangle or filter paths. It receives paths
  125. ## specified in the rst document and has to return a valid path to existing
  126. ## files or the empty string otherwise. If you pass ``nil``, a default proc
  127. ## will be used which given a path returns the input path only if the file
  128. ## exists. One use for this proc is to transform relative paths found in the
  129. ## document to absolute path, useful if the rst file and the resources it
  130. ## references are not in the same directory as the current working directory.
  131. ##
  132. ## The ``msgHandler`` is a proc used for user error reporting. It will be
  133. ## called with the filename, line, col, and type of any error found during
  134. ## parsing. If you pass ``nil``, a default message handler will be used which
  135. ## writes the messages to the standard output.
  136. ##
  137. ## Example:
  138. ##
  139. ## .. code-block:: nim
  140. ##
  141. ## import packages/docutils/rstgen
  142. ##
  143. ## var gen: RstGenerator
  144. ## gen.initRstGenerator(outHtml, defaultConfig(), "filename", {})
  145. g.config = config
  146. g.target = target
  147. g.tocPart = @[]
  148. g.filename = filename
  149. g.filenames = filenames
  150. g.splitAfter = 20
  151. g.theIndex = ""
  152. g.findFile = findFile
  153. g.currentSection = ""
  154. g.id = 0
  155. g.escMode = emText
  156. let fileParts = filename.splitFile
  157. if fileParts.ext == ".nim":
  158. g.currentSection = "Module " & fileParts.name
  159. g.seenIndexTerms = initTable[string, int]()
  160. g.msgHandler = msgHandler
  161. let s = config.getOrDefault"split.item.toc"
  162. if s != "": g.splitAfter = parseInt(s)
  163. for i in low(g.meta)..high(g.meta): g.meta[i] = ""
  164. proc writeIndexFile*(g: var RstGenerator, outfile: string) =
  165. ## Writes the current index buffer to the specified output file.
  166. ##
  167. ## You previously need to add entries to the index with the `setIndexTerm()
  168. ## <#setIndexTerm,RstGenerator,string,string,string,string,string>`_ proc.
  169. ## If the index is empty the file won't be created.
  170. if g.theIndex.len > 0: writeFile(outfile, g.theIndex)
  171. proc addHtmlChar(dest: var string, c: char) =
  172. # Escapes HTML characters. Note that single quote ' is not escaped as
  173. # &apos; -- unlike XML (for standards pre HTML5 it was even forbidden).
  174. case c
  175. of '&': add(dest, "&amp;")
  176. of '<': add(dest, "&lt;")
  177. of '>': add(dest, "&gt;")
  178. of '\"': add(dest, "&quot;")
  179. else: add(dest, c)
  180. proc addTexChar(dest: var string, c: char, escMode: EscapeMode) =
  181. ## Escapes 10 special Latex characters and sometimes ` and [, ].
  182. ## TODO: @ is always a normal symbol (besides the header), am I wrong?
  183. ## All escapes that need to work in text and code blocks (`emText` mode)
  184. ## should start from \ (to be compatible with fancyvrb/fvextra).
  185. case c
  186. of '_', '$', '&', '#', '%': add(dest, "\\" & c)
  187. # \~ and \^ have a special meaning unless they are followed by {}
  188. of '~', '^': add(dest, "\\" & c & "{}")
  189. # Latex loves to substitute ` to opening quote, even in texttt mode!
  190. of '`': add(dest, "\\textasciigrave{}")
  191. # add {} to avoid gobbling up space by \textbackslash
  192. of '\\': add(dest, "\\textbackslash{}")
  193. # Using { and } in URL in Latex: https://tex.stackexchange.com/a/469175
  194. of '{':
  195. add(dest, if escMode == emUrl: "\\%7B" else: "\\{")
  196. of '}':
  197. add(dest, if escMode == emUrl: "\\%7D" else: "\\}")
  198. of ']':
  199. # escape ] inside an optional argument in e.g. \section[static[T]]{..
  200. add(dest, if escMode == emOption: "\\text{]}" else: "]")
  201. else: add(dest, c)
  202. proc escChar*(target: OutputTarget, dest: var string,
  203. c: char, escMode: EscapeMode) {.inline.} =
  204. case target
  205. of outHtml: addHtmlChar(dest, c)
  206. of outLatex: addTexChar(dest, c, escMode)
  207. proc addSplitter(target: OutputTarget; dest: var string) {.inline.} =
  208. case target
  209. of outHtml: add(dest, "<wbr />")
  210. of outLatex: add(dest, "\\-")
  211. proc nextSplitPoint*(s: string, start: int): int =
  212. result = start
  213. while result < len(s) + 0:
  214. case s[result]
  215. of '_': return
  216. of 'a'..'z':
  217. if result + 1 < len(s) + 0:
  218. if s[result + 1] in {'A'..'Z'}: return
  219. else: discard
  220. inc(result)
  221. dec(result) # last valid index
  222. proc esc*(target: OutputTarget, s: string, splitAfter = -1, escMode = emText): string =
  223. ## Escapes the HTML.
  224. result = ""
  225. if splitAfter >= 0:
  226. var partLen = 0
  227. var j = 0
  228. while j < len(s):
  229. var k = nextSplitPoint(s, j)
  230. #if (splitter != " ") or (partLen + k - j + 1 > splitAfter):
  231. partLen = 0
  232. addSplitter(target, result)
  233. for i in countup(j, k): escChar(target, result, s[i], escMode)
  234. inc(partLen, k - j + 1)
  235. j = k + 1
  236. else:
  237. for i in countup(0, len(s) - 1): escChar(target, result, s[i], escMode)
  238. proc disp(target: OutputTarget, xml, tex: string): string =
  239. if target != outLatex: result = xml
  240. else: result = tex
  241. proc dispF(target: OutputTarget, xml, tex: string,
  242. args: varargs[string]): string =
  243. if target != outLatex: result = xml % args
  244. else: result = tex % args
  245. proc dispA(target: OutputTarget, dest: var string,
  246. xml, tex: string, args: varargs[string]) =
  247. if target != outLatex: addf(dest, xml, args)
  248. else: addf(dest, tex, args)
  249. proc `or`(x, y: string): string {.inline.} =
  250. result = if x.len == 0: y else: x
  251. proc renderRstToOut*(d: var RstGenerator, n: PRstNode, result: var string)
  252. ## Writes into ``result`` the rst ast ``n`` using the ``d`` configuration.
  253. ##
  254. ## Before using this proc you need to initialise a ``RstGenerator`` with
  255. ## ``initRstGenerator`` and parse a rst file with ``rstParse`` from the
  256. ## `packages/docutils/rst module <rst.html>`_. Example:
  257. ##
  258. ## .. code-block:: nim
  259. ##
  260. ## # ...configure gen and rst vars...
  261. ## var generatedHtml = ""
  262. ## renderRstToOut(gen, rst, generatedHtml)
  263. ## echo generatedHtml
  264. proc renderAux(d: PDoc, n: PRstNode, result: var string) =
  265. for i in countup(0, len(n)-1): renderRstToOut(d, n.sons[i], result)
  266. template idS(txt: string): string =
  267. if txt == "": ""
  268. else:
  269. case d.target
  270. of outHtml:
  271. " id=\"" & txt & "\""
  272. of outLatex:
  273. "\\label{" & txt & "}\\hypertarget{" & txt & "}{}"
  274. # we add \label for page number references via \pageref, while
  275. # \hypertarget is for clickable links via \hyperlink.
  276. proc renderAux(d: PDoc, n: PRstNode, html, tex: string, result: var string) =
  277. # formats sons of `n` as substitution variable $1 inside strings `html` and
  278. # `tex`, internal target (anchor) is provided as substitute $2.
  279. var tmp = ""
  280. for i in countup(0, len(n)-1): renderRstToOut(d, n.sons[i], tmp)
  281. case d.target
  282. of outHtml: result.addf(html, [tmp, n.anchor.idS])
  283. of outLatex: result.addf(tex, [tmp, n.anchor.idS])
  284. # ---------------- index handling --------------------------------------------
  285. proc quoteIndexColumn(text: string): string =
  286. ## Returns a safe version of `text` for serialization to the ``.idx`` file.
  287. ##
  288. ## The returned version can be put without worries in a line based tab
  289. ## separated column text file. The following character sequence replacements
  290. ## will be performed for that goal:
  291. ##
  292. ## * ``"\\"`` => ``"\\\\"``
  293. ## * ``"\n"`` => ``"\\n"``
  294. ## * ``"\t"`` => ``"\\t"``
  295. result = newStringOfCap(text.len + 3)
  296. for c in text:
  297. case c
  298. of '\\': result.add "\\"
  299. of '\L': result.add "\\n"
  300. of '\C': discard
  301. of '\t': result.add "\\t"
  302. else: result.add c
  303. proc unquoteIndexColumn(text: string): string =
  304. ## Returns the unquoted version generated by ``quoteIndexColumn``.
  305. result = text.multiReplace(("\\t", "\t"), ("\\n", "\n"), ("\\\\", "\\"))
  306. proc setIndexTerm*(d: var RstGenerator, htmlFile, id, term: string,
  307. linkTitle, linkDesc = "") =
  308. ## Adds a `term` to the index using the specified hyperlink identifier.
  309. ##
  310. ## A new entry will be added to the index using the format
  311. ## ``term<tab>file#id``. The file part will come from the `htmlFile`
  312. ## parameter.
  313. ##
  314. ## The `id` will be appended with a hash character only if its length is not
  315. ## zero, otherwise no specific anchor will be generated. In general you
  316. ## should only pass an empty `id` value for the title of standalone rst
  317. ## documents (they are special for the `mergeIndexes() <#mergeIndexes,string>`_
  318. ## proc, see `Index (idx) file format <docgen.html#index-idx-file-format>`_
  319. ## for more information). Unlike other index terms, title entries are
  320. ## inserted at the beginning of the accumulated buffer to maintain a logical
  321. ## order of entries.
  322. ##
  323. ## If `linkTitle` or `linkDesc` are not the empty string, two additional
  324. ## columns with their contents will be added.
  325. ##
  326. ## The index won't be written to disk unless you call `writeIndexFile()
  327. ## <#writeIndexFile,RstGenerator,string>`_. The purpose of the index is
  328. ## documented in the `docgen tools guide
  329. ## <docgen.html#related-options-index-switch>`_.
  330. var
  331. entry = term
  332. isTitle = false
  333. entry.add('\t')
  334. entry.add(htmlFile)
  335. if id.len > 0:
  336. entry.add('#')
  337. entry.add(id)
  338. else:
  339. isTitle = true
  340. if linkTitle.len > 0 or linkDesc.len > 0:
  341. entry.add('\t' & linkTitle.quoteIndexColumn)
  342. entry.add('\t' & linkDesc.quoteIndexColumn)
  343. entry.add("\n")
  344. if isTitle: d.theIndex.insert(entry)
  345. else: d.theIndex.add(entry)
  346. proc hash(n: PRstNode): int =
  347. if n.kind == rnLeaf:
  348. result = hash(n.text)
  349. elif n.len > 0:
  350. result = hash(n.sons[0])
  351. for i in 1 ..< len(n):
  352. result = result !& hash(n.sons[i])
  353. result = !$result
  354. proc renderIndexTerm*(d: PDoc, n: PRstNode, result: var string) =
  355. ## Renders the string decorated within \`foobar\`\:idx\: markers.
  356. ##
  357. ## Additionally adds the enclosed text to the index as a term. Since we are
  358. ## interested in different instances of the same term to have different
  359. ## entries, a table is used to keep track of the amount of times a term has
  360. ## previously appeared to give a different identifier value for each.
  361. let refname = n.rstnodeToRefname
  362. if d.seenIndexTerms.hasKey(refname):
  363. d.seenIndexTerms[refname] = d.seenIndexTerms.getOrDefault(refname) + 1
  364. else:
  365. d.seenIndexTerms[refname] = 1
  366. let id = refname & '_' & $d.seenIndexTerms.getOrDefault(refname)
  367. var term = ""
  368. renderAux(d, n, term)
  369. setIndexTerm(d, changeFileExt(extractFilename(d.filename), HtmlExt), id, term, d.currentSection)
  370. dispA(d.target, result, "<span id=\"$1\">$2</span>", "$2\\label{$1}",
  371. [id, term])
  372. type
  373. IndexEntry = object
  374. keyword: string
  375. link: string
  376. linkTitle: string ## contains a prettier text for the href
  377. linkDesc: string ## the title attribute of the final href
  378. IndexedDocs = Table[IndexEntry, seq[IndexEntry]] ## \
  379. ## Contains the index sequences for doc types.
  380. ##
  381. ## The key is a *fake* IndexEntry which will contain the title of the
  382. ## document in the `keyword` field and `link` will contain the html
  383. ## filename for the document. `linkTitle` and `linkDesc` will be empty.
  384. ##
  385. ## The value indexed by this IndexEntry is a sequence with the real index
  386. ## entries found in the ``.idx`` file.
  387. proc cmp(a, b: IndexEntry): int =
  388. ## Sorts two ``IndexEntry`` first by `keyword` field, then by `link`.
  389. result = cmpIgnoreStyle(a.keyword, b.keyword)
  390. if result == 0:
  391. result = cmpIgnoreStyle(a.link, b.link)
  392. proc hash(x: IndexEntry): Hash =
  393. ## Returns the hash for the combined fields of the type.
  394. ##
  395. ## The hash is computed as the chained hash of the individual string hashes.
  396. result = x.keyword.hash !& x.link.hash
  397. result = result !& x.linkTitle.hash
  398. result = result !& x.linkDesc.hash
  399. result = !$result
  400. when defined(gcDestructors):
  401. template `<-`(a, b: var IndexEntry) = a = move(b)
  402. else:
  403. proc `<-`(a: var IndexEntry, b: IndexEntry) =
  404. shallowCopy a.keyword, b.keyword
  405. shallowCopy a.link, b.link
  406. shallowCopy a.linkTitle, b.linkTitle
  407. shallowCopy a.linkDesc, b.linkDesc
  408. proc sortIndex(a: var openArray[IndexEntry]) =
  409. # we use shellsort here; fast and simple
  410. let n = len(a)
  411. var h = 1
  412. while true:
  413. h = 3 * h + 1
  414. if h > n: break
  415. while true:
  416. h = h div 3
  417. for i in countup(h, n - 1):
  418. var v: IndexEntry
  419. v <- a[i]
  420. var j = i
  421. while cmp(a[j-h], v) >= 0:
  422. a[j] <- a[j-h]
  423. j = j-h
  424. if j < h: break
  425. a[j] <- v
  426. if h == 1: break
  427. proc escapeLink(s: string): string =
  428. ## This proc is mostly copied from uri/encodeUrl except that
  429. ## these chars are also left unencoded: '#', '/'.
  430. result = newStringOfCap(s.len + s.len shr 2)
  431. for c in items(s):
  432. case c
  433. of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~': # same as that in uri/encodeUrl
  434. add(result, c)
  435. of '#', '/': # example.com/foo/#bar (don't escape the '/' and '#' in such links)
  436. add(result, c)
  437. else:
  438. add(result, "%")
  439. add(result, toHex(ord(c), 2))
  440. proc generateSymbolIndex(symbols: seq[IndexEntry]): string =
  441. result = "<dl>"
  442. var i = 0
  443. while i < symbols.len:
  444. let keyword = symbols[i].keyword
  445. let cleanedKeyword = keyword.escapeLink
  446. result.addf("<dt><a name=\"$2\" href=\"#$2\"><span>$1:</span></a></dt><dd><ul class=\"simple\">\n",
  447. [keyword, cleanedKeyword])
  448. var j = i
  449. while j < symbols.len and keyword == symbols[j].keyword:
  450. let
  451. url = symbols[j].link.escapeLink
  452. text = if symbols[j].linkTitle.len > 0: symbols[j].linkTitle else: url
  453. desc = if symbols[j].linkDesc.len > 0: symbols[j].linkDesc else: ""
  454. if desc.len > 0:
  455. result.addf("""<li><a class="reference external"
  456. title="$3" data-doc-search-tag="$2" href="$1">$2</a></li>
  457. """, [url, text, desc])
  458. else:
  459. result.addf("""<li><a class="reference external"
  460. data-doc-search-tag="$2" href="$1">$2</a></li>
  461. """, [url, text])
  462. inc j
  463. result.add("</ul></dd>\n")
  464. i = j
  465. result.add("</dl>")
  466. proc isDocumentationTitle(hyperlink: string): bool =
  467. ## Returns true if the hyperlink is actually a documentation title.
  468. ##
  469. ## Documentation titles lack the hash. See `mergeIndexes()
  470. ## <#mergeIndexes,string>`_ for a more detailed explanation.
  471. result = hyperlink.find('#') < 0
  472. proc stripTocLevel(s: string): tuple[level: int, text: string] =
  473. ## Returns the *level* of the toc along with the text without it.
  474. for c in 0 ..< s.len:
  475. result.level = c
  476. if s[c] != ' ': break
  477. result.text = s[result.level ..< s.len]
  478. proc indentToLevel(level: var int, newLevel: int): string =
  479. ## Returns the sequence of <ul>|</ul> characters to switch to `newLevel`.
  480. ##
  481. ## The amount of lists added/removed will be based on the `level` variable,
  482. ## which will be reset to `newLevel` at the end of the proc.
  483. result = ""
  484. if level == newLevel:
  485. return
  486. if newLevel > level:
  487. result = repeat("<li><ul>", newLevel - level)
  488. else:
  489. result = repeat("</ul></li>", level - newLevel)
  490. level = newLevel
  491. proc generateDocumentationToc(entries: seq[IndexEntry]): string =
  492. ## Returns the sequence of index entries in an HTML hierarchical list.
  493. result = ""
  494. # Build a list of levels and extracted titles to make processing easier.
  495. var
  496. titleRef: string
  497. titleTag: string
  498. levels: seq[tuple[level: int, text: string]]
  499. L = 0
  500. level = 1
  501. levels.newSeq(entries.len)
  502. for entry in entries:
  503. let (rawLevel, rawText) = stripTocLevel(entry.linkTitle or entry.keyword)
  504. if rawLevel < 1:
  505. # This is a normal symbol, push it *inside* one level from the last one.
  506. levels[L].level = level + 1
  507. # Also, ignore the linkTitle and use directly the keyword.
  508. levels[L].text = entry.keyword
  509. else:
  510. # The level did change, update the level indicator.
  511. level = rawLevel
  512. levels[L].level = rawLevel
  513. levels[L].text = rawText
  514. inc L
  515. # Now generate hierarchical lists based on the precalculated levels.
  516. result = "<ul>\n"
  517. level = 1
  518. L = 0
  519. while L < entries.len:
  520. let link = entries[L].link
  521. if link.isDocumentationTitle:
  522. titleRef = link
  523. titleTag = levels[L].text
  524. else:
  525. result.add(level.indentToLevel(levels[L].level))
  526. result.addf("""<li><a class="reference" data-doc-search-tag="$1: $2" href="$3">
  527. $3</a></li>
  528. """, [titleTag, levels[L].text, link, levels[L].text])
  529. inc L
  530. result.add(level.indentToLevel(1) & "</ul>\n")
  531. proc generateDocumentationIndex(docs: IndexedDocs): string =
  532. ## Returns all the documentation TOCs in an HTML hierarchical list.
  533. result = ""
  534. # Sort the titles to generate their toc in alphabetical order.
  535. var titles = toSeq(keys[IndexEntry, seq[IndexEntry]](docs))
  536. sort(titles, cmp)
  537. for title in titles:
  538. let tocList = generateDocumentationToc(docs.getOrDefault(title))
  539. result.add("<ul><li><a href=\"" &
  540. title.link & "\">" & title.keyword & "</a>\n" & tocList & "</li></ul>\n")
  541. proc generateDocumentationJumps(docs: IndexedDocs): string =
  542. ## Returns a plain list of hyperlinks to documentation TOCs in HTML.
  543. result = "Documents: "
  544. # Sort the titles to generate their toc in alphabetical order.
  545. var titles = toSeq(keys[IndexEntry, seq[IndexEntry]](docs))
  546. sort(titles, cmp)
  547. var chunks: seq[string] = @[]
  548. for title in titles:
  549. chunks.add("<a href=\"" & title.link & "\">" & title.keyword & "</a>")
  550. result.add(chunks.join(", ") & ".<br/>")
  551. proc generateModuleJumps(modules: seq[string]): string =
  552. ## Returns a plain list of hyperlinks to the list of modules.
  553. result = "Modules: "
  554. var chunks: seq[string] = @[]
  555. for name in modules:
  556. chunks.add("<a href=\"$1.html\">$2</a>" % [name, name.prettyLink])
  557. result.add(chunks.join(", ") & ".<br/>")
  558. proc readIndexDir(dir: string):
  559. tuple[modules: seq[string], symbols: seq[IndexEntry], docs: IndexedDocs] =
  560. ## Walks `dir` reading ``.idx`` files converting them in IndexEntry items.
  561. ##
  562. ## Returns the list of found module names, the list of free symbol entries
  563. ## and the different documentation indexes. The list of modules is sorted.
  564. ## See the documentation of ``mergeIndexes`` for details.
  565. result.modules = @[]
  566. result.docs = initTable[IndexEntry, seq[IndexEntry]](32)
  567. newSeq(result.symbols, 15_000)
  568. setLen(result.symbols, 0)
  569. var L = 0
  570. # Scan index files and build the list of symbols.
  571. for path in walkDirRec(dir):
  572. if path.endsWith(IndexExt):
  573. var
  574. fileEntries: seq[IndexEntry]
  575. title: IndexEntry
  576. f = 0
  577. newSeq(fileEntries, 500)
  578. setLen(fileEntries, 0)
  579. for line in lines(path):
  580. let s = line.find('\t')
  581. if s < 0: continue
  582. setLen(fileEntries, f+1)
  583. fileEntries[f].keyword = line.substr(0, s-1)
  584. fileEntries[f].link = line.substr(s+1)
  585. # See if we detect a title, a link without a `#foobar` trailing part.
  586. if title.keyword.len == 0 and fileEntries[f].link.isDocumentationTitle:
  587. title.keyword = fileEntries[f].keyword
  588. title.link = fileEntries[f].link
  589. if fileEntries[f].link.find('\t') > 0:
  590. let extraCols = fileEntries[f].link.split('\t')
  591. fileEntries[f].link = extraCols[0]
  592. assert extraCols.len == 3
  593. fileEntries[f].linkTitle = extraCols[1].unquoteIndexColumn
  594. fileEntries[f].linkDesc = extraCols[2].unquoteIndexColumn
  595. else:
  596. fileEntries[f].linkTitle = ""
  597. fileEntries[f].linkDesc = ""
  598. inc f
  599. # Depending on type add this to the list of symbols or table of APIs.
  600. if title.keyword.len == 0:
  601. for i in 0 ..< f:
  602. # Don't add to symbols TOC entries (they start with a whitespace).
  603. let toc = fileEntries[i].linkTitle
  604. if toc.len > 0 and toc[0] == ' ':
  605. continue
  606. # Ok, non TOC entry, add it.
  607. setLen(result.symbols, L + 1)
  608. result.symbols[L] = fileEntries[i]
  609. inc L
  610. if fileEntries.len > 0:
  611. var x = fileEntries[0].link
  612. let i = find(x, '#')
  613. if i > 0:
  614. x.setLen(i)
  615. if i != 0:
  616. # don't add entries starting with '#'
  617. result.modules.add(x.changeFileExt(""))
  618. else:
  619. # Generate the symbolic anchor for index quickjumps.
  620. title.linkTitle = "doc_toc_" & $result.docs.len
  621. result.docs[title] = fileEntries
  622. sort(result.modules, system.cmp)
  623. proc mergeIndexes*(dir: string): string =
  624. ## Merges all index files in `dir` and returns the generated index as HTML.
  625. ##
  626. ## This proc will first scan `dir` for index files with the ``.idx``
  627. ## extension previously created by commands like ``nim doc|rst2html``
  628. ## which use the ``--index:on`` switch. These index files are the result of
  629. ## calls to `setIndexTerm()
  630. ## <#setIndexTerm,RstGenerator,string,string,string,string,string>`_
  631. ## and `writeIndexFile() <#writeIndexFile,RstGenerator,string>`_, so they are
  632. ## simple tab separated files.
  633. ##
  634. ## As convention this proc will split index files into two categories:
  635. ## documentation and API. API indices will be all joined together into a
  636. ## single big sorted index, making the bulk of the final index. This is good
  637. ## for API documentation because many symbols are repeated in different
  638. ## modules. On the other hand, documentation indices are essentially table of
  639. ## contents plus a few special markers. These documents will be rendered in a
  640. ## separate section which tries to maintain the order and hierarchy of the
  641. ## symbols in the index file.
  642. ##
  643. ## To differentiate between a documentation and API file a convention is
  644. ## used: indices which contain one entry without the HTML hash character (#)
  645. ## will be considered `documentation`, since this hash-less entry is the
  646. ## explicit title of the document. Indices without this explicit entry will
  647. ## be considered `generated API` extracted out of a source ``.nim`` file.
  648. ##
  649. ## Returns the merged and sorted indices into a single HTML block which can
  650. ## be further embedded into nimdoc templates.
  651. var (modules, symbols, docs) = readIndexDir(dir)
  652. result = ""
  653. # Generate a quick jump list of documents.
  654. if docs.len > 0:
  655. result.add(generateDocumentationJumps(docs))
  656. result.add("<p />")
  657. # Generate hyperlinks to all the linked modules.
  658. if modules.len > 0:
  659. result.add(generateModuleJumps(modules))
  660. result.add("<p />")
  661. when false:
  662. # Generate the HTML block with API documents.
  663. if docs.len > 0:
  664. result.add("<h2>Documentation files</h2>\n")
  665. result.add(generateDocumentationIndex(docs))
  666. # Generate the HTML block with symbols.
  667. if symbols.len > 0:
  668. sortIndex(symbols)
  669. result.add("<h2>API symbols</h2>\n")
  670. result.add(generateSymbolIndex(symbols))
  671. # ----------------------------------------------------------------------------
  672. proc stripTocHtml(s: string): string =
  673. ## Ugly quick hack to remove HTML tags from TOC titles.
  674. ##
  675. ## A TocEntry.header field already contains rendered HTML tags. Instead of
  676. ## implementing a proper version of renderRstToOut() which recursively
  677. ## renders an rst tree to plain text, we simply remove text found between
  678. ## angled brackets. Given the limited possibilities of rst inside TOC titles
  679. ## this should be enough.
  680. result = s
  681. var first = result.find('<')
  682. while first >= 0:
  683. let last = result.find('>', first)
  684. if last < 0:
  685. # Abort, since we didn't found a closing angled bracket.
  686. return
  687. result.delete(first, last)
  688. first = result.find('<', first)
  689. proc renderHeadline(d: PDoc, n: PRstNode, result: var string) =
  690. var tmp = ""
  691. for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp)
  692. d.currentSection = tmp
  693. # Find the last higher level section for unique reference name
  694. var sectionPrefix = ""
  695. for i in countdown(d.tocPart.high, 0):
  696. let n2 = d.tocPart[i].n
  697. if n2.level < n.level:
  698. sectionPrefix = rstnodeToRefname(n2) & "-"
  699. break
  700. var refname = sectionPrefix & rstnodeToRefname(n)
  701. var tocName = esc(d.target, renderRstToText(n), escMode = emOption)
  702. # for Latex: simple text without commands that may break TOC/hyperref
  703. if d.hasToc:
  704. var length = len(d.tocPart)
  705. setLen(d.tocPart, length + 1)
  706. d.tocPart[length].refname = refname
  707. d.tocPart[length].n = n
  708. d.tocPart[length].header = tmp
  709. dispA(d.target, result, "\n<h$1><a class=\"toc-backref\"" &
  710. "$2 href=\"#$5\">$3</a></h$1>", "\\rsth$4[$6]{$3}$2\n",
  711. [$n.level, refname.idS, tmp,
  712. $chr(n.level - 1 + ord('A')), refname, tocName])
  713. else:
  714. dispA(d.target, result, "\n<h$1$2>$3</h$1>",
  715. "\\rsth$4[$5]{$3}$2\n", [
  716. $n.level, refname.idS, tmp,
  717. $chr(n.level - 1 + ord('A')), tocName])
  718. # Generate index entry using spaces to indicate TOC level for the output HTML.
  719. assert n.level >= 0
  720. let
  721. htmlFileRelPath = if d.outDir.len == 0:
  722. # /foo/bar/zoo.nim -> zoo.html
  723. changeFileExt(extractFilename(d.filename), HtmlExt)
  724. else: # d is initialized in docgen.nim
  725. # outDir = /foo -\
  726. # destFile = /foo/bar/zoo.html -|-> bar/zoo.html
  727. d.destFile.relativePath(d.outDir, '/')
  728. setIndexTerm(d, htmlFileRelPath, refname, tmp.stripTocHtml,
  729. spaces(max(0, n.level)) & tmp)
  730. proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
  731. if n.level == 0 and d.meta[metaTitle].len == 0:
  732. for i in countup(0, len(n)-1):
  733. renderRstToOut(d, n.sons[i], d.meta[metaTitle])
  734. d.currentSection = d.meta[metaTitle]
  735. elif n.level == 0 and d.meta[metaSubtitle].len == 0:
  736. for i in countup(0, len(n)-1):
  737. renderRstToOut(d, n.sons[i], d.meta[metaSubtitle])
  738. d.currentSection = d.meta[metaSubtitle]
  739. else:
  740. var tmp = ""
  741. for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp)
  742. d.currentSection = tmp
  743. var tocName = esc(d.target, renderRstToText(n), escMode=emOption)
  744. dispA(d.target, result, "<h$1$2><center>$3</center></h$1>",
  745. "\\rstov$4[$5]{$3}$2\n", [$n.level,
  746. rstnodeToRefname(n).idS, tmp, $chr(n.level - 1 + ord('A')), tocName])
  747. proc renderTocEntry(d: PDoc, e: TocEntry, result: var string) =
  748. dispA(d.target, result,
  749. "<li><a class=\"reference\" id=\"$1_toc\" href=\"#$1\">$2</a></li>\n",
  750. "\\item\\label{$1_toc} $2\\ref{$1}\n", [e.refname, e.header])
  751. proc renderTocEntries*(d: var RstGenerator, j: var int, lvl: int,
  752. result: var string) =
  753. var tmp = ""
  754. while j <= high(d.tocPart):
  755. var a = abs(d.tocPart[j].n.level)
  756. if a == lvl:
  757. renderTocEntry(d, d.tocPart[j], tmp)
  758. inc(j)
  759. elif a > lvl:
  760. renderTocEntries(d, j, a, tmp)
  761. else:
  762. break
  763. if lvl > 1:
  764. dispA(d.target, result, "<ul class=\"simple\">$1</ul>",
  765. "\\begin{enumerate}$1\\end{enumerate}", [tmp])
  766. else:
  767. result.add(tmp)
  768. proc renderImage(d: PDoc, n: PRstNode, result: var string) =
  769. let
  770. arg = getArgument(n)
  771. var
  772. options = ""
  773. var s = esc(d.target, getFieldValue(n, "scale").strip())
  774. if s.len > 0:
  775. dispA(d.target, options, " scale=\"$1\"", " scale=$1", [s])
  776. s = esc(d.target, getFieldValue(n, "height").strip())
  777. if s.len > 0:
  778. dispA(d.target, options, " height=\"$1\"", " height=$1", [s])
  779. s = esc(d.target, getFieldValue(n, "width").strip())
  780. if s.len > 0:
  781. dispA(d.target, options, " width=\"$1\"", " width=$1", [s])
  782. s = esc(d.target, getFieldValue(n, "alt").strip())
  783. if s.len > 0:
  784. dispA(d.target, options, " alt=\"$1\"", "", [s])
  785. s = esc(d.target, getFieldValue(n, "align").strip())
  786. if s.len > 0:
  787. dispA(d.target, options, " align=\"$1\"", "", [s])
  788. if options.len > 0: options = dispF(d.target, "$1", "[$1]", [options])
  789. var htmlOut = ""
  790. if arg.endsWith(".mp4") or arg.endsWith(".ogg") or
  791. arg.endsWith(".webm"):
  792. htmlOut = """
  793. <video$3 src="$1"$2 autoPlay='true' loop='true' muted='true'>
  794. Sorry, your browser doesn't support embedded videos
  795. </video>
  796. """
  797. else:
  798. htmlOut = "<img$3 src=\"$1\"$2/>"
  799. # support for `:target:` links for images:
  800. var target = esc(d.target, getFieldValue(n, "target").strip(), escMode=emUrl)
  801. if target.len > 0:
  802. # `htmlOut` needs to be of the following format for link to work for images:
  803. # <a class="reference external" href="target"><img src=\"$1\"$2/></a>
  804. var htmlOutWithLink = ""
  805. dispA(d.target, htmlOutWithLink,
  806. "<a class=\"reference external\" href=\"$2\">$1</a>",
  807. "\\href{$2}{$1}", [htmlOut, target])
  808. htmlOut = htmlOutWithLink
  809. dispA(d.target, result, htmlOut, "$3\\includegraphics$2{$1}",
  810. [esc(d.target, arg), options, n.anchor.idS])
  811. if len(n) >= 3: renderRstToOut(d, n.sons[2], result)
  812. proc renderSmiley(d: PDoc, n: PRstNode, result: var string) =
  813. dispA(d.target, result,
  814. """<img src="$1" width="15"
  815. height="17" hspace="2" vspace="2" class="smiley" />""",
  816. "\\includegraphics{$1}",
  817. [d.config.getOrDefault"doc.smiley_format" % n.text])
  818. proc getField1Int(d: PDoc, n: PRstNode, fieldName: string): int =
  819. template err(msg: string) =
  820. rstMessage(d.filenames, d.msgHandler, n.info, meInvalidField, msg)
  821. let value = n.getFieldValue
  822. var number: int
  823. let nChars = parseInt(value, number)
  824. if nChars == 0:
  825. if value.len == 0:
  826. err("field $1 requires an argument" % [fieldName])
  827. else:
  828. err("field $1 requires an integer, but '$2' was given" %
  829. [fieldName, value])
  830. elif nChars < value.len:
  831. err("extra arguments were given to $1: '$2'" %
  832. [fieldName, value[nChars..^1]])
  833. else:
  834. result = number
  835. proc parseCodeBlockField(d: PDoc, n: PRstNode, params: var CodeBlockParams) =
  836. ## Parses useful fields which can appear before a code block.
  837. ##
  838. ## This supports the special ``default-language`` internal string generated
  839. ## by the ``rst`` module to communicate a specific default language.
  840. case n.getArgument.toLowerAscii
  841. of "number-lines":
  842. params.numberLines = true
  843. # See if the field has a parameter specifying a different line than 1.
  844. params.startLine = getField1Int(d, n, "number-lines")
  845. of "file", "filename":
  846. # The ``file`` option is a Nim extension to the official spec, it acts
  847. # like it would for other directives like ``raw`` or ``cvs-table``. This
  848. # field is dealt with in ``rst.nim`` which replaces the existing block with
  849. # the referenced file, so we only need to ignore it here to avoid incorrect
  850. # warning messages.
  851. params.filename = n.getFieldValue.strip
  852. of "test":
  853. params.testCmd = n.getFieldValue.strip
  854. if params.testCmd.len == 0:
  855. # factor with D20210224T221756. Note that `$docCmd` should appear before `$file`
  856. # but after all other options, but currently `$options` merges both options and `$file` so it's tricky.
  857. params.testCmd = "$nim r --backend:$backend --lib:$libpath $docCmd $options"
  858. else:
  859. # consider whether `$docCmd` should be appended here too
  860. params.testCmd = unescape(params.testCmd)
  861. of "status", "exitcode":
  862. params.status = getField1Int(d, n, n.getArgument)
  863. of "default-language":
  864. params.langStr = n.getFieldValue.strip
  865. params.lang = params.langStr.getSourceLanguage
  866. else:
  867. rstMessage(d.filenames, d.msgHandler, n.info, mwUnsupportedField,
  868. n.getArgument)
  869. proc parseCodeBlockParams(d: PDoc, n: PRstNode): CodeBlockParams =
  870. ## Iterates over all code block fields and returns processed params.
  871. ##
  872. ## Also processes the argument of the directive as the default language. This
  873. ## is done last so as to override any internal communication field variables.
  874. result.init
  875. if n.isNil:
  876. return
  877. assert n.kind in {rnCodeBlock, rnInlineCode}
  878. # Parse the field list for rendering parameters if there are any.
  879. if not n.sons[1].isNil:
  880. for son in n.sons[1].sons: d.parseCodeBlockField(son, result)
  881. # Parse the argument and override the language.
  882. result.langStr = strip(getArgument(n))
  883. if result.langStr != "":
  884. result.lang = getSourceLanguage(result.langStr)
  885. proc buildLinesHtmlTable(d: PDoc; params: CodeBlockParams, code: string,
  886. idStr: string):
  887. tuple[beginTable, endTable: string] =
  888. ## Returns the necessary tags to start/end a code block in HTML.
  889. ##
  890. ## If the numberLines has not been used, the tags will default to a simple
  891. ## <pre> pair. Otherwise it will build a table and insert an initial column
  892. ## with all the line numbers, which requires you to pass the `code` to detect
  893. ## how many lines have to be generated (and starting at which point!).
  894. inc d.listingCounter
  895. let id = $d.listingCounter
  896. if not params.numberLines:
  897. result = (d.config.getOrDefault"doc.listing_start" %
  898. [id, sourceLanguageToStr[params.lang], idStr],
  899. d.config.getOrDefault"doc.listing_end" % id)
  900. return
  901. var codeLines = code.strip.countLines
  902. assert codeLines > 0
  903. result.beginTable = """<table$1 class="line-nums-table">""" % [idStr] &
  904. """<tbody><tr><td class="blob-line-nums"><pre class="line-nums">"""
  905. var line = params.startLine
  906. while codeLines > 0:
  907. result.beginTable.add($line & "\n")
  908. line.inc
  909. codeLines.dec
  910. result.beginTable.add("</pre></td><td>" & (
  911. d.config.getOrDefault"doc.listing_start" %
  912. [id, sourceLanguageToStr[params.lang], idStr]))
  913. result.endTable = (d.config.getOrDefault"doc.listing_end" % id) &
  914. "</td></tr></tbody></table>" & (
  915. d.config.getOrDefault"doc.listing_button" % id)
  916. proc renderCodeLang*(result: var string, lang: SourceLanguage, code: string,
  917. target: OutputTarget) =
  918. var g: GeneralTokenizer
  919. initGeneralTokenizer(g, code)
  920. while true:
  921. getNextToken(g, lang)
  922. case g.kind
  923. of gtEof: break
  924. of gtNone, gtWhitespace:
  925. add(result, substr(code, g.start, g.length + g.start - 1))
  926. else:
  927. dispA(target, result, "<span class=\"$2\">$1</span>", "\\span$2{$1}", [
  928. esc(target, substr(code, g.start, g.length+g.start-1)),
  929. tokenClassToStr[g.kind]])
  930. deinitGeneralTokenizer(g)
  931. proc renderNimCode*(result: var string, code: string, target: OutputTarget) =
  932. renderCodeLang(result, langNim, code, target)
  933. proc renderCode(d: PDoc, n: PRstNode, result: var string) =
  934. ## Renders a code (code block or inline code), appending it to `result`.
  935. ##
  936. ## If the code block uses the ``number-lines`` option, a table will be
  937. ## generated with two columns, the first being a list of numbers and the
  938. ## second the code block itself. The code block can use syntax highlighting,
  939. ## which depends on the directive argument specified by the rst input, and
  940. ## may also come from the parser through the internal ``default-language``
  941. ## option to differentiate between a plain code block and Nim's code block
  942. ## extension.
  943. assert n.kind in {rnCodeBlock, rnInlineCode}
  944. var params = d.parseCodeBlockParams(n)
  945. if n.sons[2] == nil: return
  946. var m = n.sons[2].sons[0]
  947. assert m.kind == rnLeaf
  948. if params.testCmd.len > 0 and d.onTestSnippet != nil:
  949. d.onTestSnippet(d, params.filename, params.testCmd, params.status, m.text)
  950. var blockStart, blockEnd: string
  951. case d.target
  952. of outHtml:
  953. if n.kind == rnCodeBlock:
  954. (blockStart, blockEnd) = buildLinesHtmlTable(d, params, m.text,
  955. n.anchor.idS)
  956. else: # rnInlineCode
  957. blockStart = "<tt class=\"docutils literal\"><span class=\"pre\">"
  958. blockEnd = "</span></tt>"
  959. of outLatex:
  960. if n.kind == rnCodeBlock:
  961. blockStart = "\n\n" & n.anchor.idS & "\\begin{rstpre}\n"
  962. blockEnd = "\n\\end{rstpre}\n\n"
  963. else: # rnInlineCode
  964. blockStart = "\\rstcode{"
  965. blockEnd = "}"
  966. dispA(d.target, result, blockStart, blockStart, [])
  967. if params.lang == langNone:
  968. if len(params.langStr) > 0:
  969. rstMessage(d.filenames, d.msgHandler, n.info, mwUnsupportedLanguage,
  970. params.langStr)
  971. for letter in m.text: escChar(d.target, result, letter, emText)
  972. else:
  973. renderCodeLang(result, params.lang, m.text, d.target)
  974. dispA(d.target, result, blockEnd, blockEnd)
  975. proc renderContainer(d: PDoc, n: PRstNode, result: var string) =
  976. var tmp = ""
  977. renderRstToOut(d, n.sons[2], tmp)
  978. var arg = esc(d.target, strip(getArgument(n)))
  979. if arg == "":
  980. dispA(d.target, result, "<div>$1</div>", "$1", [tmp])
  981. else:
  982. dispA(d.target, result, "<div class=\"$1\">$2</div>", "$2", [arg, tmp])
  983. proc texColumns(n: PRstNode): string =
  984. let nColumns = if n.sons.len > 0: len(n.sons[0]) else: 1
  985. result = "L".repeat(nColumns)
  986. proc renderField(d: PDoc, n: PRstNode, result: var string) =
  987. var b = false
  988. if d.target == outLatex:
  989. var fieldname = addNodes(n.sons[0])
  990. var fieldval = esc(d.target, strip(addNodes(n.sons[1])))
  991. if cmpIgnoreStyle(fieldname, "author") == 0 or
  992. cmpIgnoreStyle(fieldname, "authors") == 0:
  993. if d.meta[metaAuthor].len == 0:
  994. d.meta[metaAuthor] = fieldval
  995. b = true
  996. elif cmpIgnoreStyle(fieldname, "version") == 0:
  997. if d.meta[metaVersion].len == 0:
  998. d.meta[metaVersion] = fieldval
  999. b = true
  1000. if not b:
  1001. renderAux(d, n, "<tr>$1</tr>\n", "$1", result)
  1002. proc renderEnumList(d: PDoc, n: PRstNode, result: var string) =
  1003. var
  1004. specifier = ""
  1005. specStart = ""
  1006. i1 = 0
  1007. pre = ""
  1008. i2 = n.labelFmt.len - 1
  1009. post = ""
  1010. if n.labelFmt[0] == '(':
  1011. i1 = 1
  1012. pre = "("
  1013. if n.labelFmt[^1] == ')' or n.labelFmt[^1] == '.':
  1014. i2 = n.labelFmt.len - 2
  1015. post = $n.labelFmt[^1]
  1016. let enumR = i1 .. i2 # enumerator range without surrounding (, ), .
  1017. if d.target == outLatex:
  1018. result.add ("\n%" & n.labelFmt & "\n")
  1019. # use enumerate parameters from package enumitem
  1020. if n.labelFmt[i1].isDigit:
  1021. var labelDef = ""
  1022. if pre != "" or post != "":
  1023. labelDef = "label=" & pre & "\\arabic*" & post & ","
  1024. if n.labelFmt[enumR] != "1":
  1025. specStart = "start=$1" % [n.labelFmt[enumR]]
  1026. if labelDef != "" or specStart != "":
  1027. specifier = "[$1$2]" % [labelDef, specStart]
  1028. else:
  1029. let (first, labelDef) =
  1030. if n.labelFmt[i1].isUpperAscii: ('A', "label=" & pre & "\\Alph*" & post)
  1031. else: ('a', "label=" & pre & "\\alph*" & post)
  1032. if n.labelFmt[i1] != first:
  1033. specStart = ",start=" & $(ord(n.labelFmt[i1]) - ord(first) + 1)
  1034. specifier = "[$1$2]" % [labelDef, specStart]
  1035. else: # HTML
  1036. # TODO: implement enumerator formatting using pre and post ( and ) for HTML
  1037. if n.labelFmt[i1].isDigit:
  1038. if n.labelFmt[enumR] != "1":
  1039. specStart = " start=\"$1\"" % [n.labelFmt[enumR]]
  1040. specifier = "class=\"simple\"" & specStart
  1041. else:
  1042. let (first, labelDef) =
  1043. if n.labelFmt[i1].isUpperAscii: ('A', "class=\"upperalpha simple\"")
  1044. else: ('a', "class=\"loweralpha simple\"")
  1045. if n.labelFmt[i1] != first:
  1046. specStart = " start=\"$1\"" % [ $(ord(n.labelFmt[i1]) - ord(first) + 1) ]
  1047. specifier = labelDef & specStart
  1048. renderAux(d, n, "<ol$2 " & specifier & ">$1</ol>\n",
  1049. "\\begin{enumerate}" & specifier & "$2$1\\end{enumerate}\n",
  1050. result)
  1051. proc renderAdmonition(d: PDoc, n: PRstNode, result: var string) =
  1052. var
  1053. htmlCls = "admonition_warning"
  1054. texSz = "\\large"
  1055. texColor = "orange"
  1056. case n.adType
  1057. of "hint", "note", "tip":
  1058. htmlCls = "admonition-info"; texSz = "\\normalsize"; texColor = "green"
  1059. of "attention", "admonition", "important", "warning":
  1060. htmlCls = "admonition-warning"; texSz = "\\large"; texColor = "orange"
  1061. of "danger", "error":
  1062. htmlCls = "admonition-error"; texSz = "\\Large"; texColor = "red"
  1063. else: discard
  1064. let txt = n.adType.capitalizeAscii()
  1065. let htmlHead = "<div class=\"admonition " & htmlCls & "\">"
  1066. renderAux(d, n,
  1067. htmlHead & "<span$2 class=\"" & htmlCls & "-text\"><b>" & txt &
  1068. ":</b></span>\n" & "$1</div>\n",
  1069. "\n\n\\begin{rstadmonition}[borderline west={0.2em}{0pt}{" &
  1070. texColor & "}]$2\n" &
  1071. "{" & texSz & "\\color{" & texColor & "}{\\textbf{" & txt & ":}}} " &
  1072. "$1\n\\end{rstadmonition}\n",
  1073. result)
  1074. proc renderHyperlink(d: PDoc, text, link: PRstNode, result: var string, external: bool) =
  1075. var linkStr = ""
  1076. block:
  1077. let mode = d.escMode
  1078. d.escMode = emUrl
  1079. renderRstToOut(d, link, linkStr)
  1080. d.escMode = mode
  1081. var textStr = ""
  1082. renderRstToOut(d, text, textStr)
  1083. if external:
  1084. dispA(d.target, result,
  1085. "<a class=\"reference external\" href=\"$2\">$1</a>",
  1086. "\\href{$2}{$1}", [textStr, linkStr])
  1087. else:
  1088. dispA(d.target, result,
  1089. "<a class=\"reference internal\" href=\"#$2\">$1</a>",
  1090. "\\hyperlink{$2}{$1} (p.~\\pageref{$2})", [textStr, linkStr])
  1091. proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
  1092. if n == nil: return
  1093. case n.kind
  1094. of rnInner: renderAux(d, n, result)
  1095. of rnHeadline, rnMarkdownHeadline: renderHeadline(d, n, result)
  1096. of rnOverline: renderOverline(d, n, result)
  1097. of rnTransition: renderAux(d, n, "<hr$2 />\n", "\n\n\\vspace{0.6em}\\hrule$2\n", result)
  1098. of rnParagraph: renderAux(d, n, "<p$2>$1</p>\n", "\n\n$2\n$1\n\n", result)
  1099. of rnBulletList:
  1100. renderAux(d, n, "<ul$2 class=\"simple\">$1</ul>\n",
  1101. "\\begin{itemize}\n$2\n$1\\end{itemize}\n", result)
  1102. of rnBulletItem, rnEnumItem:
  1103. renderAux(d, n, "<li$2>$1</li>\n", "\\item $2$1\n", result)
  1104. of rnEnumList: renderEnumList(d, n, result)
  1105. of rnDefList:
  1106. renderAux(d, n, "<dl$2 class=\"docutils\">$1</dl>\n",
  1107. "\\begin{description}\n$2\n$1\\end{description}\n", result)
  1108. of rnDefItem: renderAux(d, n, result)
  1109. of rnDefName: renderAux(d, n, "<dt$2>$1</dt>\n", "$2\\item[$1]\\ ", result)
  1110. of rnDefBody: renderAux(d, n, "<dd$2>$1</dd>\n", "$2\n$1\n", result)
  1111. of rnFieldList:
  1112. var tmp = ""
  1113. for i in countup(0, len(n) - 1):
  1114. renderRstToOut(d, n.sons[i], tmp)
  1115. if tmp.len != 0:
  1116. dispA(d.target, result,
  1117. "<table$2 class=\"docinfo\" frame=\"void\" rules=\"none\">" &
  1118. "<col class=\"docinfo-name\" />" &
  1119. "<col class=\"docinfo-content\" />" &
  1120. "<tbody valign=\"top\">$1" &
  1121. "</tbody></table>",
  1122. "\\begin{description}\n$2\n$1\\end{description}\n",
  1123. [tmp, n.anchor.idS])
  1124. of rnField: renderField(d, n, result)
  1125. of rnFieldName:
  1126. renderAux(d, n, "<th class=\"docinfo-name\">$1:</th>",
  1127. "\\item[$1:]", result)
  1128. of rnFieldBody:
  1129. renderAux(d, n, "<td>$1</td>", " $1\n", result)
  1130. of rnIndex:
  1131. renderRstToOut(d, n.sons[2], result)
  1132. of rnOptionList:
  1133. renderAux(d, n, "<div$2 class=\"option-list\">$1</div>",
  1134. "\\begin{rstoptlist}$2\n$1\\end{rstoptlist}", result)
  1135. of rnOptionListItem:
  1136. var addclass = if n.order mod 2 == 1: " odd" else: ""
  1137. renderAux(d, n,
  1138. "<div class=\"option-list-item" & addclass & "\">$1</div>\n",
  1139. "$1", result)
  1140. of rnOptionGroup:
  1141. renderAux(d, n,
  1142. "<div class=\"option-list-label\"><tt><span class=\"option\">" &
  1143. "$1</span></tt></div>",
  1144. "\\item[\\rstcodeitem{\\spanoption{$1}}]", result)
  1145. of rnDescription:
  1146. renderAux(d, n, "<div class=\"option-list-description\">$1</div>",
  1147. " $1\n", result)
  1148. of rnOption, rnOptionString, rnOptionArgument:
  1149. doAssert false, "renderRstToOut"
  1150. of rnLiteralBlock:
  1151. renderAux(d, n, "<pre$2>$1</pre>\n",
  1152. "\n\n$2\\begin{rstpre}\n$1\n\\end{rstpre}\n\n", result)
  1153. of rnQuotedLiteralBlock:
  1154. doAssert false, "renderRstToOut"
  1155. of rnLineBlock:
  1156. if n.sons.len == 1 and n.sons[0].lineIndent == "\n":
  1157. # whole line block is one empty line, no need to add extra spacing
  1158. renderAux(d, n, "<p$2>$1</p> ", "\n\n$2\n$1", result)
  1159. else: # add extra spacing around the line block for Latex
  1160. renderAux(d, n, "<p$2>$1</p>",
  1161. "\n\\vspace{0.5em}$2\n$1\\vspace{0.5em}\n", result)
  1162. of rnLineBlockItem:
  1163. if n.lineIndent.len == 0: # normal case - no additional indentation
  1164. renderAux(d, n, "$1<br/>", "\\noindent $1\n\n", result)
  1165. elif n.lineIndent == "\n": # add one empty line
  1166. renderAux(d, n, "<br/>", "\\vspace{1em}\n", result)
  1167. else: # additional indentation w.r.t. '| '
  1168. let indent = $(0.5 * (n.lineIndent.len - 1).toFloat) & "em"
  1169. renderAux(d, n,
  1170. "<span style=\"margin-left: " & indent & "\">$1</span><br/>",
  1171. "\\noindent\\hspace{" & indent & "}$1\n\n", result)
  1172. of rnBlockQuote:
  1173. renderAux(d, n, "<blockquote$2><p>$1</p></blockquote>\n",
  1174. "\\begin{quote}\n$2\n$1\\end{quote}\n", result)
  1175. of rnAdmonition: renderAdmonition(d, n, result)
  1176. of rnTable, rnGridTable, rnMarkdownTable:
  1177. renderAux(d, n,
  1178. "<table$2 border=\"1\" class=\"docutils\">$1</table>",
  1179. "\n$2\n\\begin{rsttab}{" &
  1180. texColumns(n) & "}\n\\hline\n$1\\end{rsttab}", result)
  1181. of rnTableRow:
  1182. if len(n) >= 1:
  1183. if d.target == outLatex:
  1184. #var tmp = ""
  1185. renderRstToOut(d, n.sons[0], result)
  1186. for i in countup(1, len(n) - 1):
  1187. result.add(" & ")
  1188. renderRstToOut(d, n.sons[i], result)
  1189. result.add("\\\\\n\\hline\n")
  1190. else:
  1191. result.add("<tr>")
  1192. renderAux(d, n, result)
  1193. result.add("</tr>\n")
  1194. of rnTableDataCell:
  1195. renderAux(d, n, "<td>$1</td>", "$1", result)
  1196. of rnTableHeaderCell:
  1197. renderAux(d, n, "<th>$1</th>", "\\textbf{$1}", result)
  1198. of rnFootnoteGroup:
  1199. renderAux(d, n,
  1200. "<hr class=\"footnote\">" &
  1201. "<div class=\"footnote-group\">\n$1</div>\n",
  1202. "\n\n\\noindent\\rule{0.25\\linewidth}{.4pt}\n" &
  1203. "\\begin{rstfootnote}\n$1\\end{rstfootnote}\n\n",
  1204. result)
  1205. of rnFootnote, rnCitation:
  1206. var mark = ""
  1207. renderAux(d, n.sons[0], mark)
  1208. var body = ""
  1209. renderRstToOut(d, n.sons[1], body)
  1210. dispA(d.target, result,
  1211. "<div$2><div class=\"footnote-label\">" &
  1212. "<sup><strong><a href=\"#$4\">[$3]</a></strong></sup>" &
  1213. "</div> &ensp; $1\n</div>\n",
  1214. "\\item[\\textsuperscript{[$3]}]$2 $1\n",
  1215. [body, n.anchor.idS, mark, n.anchor])
  1216. of rnRef:
  1217. renderHyperlink(d, text=n.sons[0], link=n.sons[0], result, external=false)
  1218. of rnStandaloneHyperlink:
  1219. renderHyperlink(d, text=n.sons[0], link=n.sons[0], result, external=true)
  1220. of rnInternalRef:
  1221. renderHyperlink(d, text=n.sons[0], link=n.sons[1], result, external=false)
  1222. of rnHyperlink:
  1223. renderHyperlink(d, text=n.sons[0], link=n.sons[1], result, external=true)
  1224. of rnFootnoteRef:
  1225. var tmp = "["
  1226. renderAux(d, n.sons[0], tmp)
  1227. tmp.add "]"
  1228. dispA(d.target, result,
  1229. "<sup><strong><a class=\"reference internal\" href=\"#$2\">" &
  1230. "$1</a></strong></sup>",
  1231. "\\textsuperscript{\\hyperlink{$2}{\\textbf{$1}}}",
  1232. [tmp, n.sons[1].text])
  1233. of rnDirArg, rnRaw: renderAux(d, n, result)
  1234. of rnRawHtml:
  1235. if d.target != outLatex and not lastSon(n).isNil:
  1236. result.add addNodes(lastSon(n))
  1237. of rnRawLatex:
  1238. if d.target == outLatex and not lastSon(n).isNil:
  1239. result.add addNodes(lastSon(n))
  1240. of rnImage, rnFigure: renderImage(d, n, result)
  1241. of rnCodeBlock, rnInlineCode: renderCode(d, n, result)
  1242. of rnContainer: renderContainer(d, n, result)
  1243. of rnSubstitutionReferences, rnSubstitutionDef:
  1244. renderAux(d, n, "|$1|", "|$1|", result)
  1245. of rnDirective:
  1246. renderAux(d, n, "", "", result)
  1247. of rnUnknownRole, rnCodeFragment:
  1248. var tmp0 = ""
  1249. var tmp1 = ""
  1250. renderRstToOut(d, n.sons[0], tmp0)
  1251. renderRstToOut(d, n.sons[1], tmp1)
  1252. var class = tmp1
  1253. # don't allow missing role break latex compilation:
  1254. if d.target == outLatex and n.kind == rnUnknownRole: class = "Other"
  1255. if n.kind == rnCodeFragment:
  1256. dispA(d.target, result,
  1257. "<tt class=\"docutils literal\"><span class=\"pre $2\">" &
  1258. "$1</span></tt>",
  1259. "\\rstcode{\\span$2{$1}}", [tmp0, class])
  1260. else: # rnUnknownRole, not necessarily code/monospace font
  1261. dispA(d.target, result, "<span class=\"$2\">$1</span>", "\\span$2{$1}",
  1262. [tmp0, class])
  1263. of rnSub: renderAux(d, n, "<sub>$1</sub>", "\\rstsub{$1}", result)
  1264. of rnSup: renderAux(d, n, "<sup>$1</sup>", "\\rstsup{$1}", result)
  1265. of rnEmphasis: renderAux(d, n, "<em>$1</em>", "\\emph{$1}", result)
  1266. of rnStrongEmphasis:
  1267. renderAux(d, n, "<strong>$1</strong>", "\\textbf{$1}", result)
  1268. of rnTripleEmphasis:
  1269. renderAux(d, n, "<strong><em>$1</em></strong>",
  1270. "\\textbf{emph{$1}}", result)
  1271. of rnIdx:
  1272. renderIndexTerm(d, n, result)
  1273. of rnInlineLiteral, rnInterpretedText:
  1274. renderAux(d, n,
  1275. "<tt class=\"docutils literal\"><span class=\"pre\">$1</span></tt>",
  1276. "\\rstcode{$1}", result)
  1277. of rnInlineTarget:
  1278. var tmp = ""
  1279. renderAux(d, n, tmp)
  1280. dispA(d.target, result,
  1281. "<span class=\"target\" id=\"$2\">$1</span>",
  1282. "\\label{$2}\\hypertarget{$2}{$1}",
  1283. [tmp, rstnodeToRefname(n)])
  1284. of rnSmiley: renderSmiley(d, n, result)
  1285. of rnLeaf: result.add(esc(d.target, n.text, escMode=d.escMode))
  1286. of rnContents: d.hasToc = true
  1287. of rnDefaultRole: discard
  1288. of rnTitle:
  1289. d.meta[metaTitle] = ""
  1290. renderRstToOut(d, n.sons[0], d.meta[metaTitle])
  1291. # -----------------------------------------------------------------------------
  1292. proc getVarIdx(varnames: openArray[string], id: string): int =
  1293. for i in countup(0, high(varnames)):
  1294. if cmpIgnoreStyle(varnames[i], id) == 0:
  1295. return i
  1296. result = -1
  1297. proc formatNamedVars*(frmt: string, varnames: openArray[string],
  1298. varvalues: openArray[string]): string =
  1299. var i = 0
  1300. var L = len(frmt)
  1301. result = ""
  1302. var num = 0
  1303. while i < L:
  1304. if frmt[i] == '$':
  1305. inc(i) # skip '$'
  1306. case frmt[i]
  1307. of '#':
  1308. add(result, varvalues[num])
  1309. inc(num)
  1310. inc(i)
  1311. of '$':
  1312. add(result, "$")
  1313. inc(i)
  1314. of '0'..'9':
  1315. var j = 0
  1316. while true:
  1317. j = (j * 10) + ord(frmt[i]) - ord('0')
  1318. inc(i)
  1319. if i > L-1 or frmt[i] notin {'0'..'9'}: break
  1320. if j > high(varvalues) + 1:
  1321. raise newException(ValueError, "invalid index: " & $j)
  1322. num = j
  1323. add(result, varvalues[j - 1])
  1324. of 'A'..'Z', 'a'..'z', '\x80'..'\xFF':
  1325. var id = ""
  1326. while true:
  1327. add(id, frmt[i])
  1328. inc(i)
  1329. if frmt[i] notin {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}: break
  1330. var idx = getVarIdx(varnames, id)
  1331. if idx >= 0:
  1332. add(result, varvalues[idx])
  1333. else:
  1334. raise newException(ValueError, "unknown substitution var: " & id)
  1335. of '{':
  1336. var id = ""
  1337. inc(i)
  1338. while frmt[i] != '}':
  1339. if frmt[i] == '\0':
  1340. raise newException(ValueError, "'}' expected")
  1341. add(id, frmt[i])
  1342. inc(i)
  1343. inc(i) # skip }
  1344. # search for the variable:
  1345. var idx = getVarIdx(varnames, id)
  1346. if idx >= 0: add(result, varvalues[idx])
  1347. else:
  1348. raise newException(ValueError, "unknown substitution var: " & id)
  1349. else:
  1350. raise newException(ValueError, "unknown substitution: $" & $frmt[i])
  1351. var start = i
  1352. while i < L:
  1353. if frmt[i] != '$': inc(i)
  1354. else: break
  1355. if i-1 >= start: add(result, substr(frmt, start, i - 1))
  1356. proc defaultConfig*(): StringTableRef =
  1357. ## Returns a default configuration for embedded HTML generation.
  1358. ##
  1359. ## The returned ``StringTableRef`` contains the parameters used by the HTML
  1360. ## engine to build the final output. For information on what these parameters
  1361. ## are and their purpose, please look up the file ``config/nimdoc.cfg``
  1362. ## bundled with the compiler.
  1363. ##
  1364. ## The only difference between the contents of that file and the values
  1365. ## provided by this proc is the ``doc.file`` variable. The ``doc.file``
  1366. ## variable of the configuration file contains HTML to build standalone
  1367. ## pages, while this proc returns just the content for procs like
  1368. ## ``rstToHtml`` to generate the bare minimum HTML.
  1369. result = newStringTable(modeStyleInsensitive)
  1370. template setConfigVar(key, val) =
  1371. result[key] = val
  1372. # If you need to modify these values, it might be worth updating the template
  1373. # file in config/nimdoc.cfg.
  1374. setConfigVar("split.item.toc", "20")
  1375. setConfigVar("doc.section", """
  1376. <div class="section" id="$sectionID">
  1377. <h1><a class="toc-backref" href="#$sectionTitleID">$sectionTitle</a></h1>
  1378. <dl class="item">
  1379. $content
  1380. </dl></div>
  1381. """)
  1382. setConfigVar("doc.section.toc", """
  1383. <li>
  1384. <a class="reference" href="#$sectionID" id="$sectionTitleID">$sectionTitle</a>
  1385. <ul class="simple">
  1386. $content
  1387. </ul>
  1388. </li>
  1389. """)
  1390. setConfigVar("doc.item", """
  1391. <dt id="$itemID"><a name="$itemSymOrIDEnc"></a><pre>$header</pre></dt>
  1392. <dd>
  1393. $desc
  1394. </dd>
  1395. """)
  1396. setConfigVar("doc.item.toc", """
  1397. <li><a class="reference" href="#$itemSymOrIDEnc"
  1398. title="$header_plain">$name</a></li>
  1399. """)
  1400. setConfigVar("doc.toc", """
  1401. <div class="navigation" id="navigation">
  1402. <ul class="simple">
  1403. $content
  1404. </ul>
  1405. </div>""")
  1406. setConfigVar("doc.body_toc", """
  1407. $tableofcontents
  1408. <div class="content" id="content">
  1409. $moduledesc
  1410. $content
  1411. </div>
  1412. """)
  1413. setConfigVar("doc.listing_start", "<pre$3 class = \"listing\">")
  1414. setConfigVar("doc.listing_end", "</pre>")
  1415. setConfigVar("doc.listing_button", "</pre>")
  1416. setConfigVar("doc.body_no_toc", "$moduledesc $content")
  1417. setConfigVar("doc.file", "$content")
  1418. setConfigVar("doc.smiley_format", "/images/smilies/$1.gif")
  1419. # ---------- forum ---------------------------------------------------------
  1420. proc rstToHtml*(s: string, options: RstParseOptions,
  1421. config: StringTableRef,
  1422. msgHandler: MsgHandler = rst.defaultMsgHandler): string =
  1423. ## Converts an input rst string into embeddable HTML.
  1424. ##
  1425. ## This convenience proc parses any input string using rst markup (it doesn't
  1426. ## have to be a full document!) and returns an embeddable piece of HTML. The
  1427. ## proc is meant to be used in *online* environments without access to a
  1428. ## meaningful filesystem, and therefore rst ``include`` like directives won't
  1429. ## work. For an explanation of the ``config`` parameter see the
  1430. ## ``initRstGenerator`` proc. Example:
  1431. ##
  1432. ## .. code-block:: nim
  1433. ## import packages/docutils/rstgen, strtabs
  1434. ##
  1435. ## echo rstToHtml("*Hello* **world**!", {},
  1436. ## newStringTable(modeStyleInsensitive))
  1437. ## # --> <em>Hello</em> <strong>world</strong>!
  1438. ##
  1439. ## If you need to allow the rst ``include`` directive or tweak the generated
  1440. ## output you have to create your own ``RstGenerator`` with
  1441. ## ``initRstGenerator`` and related procs.
  1442. proc myFindFile(filename: string): string =
  1443. # we don't find any files in online mode:
  1444. result = ""
  1445. const filen = "input"
  1446. let (rst, filenames, _) = rstParse(s, filen,
  1447. line=LineRstInit, column=ColRstInit,
  1448. options, myFindFile, msgHandler)
  1449. var d: RstGenerator
  1450. initRstGenerator(d, outHtml, config, filen, myFindFile, msgHandler, filenames)
  1451. result = ""
  1452. renderRstToOut(d, rst, result)
  1453. strbasics.strip(result)
  1454. proc rstToLatex*(rstSource: string; options: RstParseOptions): string {.inline, since: (1, 3).} =
  1455. ## Convenience proc for `renderRstToOut` and `initRstGenerator`.
  1456. runnableExamples: doAssert rstToLatex("*Hello* **world**", {}) == """\emph{Hello} \textbf{world}"""
  1457. if rstSource.len == 0: return
  1458. let (rst, filenames, _) = rstParse(rstSource, "",
  1459. line=LineRstInit, column=ColRstInit,
  1460. options)
  1461. var rstGenera: RstGenerator
  1462. rstGenera.initRstGenerator(outLatex, defaultConfig(), "input", filenames=filenames)
  1463. rstGenera.renderRstToOut(rst, result)
  1464. strbasics.strip(result)