zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

markdown.zig (31700B) - Raw


      1 //! Markdown parsing and rendering support.
      2 //!
      3 //! A Markdown document consists of a series of blocks. Depending on its type,
      4 //! each block may contain other blocks, inline content, or nothing. The
      5 //! supported blocks are as follows:
      6 //!
      7 //! - **List** - a sequence of list items of the same type.
      8 //!
      9 //! - **List item** - unordered list items start with `-`, `*`, or `+` followed
     10 //!   by a space. Ordered list items start with a number between 0 and
     11 //!   999,999,999, followed by a `.` or `)` and a space. The number of an
     12 //!   ordered list item only matters for the first item in the list (to
     13 //!   determine the starting number of the list). All subsequent ordered list
     14 //!   items will have sequentially increasing numbers.
     15 //!
     16 //!   All list items may contain block content. Any content indented at least as
     17 //!   far as the end of the list item marker (including the space after it) is
     18 //!   considered part of the list item.
     19 //!
     20 //!   Lists which have no blank lines between items or between direct children
     21 //!   of items are considered _tight_, and direct child paragraphs of tight list
     22 //!   items are rendered without `<p>` tags.
     23 //!
     24 //! - **Table** - a sequence of adjacent table row lines, where each line starts
     25 //!   and ends with a `|`, and cells within the row are delimited by `|`s.
     26 //!
     27 //!   The first or second row of a table may be a _header delimiter row_, which
     28 //!   is a row consisting of cells of the pattern `---` (for unset column
     29 //!   alignment), `:--` (for left alignment), `:-:` (for center alignment), or
     30 //!   `--:` (for right alignment). The number of `-`s must be at least one, but
     31 //!   is otherwise arbitrary. If there is a row just before the header delimiter
     32 //!   row, it becomes the header row for the table (a table need not have a
     33 //!   header row at all).
     34 //!
     35 //! - **Heading** - a sequence of between 1 and 6 `#` characters, followed by a
     36 //!   space and further inline content on the same line.
     37 //!
     38 //! - **Code block** - a sequence of at least 3 `` ` `` characters (a _fence_),
     39 //!   optionally followed by a "tag" on the same line, and continuing until a
     40 //!   line consisting only of a closing fence whose length matches the opening
     41 //!   fence, or until the end of the containing block.
     42 //!
     43 //!   The content of a code block is not parsed as inline content. It is
     44 //!   included verbatim in the output document (minus leading indentation up to
     45 //!   the position of the opening fence).
     46 //!
     47 //! - **Blockquote** - a sequence of lines preceded by `>` characters.
     48 //!
     49 //! - **Paragraph** - ordinary text, parsed as inline content, ending with a
     50 //!   blank line or the end of the containing block.
     51 //!
     52 //!   Paragraphs which are part of another block may be "lazily" continued by
     53 //!   subsequent paragraph lines even if those lines would not ordinarily be
     54 //!   considered part of the containing block. For example, this is a single
     55 //!   list item, not a list item followed by a paragraph:
     56 //!
     57 //!   ```markdown
     58 //!   - First line of content.
     59 //!   This content is still part of the paragraph,
     60 //!   even though it isn't indented far enough.
     61 //!   ```
     62 //!
     63 //! - **Thematic break** - a line consisting of at least three matching `-`,
     64 //!   `_`, or `*` characters and, optionally, spaces.
     65 //!
     66 //! Indentation may consist of spaces and tabs. The use of tabs is not
     67 //! recommended: a tab is treated the same as a single space for the purpose of
     68 //! determining the indentation level, and is not recognized as a space for
     69 //! block starters which require one (for example, `-` followed by a tab is not
     70 //! a valid list item).
     71 //!
     72 //! The supported inlines are as follows:
     73 //!
     74 //! - **Link** - of the format `[text](target)`. `text` may contain inline
     75 //!   content. `target` may contain `\`-escaped characters and balanced
     76 //!   parentheses.
     77 //!
     78 //! - **Autolink** - an abbreviated link, of the format `<target>`, where
     79 //!   `target` serves as both the link target and text. `target` may not
     80 //!   contain spaces or `<`, and any `\` in it are interpreted literally (not as
     81 //!   escapes). `target` is expected to be an absolute URI: an autolink will not
     82 //!   be recognized unless `target` starts with a URI scheme followed by a `:`.
     83 //!
     84 //!   For convenience, autolinks may also be recognized in plain text without
     85 //!   any `<>` delimiters. Such autolinks are restricted to start with `http://`
     86 //!   or `https://` followed by at least one other character, not including any
     87 //!   trailing punctuation after the link.
     88 //!
     89 //! - **Image** - a link directly preceded by a `!`. The link text is
     90 //!   interpreted as the alt text of the image.
     91 //!
     92 //! - **Emphasis** - a run of `*` or `_` characters may be an emphasis opener,
     93 //!   closer, or both. For `*` characters, the run may be an opener as long as
     94 //!   it is not directly followed by a whitespace character (or the end of the
     95 //!   inline content) and a closer as long as it is not directly preceded by
     96 //!   one. For `_` characters, this rule is strengthened by requiring that the
     97 //!   run also be preceded by a whitespace or punctuation character (for
     98 //!   openers) or followed by one (for closers), to avoid mangling `snake_case`
     99 //!   words.
    100 //!
    101 //!   The rule for emphasis handling is greedy: any run that can close existing
    102 //!   emphasis will do so, otherwise it will open emphasis. A single run may
    103 //!   serve both functions: the middle `**` in the following example both closes
    104 //!   the initial emphasis and opens a new one:
    105 //!
    106 //!   ```markdown
    107 //!   *one**two*
    108 //!   ```
    109 //!
    110 //!   A single `*` or `_` is used for normal emphasis (HTML `<em>`), and a
    111 //!   double `**` or `__` is used for strong emphasis (HTML `<strong>`). Even
    112 //!   longer runs may be used to produce further nested emphasis (though only
    113 //!   `***` and `___` to produce `<em><strong>` is really useful).
    114 //!
    115 //! - **Code span** - a run of `` ` `` characters, terminated by a matching run
    116 //!   or the end of inline content. The content of a code span is not parsed
    117 //!   further.
    118 //!
    119 //! - **Text** - normal text is interpreted as-is, except that `\` may be used
    120 //!   to escape any punctuation character, preventing it from being interpreted
    121 //!   according to other syntax rules. A `\` followed by a line break within a
    122 //!   paragraph is interpreted as a hard line break.
    123 //!
    124 //!   Any null bytes or invalid UTF-8 bytes within text are replaced with Unicode
    125 //!   replacement characters, `U+FFFD`.
    126 
    127 const std = @import("std");
    128 const testing = std.testing;
    129 
    130 pub const Document = @import("markdown/Document.zig");
    131 pub const Parser = @import("markdown/Parser.zig");
    132 pub const Renderer = @import("markdown/renderer.zig").Renderer;
    133 pub const renderNodeInlineText = @import("markdown/renderer.zig").renderNodeInlineText;
    134 pub const fmtHtml = @import("markdown/renderer.zig").fmtHtml;
    135 
    136 // Avoid exposing main to other files merely importing this one.
    137 pub const main = if (@import("root") == @This())
    138     mainImpl
    139 else
    140     @compileError("only available as root source file");
    141 
    142 fn mainImpl() !void {
    143     const gpa = std.heap.c_allocator;
    144 
    145     var parser = try Parser.init(gpa);
    146     defer parser.deinit();
    147 
    148     var stdin_buffer: [1024]u8 = undefined;
    149     var stdin_reader = std.fs.File.stdin().reader(&stdin_buffer);
    150 
    151     while (stdin_reader.takeDelimiterExclusive('\n')) |line| {
    152         const trimmed = std.mem.trimRight(u8, line, '\r');
    153         try parser.feedLine(trimmed);
    154     } else |err| switch (err) {
    155         error.EndOfStream => {},
    156         else => |e| return e,
    157     }
    158 
    159     var doc = try parser.endInput();
    160     defer doc.deinit(gpa);
    161 
    162     var stdout_buffer: [1024]u8 = undefined;
    163     var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
    164     try doc.render(&stdout_writer.interface);
    165     try stdout_writer.interface.flush();
    166 }
    167 
    168 test "empty document" {
    169     try testRender("", "");
    170     try testRender("   ", "");
    171     try testRender("\n \n\t\n   \n", "");
    172 }
    173 
    174 test "unordered lists" {
    175     try testRender(
    176         \\- Spam
    177         \\- Spam
    178         \\- Spam
    179         \\- Eggs
    180         \\- Bacon
    181         \\- Spam
    182         \\
    183         \\* Spam
    184         \\* Spam
    185         \\* Spam
    186         \\* Eggs
    187         \\* Bacon
    188         \\* Spam
    189         \\
    190         \\+ Spam
    191         \\+ Spam
    192         \\+ Spam
    193         \\+ Eggs
    194         \\+ Bacon
    195         \\+ Spam
    196         \\
    197     ,
    198         \\<ul>
    199         \\<li>Spam</li>
    200         \\<li>Spam</li>
    201         \\<li>Spam</li>
    202         \\<li>Eggs</li>
    203         \\<li>Bacon</li>
    204         \\<li>Spam</li>
    205         \\</ul>
    206         \\<ul>
    207         \\<li>Spam</li>
    208         \\<li>Spam</li>
    209         \\<li>Spam</li>
    210         \\<li>Eggs</li>
    211         \\<li>Bacon</li>
    212         \\<li>Spam</li>
    213         \\</ul>
    214         \\<ul>
    215         \\<li>Spam</li>
    216         \\<li>Spam</li>
    217         \\<li>Spam</li>
    218         \\<li>Eggs</li>
    219         \\<li>Bacon</li>
    220         \\<li>Spam</li>
    221         \\</ul>
    222         \\
    223     );
    224 }
    225 
    226 test "ordered lists" {
    227     try testRender(
    228         \\1. Breakfast
    229         \\2. Second breakfast
    230         \\3. Lunch
    231         \\2. Afternoon snack
    232         \\1. Dinner
    233         \\6. Dessert
    234         \\7. Midnight snack
    235         \\
    236         \\1) Breakfast
    237         \\2) Second breakfast
    238         \\3) Lunch
    239         \\2) Afternoon snack
    240         \\1) Dinner
    241         \\6) Dessert
    242         \\7) Midnight snack
    243         \\
    244         \\1001. Breakfast
    245         \\2. Second breakfast
    246         \\3. Lunch
    247         \\2. Afternoon snack
    248         \\1. Dinner
    249         \\6. Dessert
    250         \\7. Midnight snack
    251         \\
    252         \\1001) Breakfast
    253         \\2) Second breakfast
    254         \\3) Lunch
    255         \\2) Afternoon snack
    256         \\1) Dinner
    257         \\6) Dessert
    258         \\7) Midnight snack
    259         \\
    260     ,
    261         \\<ol>
    262         \\<li>Breakfast</li>
    263         \\<li>Second breakfast</li>
    264         \\<li>Lunch</li>
    265         \\<li>Afternoon snack</li>
    266         \\<li>Dinner</li>
    267         \\<li>Dessert</li>
    268         \\<li>Midnight snack</li>
    269         \\</ol>
    270         \\<ol>
    271         \\<li>Breakfast</li>
    272         \\<li>Second breakfast</li>
    273         \\<li>Lunch</li>
    274         \\<li>Afternoon snack</li>
    275         \\<li>Dinner</li>
    276         \\<li>Dessert</li>
    277         \\<li>Midnight snack</li>
    278         \\</ol>
    279         \\<ol start="1001">
    280         \\<li>Breakfast</li>
    281         \\<li>Second breakfast</li>
    282         \\<li>Lunch</li>
    283         \\<li>Afternoon snack</li>
    284         \\<li>Dinner</li>
    285         \\<li>Dessert</li>
    286         \\<li>Midnight snack</li>
    287         \\</ol>
    288         \\<ol start="1001">
    289         \\<li>Breakfast</li>
    290         \\<li>Second breakfast</li>
    291         \\<li>Lunch</li>
    292         \\<li>Afternoon snack</li>
    293         \\<li>Dinner</li>
    294         \\<li>Dessert</li>
    295         \\<li>Midnight snack</li>
    296         \\</ol>
    297         \\
    298     );
    299 }
    300 
    301 test "nested lists" {
    302     try testRender(
    303         \\- - Item 1.
    304         \\  - Item 2.
    305         \\Item 2 continued.
    306         \\  * New list.
    307         \\
    308     ,
    309         \\<ul>
    310         \\<li><ul>
    311         \\<li>Item 1.</li>
    312         \\<li>Item 2.
    313         \\Item 2 continued.</li>
    314         \\</ul>
    315         \\<ul>
    316         \\<li>New list.</li>
    317         \\</ul>
    318         \\</li>
    319         \\</ul>
    320         \\
    321     );
    322 }
    323 
    324 test "lists with block content" {
    325     try testRender(
    326         \\1. Item 1.
    327         \\2. Item 2.
    328         \\
    329         \\   This one has another paragraph.
    330         \\3. Item 3.
    331         \\
    332         \\- > Blockquote.
    333         \\- - Sub-list.
    334         \\  - Sub-list continued.
    335         \\  * Different sub-list.
    336         \\- ## Heading.
    337         \\
    338         \\  Some contents below the heading.
    339         \\  1. Item 1.
    340         \\  2. Item 2.
    341         \\  3. Item 3.
    342         \\
    343     ,
    344         \\<ol>
    345         \\<li><p>Item 1.</p>
    346         \\</li>
    347         \\<li><p>Item 2.</p>
    348         \\<p>This one has another paragraph.</p>
    349         \\</li>
    350         \\<li><p>Item 3.</p>
    351         \\</li>
    352         \\</ol>
    353         \\<ul>
    354         \\<li><blockquote>
    355         \\<p>Blockquote.</p>
    356         \\</blockquote>
    357         \\</li>
    358         \\<li><ul>
    359         \\<li>Sub-list.</li>
    360         \\<li>Sub-list continued.</li>
    361         \\</ul>
    362         \\<ul>
    363         \\<li>Different sub-list.</li>
    364         \\</ul>
    365         \\</li>
    366         \\<li><h2>Heading.</h2>
    367         \\<p>Some contents below the heading.</p>
    368         \\<ol>
    369         \\<li>Item 1.</li>
    370         \\<li>Item 2.</li>
    371         \\<li>Item 3.</li>
    372         \\</ol>
    373         \\</li>
    374         \\</ul>
    375         \\
    376     );
    377 }
    378 
    379 test "indented lists" {
    380     try testRender(
    381         \\Test:
    382         \\ * a1
    383         \\ * a2
    384         \\      * b1
    385         \\      * b2
    386         \\
    387         \\---
    388         \\
    389         \\    Test:
    390         \\  - One
    391         \\Two
    392         \\    - Three
    393         \\Four
    394         \\    Five
    395         \\Six
    396         \\
    397         \\---
    398         \\
    399         \\None of these items are indented far enough from the previous one to
    400         \\start a nested list:
    401         \\  - One
    402         \\   - Two
    403         \\    - Three
    404         \\     - Four
    405         \\      - Five
    406         \\     - Six
    407         \\    - Seven
    408         \\   - Eight
    409         \\  - Nine
    410         \\
    411         \\---
    412         \\
    413         \\   - One
    414         \\     - Two
    415         \\       - Three
    416         \\         - Four
    417         \\     - Five
    418         \\         - Six
    419         \\- Seven
    420         \\
    421     ,
    422         \\<p>Test:</p>
    423         \\<ul>
    424         \\<li>a1</li>
    425         \\<li>a2<ul>
    426         \\<li>b1</li>
    427         \\<li>b2</li>
    428         \\</ul>
    429         \\</li>
    430         \\</ul>
    431         \\<hr />
    432         \\<p>Test:</p>
    433         \\<ul>
    434         \\<li>One
    435         \\Two<ul>
    436         \\<li>Three
    437         \\Four
    438         \\Five
    439         \\Six</li>
    440         \\</ul>
    441         \\</li>
    442         \\</ul>
    443         \\<hr />
    444         \\<p>None of these items are indented far enough from the previous one to
    445         \\start a nested list:</p>
    446         \\<ul>
    447         \\<li>One</li>
    448         \\<li>Two</li>
    449         \\<li>Three</li>
    450         \\<li>Four</li>
    451         \\<li>Five</li>
    452         \\<li>Six</li>
    453         \\<li>Seven</li>
    454         \\<li>Eight</li>
    455         \\<li>Nine</li>
    456         \\</ul>
    457         \\<hr />
    458         \\<ul>
    459         \\<li>One<ul>
    460         \\<li>Two<ul>
    461         \\<li>Three<ul>
    462         \\<li>Four</li>
    463         \\</ul>
    464         \\</li>
    465         \\</ul>
    466         \\</li>
    467         \\<li>Five<ul>
    468         \\<li>Six</li>
    469         \\</ul>
    470         \\</li>
    471         \\</ul>
    472         \\</li>
    473         \\<li>Seven</li>
    474         \\</ul>
    475         \\
    476     );
    477 }
    478 
    479 test "tables" {
    480     try testRender(
    481         \\| Operator | Meaning          |
    482         \\| :------: | ---------------- |
    483         \\| `+`      | Add              |
    484         \\| `-`      | Subtract         |
    485         \\| `*`      | Multiply         |
    486         \\| `/`      | Divide           |
    487         \\| `??`     | **Not sure yet** |
    488         \\
    489         \\| Item 1 | Value 1 |
    490         \\| Item 2 | Value 2 |
    491         \\| Item 3 | Value 3 |
    492         \\| Item 4 | Value 4 |
    493         \\
    494         \\| :--- | :----: | ----: |
    495         \\| Left | Center | Right |
    496         \\
    497         \\   | One | Two |
    498         \\ | Three |     Four   |
    499         \\         | Five | Six |
    500         \\
    501     ,
    502         \\<table>
    503         \\<tr>
    504         \\<th style="text-align: center">Operator</th>
    505         \\<th>Meaning</th>
    506         \\</tr>
    507         \\<tr>
    508         \\<td style="text-align: center"><code>+</code></td>
    509         \\<td>Add</td>
    510         \\</tr>
    511         \\<tr>
    512         \\<td style="text-align: center"><code>-</code></td>
    513         \\<td>Subtract</td>
    514         \\</tr>
    515         \\<tr>
    516         \\<td style="text-align: center"><code>*</code></td>
    517         \\<td>Multiply</td>
    518         \\</tr>
    519         \\<tr>
    520         \\<td style="text-align: center"><code>/</code></td>
    521         \\<td>Divide</td>
    522         \\</tr>
    523         \\<tr>
    524         \\<td style="text-align: center"><code>??</code></td>
    525         \\<td><strong>Not sure yet</strong></td>
    526         \\</tr>
    527         \\</table>
    528         \\<table>
    529         \\<tr>
    530         \\<td>Item 1</td>
    531         \\<td>Value 1</td>
    532         \\</tr>
    533         \\<tr>
    534         \\<td>Item 2</td>
    535         \\<td>Value 2</td>
    536         \\</tr>
    537         \\<tr>
    538         \\<td>Item 3</td>
    539         \\<td>Value 3</td>
    540         \\</tr>
    541         \\<tr>
    542         \\<td>Item 4</td>
    543         \\<td>Value 4</td>
    544         \\</tr>
    545         \\</table>
    546         \\<table>
    547         \\<tr>
    548         \\<td style="text-align: left">Left</td>
    549         \\<td style="text-align: center">Center</td>
    550         \\<td style="text-align: right">Right</td>
    551         \\</tr>
    552         \\</table>
    553         \\<table>
    554         \\<tr>
    555         \\<td>One</td>
    556         \\<td>Two</td>
    557         \\</tr>
    558         \\<tr>
    559         \\<td>Three</td>
    560         \\<td>Four</td>
    561         \\</tr>
    562         \\<tr>
    563         \\<td>Five</td>
    564         \\<td>Six</td>
    565         \\</tr>
    566         \\</table>
    567         \\
    568     );
    569 }
    570 
    571 test "table with uneven number of columns" {
    572     try testRender(
    573         \\| One |
    574         \\| :-- | :--: |
    575         \\| One | Two | Three |
    576         \\
    577     ,
    578         \\<table>
    579         \\<tr>
    580         \\<th style="text-align: left">One</th>
    581         \\</tr>
    582         \\<tr>
    583         \\<td style="text-align: left">One</td>
    584         \\<td style="text-align: center">Two</td>
    585         \\<td>Three</td>
    586         \\</tr>
    587         \\</table>
    588         \\
    589     );
    590 }
    591 
    592 test "table with escaped pipes" {
    593     try testRender(
    594         \\| One \| Two |
    595         \\| --- | --- |
    596         \\| One \| Two |
    597         \\
    598     ,
    599         \\<table>
    600         \\<tr>
    601         \\<th>One | Two</th>
    602         \\</tr>
    603         \\<tr>
    604         \\<td>One | Two</td>
    605         \\</tr>
    606         \\</table>
    607         \\
    608     );
    609 }
    610 
    611 test "table with pipes in code spans" {
    612     try testRender(
    613         \\| `|` | Bitwise _OR_ |
    614         \\| `||` | Combines error sets |
    615         \\| `` `||` `` | Escaped version |
    616         \\| ` ``||`` ` | Another escaped version |
    617         \\| `Oops unterminated code span |
    618         \\
    619     ,
    620         \\<table>
    621         \\<tr>
    622         \\<td><code>|</code></td>
    623         \\<td>Bitwise <em>OR</em></td>
    624         \\</tr>
    625         \\<tr>
    626         \\<td><code>||</code></td>
    627         \\<td>Combines error sets</td>
    628         \\</tr>
    629         \\<tr>
    630         \\<td><code>`||`</code></td>
    631         \\<td>Escaped version</td>
    632         \\</tr>
    633         \\<tr>
    634         \\<td><code>``||``</code></td>
    635         \\<td>Another escaped version</td>
    636         \\</tr>
    637         \\</table>
    638         \\<p>| <code>Oops unterminated code span |</code></p>
    639         \\
    640     );
    641 }
    642 
    643 test "tables require leading and trailing pipes" {
    644     try testRender(
    645         \\Not | a | table
    646         \\
    647         \\| But | this | is |
    648         \\
    649         \\Also not a table:
    650         \\|
    651         \\     |
    652         \\
    653     ,
    654         \\<p>Not | a | table</p>
    655         \\<table>
    656         \\<tr>
    657         \\<td>But</td>
    658         \\<td>this</td>
    659         \\<td>is</td>
    660         \\</tr>
    661         \\</table>
    662         \\<p>Also not a table:
    663         \\|
    664         \\|</p>
    665         \\
    666     );
    667 }
    668 
    669 test "headings" {
    670     try testRender(
    671         \\# Level one
    672         \\## Level two
    673         \\### Level three
    674         \\#### Level four
    675         \\##### Level five
    676         \\###### Level six
    677         \\####### Not a heading
    678         \\
    679     ,
    680         \\<h1>Level one</h1>
    681         \\<h2>Level two</h2>
    682         \\<h3>Level three</h3>
    683         \\<h4>Level four</h4>
    684         \\<h5>Level five</h5>
    685         \\<h6>Level six</h6>
    686         \\<p>####### Not a heading</p>
    687         \\
    688     );
    689 }
    690 
    691 test "headings with inline content" {
    692     try testRender(
    693         \\# Outline of `std.zig`
    694         \\## **Important** notes
    695         \\### ***Nested* inline content**
    696         \\
    697     ,
    698         \\<h1>Outline of <code>std.zig</code></h1>
    699         \\<h2><strong>Important</strong> notes</h2>
    700         \\<h3><strong><em>Nested</em> inline content</strong></h3>
    701         \\
    702     );
    703 }
    704 
    705 test "code blocks" {
    706     try testRender(
    707         \\```
    708         \\Hello, world!
    709         \\This is some code.
    710         \\```
    711         \\``` zig test
    712         \\const std = @import("std");
    713         \\
    714         \\test {
    715         \\    try std.testing.expect(2 + 2 == 4);
    716         \\}
    717         \\```
    718         \\   ```
    719         \\   Indentation up to the fence is removed.
    720         \\        Like this.
    721         \\ Doesn't need to be fully indented.
    722         \\  ```
    723         \\```
    724         \\Overly indented closing fence is fine:
    725         \\    ```
    726         \\
    727     ,
    728         \\<pre><code>Hello, world!
    729         \\This is some code.
    730         \\</code></pre>
    731         \\<pre><code>const std = @import(&quot;std&quot;);
    732         \\
    733         \\test {
    734         \\    try std.testing.expect(2 + 2 == 4);
    735         \\}
    736         \\</code></pre>
    737         \\<pre><code>Indentation up to the fence is removed.
    738         \\     Like this.
    739         \\Doesn't need to be fully indented.
    740         \\</code></pre>
    741         \\<pre><code>Overly indented closing fence is fine:
    742         \\</code></pre>
    743         \\
    744     );
    745 }
    746 
    747 test "blockquotes" {
    748     try testRender(
    749         \\> > You miss 100% of the shots you don't take.
    750         \\> >
    751         \\> > ~ Wayne Gretzky
    752         \\>
    753         \\> ~ Michael Scott
    754         \\
    755     ,
    756         \\<blockquote>
    757         \\<blockquote>
    758         \\<p>You miss 100% of the shots you don't take.</p>
    759         \\<p>~ Wayne Gretzky</p>
    760         \\</blockquote>
    761         \\<p>~ Michael Scott</p>
    762         \\</blockquote>
    763         \\
    764     );
    765 }
    766 
    767 test "blockquote lazy continuation lines" {
    768     try testRender(
    769         \\>>>>Deeply nested blockquote
    770         \\>>which continues on another line
    771         \\and then yet another one.
    772         \\>>
    773         \\>> But now two of them have been closed.
    774         \\
    775         \\And then there were none.
    776         \\
    777     ,
    778         \\<blockquote>
    779         \\<blockquote>
    780         \\<blockquote>
    781         \\<blockquote>
    782         \\<p>Deeply nested blockquote
    783         \\which continues on another line
    784         \\and then yet another one.</p>
    785         \\</blockquote>
    786         \\</blockquote>
    787         \\<p>But now two of them have been closed.</p>
    788         \\</blockquote>
    789         \\</blockquote>
    790         \\<p>And then there were none.</p>
    791         \\
    792     );
    793 }
    794 
    795 test "paragraphs" {
    796     try testRender(
    797         \\Paragraph one.
    798         \\
    799         \\Paragraph two.
    800         \\Still in the paragraph.
    801         \\    So is this.
    802         \\
    803         \\
    804         \\
    805         \\
    806         \\ Last paragraph.
    807         \\
    808     ,
    809         \\<p>Paragraph one.</p>
    810         \\<p>Paragraph two.
    811         \\Still in the paragraph.
    812         \\So is this.</p>
    813         \\<p>Last paragraph.</p>
    814         \\
    815     );
    816 }
    817 
    818 test "thematic breaks" {
    819     try testRender(
    820         \\---
    821         \\***
    822         \\___
    823         \\          ---
    824         \\ - - - - - - - - - - -
    825         \\
    826     ,
    827         \\<hr />
    828         \\<hr />
    829         \\<hr />
    830         \\<hr />
    831         \\<hr />
    832         \\
    833     );
    834 }
    835 
    836 test "links" {
    837     try testRender(
    838         \\[Link](https://example.com)
    839         \\[Link *with inlines*](https://example.com)
    840         \\[Nested parens](https://example.com/nested(parens(inside)))
    841         \\[Escaped parens](https://example.com/\)escaped\()
    842         \\[Line break in target](test\
    843         \\target)
    844         \\
    845     ,
    846         \\<p><a href="https://example.com">Link</a>
    847         \\<a href="https://example.com">Link <em>with inlines</em></a>
    848         \\<a href="https://example.com/nested(parens(inside))">Nested parens</a>
    849         \\<a href="https://example.com/)escaped(">Escaped parens</a>
    850         \\<a href="test\
    851         \\target">Line break in target</a></p>
    852         \\
    853     );
    854 }
    855 
    856 test "autolinks" {
    857     try testRender(
    858         \\<https://example.com>
    859         \\**This is important: <https://example.com/strong>**
    860         \\<https://example.com?query=abc.123#page(parens)>
    861         \\<placeholder>
    862         \\<data:>
    863         \\1 < 2
    864         \\4 > 3
    865         \\Unclosed: <
    866         \\
    867     ,
    868         \\<p><a href="https://example.com">https://example.com</a>
    869         \\<strong>This is important: <a href="https://example.com/strong">https://example.com/strong</a></strong>
    870         \\<a href="https://example.com?query=abc.123#page(parens)">https://example.com?query=abc.123#page(parens)</a>
    871         \\&lt;placeholder&gt;
    872         \\<a href="data:">data:</a>
    873         \\1 &lt; 2
    874         \\4 &gt; 3
    875         \\Unclosed: &lt;</p>
    876         \\
    877     );
    878 }
    879 
    880 test "text autolinks" {
    881     try testRender(
    882         \\Text autolinks must start with http:// or https://.
    883         \\This doesn't count: ftp://example.com.
    884         \\Example: https://ziglang.org.
    885         \\Here is an important link: **http://example.com**
    886         \\(Links may be in parentheses: https://example.com/?q=(parens))
    887         \\Escaping a link so it's plain text: https\://example.com
    888         \\
    889     ,
    890         \\<p>Text autolinks must start with http:// or https://.
    891         \\This doesn't count: ftp://example.com.
    892         \\Example: <a href="https://ziglang.org">https://ziglang.org</a>.
    893         \\Here is an important link: <strong><a href="http://example.com">http://example.com</a></strong>
    894         \\(Links may be in parentheses: <a href="https://example.com/?q=(parens)">https://example.com/?q=(parens)</a>)
    895         \\Escaping a link so it's plain text: https://example.com</p>
    896         \\
    897     );
    898 }
    899 
    900 test "images" {
    901     try testRender(
    902         \\![Alt text](https://example.com/image.png)
    903         \\![Alt text *with inlines*](https://example.com/image.png)
    904         \\![Nested parens](https://example.com/nested(parens(inside)).png)
    905         \\![Escaped parens](https://example.com/\)escaped\(.png)
    906         \\![Line break in target](test\
    907         \\target)
    908         \\
    909     ,
    910         \\<p><img src="https://example.com/image.png" alt="Alt text" />
    911         \\<img src="https://example.com/image.png" alt="Alt text with inlines" />
    912         \\<img src="https://example.com/nested(parens(inside)).png" alt="Nested parens" />
    913         \\<img src="https://example.com/)escaped(.png" alt="Escaped parens" />
    914         \\<img src="test\
    915         \\target" alt="Line break in target" /></p>
    916         \\
    917     );
    918 }
    919 
    920 test "emphasis" {
    921     try testRender(
    922         \\*Emphasis.*
    923         \\**Strong.**
    924         \\***Strong emphasis.***
    925         \\****More...****
    926         \\*****MORE...*****
    927         \\******Even more...******
    928         \\*******OK, this is enough.*******
    929         \\
    930     ,
    931         \\<p><em>Emphasis.</em>
    932         \\<strong>Strong.</strong>
    933         \\<em><strong>Strong emphasis.</strong></em>
    934         \\<em><strong><em>More...</em></strong></em>
    935         \\<em><strong><strong>MORE...</strong></strong></em>
    936         \\<em><strong><em><strong>Even more...</strong></em></strong></em>
    937         \\<em><strong><em><strong><em>OK, this is enough.</em></strong></em></strong></em></p>
    938         \\
    939     );
    940     try testRender(
    941         \\_Emphasis._
    942         \\__Strong.__
    943         \\___Strong emphasis.___
    944         \\____More...____
    945         \\_____MORE..._____
    946         \\______Even more...______
    947         \\_______OK, this is enough._______
    948         \\
    949     ,
    950         \\<p><em>Emphasis.</em>
    951         \\<strong>Strong.</strong>
    952         \\<em><strong>Strong emphasis.</strong></em>
    953         \\<em><strong><em>More...</em></strong></em>
    954         \\<em><strong><strong>MORE...</strong></strong></em>
    955         \\<em><strong><em><strong>Even more...</strong></em></strong></em>
    956         \\<em><strong><em><strong><em>OK, this is enough.</em></strong></em></strong></em></p>
    957         \\
    958     );
    959 }
    960 
    961 test "nested emphasis" {
    962     try testRender(
    963         \\**Hello, *world!***
    964         \\*Hello, **world!***
    965         \\**Hello, _world!_**
    966         \\_Hello, **world!**_
    967         \\*Hello, **nested** *world!**
    968         \\***Hello,* world!**
    969         \\__**Hello, world!**__
    970         \\****Hello,** world!**
    971         \\__Hello,_ world!_
    972         \\*Test**123*
    973         \\__Test____123__
    974         \\
    975     ,
    976         \\<p><strong>Hello, <em>world!</em></strong>
    977         \\<em>Hello, <strong>world!</strong></em>
    978         \\<strong>Hello, <em>world!</em></strong>
    979         \\<em>Hello, <strong>world!</strong></em>
    980         \\<em>Hello, <strong>nested</strong> <em>world!</em></em>
    981         \\<strong><em>Hello,</em> world!</strong>
    982         \\<strong><strong>Hello, world!</strong></strong>
    983         \\<strong><strong>Hello,</strong> world!</strong>
    984         \\<em><em>Hello,</em> world!</em>
    985         \\<em>Test</em><em>123</em>
    986         \\<strong>Test____123</strong></p>
    987         \\
    988     );
    989 }
    990 
    991 test "emphasis precedence" {
    992     try testRender(
    993         \\*First one _wins*_.
    994         \\_*No other __rule matters.*_
    995         \\
    996     ,
    997         \\<p><em>First one _wins</em>_.
    998         \\<em><em>No other __rule matters.</em></em></p>
    999         \\
   1000     );
   1001 }
   1002 
   1003 test "emphasis open and close" {
   1004     try testRender(
   1005         \\Cannot open: *
   1006         \\Cannot open: _
   1007         \\*Cannot close: *
   1008         \\_Cannot close: _
   1009         \\
   1010         \\foo*bar*baz
   1011         \\foo_bar_baz
   1012         \\foo**bar**baz
   1013         \\foo__bar__baz
   1014         \\
   1015     ,
   1016         \\<p>Cannot open: *
   1017         \\Cannot open: _
   1018         \\*Cannot close: *
   1019         \\_Cannot close: _</p>
   1020         \\<p>foo<em>bar</em>baz
   1021         \\foo_bar_baz
   1022         \\foo<strong>bar</strong>baz
   1023         \\foo__bar__baz</p>
   1024         \\
   1025     );
   1026 }
   1027 
   1028 test "code spans" {
   1029     try testRender(
   1030         \\`Hello, world!`
   1031         \\```Multiple `backticks` can be used.```
   1032         \\`**This** does not produce emphasis.`
   1033         \\`` `Backtick enclosed string.` ``
   1034         \\`Delimiter lengths ```must``` match.`
   1035         \\
   1036         \\Unterminated ``code...
   1037         \\
   1038         \\Weird empty code span: `
   1039         \\
   1040         \\**Very important code: `hi`**
   1041         \\
   1042     ,
   1043         \\<p><code>Hello, world!</code>
   1044         \\<code>Multiple `backticks` can be used.</code>
   1045         \\<code>**This** does not produce emphasis.</code>
   1046         \\<code>`Backtick enclosed string.`</code>
   1047         \\<code>Delimiter lengths ```must``` match.</code></p>
   1048         \\<p>Unterminated <code>code...</code></p>
   1049         \\<p>Weird empty code span: <code></code></p>
   1050         \\<p><strong>Very important code: <code>hi</code></strong></p>
   1051         \\
   1052     );
   1053 }
   1054 
   1055 test "backslash escapes" {
   1056     try testRender(
   1057         \\Not \*emphasized\*.
   1058         \\Literal \\backslashes\\.
   1059         \\Not code: \`hi\`.
   1060         \\\# Not a title.
   1061         \\#\# Also not a title.
   1062         \\\> Not a blockquote.
   1063         \\\- Not a list item.
   1064         \\\| Not a table. |
   1065         \\| Also not a table. \|
   1066         \\Any \punctuation\ characte\r can be escaped:
   1067         \\\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~
   1068         \\
   1069     ,
   1070         \\<p>Not *emphasized*.
   1071         \\Literal \backslashes\.
   1072         \\Not code: `hi`.
   1073         \\# Not a title.
   1074         \\## Also not a title.
   1075         \\&gt; Not a blockquote.
   1076         \\- Not a list item.
   1077         \\| Not a table. |
   1078         \\| Also not a table. |
   1079         \\Any \punctuation\ characte\r can be escaped:
   1080         \\!&quot;#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~</p>
   1081         \\
   1082     );
   1083 }
   1084 
   1085 test "hard line breaks" {
   1086     try testRender(
   1087         \\The iguana sits\
   1088         \\Perched atop a short desk chair\
   1089         \\Writing code in Zig
   1090         \\
   1091     ,
   1092         \\<p>The iguana sits<br />
   1093         \\Perched atop a short desk chair<br />
   1094         \\Writing code in Zig</p>
   1095         \\
   1096     );
   1097 }
   1098 
   1099 test "Unicode handling" {
   1100     // Null bytes must be replaced.
   1101     try testRender("\x00\x00\x00", "<p>\u{FFFD}\u{FFFD}\u{FFFD}</p>\n");
   1102 
   1103     // Invalid UTF-8 must be replaced.
   1104     try testRender("\xC0\x80\xE0\x80\x80\xF0\x80\x80\x80", "<p>\u{FFFD}\u{FFFD}\u{FFFD}</p>\n");
   1105     try testRender("\xED\xA0\x80\xED\xBF\xBF", "<p>\u{FFFD}\u{FFFD}</p>\n");
   1106 
   1107     // Incomplete UTF-8 must be replaced.
   1108     try testRender("\xE2\x82", "<p>\u{FFFD}</p>\n");
   1109 }
   1110 
   1111 fn testRender(input: []const u8, expected: []const u8) !void {
   1112     var parser = try Parser.init(testing.allocator);
   1113     defer parser.deinit();
   1114 
   1115     var lines = std.mem.splitScalar(u8, input, '\n');
   1116     while (lines.next()) |line| {
   1117         try parser.feedLine(line);
   1118     }
   1119     var doc = try parser.endInput();
   1120     defer doc.deinit(testing.allocator);
   1121 
   1122     var actual = std.array_list.Managed(u8).init(testing.allocator);
   1123     defer actual.deinit();
   1124     try doc.render(actual.writer());
   1125 
   1126     try testing.expectEqualStrings(expected, actual.items);
   1127 }