rstgen.nim 63 KB

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