master
   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
 127const std = @import("std");
 128const testing = std.testing;
 129
 130pub const Document = @import("markdown/Document.zig");
 131pub const Parser = @import("markdown/Parser.zig");
 132pub const Renderer = @import("markdown/renderer.zig").Renderer;
 133pub const renderNodeInlineText = @import("markdown/renderer.zig").renderNodeInlineText;
 134pub const fmtHtml = @import("markdown/renderer.zig").fmtHtml;
 135
 136// Avoid exposing main to other files merely importing this one.
 137pub const main = if (@import("root") == @This())
 138    mainImpl
 139else
 140    @compileError("only available as root source file");
 141
 142fn 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
 168test "empty document" {
 169    try testRender("", "");
 170    try testRender("   ", "");
 171    try testRender("\n \n\t\n   \n", "");
 172}
 173
 174test "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
 226test "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
 301test "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
 324test "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
 379test "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
 479test "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
 571test "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
 592test "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
 611test "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
 643test "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
 669test "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
 691test "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
 705test "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
 747test "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
 767test "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
 795test "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
 818test "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
 836test "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
 856test "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
 880test "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
 900test "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
 920test "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
 961test "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
 991test "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
1003test "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
1028test "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
1055test "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
1085test "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
1099test "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
1111fn 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}