trstgen.nim 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692
  1. discard """
  2. outputsub: ""
  3. """
  4. # tests for rstgen module.
  5. import ../../lib/packages/docutils/rstgen
  6. import ../../lib/packages/docutils/rst
  7. import unittest, strutils, strtabs
  8. import std/private/miscdollars
  9. import std/assertions
  10. const
  11. NoSandboxOpts = {roPreferMarkdown, roSupportMarkdown, roNimFile, roSandboxDisabled}
  12. preferMarkdown = {roPreferMarkdown, roSupportMarkdown, roNimFile}
  13. preferRst = {roSupportMarkdown, roNimFile}
  14. proc toHtml(input: string,
  15. rstOptions: RstParseOptions = preferMarkdown,
  16. error: ref string = nil,
  17. warnings: ref seq[string] = nil): string =
  18. ## If `error` is nil then no errors should be generated.
  19. ## The same goes for `warnings`.
  20. proc testMsgHandler(filename: string, line, col: int, msgkind: MsgKind,
  21. arg: string) =
  22. let mc = msgkind.whichMsgClass
  23. let a = $msgkind % arg
  24. var message: string
  25. toLocation(message, filename, line, col + ColRstOffset)
  26. message.add " $1: $2" % [$mc, a]
  27. if mc == mcError:
  28. if error == nil:
  29. raise newException(EParseError, "[unexpected error] " & message)
  30. error[] = message
  31. # we check only first error because subsequent ones may be meaningless
  32. raise newException(EParseError, "")
  33. else:
  34. doAssert warnings != nil, "unexpected RST warning '" & message & "'"
  35. warnings[].add message
  36. try:
  37. result = rstToHtml(input, rstOptions, defaultConfig(),
  38. msgHandler=testMsgHandler)
  39. except EParseError as e:
  40. if e.msg != "":
  41. result = e.msg
  42. # inline code tags (for parsing originated from highlite.nim)
  43. proc id(str: string): string = """<span class="Identifier">""" & str & "</span>"
  44. proc op(str: string): string = """<span class="Operator">""" & str & "</span>"
  45. proc pu(str: string): string = """<span class="Punctuation">""" & str & "</span>"
  46. proc optionListLabel(opt: string): string =
  47. """<div class="option-list-label"><tt><span class="option">""" &
  48. opt &
  49. "</span></tt></div>"
  50. suite "YAML syntax highlighting":
  51. test "Basics":
  52. let input = """.. code-block:: yaml
  53. %YAML 1.2
  54. ---
  55. a string: string
  56. a list:
  57. - item 1
  58. - item 2
  59. a map:
  60. ? key
  61. : value
  62. ..."""
  63. let output = input.toHtml({})
  64. doAssert output == """<pre class = "listing"><span class="Directive">%YAML 1.2</span>
  65. <span class="Keyword">---</span>
  66. <span class="StringLit">a string</span><span class="Punctuation">:</span> <span class="StringLit">string</span>
  67. <span class="StringLit">a list</span><span class="Punctuation">:</span>
  68. <span class="Punctuation">-</span> <span class="StringLit">item 1</span>
  69. <span class="Punctuation">-</span> <span class="StringLit">item 2</span>
  70. <span class="StringLit">a map</span><span class="Punctuation">:</span>
  71. <span class="Punctuation">?</span> <span class="StringLit">key</span>
  72. <span class="Punctuation">:</span> <span class="StringLit">value</span>
  73. <span class="Keyword">...</span></pre>"""
  74. test "Block scalars":
  75. let input = """.. code-block:: yaml
  76. a literal block scalar: |
  77. some text
  78. # not a comment
  79. # a comment, since less indented
  80. # another comment
  81. a folded block scalar: >2
  82. some text
  83. # not a comment since indented as specified
  84. # a comment
  85. another literal block scalar:
  86. |+ # comment after header
  87. allowed, since more indented than parent"""
  88. let output = input.toHtml({})
  89. doAssert output == """<pre class = "listing"><span class="StringLit">a literal block scalar</span><span class="Punctuation">:</span> <span class="Command">|</span><span class="Command"></span><span class="LongStringLit">
  90. some text
  91. # not a comment
  92. </span><span class="Comment"># a comment, since less indented</span>
  93. <span class="Comment"># another comment</span>
  94. <span class="StringLit">a folded block scalar</span><span class="Punctuation">:</span> <span class="Command">&gt;2</span><span class="Command"></span><span class="LongStringLit">
  95. some text
  96. # not a comment since indented as specified
  97. </span><span class="Comment"># a comment</span>
  98. <span class="StringLit">another literal block scalar</span><span class="Punctuation">:</span>
  99. <span class="Command">|+</span> <span class="Comment"># comment after header</span><span class="LongStringLit">
  100. allowed, since more indented than parent</span></pre>"""
  101. test "Directives":
  102. let input = """.. code-block:: yaml
  103. %YAML 1.2
  104. ---
  105. %not a directive
  106. ...
  107. %a directive
  108. ...
  109. a string
  110. % not a directive
  111. ...
  112. %TAG ! !foo:"""
  113. let output = input.toHtml({})
  114. doAssert output == """<pre class = "listing"><span class="Directive">%YAML 1.2</span>
  115. <span class="Keyword">---</span>
  116. <span class="StringLit">%not a directive</span>
  117. <span class="Keyword">...</span>
  118. <span class="Directive">%a directive</span>
  119. <span class="Keyword">...</span>
  120. <span class="StringLit">a string</span>
  121. <span class="StringLit">% not a directive</span>
  122. <span class="Keyword">...</span>
  123. <span class="Directive">%TAG ! !foo:</span></pre>"""
  124. test "Flow Style and Numbers":
  125. let input = """.. code-block:: yaml
  126. {
  127. "quoted string": 42,
  128. 'single quoted string': false,
  129. [ list, "with", 'entries' ]: 73.32e-73,
  130. more numbers: [-783, 11e78],
  131. not numbers: [ 42e, 0023, +32.37, 8 ball]
  132. }"""
  133. let output = input.toHtml({})
  134. doAssert output == """<pre class = "listing"><span class="Punctuation">{</span>
  135. <span class="StringLit">&quot;</span><span class="StringLit">quoted string&quot;</span><span class="Punctuation">:</span> <span class="DecNumber">42</span><span class="Punctuation">,</span>
  136. <span class="StringLit">'single quoted string'</span><span class="Punctuation">:</span> <span class="StringLit">false</span><span class="Punctuation">,</span>
  137. <span class="Punctuation">[</span> <span class="StringLit">list</span><span class="Punctuation">,</span> <span class="StringLit">&quot;</span><span class="StringLit">with&quot;</span><span class="Punctuation">,</span> <span class="StringLit">'entries'</span> <span class="Punctuation">]</span><span class="Punctuation">:</span> <span class="FloatNumber">73.32e-73</span><span class="Punctuation">,</span>
  138. <span class="StringLit">more numbers</span><span class="Punctuation">:</span> <span class="Punctuation">[</span><span class="DecNumber">-783</span><span class="Punctuation">,</span> <span class="FloatNumber">11e78</span><span class="Punctuation">]</span><span class="Punctuation">,</span>
  139. <span class="StringLit">not numbers</span><span class="Punctuation">:</span> <span class="Punctuation">[</span> <span class="StringLit">42e</span><span class="Punctuation">,</span> <span class="StringLit">0023</span><span class="Punctuation">,</span> <span class="StringLit">+32.37</span><span class="Punctuation">,</span> <span class="StringLit">8 ball</span><span class="Punctuation">]</span>
  140. <span class="Punctuation">}</span></pre>"""
  141. test "Directives: warnings":
  142. let input = dedent"""
  143. .. non-existent-warning: Paragraph.
  144. .. another.wrong:warning::: Paragraph.
  145. """
  146. var warnings = new seq[string]
  147. let output = input.toHtml(warnings=warnings)
  148. check output == ""
  149. doAssert warnings[].len == 2
  150. check "(1, 24) Warning: RST style:" in warnings[0]
  151. check "double colon :: may be missing at end of 'non-existent-warning'" in warnings[0]
  152. check "(3, 25) Warning: RST style:" in warnings[1]
  153. check "RST style: too many colons for a directive (should be ::)" in warnings[1]
  154. test "not a directive":
  155. let input = "..warning:: I am not a warning."
  156. check input.toHtml == input
  157. test "Anchors, Aliases, Tags":
  158. let input = """.. code-block:: yaml
  159. --- !!map
  160. !!str string: !<tag:yaml.org,2002:int> 42
  161. ? &anchor !!seq []:
  162. : !localtag foo
  163. alias: *anchor
  164. """
  165. let output = input.toHtml({})
  166. doAssert output == """<pre class = "listing"><span class="Keyword">---</span> <span class="TagStart">!!map</span>
  167. <span class="TagStart">!!str</span> <span class="StringLit">string</span><span class="Punctuation">:</span> <span class="TagStart">!&lt;tag:yaml.org,2002:int&gt;</span> <span class="DecNumber">42</span>
  168. <span class="Punctuation">?</span> <span class="Label">&amp;anchor</span> <span class="TagStart">!!seq</span> <span class="Punctuation">[</span><span class="Punctuation">]</span><span class="Punctuation">:</span>
  169. <span class="Punctuation">:</span> <span class="TagStart">!localtag</span> <span class="StringLit">foo</span>
  170. <span class="StringLit">alias</span><span class="Punctuation">:</span> <span class="Reference">*anchor</span></pre>"""
  171. test "Edge cases":
  172. let input = """.. code-block:: yaml
  173. ...
  174. %a string:
  175. a:string:not:a:map
  176. ...
  177. not a list:
  178. -2
  179. -3
  180. -4
  181. example.com/not/a#comment:
  182. ?not a map key
  183. """
  184. let output = input.toHtml({})
  185. doAssert output == """<pre class = "listing"><span class="Keyword">...</span>
  186. <span class="StringLit">%a string</span><span class="Punctuation">:</span>
  187. <span class="StringLit">a:string:not:a:map</span>
  188. <span class="Keyword">...</span>
  189. <span class="StringLit">not a list</span><span class="Punctuation">:</span>
  190. <span class="DecNumber">-2</span>
  191. <span class="DecNumber">-3</span>
  192. <span class="DecNumber">-4</span>
  193. <span class="StringLit">example.com/not/a#comment</span><span class="Punctuation">:</span>
  194. <span class="StringLit">?not a map key</span></pre>"""
  195. suite "RST/Markdown general":
  196. test "RST emphasis":
  197. doAssert rstToHtml("*Hello* **world**!", {},
  198. newStringTable(modeStyleInsensitive)) ==
  199. "<em>Hello</em> <strong>world</strong>!"
  200. test "Markdown links":
  201. check("(( [Nim](https://nim-lang.org/) ))".toHtml ==
  202. """(( <a class="reference external" href="https://nim-lang.org/">Nim</a> ))""")
  203. check("(([Nim](https://nim-lang.org/)))".toHtml ==
  204. """((<a class="reference external" href="https://nim-lang.org/">Nim</a>))""")
  205. check("[[Nim](https://nim-lang.org/)]".toHtml ==
  206. """[<a class="reference external" href="https://nim-lang.org/">Nim</a>]""")
  207. test "Markdown tables":
  208. let input1 = """
  209. | A1 header | A2 \| not fooled
  210. | :--- | ----: |
  211. | C1 | C2 **bold** | ignored |
  212. | D1 `code \|` | D2 | also ignored
  213. | E1 \| text |
  214. | | F2 without pipe
  215. not in table"""
  216. let output1 = input1.toHtml
  217. #[
  218. TODO: `\|` inside a table cell should render as `|`
  219. `|` outside a table cell should render as `\|`
  220. consistently with markdown, see https://stackoverflow.com/a/66557930/1426932
  221. ]#
  222. check(output1 == """
  223. <table border="1" class="docutils"><tr><th>A1 header</th><th>A2 | not fooled</th></tr>
  224. <tr><td>C1</td><td>C2 <strong>bold</strong></td></tr>
  225. <tr><td>D1 <tt class="docutils literal"><span class="pre">""" & id"code" & " " & op"\|" & """</span></tt></td><td>D2</td></tr>
  226. <tr><td>E1 | text</td><td></td></tr>
  227. <tr><td></td><td>F2 without pipe</td></tr>
  228. </table><p>not in table</p>""")
  229. let input2 = """
  230. | A1 header | A2 |
  231. | --- | --- |"""
  232. let output2 = input2.toHtml
  233. doAssert output2 == """<table border="1" class="docutils"><tr><th>A1 header</th><th>A2</th></tr>
  234. </table>"""
  235. test "RST tables":
  236. let input1 = """
  237. Test 2 column/4 rows table:
  238. ==== ===
  239. H0 H1
  240. ==== ===
  241. A0 A1
  242. ==== ===
  243. A2 A3
  244. ==== ===
  245. A4 A5
  246. ==== === """
  247. let output1 = rstToLatex(input1, {})
  248. doAssert "{LL}" in output1 # 2 columns
  249. doAssert count(output1, "\\\\") == 4 # 4 rows
  250. for cell in ["H0", "H1", "A0", "A1", "A2", "A3", "A4", "A5"]:
  251. doAssert cell in output1
  252. let input2 = """
  253. Now test 3 columns / 2 rows, and also borders containing 4 =, 3 =, 1 = signs:
  254. ==== === =
  255. H0 H1 H
  256. ==== === =
  257. A0 A1 X
  258. Ax Y
  259. ==== === = """
  260. let output2 = rstToLatex(input2, {})
  261. doAssert "{LLL}" in output2 # 3 columns
  262. doAssert count(output2, "\\\\") == 2 # 2 rows
  263. for cell in ["H0", "H1", "H", "A0", "A1", "X", "Ax", "Y"]:
  264. doAssert cell in output2
  265. test "RST adornments":
  266. let input1 = """
  267. Check that a few punctuation symbols are not parsed as adornments:
  268. :word1: word2 .... word3 """
  269. let output1 = input1.toHtml
  270. discard output1
  271. test "RST sections":
  272. let input1 = """
  273. Long chapter name
  274. '''''''''''''''''''
  275. """
  276. let output1 = input1.toHtml
  277. doAssert "Long chapter name" in output1 and "<h1" in output1
  278. let input2 = """
  279. Short chapter name:
  280. ChA
  281. ===
  282. """
  283. let output2 = input2.toHtml
  284. doAssert "ChA" in output2 and "<h1" in output2
  285. let input3 = """
  286. Very short chapter name:
  287. X
  288. ~
  289. """
  290. let output3 = input3.toHtml
  291. doAssert "X" in output3 and "<h1" in output3
  292. let input4 = """
  293. Check that short underline is not enough to make section:
  294. Wrong chapter
  295. ------------
  296. """
  297. var error4 = new string
  298. let output4 = input4.toHtml(error = error4)
  299. check(error4[] == "input(3, 1) Error: new section expected (underline " &
  300. "\'------------\' is too short)")
  301. let input5 = """
  302. Check that punctuation after adornment and indent are not detected as adornment.
  303. Some chapter
  304. --------------
  305. "punctuation symbols" """
  306. let output5 = input5.toHtml
  307. doAssert "&quot;punctuation symbols&quot;" in output5 and "<h1" in output5
  308. # check that EOF after adornment does not prevent it parsing as heading
  309. let input6 = dedent """
  310. Some chapter
  311. ------------"""
  312. let output6 = input6.toHtml
  313. doAssert "<h1 id=\"some-chapter\">Some chapter</h1>" in output6
  314. # check that overline and underline match
  315. let input7 = dedent """
  316. ------------
  317. Some chapter
  318. -----------
  319. """
  320. var error7 = new string
  321. let output7 = input7.toHtml(error=error7)
  322. check(error7[] == "input(1, 1) Error: new section expected (underline " &
  323. "\'-----------\' does not match overline \'------------\')")
  324. let input8 = dedent """
  325. -----------
  326. Overflow
  327. -----------
  328. """
  329. var error8 = new string
  330. let output8 = input8.toHtml(error=error8)
  331. check(error8[] == "input(1, 1) Error: new section expected (overline " &
  332. "\'-----------\' is too short)")
  333. # check that hierarchy of title styles works
  334. let input9good = dedent """
  335. Level1
  336. ======
  337. Level2
  338. ------
  339. Level3
  340. ~~~~~~
  341. L1
  342. ==
  343. Another2
  344. --------
  345. More3
  346. ~~~~~
  347. """
  348. let output9good = input9good.toHtml(preferRst)
  349. doAssert "<h1 id=\"level1\">Level1</h1>" in output9good
  350. doAssert "<h2 id=\"level2\">Level2</h2>" in output9good
  351. doAssert "<h3 id=\"level3\">Level3</h3>" in output9good
  352. doAssert "<h1 id=\"l1\">L1</h1>" in output9good
  353. doAssert "<h2 id=\"another2\">Another2</h2>" in output9good
  354. doAssert "<h3 id=\"more3\">More3</h3>" in output9good
  355. # check that swap causes an exception
  356. let input9Bad = dedent """
  357. Level1
  358. ======
  359. Level2
  360. ------
  361. Level3
  362. ~~~~~~
  363. L1
  364. ==
  365. More
  366. ~~~~
  367. Another
  368. -------
  369. """
  370. var error9Bad = new string
  371. let output9Bad = input9Bad.toHtml(preferRst, error=error9Bad)
  372. check(error9Bad[] == "input(15, 1) Error: new section expected (section " &
  373. "level inconsistent: underline ~~~~~ unexpectedly found, while " &
  374. "the following intermediate section level(s) are missing on " &
  375. "lines 12..15: underline -----)")
  376. test "RST sections overline":
  377. # the same as input9good but with overline headings
  378. # first overline heading has a special meaning: document title
  379. let input = dedent """
  380. ======
  381. Title0
  382. ======
  383. +++++++++
  384. SubTitle0
  385. +++++++++
  386. ------
  387. Level1
  388. ------
  389. Level2
  390. ------
  391. ~~~~~~
  392. Level3
  393. ~~~~~~
  394. --
  395. L1
  396. --
  397. Another2
  398. --------
  399. ~~~~~
  400. More3
  401. ~~~~~
  402. """
  403. var rstGenera: RstGenerator
  404. var output: string
  405. let (rst, files, _) = rstParse(input, "", 1, 1, {})
  406. rstGenera.initRstGenerator(outHtml, defaultConfig(), "input", filenames = files)
  407. rstGenera.renderRstToOut(rst, output)
  408. doAssert rstGenera.meta[metaTitle] == "Title0"
  409. doAssert rstGenera.meta[metaSubtitle] == "SubTitle0"
  410. doAssert "<h1 id=\"level1\"><center>Level1</center></h1>" in output
  411. doAssert "<h2 id=\"level2\">Level2</h2>" in output
  412. doAssert "<h3 id=\"level3\"><center>Level3</center></h3>" in output
  413. doAssert "<h1 id=\"l1\"><center>L1</center></h1>" in output
  414. doAssert "<h2 id=\"another2\">Another2</h2>" in output
  415. doAssert "<h3 id=\"more3\"><center>More3</center></h3>" in output
  416. test "RST sections overline 2":
  417. # check that a paragraph prevents interpreting overlines as document titles
  418. let input = dedent """
  419. Paragraph
  420. ======
  421. Title0
  422. ======
  423. +++++++++
  424. SubTitle0
  425. +++++++++
  426. """
  427. var rstGenera: RstGenerator
  428. var output: string
  429. let (rst, files, _) = rstParse(input, "", 1, 1, {})
  430. rstGenera.initRstGenerator(outHtml, defaultConfig(), "input", filenames=files)
  431. rstGenera.renderRstToOut(rst, output)
  432. doAssert rstGenera.meta[metaTitle] == ""
  433. doAssert rstGenera.meta[metaSubtitle] == ""
  434. doAssert "<h1 id=\"title0\"><center>Title0</center></h1>" in output
  435. doAssert "<h2 id=\"subtitle0\"><center>SubTitle0</center></h2>" in output
  436. test "RST+Markdown sections":
  437. # check that RST and Markdown headings don't interfere
  438. let input = dedent """
  439. ======
  440. Title0
  441. ======
  442. MySection1a
  443. +++++++++++
  444. # MySection1b
  445. MySection1c
  446. +++++++++++
  447. ##### MySection5a
  448. MySection2a
  449. -----------
  450. """
  451. var rstGenera: RstGenerator
  452. var output: string
  453. let (rst, files, _) = rstParse(input, "", 1, 1, {roSupportMarkdown})
  454. rstGenera.initRstGenerator(outHtml, defaultConfig(), "input", filenames=files)
  455. rstGenera.renderRstToOut(rst, output)
  456. doAssert rstGenera.meta[metaTitle] == "Title0"
  457. doAssert rstGenera.meta[metaSubtitle] == ""
  458. doAssert output ==
  459. "\n<h1 id=\"mysection1a\">MySection1a</h1>" & # RST
  460. "\n<h1 id=\"mysection1b\">MySection1b</h1>" & # Markdown
  461. "\n<h1 id=\"mysection1c\">MySection1c</h1>" & # RST
  462. "\n<h5 id=\"mysection5a\">MySection5a</h5>" & # Markdown
  463. "\n<h2 id=\"mysection2a\">MySection2a</h2>" # RST
  464. test "RST inline text":
  465. let input1 = "GC_step"
  466. let output1 = input1.toHtml
  467. doAssert output1 == "GC_step"
  468. test "RST anchors/links to headings":
  469. # Currently in TOC mode anchors are modified (for making links from
  470. # the TOC unique)
  471. let inputNoToc = dedent"""
  472. Type relations
  473. ==============
  474. Convertible relation
  475. --------------------
  476. Ref. `Convertible relation`_
  477. """
  478. let outputNoToc = inputNoToc.toHtml
  479. check outputNoToc.count("id=\"type-relations\"") == 1
  480. check outputNoToc.count("id=\"convertible-relation\"") == 1
  481. check outputNoToc.count("href=\"#convertible-relation\"") == 1
  482. let inputTocCases = @[
  483. dedent"""
  484. .. contents::
  485. Type relations
  486. ==============
  487. Convertible relation
  488. --------------------
  489. Ref. `Convertible relation`_
  490. Guards and locks
  491. ================
  492. """,
  493. dedent"""
  494. Ref. `Convertible relation`_
  495. .. contents::
  496. Type relations
  497. ==============
  498. Convertible relation
  499. --------------------
  500. Guards and locks
  501. ================
  502. """
  503. ]
  504. for inputToc in inputTocCases:
  505. let outputToc = inputToc.toHtml
  506. check outputToc.count("id=\"type-relations\"") == 1
  507. check outputToc.count("id=\"type-relations-convertible-relation\"") == 1
  508. check outputToc.count("id=\"convertible-relation\">") == 0
  509. # Besides "Ref.", heading also contains link to itself:
  510. check outputToc.count(
  511. "href=\"#type-relations-convertible-relation\">") == 2
  512. check outputToc.count("href=\"#convertible-relation\"") == 0
  513. test "RST links":
  514. let input1 = """
  515. Want to learn about `my favorite programming language`_?
  516. .. _my favorite programming language: https://nim-lang.org"""
  517. let output1 = input1.toHtml
  518. doAssert "<a" in output1 and "href=\"https://nim-lang.org\"" in output1
  519. test "RST transitions":
  520. let input1 = """
  521. context1
  522. ~~~~
  523. context2
  524. """
  525. let output1 = input1.toHtml(preferRst)
  526. doAssert "<hr" in output1
  527. let input2 = """
  528. This is too short to be a transition:
  529. ---
  530. context2
  531. """
  532. var error2 = new string
  533. let output2 = input2.toHtml(error=error2)
  534. check(error2[] == "input(3, 1) Error: new section expected (overline " &
  535. "\'---\' is too short)")
  536. test "RST literal block":
  537. let input1 = """
  538. Test literal block
  539. ::
  540. check """
  541. let output1 = input1.toHtml(preferRst)
  542. doAssert "<pre>" in output1
  543. test "Markdown code block":
  544. let input1 = """
  545. ```
  546. let x = 1
  547. ``` """
  548. let output1 = input1.toHtml({roSupportMarkdown, roPreferMarkdown})
  549. doAssert "<pre" in output1 and "class=\"Keyword\"" notin output1
  550. # Check Nim highlighting by default in .nim files:
  551. let output1nim = input1.toHtml({roSupportMarkdown, roPreferMarkdown,
  552. roNimFile})
  553. doAssert "<pre" in output1nim and "class=\"Keyword\"" in output1nim
  554. let input2 = """
  555. Parse the block with language specifier:
  556. ```Nim
  557. let x = 1
  558. ``` """
  559. let output2 = input2.toHtml
  560. doAssert "<pre" in output2 and "class=\"Keyword\"" in output2
  561. test "interpreted text":
  562. check("""`foo.bar`""".toHtml ==
  563. """<tt class="docutils literal"><span class="pre">""" &
  564. id"foo" & op"." & id"bar" & "</span></tt>")
  565. check("""`foo\`\`bar`""".toHtml ==
  566. """<tt class="docutils literal"><span class="pre">""" &
  567. id"foo" & pu"`" & pu"`" & id"bar" & "</span></tt>")
  568. check("""`foo\`bar`""".toHtml ==
  569. """<tt class="docutils literal"><span class="pre">""" &
  570. id"foo" & pu"`" & id"bar" & "</span></tt>")
  571. check("""`\`bar`""".toHtml ==
  572. """<tt class="docutils literal"><span class="pre">""" &
  573. pu"`" & id"bar" & "</span></tt>")
  574. check("""`a\b\x\\ar`""".toHtml ==
  575. """<tt class="docutils literal"><span class="pre">""" &
  576. id"a" & op"""\""" & id"b" & op"""\""" & id"x" & op"""\\""" & id"ar" &
  577. "</span></tt>")
  578. test "inline literal":
  579. check """``foo.bar``""".toHtml == """<tt class="docutils literal"><span class="pre">foo.bar</span></tt>"""
  580. check """``foo\bar``""".toHtml == """<tt class="docutils literal"><span class="pre">foo\bar</span></tt>"""
  581. check """``f\`o\\o\b`ar``""".toHtml == """<tt class="docutils literal"><span class="pre">f\`o\\o\b`ar</span></tt>"""
  582. test "default-role":
  583. # nim(default) -> literal -> nim -> code(=literal)
  584. let input = dedent"""
  585. Par1 `value1`.
  586. .. default-role:: literal
  587. Par2 `value2`.
  588. .. default-role:: nim
  589. Par3 `value3`.
  590. .. default-role:: code
  591. Par4 `value4`."""
  592. let p1 = """Par1 <tt class="docutils literal"><span class="pre">""" & id"value1" & "</span></tt>."
  593. let p2 = """<p>Par2 <tt class="docutils literal"><span class="pre">value2</span></tt>.</p>"""
  594. let p3 = """<p>Par3 <tt class="docutils literal"><span class="pre">""" & id"value3" & "</span></tt>.</p>"
  595. let p4 = """<p>Par4 <tt class="docutils literal"><span class="pre">value4</span></tt>.</p>"""
  596. let expected = p1 & p2 & "\n" & p3 & "\n" & p4
  597. check(
  598. input.toHtml(NoSandboxOpts) == expected
  599. )
  600. test "role directive":
  601. let input = dedent"""
  602. .. role:: y(code)
  603. :language: yaml
  604. .. role:: brainhelp(code)
  605. :language: brainhelp
  606. """
  607. var warnings = new seq[string]
  608. let output = input.toHtml(
  609. NoSandboxOpts,
  610. warnings=warnings
  611. )
  612. check(warnings[].len == 1 and "language 'brainhelp' not supported" in warnings[0])
  613. test "RST comments":
  614. let input1 = """
  615. Check that comment disappears:
  616. ..
  617. some comment """
  618. let output1 = input1.toHtml
  619. doAssert output1 == "Check that comment disappears:"
  620. test "RST line blocks + headings":
  621. let input = """
  622. =====
  623. Test1
  624. =====
  625. |
  626. |
  627. | line block
  628. | other line
  629. """
  630. var rstGenera: RstGenerator
  631. var output: string
  632. let (rst, files, _) = rstParse(input, "", 1, 1, {})
  633. rstGenera.initRstGenerator(outHtml, defaultConfig(), "input", filenames=files)
  634. rstGenera.renderRstToOut(rst, output)
  635. doAssert rstGenera.meta[metaTitle] == "Test1"
  636. # check that title was not overwritten to '|'
  637. doAssert output == "<p><br/><br/>line block<br/>other line<br/></p>"
  638. let output1l = rstToLatex(input, {})
  639. doAssert "line block\n\n" in output1l
  640. doAssert "other line\n\n" in output1l
  641. doAssert output1l.count("\\vspace") == 2 + 2 # +2 surrounding paddings
  642. test "RST line blocks":
  643. let input2 = dedent"""
  644. Paragraph1
  645. |
  646. Paragraph2"""
  647. let output2 = input2.toHtml
  648. doAssert "Paragraph1<p><br/></p> <p>Paragraph2</p>" == output2
  649. let input3 = dedent"""
  650. | xxx
  651. | yyy
  652. | zzz"""
  653. let output3 = input3.toHtml
  654. doAssert "xxx<br/>" in output3
  655. doAssert "<span style=\"margin-left: 1.0em\">yyy</span><br/>" in output3
  656. doAssert "<span style=\"margin-left: 2.0em\">zzz</span><br/>" in output3
  657. # check that '| ' with a few spaces is still parsed as new line
  658. let input4 = dedent"""
  659. | xxx
  660. |
  661. | zzz"""
  662. let output4 = input4.toHtml
  663. doAssert "xxx<br/><br/>" in output4
  664. doAssert "<span style=\"margin-left: 2.0em\">zzz</span><br/>" in output4
  665. test "RST enumerated lists":
  666. let input1 = dedent """
  667. 1. line1
  668. 1
  669. 2. line2
  670. 2
  671. 3. line3
  672. 3
  673. 4. line4
  674. 4
  675. 5. line5
  676. 5
  677. """
  678. let output1 = input1.toHtml
  679. for i in 1..5:
  680. doAssert ($i & ". line" & $i) notin output1
  681. doAssert ("<li>line" & $i & " " & $i & "</li>") in output1
  682. let input2 = dedent """
  683. 3. line3
  684. 4. line4
  685. 5. line5
  686. 7. line7
  687. 8. line8
  688. """
  689. let output2 = input2.toHtml
  690. for i in [3, 4, 5, 7, 8]:
  691. doAssert ($i & ". line" & $i) notin output2
  692. doAssert ("<li>line" & $i & "</li>") in output2
  693. # check that nested enumerated lists work
  694. let input3 = dedent """
  695. 1. a) string1
  696. 2. string2
  697. """
  698. let output3 = input3.toHtml
  699. doAssert count(output3, "<ol ") == 2
  700. doAssert count(output3, "</ol>") == 2
  701. doAssert "<li>string1</li>" in output3 and "<li>string2</li>" in output3
  702. let input4 = dedent """
  703. Check that enumeration specifiers are respected
  704. 9. string1
  705. 10. string2
  706. 12. string3
  707. b) string4
  708. c) string5
  709. e) string6
  710. """
  711. let output4 = input4.toHtml
  712. doAssert count(output4, "<ol ") == 4
  713. doAssert count(output4, "</ol>") == 4
  714. for enumerator in [9, 12]:
  715. doAssert "start=\"$1\"" % [$enumerator] in output4
  716. for enumerator in [2, 5]: # 2=b, 5=e
  717. doAssert "start=\"$1\"" % [$enumerator] in output4
  718. let input5 = dedent """
  719. Check that auto-numbered enumeration lists work.
  720. #. string1
  721. #. string2
  722. #. string3
  723. #) string5
  724. #) string6
  725. """
  726. let output5 = input5.toHtml
  727. doAssert count(output5, "<ol ") == 2
  728. doAssert count(output5, "</ol>") == 2
  729. doAssert count(output5, "<li>") == 5
  730. let input5a = dedent """
  731. Auto-numbered RST list can start with 1 even when Markdown support is on.
  732. 1. string1
  733. #. string2
  734. #. string3
  735. """
  736. let output5a = input5a.toHtml
  737. doAssert count(output5a, "<ol ") == 1
  738. doAssert count(output5a, "</ol>") == 1
  739. doAssert count(output5a, "<li>") == 3
  740. let input6 = dedent """
  741. ... And for alphabetic enumerators too!
  742. b. string1
  743. #. string2
  744. #. string3
  745. """
  746. let output6 = input6.toHtml
  747. doAssert count(output6, "<ol ") == 1
  748. doAssert count(output6, "</ol>") == 1
  749. doAssert count(output6, "<li>") == 3
  750. doAssert "start=\"2\"" in output6 and "class=\"loweralpha simple\"" in output6
  751. let input7 = dedent """
  752. ... And for uppercase alphabetic enumerators.
  753. C. string1
  754. #. string2
  755. #. string3
  756. """
  757. let output7 = input7.toHtml
  758. doAssert count(output7, "<ol ") == 1
  759. doAssert count(output7, "</ol>") == 1
  760. doAssert count(output7, "<li>") == 3
  761. doAssert "start=\"3\"" in output7 and "class=\"upperalpha simple\"" in output7
  762. # check that it's not recognized as enum.list without indentation on 2nd line
  763. let input8 = dedent """
  764. Paragraph.
  765. A. stringA
  766. B. stringB
  767. C. string1
  768. string2
  769. """
  770. var warnings8 = new seq[string]
  771. let output8 = input8.toHtml(warnings = warnings8)
  772. check(warnings8[].len == 1)
  773. check("input(6, 1) Warning: RST style: \n" &
  774. "not enough indentation on line 6" in warnings8[0])
  775. doAssert output8 == "Paragraph.<ol class=\"upperalpha simple\">" &
  776. "<li>stringA</li>\n<li>stringB</li>\n</ol>\n<p>C. string1 string2 </p>"
  777. test "Markdown enumerated lists":
  778. let input1 = dedent """
  779. Below are 2 enumerated lists: Markdown-style (5 items) and RST (1 item)
  780. 1. line1
  781. 1. line2
  782. 1. line3
  783. 1. line4
  784. 1. line5
  785. #. lineA
  786. """
  787. let output1 = input1.toHtml
  788. for i in 1..5:
  789. doAssert ($i & ". line" & $i) notin output1
  790. doAssert ("<li>line" & $i & "</li>") in output1
  791. doAssert count(output1, "<ol ") == 2
  792. doAssert count(output1, "</ol>") == 2
  793. test "RST bullet lists":
  794. let input1 = dedent """
  795. * line1
  796. 1
  797. * line2
  798. 2
  799. * line3
  800. 3
  801. * line4
  802. 4
  803. * line5
  804. 5
  805. """
  806. let output1 = input1.toHtml
  807. for i in 1..5:
  808. doAssert ("<li>line" & $i & " " & $i & "</li>") in output1
  809. doAssert count(output1, "<ul ") == 1
  810. doAssert count(output1, "</ul>") == 1
  811. test "Nim RST footnotes and citations":
  812. # check that auto-label footnote enumerated properly after a manual one
  813. let input1 = dedent """
  814. .. [1] Body1.
  815. .. [#note] Body2
  816. Ref. [#note]_
  817. """
  818. let output1 = input1.toHtml(preferRst)
  819. doAssert output1.count(">[1]</a>") == 1
  820. doAssert output1.count(">[2]</a>") == 2
  821. doAssert "href=\"#footnote-note\"" in output1
  822. doAssert ">[-1]" notin output1
  823. doAssert "Body1." in output1
  824. doAssert "Body2" in output1
  825. # check that there are NO footnotes/citations, only comments:
  826. let input2 = dedent """
  827. .. [1 #] Body1.
  828. .. [# note] Body2.
  829. .. [wrong citation] That gives you a comment.
  830. .. [not&allowed] That gives you a comment.
  831. Not references[#note]_[1 #]_ [wrong citation]_ and [not&allowed]_.
  832. """
  833. let output2 = input2.toHtml(preferRst)
  834. doAssert output2 == "Not references[#note]_[1 #]_ [wrong citation]_ and [not&amp;allowed]_."
  835. # check that auto-symbol footnotes work:
  836. let input3 = dedent """
  837. Ref. [*]_ and [*]_ and [*]_.
  838. .. [*] Body1
  839. .. [*] Body2.
  840. .. [*] Body3.
  841. .. [*] Body4
  842. And [*]_.
  843. """
  844. let output3 = input3.toHtml(preferRst)
  845. # both references and footnotes. Footnotes have link to themselves.
  846. doAssert output3.count("href=\"#footnotesym-1\">[*]</a>") == 2
  847. doAssert output3.count("href=\"#footnotesym-2\">[**]</a>") == 2
  848. doAssert output3.count("href=\"#footnotesym-3\">[***]</a>") == 2
  849. doAssert output3.count("href=\"#footnotesym-4\">[^]</a>") == 2
  850. # footnote group
  851. doAssert output3.count("<hr class=\"footnote\">" &
  852. "<div class=\"footnote-group\">") == 1
  853. # footnotes
  854. doAssert output3.count("<div class=\"footnote-label\"><sup><strong>" &
  855. "<a href=\"#footnotesym-1\">[*]</a></strong></sup></div>") == 1
  856. doAssert output3.count("<div class=\"footnote-label\"><sup><strong>" &
  857. "<a href=\"#footnotesym-2\">[**]</a></strong></sup></div>") == 1
  858. doAssert output3.count("<div class=\"footnote-label\"><sup><strong>" &
  859. "<a href=\"#footnotesym-3\">[***]</a></strong></sup></div>") == 1
  860. doAssert output3.count("<div class=\"footnote-label\"><sup><strong>" &
  861. "<a href=\"#footnotesym-4\">[^]</a></strong></sup></div>") == 1
  862. for i in 1 .. 4: doAssert ("Body" & $i) in output3
  863. # check manual, auto-number and auto-label footnote enumeration
  864. let input4 = dedent """
  865. .. [3] Manual1.
  866. .. [#] Auto-number1.
  867. .. [#mylabel] Auto-label1.
  868. .. [#note] Auto-label2.
  869. .. [#] Auto-number2.
  870. Ref. [#note]_ and [#]_ and [#]_.
  871. """
  872. let output4 = input4.toHtml(preferRst)
  873. doAssert ">[-1]" notin output1
  874. let order = @[
  875. "footnote-3", "[3]", "Manual1.",
  876. "footnoteauto-1", "[1]", "Auto-number1",
  877. "footnote-mylabel", "[2]", "Auto-label1",
  878. "footnote-note", "[4]", "Auto-label2",
  879. "footnoteauto-2", "[5]", "Auto-number2",
  880. ]
  881. for i in 0 .. order.len-2:
  882. let pos1 = output4.find(order[i])
  883. let pos2 = output4.find(order[i+1])
  884. doAssert pos1 >= 0
  885. doAssert pos2 >= 0
  886. doAssert pos1 < pos2
  887. # forgot [#]_
  888. let input5 = dedent """
  889. .. [3] Manual1.
  890. .. [#] Auto-number1.
  891. .. [#note] Auto-label2.
  892. Ref. [#note]_
  893. """
  894. var error5 = new string
  895. let output5 = input5.toHtml(preferRst, error=error5)
  896. check(error5[] == "input(1, 1) Error: mismatch in number of footnotes " &
  897. "and their refs: 1 (lines 2) != 0 (lines ) for auto-numbered " &
  898. "footnotes")
  899. # extra [*]_
  900. let input6 = dedent """
  901. Ref. [*]_
  902. .. [*] Auto-Symbol.
  903. Ref. [*]_
  904. """
  905. var error6 = new string
  906. let output6 = input6.toHtml(preferRst, error=error6)
  907. check(error6[] == "input(1, 1) Error: mismatch in number of footnotes " &
  908. "and their refs: 1 (lines 3) != 2 (lines 2, 6) for auto-symbol " &
  909. "footnotes")
  910. let input7 = dedent """
  911. .. [Some:CITATION-2020] Citation.
  912. Ref. [some:citation-2020]_.
  913. """
  914. let output7 = input7.toHtml(preferRst)
  915. doAssert output7.count("href=\"#citation-somecoloncitationminus2020\"") == 2
  916. doAssert output7.count("[Some:CITATION-2020]") == 1
  917. doAssert output7.count("[some:citation-2020]") == 1
  918. doAssert output3.count("<hr class=\"footnote\">" &
  919. "<div class=\"footnote-group\">") == 1
  920. let input8 = dedent """
  921. .. [Some] Citation.
  922. Ref. [som]_.
  923. """
  924. var warnings8 = new seq[string]
  925. let output8 = input8.toHtml(preferRst, warnings=warnings8)
  926. check(warnings8[] == @["input(3, 7) Warning: broken link 'citation-som'"])
  927. # check that footnote group does not break parsing of other directives:
  928. let input9 = dedent """
  929. .. [Some] Citation.
  930. .. _`internal anchor`:
  931. .. [Another] Citation.
  932. .. just comment.
  933. .. [Third] Citation.
  934. Paragraph1.
  935. Paragraph2 ref `internal anchor`_.
  936. """
  937. let output9 = input9.toHtml
  938. # _`internal anchor` got erased:
  939. check "href=\"#internal-anchor\"" notin output9
  940. check "href=\"#citation-another\"" in output9
  941. doAssert output9.count("<hr class=\"footnote\">" &
  942. "<div class=\"footnote-group\">") == 1
  943. doAssert output9.count("<div class=\"footnote-label\">") == 3
  944. doAssert "just comment" notin output9
  945. # check that nested citations/footnotes work
  946. let input10 = dedent """
  947. Paragraph1 [#]_.
  948. .. [First] Citation.
  949. .. [#] Footnote.
  950. .. [Third] Citation.
  951. """
  952. let output10 = input10.toHtml(preferRst)
  953. doAssert output10.count("<hr class=\"footnote\">" &
  954. "<div class=\"footnote-group\">") == 3
  955. doAssert output10.count("<div class=\"footnote-label\">") == 3
  956. doAssert "<a href=\"#citation-first\">[First]</a>" in output10
  957. doAssert "<a href=\"#footnoteauto-1\">[1]</a>" in output10
  958. doAssert "<a href=\"#citation-third\">[Third]</a>" in output10
  959. let input11 = ".. [note]\n" # should not crash
  960. let output11 = input11.toHtml
  961. doAssert "<a href=\"#citation-note\">[note]</a>" in output11
  962. # check that references to auto-numbered footnotes work
  963. let input12 = dedent """
  964. Ref. [#]_ and [#]_ STOP.
  965. .. [#] Body1.
  966. .. [#] Body3
  967. .. [2] Body2.
  968. """
  969. let output12 = input12.toHtml(preferRst)
  970. let orderAuto = @[
  971. "#footnoteauto-1", "[1]",
  972. "#footnoteauto-2", "[3]",
  973. "STOP.",
  974. "Body1.", "Body3", "Body2."
  975. ]
  976. for i in 0 .. orderAuto.len-2:
  977. let pos1 = output12.find(orderAuto[i])
  978. let pos2 = output12.find(orderAuto[i+1])
  979. doAssert pos1 >= 0
  980. doAssert pos2 >= 0
  981. doAssert pos1 < pos2
  982. test "Nim (RST extension) code-block":
  983. # check that presence of fields doesn't consume the following text as
  984. # its code (which is a literal block)
  985. let input0 = dedent """
  986. .. code-block:: nim
  987. :number-lines: 0
  988. Paragraph1"""
  989. let output0 = input0.toHtml
  990. doAssert "<p>Paragraph1</p>" in output0
  991. test "Nim code-block :number-lines:":
  992. let input = dedent """
  993. .. code-block:: nim
  994. :number-lines: 55
  995. x
  996. y
  997. """
  998. check "<pre class=\"line-nums\">55\n56\n</pre>" in input.toHtml
  999. test "Nim code-block indentation":
  1000. let input = dedent """
  1001. .. code-block:: nim
  1002. :number-lines: 55
  1003. x
  1004. """
  1005. let output = input.toHtml
  1006. check "<pre class=\"line-nums\">55\n</pre>" in output
  1007. check "<span class=\"Identifier\">x</span>" in output
  1008. test "Nim code-block indentation":
  1009. let input = dedent """
  1010. .. code-block:: nim
  1011. :number-lines: 55
  1012. let a = 1
  1013. """
  1014. var error = new string
  1015. let output = input.toHtml(error=error)
  1016. check(error[] == "input(2, 3) Error: invalid field: " &
  1017. "extra arguments were given to number-lines: ' let a = 1'")
  1018. check "" == output
  1019. test "code-block warning":
  1020. let input = dedent """
  1021. .. code:: Nim
  1022. :unsupportedField: anything
  1023. .. code:: unsupportedLang
  1024. anything
  1025. ```anotherLang
  1026. someCode
  1027. ```
  1028. """
  1029. let warnings = new seq[string]
  1030. let output = input.toHtml(warnings=warnings)
  1031. check(warnings[] == @[
  1032. "input(2, 4) Warning: field 'unsupportedField' not supported",
  1033. "input(4, 11) Warning: language 'unsupportedLang' not supported",
  1034. "input(8, 4) Warning: language 'anotherLang' not supported"
  1035. ])
  1036. check(output == "<pre class = \"listing\">anything</pre>" &
  1037. "<p><pre class = \"listing\">\nsomeCode</pre> </p>")
  1038. test "RST admonitions":
  1039. # check that all admonitions are implemented
  1040. let input0 = dedent """
  1041. .. admonition:: endOf admonition
  1042. .. attention:: endOf attention
  1043. .. caution:: endOf caution
  1044. .. danger:: endOf danger
  1045. .. error:: endOf error
  1046. .. hint:: endOf hint
  1047. .. important:: endOf important
  1048. .. note:: endOf note
  1049. .. tip:: endOf tip
  1050. .. warning:: endOf warning
  1051. """
  1052. let output0 = input0.toHtml(
  1053. NoSandboxOpts
  1054. )
  1055. for a in ["admonition", "attention", "caution", "danger", "error", "hint",
  1056. "important", "note", "tip", "warning" ]:
  1057. doAssert "endOf " & a & "</div>" in output0
  1058. # Test that admonition does not swallow up the next paragraph.
  1059. let input1 = dedent """
  1060. .. error:: endOfError
  1061. Test paragraph.
  1062. """
  1063. let output1 = input1.toHtml(
  1064. NoSandboxOpts
  1065. )
  1066. doAssert "endOfError</div>" in output1
  1067. doAssert "<p>Test paragraph. </p>" in output1
  1068. doAssert "class=\"admonition admonition-error\"" in output1
  1069. # Test that second line is parsed as continuation of the first line.
  1070. let input2 = dedent """
  1071. .. error:: endOfError
  1072. Test2p.
  1073. Test paragraph.
  1074. """
  1075. let output2 = input2.toHtml(
  1076. NoSandboxOpts
  1077. )
  1078. doAssert "endOfError Test2p.</div>" in output2
  1079. doAssert "<p>Test paragraph. </p>" in output2
  1080. doAssert "class=\"admonition admonition-error\"" in output2
  1081. let input3 = dedent """
  1082. .. note:: endOfNote
  1083. """
  1084. let output3 = input3.toHtml(
  1085. NoSandboxOpts
  1086. )
  1087. doAssert "endOfNote</div>" in output3
  1088. doAssert "class=\"admonition admonition-info\"" in output3
  1089. test "RST internal links":
  1090. let input1 = dedent """
  1091. Start.
  1092. .. _target000:
  1093. Paragraph.
  1094. .. _target001:
  1095. * bullet list
  1096. * Y
  1097. .. _target002:
  1098. 1. enumeration list
  1099. 2. Y
  1100. .. _target003:
  1101. term 1
  1102. Definition list 1.
  1103. .. _target004:
  1104. | line block
  1105. .. _target005:
  1106. :a: field list value
  1107. .. _target006:
  1108. -a option description
  1109. .. _target007:
  1110. ::
  1111. Literal block
  1112. .. _target008:
  1113. Doctest blocks are not implemented.
  1114. .. _target009:
  1115. block quote
  1116. .. _target010:
  1117. ===== ===== =======
  1118. A B A and B
  1119. ===== ===== =======
  1120. False False False
  1121. ===== ===== =======
  1122. .. _target100:
  1123. .. CAUTION:: admonition
  1124. .. _target101:
  1125. .. code:: nim
  1126. const pi = 3.14
  1127. .. _target102:
  1128. .. code-block::
  1129. const pi = 3.14
  1130. Paragraph2.
  1131. .. _target202:
  1132. ----
  1133. That was a transition.
  1134. """
  1135. let output1 = input1.toHtml(
  1136. preferRst
  1137. )
  1138. doAssert "<p id=\"target000\"" in output1
  1139. doAssert "<ul id=\"target001\"" in output1
  1140. doAssert "<ol id=\"target002\"" in output1
  1141. doAssert "<dl id=\"target003\"" in output1
  1142. doAssert "<p id=\"target004\"" in output1
  1143. doAssert "<table id=\"target005\"" in output1 # field list
  1144. doAssert "<div id=\"target006\"" in output1 # option list
  1145. doAssert "<pre id=\"target007\"" in output1
  1146. doAssert "<blockquote id=\"target009\"" in output1
  1147. doAssert "<table id=\"target010\"" in output1 # just table
  1148. doAssert "<span id=\"target100\"" in output1
  1149. doAssert "<pre id=\"target101\"" in output1 # code
  1150. doAssert "<pre id=\"target102\"" in output1 # code-block
  1151. doAssert "<hr id=\"target202\"" in output1
  1152. test "RST internal links for sections":
  1153. let input1 = dedent """
  1154. .. _target101:
  1155. .. _target102:
  1156. Section xyz
  1157. -----------
  1158. Ref. target101_
  1159. """
  1160. let output1 = input1.toHtml
  1161. # "target101" should be erased and changed to "section-xyz":
  1162. check "href=\"#target101\"" notin output1
  1163. check "id=\"target101\"" notin output1
  1164. check "href=\"#target102\"" notin output1
  1165. check "id=\"target102\"" notin output1
  1166. check "id=\"section-xyz\"" in output1
  1167. check "href=\"#section-xyz\"" in output1
  1168. let input2 = dedent """
  1169. .. _target300:
  1170. Section xyz
  1171. ===========
  1172. .. _target301:
  1173. SubsectionA
  1174. -----------
  1175. Ref. target300_ and target301_.
  1176. .. _target103:
  1177. .. [cit2020] note.
  1178. Ref. target103_.
  1179. """
  1180. let output2 = input2.toHtml
  1181. # "target101" should be erased and changed to "section-xyz":
  1182. doAssert "href=\"#target300\"" notin output2
  1183. doAssert "id=\"target300\"" notin output2
  1184. doAssert "href=\"#target301\"" notin output2
  1185. doAssert "id=\"target301\"" notin output2
  1186. doAssert "<h1 id=\"section-xyz\"" in output2
  1187. doAssert "<h2 id=\"subsectiona\"" in output2
  1188. # links should preserve their original names but point to section labels:
  1189. doAssert "href=\"#section-xyz\">target300" in output2
  1190. doAssert "href=\"#subsectiona\">target301" in output2
  1191. doAssert "href=\"#citation-cit2020\">target103" in output2
  1192. let output2l = rstToLatex(input2, {})
  1193. doAssert "\\label{section-xyz}\\hypertarget{section-xyz}{}" in output2l
  1194. doAssert "\\hyperlink{section-xyz}{target300}" in output2l
  1195. doAssert "\\hyperlink{subsectiona}{target301}" in output2l
  1196. test "RST internal links (inline)":
  1197. let input1 = dedent """
  1198. Paragraph with _`some definition`.
  1199. Ref. `some definition`_.
  1200. """
  1201. let output1 = input1.toHtml
  1202. doAssert "<span class=\"target\" " &
  1203. "id=\"some-definition\">some definition</span>" in output1
  1204. doAssert "Ref. <a class=\"reference internal\" " &
  1205. "href=\"#some-definition\">some definition</a>" in output1
  1206. test "RST references (additional symbols)":
  1207. # check that ., _, -, +, : are allowed symbols in references without ` `
  1208. let input1 = dedent """
  1209. sec.1
  1210. -----
  1211. 2-other:sec+c_2
  1212. ^^^^^^^^^^^^^^^
  1213. .. _link.1_2021:
  1214. Paragraph
  1215. Ref. sec.1_! and 2-other:sec+c_2_;and link.1_2021_.
  1216. """
  1217. let output1 = input1.toHtml
  1218. doAssert "id=\"secdot1\"" in output1
  1219. doAssert "id=\"Z2minusothercolonsecplusc-2\"" in output1
  1220. check "id=\"linkdot1-2021\"" in output1
  1221. let ref1 = "<a class=\"reference internal\" href=\"#secdot1\">sec.1</a>"
  1222. let ref2 = "<a class=\"reference internal\" href=\"#Z2minusothercolonsecplusc-2\">2-other:sec+c_2</a>"
  1223. let ref3 = "<a class=\"reference internal\" href=\"#linkdot1-2021\">link.1_2021</a>"
  1224. let refline = "Ref. " & ref1 & "! and " & ref2 & ";and " & ref3 & "."
  1225. doAssert refline in output1
  1226. test "Option lists 1":
  1227. # check that "* b" is not consumed by previous bullet item because of
  1228. # incorrect indentation handling in option lists
  1229. let input = dedent """
  1230. * a
  1231. -m desc
  1232. -n very long
  1233. desc
  1234. * b"""
  1235. let output = input.toHtml
  1236. check(output.count("<ul") == 1)
  1237. check(output.count("<li>") == 2)
  1238. check(output.count("<div class=\"option-list\"") == 1)
  1239. check(optionListLabel("-m") &
  1240. """<div class="option-list-description">desc</div></div>""" in
  1241. output)
  1242. check(optionListLabel("-n") &
  1243. """<div class="option-list-description">very long desc</div></div>""" in
  1244. output)
  1245. test "Option lists 2":
  1246. # check that 2nd option list is not united with the 1st
  1247. let input = dedent """
  1248. * a
  1249. -m desc
  1250. -n very long
  1251. desc
  1252. -d option"""
  1253. let output = input.toHtml
  1254. check(output.count("<ul") == 1)
  1255. check output.count("<div class=\"option-list\"") == 2
  1256. check(optionListLabel("-m") &
  1257. """<div class="option-list-description">desc</div></div>""" in
  1258. output)
  1259. check(optionListLabel("-n") &
  1260. """<div class="option-list-description">very long desc</div></div>""" in
  1261. output)
  1262. check(optionListLabel("-d") &
  1263. """<div class="option-list-description">option</div></div>""" in
  1264. output)
  1265. check "<p>option</p>" notin output
  1266. test "Option list 3 (double /)":
  1267. let input = dedent """
  1268. * a
  1269. //compile compile1
  1270. //doc doc1
  1271. cont
  1272. -d option"""
  1273. let output = input.toHtml
  1274. check(output.count("<ul") == 1)
  1275. check output.count("<div class=\"option-list\"") == 2
  1276. check(optionListLabel("compile") &
  1277. """<div class="option-list-description">compile1</div></div>""" in
  1278. output)
  1279. check(optionListLabel("doc") &
  1280. """<div class="option-list-description">doc1 cont</div></div>""" in
  1281. output)
  1282. check(optionListLabel("-d") &
  1283. """<div class="option-list-description">option</div></div>""" in
  1284. output)
  1285. check "<p>option</p>" notin output
  1286. test "Roles: subscript prefix/postfix":
  1287. let expected = "See <sub>some text</sub>."
  1288. check "See :subscript:`some text`.".toHtml == expected
  1289. check "See `some text`:subscript:.".toHtml == expected
  1290. test "Roles: correct parsing from beginning of line":
  1291. let expected = "<sup>3</sup>He is an isotope of helium."
  1292. check """:superscript:`3`\ He is an isotope of helium.""".toHtml == expected
  1293. check """:sup:`3`\ He is an isotope of helium.""".toHtml == expected
  1294. check """`3`:sup:\ He is an isotope of helium.""".toHtml == expected
  1295. check """`3`:superscript:\ He is an isotope of helium.""".toHtml == expected
  1296. test "Roles: warnings":
  1297. let input = dedent"""
  1298. See function :py:func:`spam`.
  1299. See also `egg`:py:class:.
  1300. """
  1301. var warnings = new seq[string]
  1302. let output = input.toHtml(warnings=warnings)
  1303. doAssert warnings[].len == 2
  1304. check "(1, 14) Warning: " in warnings[0]
  1305. check "language 'py:func' not supported" in warnings[0]
  1306. check "(3, 15) Warning: " in warnings[1]
  1307. check "language 'py:class' not supported" in warnings[1]
  1308. check("""<p>See function <span class="py:func">spam</span>.</p>""" & "\n" &
  1309. """<p>See also <span class="py:class">egg</span>. </p>""" ==
  1310. output)
  1311. test "(not) Roles: check escaping 1":
  1312. let expected = """See :subscript:<tt class="docutils literal">""" &
  1313. """<span class="pre">""" & id"some" & " " & id"text" &
  1314. "</span></tt>."
  1315. check """See \:subscript:`some text`.""".toHtml == expected
  1316. check """See :subscript\:`some text`.""".toHtml == expected
  1317. test "(not) Roles: check escaping 2":
  1318. check("""See :subscript:\`some text\`.""".toHtml ==
  1319. "See :subscript:`some text`.")
  1320. test "Field list":
  1321. check(":field: text".toHtml ==
  1322. """<table class="docinfo" frame="void" rules="none">""" &
  1323. """<col class="docinfo-name" /><col class="docinfo-content" />""" &
  1324. """<tbody valign="top"><tr><th class="docinfo-name">field:</th>""" &
  1325. """<td>text</td></tr>""" & "\n</tbody></table>")
  1326. test "Field list: body after newline":
  1327. let output = dedent"""
  1328. :field:
  1329. text1""".toHtml
  1330. check "<table class=\"docinfo\"" in output
  1331. check ">field:</th>" in output
  1332. check "<td>text1</td>" in output
  1333. test "Field list (incorrect)":
  1334. check ":field:text".toHtml == ":field:text"
  1335. suite "RST/Code highlight":
  1336. test "Basic Python code highlight":
  1337. let pythonCode = """
  1338. .. code-block:: python
  1339. def f_name(arg=42):
  1340. print(f"{arg}")
  1341. """
  1342. let expected = """<blockquote><p><span class="Keyword">def</span> f_name<span class="Punctuation">(</span><span class="Punctuation">arg</span><span class="Operator">=</span><span class="DecNumber">42</span><span class="Punctuation">)</span><span class="Punctuation">:</span>
  1343. print<span class="Punctuation">(</span><span class="RawData">f&quot;{arg}&quot;</span><span class="Punctuation">)</span></p></blockquote>"""
  1344. check strip(rstToHtml(pythonCode, {}, newStringTable(modeCaseSensitive))) ==
  1345. strip(expected)
  1346. suite "invalid targets":
  1347. test "invalid image target":
  1348. let input1 = dedent """.. image:: /images/myimage.jpg
  1349. :target: https://bar.com
  1350. :alt: Alt text for the image"""
  1351. let output1 = input1.toHtml
  1352. check output1 == """<a class="reference external" href="https://bar.com"><img src="/images/myimage.jpg" alt="Alt text for the image"/></a>"""
  1353. let input2 = dedent """.. image:: /images/myimage.jpg
  1354. :target: javascript://bar.com
  1355. :alt: Alt text for the image"""
  1356. let output2 = input2.toHtml
  1357. check output2 == """<img src="/images/myimage.jpg" alt="Alt text for the image"/>"""
  1358. let input3 = dedent """.. image:: /images/myimage.jpg
  1359. :target: bar.com
  1360. :alt: Alt text for the image"""
  1361. let output3 = input3.toHtml
  1362. check output3 == """<a class="reference external" href="bar.com"><img src="/images/myimage.jpg" alt="Alt text for the image"/></a>"""
  1363. test "invalid links":
  1364. check("(([Nim](https://nim-lang.org/)))".toHtml ==
  1365. """((<a class="reference external" href="https://nim-lang.org/">Nim</a>))""")
  1366. # unknown protocol is treated just like plain text, not a link
  1367. var warnings = new seq[string]
  1368. check("(([Nim](javascript://nim-lang.org/)))".toHtml(warnings=warnings) ==
  1369. """(([Nim](javascript://nim-lang.org/)))""")
  1370. check(warnings[] == @["input(1, 9) Warning: broken link 'javascript'"])
  1371. warnings[].setLen 0
  1372. check("`Nim <javascript://nim-lang.org/>`_".toHtml(warnings=warnings) ==
  1373. """Nim &lt;javascript://nim-lang.org/&gt;""")
  1374. check(warnings[] == @["input(1, 33) Warning: broken link 'javascript'"])
  1375. suite "local file inclusion":
  1376. test "cannot include files in sandboxed mode":
  1377. var error = new string
  1378. discard ".. include:: ./readme.md".toHtml(error=error)
  1379. check(error[] == "input(1, 11) Error: disabled directive: 'include'")
  1380. test "code-block file directive is disabled":
  1381. var error = new string
  1382. discard ".. code-block:: nim\n :file: ./readme.md".toHtml(error=error)
  1383. check(error[] == "input(2, 20) Error: disabled directive: 'file'")
  1384. test "code-block file directive is disabled - Markdown":
  1385. var error = new string
  1386. discard "```nim file = ./readme.md\n```".toHtml(error=error)
  1387. check(error[] == "input(1, 23) Error: disabled directive: 'file'")
  1388. proc documentToHtml*(doc: string, isMarkdown: bool = false): string {.gcsafe.} =
  1389. var options = {roSupportMarkdown}
  1390. if isMarkdown:
  1391. options.incl roPreferMarkdown
  1392. result = rstToHtml(doc, options, defaultConfig())