rstgen.nim 60 KB

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