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("std"); 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 \\<placeholder> 872 \\<a href="data:">data:</a> 873 \\1 < 2 874 \\4 > 3 875 \\Unclosed: <</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 \\ 903 \\ 904 \\).png) 905 \\escaped\(.png) 906 \\ 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 \\> 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 \\!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</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 }