rstgen.nim 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567
  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 std/[strutils, os, hashes, strtabs, tables, sequtils,
  41. algorithm, parseutils, strbasics]
  42. import rstast, rst, rstidx, highlite
  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. for i in 0 ..< fileEntries.len:
  556. if fileEntries[i].kind != ieIdxRole:
  557. continue
  558. setLen(result.symbols, L + 1)
  559. result.symbols[L] = fileEntries[i]
  560. inc L
  561. proc mergeIndexes*(dir: string): string =
  562. ## Merges all index files in `dir` and returns the generated index as HTML.
  563. ##
  564. ## This proc will first scan `dir` for index files with the ``.idx``
  565. ## extension previously created by commands like ``nim doc|rst2html``
  566. ## which use the ``--index:on`` switch. These index files are the result of
  567. ## calls to `setIndexTerm()
  568. ## <#setIndexTerm,RstGenerator,string,string,string,string,string>`_
  569. ## and `writeIndexFile() <#writeIndexFile,RstGenerator,string>`_, so they are
  570. ## simple tab separated files.
  571. ##
  572. ## As convention this proc will split index files into two categories:
  573. ## documentation and API. API indices will be all joined together into a
  574. ## single big sorted index, making the bulk of the final index. This is good
  575. ## for API documentation because many symbols are repeated in different
  576. ## modules. On the other hand, documentation indices are essentially table of
  577. ## contents plus a few special markers. These documents will be rendered in a
  578. ## separate section which tries to maintain the order and hierarchy of the
  579. ## symbols in the index file.
  580. ##
  581. ## To differentiate between a documentation and API file a convention is
  582. ## used: indices which contain one entry without the HTML hash character (#)
  583. ## will be considered `documentation`, since this hash-less entry is the
  584. ## explicit title of the document. Indices without this explicit entry will
  585. ## be considered `generated API` extracted out of a source ``.nim`` file.
  586. ##
  587. ## Returns the merged and sorted indices into a single HTML block which can
  588. ## be further embedded into nimdoc templates.
  589. var (modules, symbols, docs) = readIndexDir(dir)
  590. sort(modules, system.cmp)
  591. result = ""
  592. # Generate a quick jump list of documents.
  593. if docs.len > 0:
  594. result.add(generateDocumentationJumps(docs))
  595. result.add("<p />")
  596. # Generate hyperlinks to all the linked modules.
  597. if modules.len > 0:
  598. result.add(generateModuleJumps(modules))
  599. result.add("<p />")
  600. when false:
  601. # Generate the HTML block with API documents.
  602. if docs.len > 0:
  603. result.add("<h2>Documentation files</h2>\n")
  604. result.add(generateDocumentationIndex(docs))
  605. # Generate the HTML block with symbols.
  606. if symbols.len > 0:
  607. sortIndex(symbols)
  608. result.add("<h2>API symbols</h2>\n")
  609. result.add(generateSymbolIndex(symbols))
  610. # ----------------------------------------------------------------------------
  611. proc renderHeadline(d: PDoc, n: PRstNode, result: var string) =
  612. var tmp = ""
  613. for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp)
  614. d.currentSection = tmp
  615. var tocName = esc(d.target, renderRstToText(n), escMode = emOption)
  616. # for Latex: simple text without commands that may break TOC/hyperref
  617. if d.hasToc:
  618. d.tocPart.add n
  619. dispA(d.target, result, "\n<h$1><a class=\"toc-backref\"" &
  620. "$2 href=\"#$5\">$3</a></h$1>", "\\rsth$4[$6]{$3}$2\n",
  621. [$n.level, n.anchor.idS, tmp,
  622. $chr(n.level - 1 + ord('A')), n.anchor, tocName])
  623. else:
  624. dispA(d.target, result, "\n<h$1$2>$3</h$1>",
  625. "\\rsth$4[$5]{$3}$2\n", [
  626. $n.level, n.anchor.idS, tmp,
  627. $chr(n.level - 1 + ord('A')), tocName])
  628. # Generate index entry using spaces to indicate TOC level for the output HTML.
  629. assert n.level >= 0
  630. setIndexTerm(d, ieHeading, htmlFile = d.htmlFileRelPath, id = n.anchor,
  631. term = n.addNodes, linkTitle = spaces(max(0, n.level)) & tmp)
  632. proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
  633. if n.level == 0 and d.meta[metaTitle].len == 0:
  634. d.meta[metaTitleRaw] = n.addNodes
  635. for i in countup(0, len(n)-1):
  636. renderRstToOut(d, n.sons[i], d.meta[metaTitle])
  637. d.currentSection = d.meta[metaTitle]
  638. elif n.level == 0 and d.meta[metaSubtitle].len == 0:
  639. for i in countup(0, len(n)-1):
  640. renderRstToOut(d, n.sons[i], d.meta[metaSubtitle])
  641. d.currentSection = d.meta[metaSubtitle]
  642. else:
  643. var tmp = ""
  644. for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp)
  645. d.currentSection = tmp
  646. var tocName = esc(d.target, renderRstToText(n), escMode=emOption)
  647. dispA(d.target, result, "<h$1$2><center>$3</center></h$1>",
  648. "\\rstov$4[$5]{$3}$2\n", [$n.level,
  649. n.anchor.idS, tmp, $chr(n.level - 1 + ord('A')), tocName])
  650. setIndexTerm(d, ieHeading, htmlFile = d.htmlFileRelPath, id = n.anchor,
  651. term = n.addNodes, linkTitle = spaces(max(0, n.level)) & tmp)
  652. proc renderTocEntry(d: PDoc, n: PRstNode, result: var string) =
  653. var header = ""
  654. for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], header)
  655. dispA(d.target, result,
  656. "<li><a class=\"reference\" id=\"$1_toc\" href=\"#$1\">$2</a></li>\n",
  657. "\\item\\label{$1_toc} $2\\ref{$1}\n", [n.anchor, header])
  658. proc renderTocEntries*(d: var RstGenerator, j: var int, lvl: int,
  659. result: var string) =
  660. var tmp = ""
  661. while j <= high(d.tocPart):
  662. var a = abs(d.tocPart[j].level)
  663. if a == lvl:
  664. renderTocEntry(d, d.tocPart[j], tmp)
  665. inc(j)
  666. elif a > lvl:
  667. renderTocEntries(d, j, a, tmp)
  668. else:
  669. break
  670. if lvl > 1:
  671. dispA(d.target, result, "<ul class=\"simple\">$1</ul>",
  672. "\\begin{enumerate}$1\\end{enumerate}", [tmp])
  673. else:
  674. result.add(tmp)
  675. proc renderImage(d: PDoc, n: PRstNode, result: var string) =
  676. let
  677. arg = getArgument(n)
  678. var
  679. options = ""
  680. var s = esc(d.target, getFieldValue(n, "scale").strip())
  681. if s.len > 0:
  682. dispA(d.target, options, " scale=\"$1\"", " scale=$1", [s])
  683. s = esc(d.target, getFieldValue(n, "height").strip())
  684. if s.len > 0:
  685. dispA(d.target, options, " height=\"$1\"", " height=$1", [s])
  686. s = esc(d.target, getFieldValue(n, "width").strip())
  687. if s.len > 0:
  688. dispA(d.target, options, " width=\"$1\"", " width=$1", [s])
  689. s = esc(d.target, getFieldValue(n, "alt").strip())
  690. if s.len > 0:
  691. dispA(d.target, options, " alt=\"$1\"", "", [s])
  692. s = esc(d.target, getFieldValue(n, "align").strip())
  693. if s.len > 0:
  694. dispA(d.target, options, " align=\"$1\"", "", [s])
  695. if options.len > 0: options = dispF(d.target, "$1", "[$1]", [options])
  696. var htmlOut = ""
  697. if arg.endsWith(".mp4") or arg.endsWith(".ogg") or
  698. arg.endsWith(".webm"):
  699. htmlOut = """
  700. <video$3 src="$1"$2 autoPlay='true' loop='true' muted='true'>
  701. Sorry, your browser doesn't support embedded videos
  702. </video>
  703. """
  704. else:
  705. htmlOut = "<img$3 src=\"$1\"$2/>"
  706. # support for `:target:` links for images:
  707. var target = esc(d.target, getFieldValue(n, "target").strip(), escMode=emUrl)
  708. discard safeProtocol(target)
  709. if target.len > 0:
  710. # `htmlOut` needs to be of the following format for link to work for images:
  711. # <a class="reference external" href="target"><img src=\"$1\"$2/></a>
  712. var htmlOutWithLink = ""
  713. dispA(d.target, htmlOutWithLink,
  714. "<a class=\"reference external\" href=\"$2\">$1</a>",
  715. "\\href{$2}{$1}", [htmlOut, target])
  716. htmlOut = htmlOutWithLink
  717. dispA(d.target, result, htmlOut, "$3\\includegraphics$2{$1}",
  718. [esc(d.target, arg), options, n.anchor.idS])
  719. if len(n) >= 3: renderRstToOut(d, n.sons[2], result)
  720. proc renderSmiley(d: PDoc, n: PRstNode, result: var string) =
  721. dispA(d.target, result,
  722. """<img src="$1" width="15"
  723. height="17" hspace="2" vspace="2" class="smiley" />""",
  724. "\\includegraphics{$1}",
  725. [d.config.getOrDefault"doc.smiley_format" % n.text])
  726. proc getField1Int(d: PDoc, n: PRstNode, fieldName: string): int =
  727. template err(msg: string) =
  728. rstMessage(d.filenames, d.msgHandler, n.info, meInvalidField, msg)
  729. let value = n.getFieldValue
  730. var number: int
  731. let nChars = parseInt(value, number)
  732. if nChars == 0:
  733. if value.len == 0:
  734. # use a good default value:
  735. result = 1
  736. else:
  737. err("field $1 requires an integer, but '$2' was given" %
  738. [fieldName, value])
  739. elif nChars < value.len:
  740. err("extra arguments were given to $1: '$2'" %
  741. [fieldName, value[nChars..^1]])
  742. else:
  743. result = number
  744. proc parseCodeBlockField(d: PDoc, n: PRstNode, params: var CodeBlockParams) =
  745. ## Parses useful fields which can appear before a code block.
  746. ##
  747. ## This supports the special ``default-language`` internal string generated
  748. ## by the ``rst`` module to communicate a specific default language.
  749. case n.getArgument.toLowerAscii
  750. of "number-lines":
  751. params.numberLines = true
  752. # See if the field has a parameter specifying a different line than 1.
  753. params.startLine = getField1Int(d, n, "number-lines")
  754. of "file", "filename":
  755. # The ``file`` option is a Nim extension to the official spec, it acts
  756. # like it would for other directives like ``raw`` or ``cvs-table``. This
  757. # field is dealt with in ``rst.nim`` which replaces the existing block with
  758. # the referenced file, so we only need to ignore it here to avoid incorrect
  759. # warning messages.
  760. params.filename = n.getFieldValue.strip
  761. of "test":
  762. params.testCmd = n.getFieldValue.strip
  763. if params.testCmd.len == 0:
  764. # factor with D20210224T221756. Note that `$docCmd` should appear before `$file`
  765. # but after all other options, but currently `$options` merges both options and `$file` so it's tricky.
  766. params.testCmd = "$nim r --backend:$backend --lib:$libpath $docCmd $options"
  767. else:
  768. # consider whether `$docCmd` should be appended here too
  769. params.testCmd = unescape(params.testCmd)
  770. of "status", "exitcode":
  771. params.status = getField1Int(d, n, n.getArgument)
  772. of "default-language":
  773. params.langStr = n.getFieldValue.strip
  774. params.lang = params.langStr.getSourceLanguage
  775. else:
  776. rstMessage(d.filenames, d.msgHandler, n.info, mwUnsupportedField,
  777. n.getArgument)
  778. proc parseCodeBlockParams(d: PDoc, n: PRstNode): CodeBlockParams =
  779. ## Iterates over all code block fields and returns processed params.
  780. ##
  781. ## Also processes the argument of the directive as the default language. This
  782. ## is done last so as to override any internal communication field variables.
  783. result.init
  784. if n.isNil:
  785. return
  786. assert n.kind in {rnCodeBlock, rnInlineCode}
  787. # Parse the field list for rendering parameters if there are any.
  788. if not n.sons[1].isNil:
  789. for son in n.sons[1].sons: d.parseCodeBlockField(son, result)
  790. # Parse the argument and override the language.
  791. result.langStr = strip(getArgument(n))
  792. if result.langStr != "":
  793. result.lang = getSourceLanguage(result.langStr)
  794. proc buildLinesHtmlTable(d: PDoc; params: CodeBlockParams, code: string,
  795. idStr: string):
  796. tuple[beginTable, endTable: string] =
  797. ## Returns the necessary tags to start/end a code block in HTML.
  798. ##
  799. ## If the numberLines has not been used, the tags will default to a simple
  800. ## <pre> pair. Otherwise it will build a table and insert an initial column
  801. ## with all the line numbers, which requires you to pass the `code` to detect
  802. ## how many lines have to be generated (and starting at which point!).
  803. inc d.listingCounter
  804. let id = $d.listingCounter
  805. if not params.numberLines:
  806. result = (d.config.getOrDefault"doc.listing_start" %
  807. [id, sourceLanguageToStr[params.lang], idStr],
  808. d.config.getOrDefault"doc.listing_end" % id)
  809. return
  810. var codeLines = code.strip.countLines
  811. assert codeLines > 0
  812. result.beginTable = """<table$1 class="line-nums-table">""" % [idStr] &
  813. """<tbody><tr><td class="blob-line-nums"><pre class="line-nums">"""
  814. var line = params.startLine
  815. while codeLines > 0:
  816. result.beginTable.add($line & "\n")
  817. line.inc
  818. codeLines.dec
  819. result.beginTable.add("</pre></td><td>" & (
  820. d.config.getOrDefault"doc.listing_start" %
  821. [id, sourceLanguageToStr[params.lang], idStr]))
  822. result.endTable = (d.config.getOrDefault"doc.listing_end" % id) &
  823. "</td></tr></tbody></table>" & (
  824. d.config.getOrDefault"doc.listing_button" % id)
  825. proc renderCodeLang*(result: var string, lang: SourceLanguage, code: string,
  826. target: OutputTarget) =
  827. var g: GeneralTokenizer
  828. initGeneralTokenizer(g, code)
  829. while true:
  830. getNextToken(g, lang)
  831. case g.kind
  832. of gtEof: break
  833. of gtNone, gtWhitespace:
  834. add(result, substr(code, g.start, g.length + g.start - 1))
  835. else:
  836. dispA(target, result, "<span class=\"$2\">$1</span>", "\\span$2{$1}", [
  837. esc(target, substr(code, g.start, g.length+g.start-1)),
  838. tokenClassToStr[g.kind]])
  839. deinitGeneralTokenizer(g)
  840. proc renderNimCode*(result: var string, code: string, target: OutputTarget) =
  841. renderCodeLang(result, langNim, code, target)
  842. proc renderCode(d: PDoc, n: PRstNode, result: var string) {.gcsafe.} =
  843. ## Renders a code (code block or inline code), appending it to `result`.
  844. ##
  845. ## If the code block uses the ``number-lines`` option, a table will be
  846. ## generated with two columns, the first being a list of numbers and the
  847. ## second the code block itself. The code block can use syntax highlighting,
  848. ## which depends on the directive argument specified by the rst input, and
  849. ## may also come from the parser through the internal ``default-language``
  850. ## option to differentiate between a plain code block and Nim's code block
  851. ## extension.
  852. assert n.kind in {rnCodeBlock, rnInlineCode}
  853. var params = d.parseCodeBlockParams(n)
  854. if n.sons[2] == nil: return
  855. var m = n.sons[2].sons[0]
  856. assert m.kind == rnLeaf
  857. if params.testCmd.len > 0 and d.onTestSnippet != nil:
  858. d.onTestSnippet(d, params.filename, params.testCmd, params.status, m.text)
  859. var blockStart, blockEnd: string
  860. case d.target
  861. of outHtml:
  862. if n.kind == rnCodeBlock:
  863. (blockStart, blockEnd) = buildLinesHtmlTable(d, params, m.text,
  864. n.anchor.idS)
  865. else: # rnInlineCode
  866. blockStart = "<tt class=\"docutils literal\"><span class=\"pre\">"
  867. blockEnd = "</span></tt>"
  868. of outLatex:
  869. if n.kind == rnCodeBlock:
  870. blockStart = "\n\n" & n.anchor.idS & "\\begin{rstpre}\n"
  871. blockEnd = "\n\\end{rstpre}\n\n"
  872. else: # rnInlineCode
  873. blockStart = "\\rstcode{"
  874. blockEnd = "}"
  875. dispA(d.target, result, blockStart, blockStart, [])
  876. if params.lang == langNone:
  877. if len(params.langStr) > 0 and params.langStr.toLowerAscii != "none":
  878. rstMessage(d.filenames, d.msgHandler, n.info, mwUnsupportedLanguage,
  879. params.langStr)
  880. for letter in m.text: escChar(d.target, result, letter, emText)
  881. else:
  882. renderCodeLang(result, params.lang, m.text, d.target)
  883. dispA(d.target, result, blockEnd, blockEnd)
  884. proc renderContainer(d: PDoc, n: PRstNode, result: var string) =
  885. var tmp = ""
  886. renderRstToOut(d, n.sons[2], tmp)
  887. var arg = esc(d.target, strip(getArgument(n)))
  888. if arg == "":
  889. dispA(d.target, result, "<div>$1</div>", "$1", [tmp])
  890. else:
  891. dispA(d.target, result, "<div class=\"$1\">$2</div>", "$2", [arg, tmp])
  892. proc renderField(d: PDoc, n: PRstNode, result: var string) =
  893. var b = false
  894. if d.target == outLatex:
  895. var fieldname = addNodes(n.sons[0])
  896. var fieldval = esc(d.target, strip(addNodes(n.sons[1])))
  897. if cmpIgnoreStyle(fieldname, "author") == 0 or
  898. cmpIgnoreStyle(fieldname, "authors") == 0:
  899. if d.meta[metaAuthor].len == 0:
  900. d.meta[metaAuthor] = fieldval
  901. b = true
  902. elif cmpIgnoreStyle(fieldname, "version") == 0:
  903. if d.meta[metaVersion].len == 0:
  904. d.meta[metaVersion] = fieldval
  905. b = true
  906. if not b:
  907. renderAux(d, n, "<tr>$1</tr>\n", "$1", result)
  908. proc renderEnumList(d: PDoc, n: PRstNode, result: var string) =
  909. var
  910. specifier = ""
  911. specStart = ""
  912. i1 = 0
  913. pre = ""
  914. i2 = n.labelFmt.len - 1
  915. post = ""
  916. if n.labelFmt[0] == '(':
  917. i1 = 1
  918. pre = "("
  919. if n.labelFmt[^1] == ')' or n.labelFmt[^1] == '.':
  920. i2 = n.labelFmt.len - 2
  921. post = $n.labelFmt[^1]
  922. let enumR = i1 .. i2 # enumerator range without surrounding (, ), .
  923. if d.target == outLatex:
  924. result.add ("\n%" & n.labelFmt & "\n")
  925. # use enumerate parameters from package enumitem
  926. if n.labelFmt[i1].isDigit:
  927. var labelDef = ""
  928. if pre != "" or post != "":
  929. labelDef = "label=" & pre & "\\arabic*" & post & ","
  930. if n.labelFmt[enumR] != "1":
  931. specStart = "start=$1" % [n.labelFmt[enumR]]
  932. if labelDef != "" or specStart != "":
  933. specifier = "[$1$2]" % [labelDef, specStart]
  934. else:
  935. let (first, labelDef) =
  936. if n.labelFmt[i1].isUpperAscii: ('A', "label=" & pre & "\\Alph*" & post)
  937. else: ('a', "label=" & pre & "\\alph*" & post)
  938. if n.labelFmt[i1] != first:
  939. specStart = ",start=" & $(ord(n.labelFmt[i1]) - ord(first) + 1)
  940. specifier = "[$1$2]" % [labelDef, specStart]
  941. else: # HTML
  942. # TODO: implement enumerator formatting using pre and post ( and ) for HTML
  943. if n.labelFmt[i1].isDigit:
  944. if n.labelFmt[enumR] != "1":
  945. specStart = " start=\"$1\"" % [n.labelFmt[enumR]]
  946. specifier = "class=\"simple\"" & specStart
  947. else:
  948. let (first, labelDef) =
  949. if n.labelFmt[i1].isUpperAscii: ('A', "class=\"upperalpha simple\"")
  950. else: ('a', "class=\"loweralpha simple\"")
  951. if n.labelFmt[i1] != first:
  952. specStart = " start=\"$1\"" % [ $(ord(n.labelFmt[i1]) - ord(first) + 1) ]
  953. specifier = labelDef & specStart
  954. renderAux(d, n, "<ol$2 " & specifier & ">$1</ol>\n",
  955. "\\begin{enumerate}" & specifier & "$2$1\\end{enumerate}\n",
  956. result)
  957. proc renderAdmonition(d: PDoc, n: PRstNode, result: var string) =
  958. var
  959. htmlCls = "admonition_warning"
  960. texSz = "\\large"
  961. texColor = "orange"
  962. case n.adType
  963. of "hint", "note", "tip":
  964. htmlCls = "admonition-info"; texSz = "\\normalsize"; texColor = "green"
  965. of "attention", "admonition", "important", "warning", "caution":
  966. htmlCls = "admonition-warning"; texSz = "\\large"; texColor = "orange"
  967. of "danger", "error":
  968. htmlCls = "admonition-error"; texSz = "\\Large"; texColor = "red"
  969. else: discard
  970. let txt = n.adType.capitalizeAscii()
  971. let htmlHead = "<div class=\"admonition " & htmlCls & "\">"
  972. renderAux(d, n,
  973. htmlHead & "<span$2 class=\"" & htmlCls & "-text\"><b>" & txt &
  974. ":</b></span>\n" & "$1</div>\n",
  975. "\n\n\\begin{rstadmonition}[borderline west={0.2em}{0pt}{" &
  976. texColor & "}]$2\n" &
  977. "{" & texSz & "\\color{" & texColor & "}{\\textbf{" & txt & ":}}} " &
  978. "$1\n\\end{rstadmonition}\n",
  979. result)
  980. proc renderHyperlink(d: PDoc, text, link: PRstNode, result: var string,
  981. external: bool, nimdoc = false, tooltip="") =
  982. var linkStr = ""
  983. block:
  984. let mode = d.escMode
  985. d.escMode = emUrl
  986. renderRstToOut(d, link, linkStr)
  987. d.escMode = mode
  988. discard safeProtocol(linkStr)
  989. var textStr = ""
  990. renderRstToOut(d, text, textStr)
  991. let nimDocStr = if nimdoc: " nimdoc" else: ""
  992. var tooltipStr = ""
  993. if tooltip != "":
  994. tooltipStr = """ title="$1"""" % [ esc(d.target, tooltip) ]
  995. if external:
  996. dispA(d.target, result,
  997. "<a class=\"reference external$3\"$4 href=\"$2\">$1</a>",
  998. "\\href{$2}{$1}", [textStr, linkStr, nimDocStr, tooltipStr])
  999. else:
  1000. dispA(d.target, result,
  1001. "<a class=\"reference internal$3\"$4 href=\"#$2\">$1</a>",
  1002. "\\hyperlink{$2}{$1} (p.~\\pageref{$2})",
  1003. [textStr, linkStr, nimDocStr, tooltipStr])
  1004. proc traverseForIndex*(d: PDoc, n: PRstNode) =
  1005. ## A version of [renderRstToOut] that only fills entries for ``.idx`` files.
  1006. var discarded: string
  1007. if n == nil: return
  1008. case n.kind
  1009. of rnIdx: renderIndexTerm(d, n, discarded)
  1010. of rnHeadline, rnMarkdownHeadline: renderHeadline(d, n, discarded)
  1011. of rnOverline: renderOverline(d, n, discarded)
  1012. else:
  1013. for i in 0 ..< len(n):
  1014. traverseForIndex(d, n.sons[i])
  1015. proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
  1016. if n == nil: return
  1017. case n.kind
  1018. of rnInner: renderAux(d, n, result)
  1019. of rnHeadline, rnMarkdownHeadline: renderHeadline(d, n, result)
  1020. of rnOverline: renderOverline(d, n, result)
  1021. of rnTransition: renderAux(d, n, "<hr$2 />\n", "\n\n\\vspace{0.6em}\\hrule$2\n", result)
  1022. of rnParagraph: renderAux(d, n, "<p$2>$1</p>\n", "\n\n$2\n$1\n\n", result)
  1023. of rnBulletList:
  1024. renderAux(d, n, "<ul$2 class=\"simple\">$1</ul>\n",
  1025. "\\begin{itemize}\n$2\n$1\\end{itemize}\n", result)
  1026. of rnBulletItem, rnEnumItem:
  1027. renderAux(d, n, "<li$2>$1</li>\n", "\\item $2$1\n", result)
  1028. of rnEnumList: renderEnumList(d, n, result)
  1029. of rnDefList, rnMdDefList:
  1030. renderAux(d, n, "<dl$2 class=\"docutils\">$1</dl>\n",
  1031. "\\begin{description}\n$2\n$1\\end{description}\n", result)
  1032. of rnDefItem: renderAux(d, n, result)
  1033. of rnDefName: renderAux(d, n, "<dt$2>$1</dt>\n", "$2\\item[$1]\\ ", result)
  1034. of rnDefBody: renderAux(d, n, "<dd$2>$1</dd>\n", "$2\n$1\n", result)
  1035. of rnFieldList:
  1036. var tmp = ""
  1037. for i in countup(0, len(n) - 1):
  1038. renderRstToOut(d, n.sons[i], tmp)
  1039. if tmp.len != 0:
  1040. dispA(d.target, result,
  1041. "<table$2 class=\"docinfo\" frame=\"void\" rules=\"none\">" &
  1042. "<col class=\"docinfo-name\" />" &
  1043. "<col class=\"docinfo-content\" />" &
  1044. "<tbody valign=\"top\">$1" &
  1045. "</tbody></table>",
  1046. "\\begin{description}\n$2\n$1\\end{description}\n",
  1047. [tmp, n.anchor.idS])
  1048. of rnField: renderField(d, n, result)
  1049. of rnFieldName:
  1050. renderAux(d, n, "<th class=\"docinfo-name\">$1:</th>",
  1051. "\\item[$1:]", result)
  1052. of rnFieldBody:
  1053. renderAux(d, n, "<td>$1</td>", " $1\n", result)
  1054. of rnIndex:
  1055. renderRstToOut(d, n.sons[2], result)
  1056. of rnOptionList:
  1057. renderAux(d, n, "<div$2 class=\"option-list\">$1</div>",
  1058. "\\begin{rstoptlist}$2\n$1\\end{rstoptlist}", result)
  1059. of rnOptionListItem:
  1060. var addclass = if n.order mod 2 == 1: " odd" else: ""
  1061. renderAux(d, n,
  1062. "<div class=\"option-list-item" & addclass & "\">$1</div>\n",
  1063. "$1", result)
  1064. of rnOptionGroup:
  1065. renderAux(d, n,
  1066. "<div class=\"option-list-label\"><tt><span class=\"option\">" &
  1067. "$1</span></tt></div>",
  1068. "\\item[\\rstcodeitem{\\spanoption{$1}}]", result)
  1069. of rnDescription:
  1070. renderAux(d, n, "<div class=\"option-list-description\">$1</div>",
  1071. " $1\n", result)
  1072. of rnOption, rnOptionString, rnOptionArgument:
  1073. raiseAssert "renderRstToOut"
  1074. of rnLiteralBlock:
  1075. renderAux(d, n, "<pre$2>$1</pre>\n",
  1076. "\n\n$2\\begin{rstpre}\n$1\n\\end{rstpre}\n\n", result)
  1077. of rnMarkdownBlockQuote:
  1078. d.curQuotationDepth = 1
  1079. var tmp = ""
  1080. renderAux(d, n, "$1", "$1", tmp)
  1081. let itemEnding =
  1082. if d.target == outHtml: "</blockquote>" else: "\\end{rstquote}"
  1083. tmp.add itemEnding.repeat(d.curQuotationDepth - 1)
  1084. dispA(d.target, result,
  1085. "<blockquote$2 class=\"markdown-quote\">$1</blockquote>\n",
  1086. "\n\\begin{rstquote}\n$2\n$1\\end{rstquote}\n", [tmp, n.anchor.idS])
  1087. of rnMarkdownBlockQuoteItem:
  1088. let addQuotationDepth = n.quotationDepth - d.curQuotationDepth
  1089. var itemPrefix: string # start or ending (quotation grey bar on the left)
  1090. if addQuotationDepth >= 0:
  1091. let s =
  1092. if d.target == outHtml: "<blockquote class=\"markdown-quote\">"
  1093. else: "\\begin{rstquote}"
  1094. itemPrefix = s.repeat(addQuotationDepth)
  1095. else:
  1096. let s =
  1097. if d.target == outHtml: "</blockquote>"
  1098. else: "\\end{rstquote}"
  1099. itemPrefix = s.repeat(-addQuotationDepth)
  1100. renderAux(d, n, itemPrefix & "<p>$1</p>", itemPrefix & "\n$1", result)
  1101. d.curQuotationDepth = n.quotationDepth
  1102. of rnLineBlock:
  1103. if n.sons.len == 1 and n.sons[0].lineIndent == "\n":
  1104. # whole line block is one empty line, no need to add extra spacing
  1105. renderAux(d, n, "<p$2>$1</p> ", "\n\n$2\n$1", result)
  1106. else: # add extra spacing around the line block for Latex
  1107. renderAux(d, n, "<p$2>$1</p>",
  1108. "\n\\vspace{0.5em}$2\n$1\\vspace{0.5em}\n", result)
  1109. of rnLineBlockItem:
  1110. if n.lineIndent.len == 0: # normal case - no additional indentation
  1111. renderAux(d, n, "$1<br/>", "\\noindent $1\n\n", result)
  1112. elif n.lineIndent == "\n": # add one empty line
  1113. renderAux(d, n, "<br/>", "\\vspace{1em}\n", result)
  1114. else: # additional indentation w.r.t. '| '
  1115. let indent = $(0.5 * (n.lineIndent.len - 1).toFloat) & "em"
  1116. renderAux(d, n,
  1117. "<span style=\"margin-left: " & indent & "\">$1</span><br/>",
  1118. "\\noindent\\hspace{" & indent & "}$1\n\n", result)
  1119. of rnBlockQuote:
  1120. renderAux(d, n, "<blockquote$2><p>$1</p></blockquote>\n",
  1121. "\\begin{quote}\n$2\n$1\\end{quote}\n", result)
  1122. of rnAdmonition: renderAdmonition(d, n, result)
  1123. of rnTable, rnGridTable, rnMarkdownTable:
  1124. renderAux(d, n,
  1125. "<table$2 border=\"1\" class=\"docutils\">$1</table>",
  1126. "\n$2\n\\begin{rsttab}{" &
  1127. "L".repeat(n.colCount) & "}\n\\toprule\n$1" &
  1128. "\\addlinespace[0.1em]\\bottomrule\n\\end{rsttab}", result)
  1129. of rnTableRow:
  1130. if len(n) >= 1:
  1131. case d.target
  1132. of outHtml:
  1133. result.add("<tr>")
  1134. renderAux(d, n, result)
  1135. result.add("</tr>\n")
  1136. of outLatex:
  1137. if n.sons[0].kind == rnTableHeaderCell:
  1138. result.add "\\rowcolor{gray!15} "
  1139. var spanLines: seq[(int, int)]
  1140. var nCell = 0
  1141. for uCell in 0 .. n.len - 1:
  1142. renderRstToOut(d, n.sons[uCell], result)
  1143. if n.sons[uCell].span > 0:
  1144. spanLines.add (nCell + 1, nCell + n.sons[uCell].span)
  1145. nCell += n.sons[uCell].span
  1146. else:
  1147. nCell += 1
  1148. if uCell != n.len - 1:
  1149. result.add(" & ")
  1150. result.add("\\\\")
  1151. if n.endsHeader: result.add("\\midrule\n")
  1152. for (start, stop) in spanLines:
  1153. result.add("\\cmidrule(lr){$1-$2}" % [$start, $stop])
  1154. result.add("\n")
  1155. of rnTableHeaderCell, rnTableDataCell:
  1156. case d.target
  1157. of outHtml:
  1158. let tag = if n.kind == rnTableHeaderCell: "th" else: "td"
  1159. var spanSpec: string
  1160. if n.span <= 1: spanSpec = ""
  1161. else:
  1162. spanSpec = " colspan=\"" & $n.span & "\" style=\"text-align: center\""
  1163. renderAux(d, n, "<$1$2>$$1</$1>" % [tag, spanSpec], "", result)
  1164. of outLatex:
  1165. let text = if n.kind == rnTableHeaderCell: "\\textbf{$1}" else: "$1"
  1166. var latexStr: string
  1167. if n.span <= 1: latexStr = text
  1168. else: latexStr = "\\multicolumn{" & $n.span & "}{c}{" & text & "}"
  1169. renderAux(d, n, "", latexStr, result)
  1170. of rnFootnoteGroup:
  1171. renderAux(d, n,
  1172. "<hr class=\"footnote\">" &
  1173. "<div class=\"footnote-group\">\n$1</div>\n",
  1174. "\n\n\\noindent\\rule{0.25\\linewidth}{.4pt}\n" &
  1175. "\\begin{rstfootnote}\n$1\\end{rstfootnote}\n\n",
  1176. result)
  1177. of rnFootnote, rnCitation:
  1178. var mark = ""
  1179. renderAux(d, n.sons[0], mark)
  1180. var body = ""
  1181. renderRstToOut(d, n.sons[1], body)
  1182. dispA(d.target, result,
  1183. "<div$2><div class=\"footnote-label\">" &
  1184. "<sup><strong><a href=\"#$4\">[$3]</a></strong></sup>" &
  1185. "</div> &ensp; $1\n</div>\n",
  1186. "\\item[\\textsuperscript{[$3]}]$2 $1\n",
  1187. [body, n.anchor.idS, mark, n.anchor])
  1188. of rnPandocRef:
  1189. renderHyperlink(d, text=n.sons[0], link=n.sons[1], result, external=false)
  1190. of rnRstRef:
  1191. renderHyperlink(d, text=n.sons[0], link=n.sons[0], result, external=false)
  1192. of rnStandaloneHyperlink:
  1193. renderHyperlink(d, text=n.sons[0], link=n.sons[0], result, external=true)
  1194. of rnInternalRef:
  1195. renderHyperlink(d, text=n.sons[0], link=n.sons[1], result, external=false)
  1196. of rnNimdocRef:
  1197. renderHyperlink(d, text=n.sons[0], link=n.sons[1], result, external=false,
  1198. nimdoc=true, tooltip=n.tooltip)
  1199. of rnHyperlink:
  1200. renderHyperlink(d, text=n.sons[0], link=n.sons[1], result, external=true)
  1201. of rnFootnoteRef:
  1202. var tmp = "["
  1203. renderAux(d, n.sons[0], tmp)
  1204. tmp.add "]"
  1205. dispA(d.target, result,
  1206. "<sup><strong><a class=\"reference internal\" href=\"#$2\">" &
  1207. "$1</a></strong></sup>",
  1208. "\\textsuperscript{\\hyperlink{$2}{\\textbf{$1}}}",
  1209. [tmp, n.sons[1].text])
  1210. of rnDirArg, rnRaw: renderAux(d, n, result)
  1211. of rnRawHtml:
  1212. if d.target != outLatex and not lastSon(n).isNil:
  1213. result.add addNodes(lastSon(n))
  1214. of rnRawLatex:
  1215. if d.target == outLatex and not lastSon(n).isNil:
  1216. result.add addNodes(lastSon(n))
  1217. of rnImage, rnFigure: renderImage(d, n, result)
  1218. of rnCodeBlock, rnInlineCode: renderCode(d, n, result)
  1219. of rnContainer: renderContainer(d, n, result)
  1220. of rnSubstitutionReferences, rnSubstitutionDef:
  1221. renderAux(d, n, "|$1|", "|$1|", result)
  1222. of rnDirective:
  1223. renderAux(d, n, "", "", result)
  1224. of rnUnknownRole, rnCodeFragment:
  1225. var tmp0 = ""
  1226. var tmp1 = ""
  1227. renderRstToOut(d, n.sons[0], tmp0)
  1228. renderRstToOut(d, n.sons[1], tmp1)
  1229. var class = tmp1
  1230. # don't allow missing role break latex compilation:
  1231. if d.target == outLatex and n.kind == rnUnknownRole: class = "Other"
  1232. if n.kind == rnCodeFragment:
  1233. dispA(d.target, result,
  1234. "<tt class=\"docutils literal\"><span class=\"pre $2\">" &
  1235. "$1</span></tt>",
  1236. "\\rstcode{\\span$2{$1}}", [tmp0, class])
  1237. else: # rnUnknownRole, not necessarily code/monospace font
  1238. dispA(d.target, result, "<span class=\"$2\">$1</span>", "\\span$2{$1}",
  1239. [tmp0, class])
  1240. of rnSub: renderAux(d, n, "<sub>$1</sub>", "\\rstsub{$1}", result)
  1241. of rnSup: renderAux(d, n, "<sup>$1</sup>", "\\rstsup{$1}", result)
  1242. of rnEmphasis: renderAux(d, n, "<em>$1</em>", "\\emph{$1}", result)
  1243. of rnStrongEmphasis:
  1244. renderAux(d, n, "<strong>$1</strong>", "\\textbf{$1}", result)
  1245. of rnTripleEmphasis:
  1246. renderAux(d, n, "<strong><em>$1</em></strong>",
  1247. "\\textbf{emph{$1}}", result)
  1248. of rnIdx:
  1249. renderIndexTerm(d, n, result)
  1250. of rnInlineLiteral, rnInterpretedText:
  1251. renderAux(d, n,
  1252. "<tt class=\"docutils literal\"><span class=\"pre\">$1</span></tt>",
  1253. "\\rstcode{$1}", result)
  1254. of rnInlineTarget:
  1255. var tmp = ""
  1256. renderAux(d, n, tmp)
  1257. dispA(d.target, result,
  1258. "<span class=\"target\" id=\"$2\">$1</span>",
  1259. "\\label{$2}\\hypertarget{$2}{$1}",
  1260. [tmp, rstnodeToRefname(n)])
  1261. of rnSmiley: renderSmiley(d, n, result)
  1262. of rnLeaf: result.add(esc(d.target, n.text, escMode=d.escMode))
  1263. of rnContents: d.hasToc = true
  1264. of rnDefaultRole: discard
  1265. of rnTitle:
  1266. d.meta[metaTitle] = ""
  1267. renderRstToOut(d, n.sons[0], d.meta[metaTitle])
  1268. d.meta[metaTitleRaw] = n.sons[0].addNodes
  1269. # -----------------------------------------------------------------------------
  1270. proc getVarIdx(varnames: openArray[string], id: string): int =
  1271. for i in countup(0, high(varnames)):
  1272. if cmpIgnoreStyle(varnames[i], id) == 0:
  1273. return i
  1274. result = -1
  1275. proc formatNamedVars*(frmt: string, varnames: openArray[string],
  1276. varvalues: openArray[string]): string =
  1277. var i = 0
  1278. var L = len(frmt)
  1279. result = ""
  1280. var num = 0
  1281. while i < L:
  1282. if frmt[i] == '$':
  1283. inc(i) # skip '$'
  1284. case frmt[i]
  1285. of '#':
  1286. add(result, varvalues[num])
  1287. inc(num)
  1288. inc(i)
  1289. of '$':
  1290. add(result, "$")
  1291. inc(i)
  1292. of '0'..'9':
  1293. var j = 0
  1294. while true:
  1295. j = (j * 10) + ord(frmt[i]) - ord('0')
  1296. inc(i)
  1297. if i > L-1 or frmt[i] notin {'0'..'9'}: break
  1298. if j > high(varvalues) + 1:
  1299. raise newException(ValueError, "invalid index: " & $j)
  1300. num = j
  1301. add(result, varvalues[j - 1])
  1302. of 'A'..'Z', 'a'..'z', '\x80'..'\xFF':
  1303. var id = ""
  1304. while true:
  1305. add(id, frmt[i])
  1306. inc(i)
  1307. if frmt[i] notin {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}: break
  1308. var idx = getVarIdx(varnames, id)
  1309. if idx >= 0:
  1310. add(result, varvalues[idx])
  1311. else:
  1312. raise newException(ValueError, "unknown substitution var: " & id)
  1313. of '{':
  1314. var id = ""
  1315. inc(i)
  1316. while frmt[i] != '}':
  1317. if frmt[i] == '\0':
  1318. raise newException(ValueError, "'}' expected")
  1319. add(id, frmt[i])
  1320. inc(i)
  1321. inc(i) # skip }
  1322. # search for the variable:
  1323. var idx = getVarIdx(varnames, id)
  1324. if idx >= 0: add(result, varvalues[idx])
  1325. else:
  1326. raise newException(ValueError, "unknown substitution var: " & id)
  1327. else:
  1328. raise newException(ValueError, "unknown substitution: $" & $frmt[i])
  1329. var start = i
  1330. while i < L:
  1331. if frmt[i] != '$': inc(i)
  1332. else: break
  1333. if i-1 >= start: add(result, substr(frmt, start, i - 1))
  1334. proc defaultConfig*(): StringTableRef =
  1335. ## Returns a default configuration for embedded HTML generation.
  1336. ##
  1337. ## The returned ``StringTableRef`` contains the parameters used by the HTML
  1338. ## engine to build the final output. For information on what these parameters
  1339. ## are and their purpose, please look up the file ``config/nimdoc.cfg``
  1340. ## bundled with the compiler.
  1341. ##
  1342. ## The only difference between the contents of that file and the values
  1343. ## provided by this proc is the ``doc.file`` variable. The ``doc.file``
  1344. ## variable of the configuration file contains HTML to build standalone
  1345. ## pages, while this proc returns just the content for procs like
  1346. ## ``rstToHtml`` to generate the bare minimum HTML.
  1347. result = newStringTable(modeStyleInsensitive)
  1348. template setConfigVar(key, val) =
  1349. result[key] = val
  1350. # If you need to modify these values, it might be worth updating the template
  1351. # file in config/nimdoc.cfg.
  1352. setConfigVar("split.item.toc", "20")
  1353. setConfigVar("doc.section", """
  1354. <div class="section" id="$sectionID">
  1355. <h1><a class="toc-backref" href="#$sectionTitleID">$sectionTitle</a></h1>
  1356. <dl class="item">
  1357. $content
  1358. </dl></div>
  1359. """)
  1360. setConfigVar("doc.section.toc", """
  1361. <li>
  1362. <a class="reference" href="#$sectionID" id="$sectionTitleID">$sectionTitle</a>
  1363. <ul class="simple">
  1364. $content
  1365. </ul>
  1366. </li>
  1367. """)
  1368. setConfigVar("doc.item", """
  1369. <dt id="$itemID"><a name="$itemSymOrIDEnc"></a><pre>$header</pre></dt>
  1370. <dd>
  1371. $desc
  1372. </dd>
  1373. """)
  1374. setConfigVar("doc.item.toc", """
  1375. <li><a class="reference" href="#$itemSymOrIDEnc"
  1376. title="$header_plain">$name</a></li>
  1377. """)
  1378. setConfigVar("doc.toc", """
  1379. <div class="navigation" id="navigation">
  1380. <ul class="simple">
  1381. $content
  1382. </ul>
  1383. </div>""")
  1384. setConfigVar("doc.body_toc", """
  1385. $tableofcontents
  1386. <div class="content" id="content">
  1387. $moduledesc
  1388. $content
  1389. </div>
  1390. """)
  1391. setConfigVar("doc.listing_start", "<pre$3 class = \"listing\">")
  1392. setConfigVar("doc.listing_end", "</pre>")
  1393. setConfigVar("doc.listing_button", "</pre>")
  1394. setConfigVar("doc.body_no_toc", "$moduledesc $content")
  1395. setConfigVar("doc.file", "$content")
  1396. setConfigVar("doc.smiley_format", "/images/smilies/$1.gif")
  1397. # ---------- forum ---------------------------------------------------------
  1398. proc rstToHtml*(s: string, options: RstParseOptions,
  1399. config: StringTableRef,
  1400. msgHandler: MsgHandler = rst.defaultMsgHandler): string {.gcsafe.} =
  1401. ## Converts an input rst string into embeddable HTML.
  1402. ##
  1403. ## This convenience proc parses any input string using rst markup (it doesn't
  1404. ## have to be a full document!) and returns an embeddable piece of HTML. The
  1405. ## proc is meant to be used in *online* environments without access to a
  1406. ## meaningful filesystem, and therefore rst ``include`` like directives won't
  1407. ## work. For an explanation of the ``config`` parameter see the
  1408. ## ``initRstGenerator`` proc. Example:
  1409. ##
  1410. ## ```nim
  1411. ## import packages/docutils/rstgen, strtabs
  1412. ##
  1413. ## echo rstToHtml("*Hello* **world**!", {},
  1414. ## newStringTable(modeStyleInsensitive))
  1415. ## # --> <em>Hello</em> <strong>world</strong>!
  1416. ## ```
  1417. ##
  1418. ## If you need to allow the rst ``include`` directive or tweak the generated
  1419. ## output you have to create your own ``RstGenerator`` with
  1420. ## ``initRstGenerator`` and related procs.
  1421. proc myFindFile(filename: string): string =
  1422. # we don't find any files in online mode:
  1423. result = ""
  1424. proc myFindRefFile(filename: string): (string, string) =
  1425. result = ("", "")
  1426. const filen = "input"
  1427. let (rst, filenames, t) = rstParse(s, filen,
  1428. line=LineRstInit, column=ColRstInit,
  1429. options, myFindFile, myFindRefFile, msgHandler)
  1430. var d: RstGenerator
  1431. initRstGenerator(d, outHtml, config, filen, myFindFile, msgHandler,
  1432. filenames, hasToc = t)
  1433. result = ""
  1434. renderRstToOut(d, rst, result)
  1435. strbasics.strip(result)
  1436. proc rstToLatex*(rstSource: string; options: RstParseOptions): string {.inline, since: (1, 3).} =
  1437. ## Convenience proc for `renderRstToOut` and `initRstGenerator`.
  1438. runnableExamples: doAssert rstToLatex("*Hello* **world**", {}) == """\emph{Hello} \textbf{world}"""
  1439. if rstSource.len == 0: return
  1440. let (rst, filenames, t) = rstParse(rstSource, "",
  1441. line=LineRstInit, column=ColRstInit,
  1442. options)
  1443. var rstGenera: RstGenerator
  1444. rstGenera.initRstGenerator(outLatex, defaultConfig(), "input",
  1445. filenames=filenames, hasToc = t)
  1446. rstGenera.renderRstToOut(rst, result)
  1447. strbasics.strip(result)