README.md (22661B)
1 2 # EarmarkParser A Pure Elixir Markdown Parser 3 4 [![CI](https://github.com/robertdober/earmark_parser/workflows/CI/badge.svg)](https://github.com/robertdober/earmark_parser/actions) 5 [![Coverage Status](https://coveralls.io/repos/github/RobertDober/earmark_parser/badge.svg?branch=master)](https://coveralls.io/github/RobertDober/earmark_parser?branch=master) 6 [![Hex.pm](https://img.shields.io/hexpm/v/earmark_parser.svg)](https://hex.pm/packages/earmark_parser) 7 [![Hex.pm](https://img.shields.io/hexpm/dw/earmark_parser.svg)](https://hex.pm/packages/earmark_parser) 8 [![Hex.pm](https://img.shields.io/hexpm/dt/earmark_parser.svg)](https://hex.pm/packages/earmark_parser) 9 10 11 ## Table Of Contents 12 13 - [Table Of Contents](#table-of-contents) 14 - [Usage](#usage) 15 - [EarmarkParser](#earmarkparser) 16 - [API](#api) 17 - [EarmarkParser.as_ast](#earmarkparseras_ast) 18 - [Options](#options) 19 - [Supports](#supports) 20 - [Extensions](#extensions) 21 - [Links](#links) 22 - [Links supported by default](#links-supported-by-default) 23 - [Autolinks](#autolinks) 24 - [Additional link parsing via options](#additional-link-parsing-via-options) 25 - [Pure links](#pure-links) 26 - [Wikilinks...](#wikilinks) 27 - [Sub and Sup HTML Elements](#sub-and-sup-html-elements) 28 - [Github Flavored Markdown](#github-flavored-markdown) 29 - [Strike Through](#strike-through) 30 - [GFM Tables](#gfm-tables) 31 - [Syntax Highlighting](#syntax-highlighting) 32 - [Footnotes](#footnotes) 33 - [Breaks](#breaks) 34 - [Enabling **all** options that are disabled by default](#enabling-all-options-that-are-disabled-by-default) 35 - [Tables](#tables) 36 - [HTML Blocks](#html-blocks) 37 - [HTML Comments](#html-comments) 38 - [Lists](#lists) 39 - [Adding Attributes with the IAL extension](#adding-attributes-with-the-ial-extension) 40 - [To block elements](#to-block-elements) 41 - [To links or images](#to-links-or-images) 42 - [Limitations](#limitations) 43 - [Annotations](#annotations) 44 - [Annotated Paragraphs](#annotated-paragraphs) 45 - [Annotated HTML elements](#annotated-html-elements) 46 - [Commenting your Markdown](#commenting-your-markdown) 47 - [EarmarkParser.as_ast/2](#earmarkparseras_ast2) 48 - [EarmarkParser.version/0](#earmarkparserversion0) 49 - [Contributing](#contributing) 50 - [Author](#author) 51 - [LICENSE](#license) 52 53 ## Usage 54 55 ### EarmarkParser 56 57 58 ### API 59 60 #### EarmarkParser.as_ast 61 62 This is the structure of the result of `as_ast`. 63 64 {:ok, ast, []} = EarmarkParser.as_ast(markdown) 65 {:ok, ast, deprecation_messages} = EarmarkParser.as_ast(markdown) 66 {:error, ast, error_messages} = EarmarkParser.as_ast(markdown) 67 68 For examples see the functiondoc below. 69 70 #### Options 71 72 Options can be passed into `as_ast/2` according to the documentation of `EarmarkParser.Options`. 73 74 {status, ast, errors} = EarmarkParser.as_ast(markdown, options) 75 76 ## Supports 77 78 Standard [Gruber markdown][gruber]. 79 80 [gruber]: <http://daringfireball.net/projects/markdown/syntax> 81 82 ## Extensions 83 84 ### Links 85 86 #### Links supported by default 87 88 ##### Oneline HTML Link tags 89 90 ```elixir 91 iex(1)> EarmarkParser.as_ast(~s{<a href="href">link</a>}) 92 {:ok, [{"a", [{"href", "href"}], ["link"], %{verbatim: true}}], []} 93 ``` 94 95 ##### Markdown links 96 97 New style ... 98 99 ```elixir 100 iex(2)> EarmarkParser.as_ast(~s{[title](destination)}) 101 {:ok, [{"p", [], [{"a", [{"href", "destination"}], ["title"], %{}}], %{}}], []} 102 ``` 103 104 and old style 105 106 ```elixir 107 iex(3)> EarmarkParser.as_ast("[foo]: /url \"title\"\n\n[foo]\n") 108 {:ok, [{"p", [], [{"a", [{"href", "/url"}, {"title", "title"}], ["foo"], %{}}], %{}}], []} 109 ``` 110 111 #### Autolinks 112 113 ```elixir 114 iex(4)> EarmarkParser.as_ast("<https://elixir-lang.com>") 115 {:ok, [{"p", [], [{"a", [{"href", "https://elixir-lang.com"}], ["https://elixir-lang.com"], %{}}], %{}}], []} 116 ``` 117 118 #### Additional link parsing via options 119 120 121 #### Pure links 122 123 **N.B.** that the `pure_links` option is `true` by default 124 125 ```elixir 126 iex(5)> EarmarkParser.as_ast("https://github.com") 127 {:ok, [{"p", [], [{"a", [{"href", "https://github.com"}], ["https://github.com"], %{}}], %{}}], []} 128 ``` 129 130 But can be deactivated 131 132 ```elixir 133 iex(6)> EarmarkParser.as_ast("https://github.com", pure_links: false) 134 {:ok, [{"p", [], ["https://github.com"], %{}}], []} 135 ``` 136 137 138 #### Wikilinks... 139 140 are disabled by default 141 142 ```elixir 143 iex(7)> EarmarkParser.as_ast("[[page]]") 144 {:ok, [{"p", [], ["[[page]]"], %{}}], []} 145 ``` 146 147 and can be enabled 148 149 ```elixir 150 iex(8)> EarmarkParser.as_ast("[[page]]", wikilinks: true) 151 {:ok, [{"p", [], [{"a", [{"href", "page"}], ["page"], %{wikilink: true}}], %{}}], []} 152 ``` 153 154 155 ### Sub and Sup HTML Elements 156 157 This feature is not enabled by default but can be enabled with the option `sub_sup: true` 158 159 Therefore we will get 160 161 ```elixir 162 iex(9)> EarmarkParser.as_ast("H~2~O or a^n^ + b^n^ = c^n^") 163 {:ok, [{"p", [], ["H~2~O or a^n^ + b^n^ = c^n^"], %{}}], []} 164 ``` 165 166 But by specifying `sub_sup: true` 167 168 ```elixir 169 iex(10)> EarmarkParser.as_ast("H~2~O or a^n^ + b^n^ = c^n^", sub_sup: true) 170 {:ok, [{"p", [], ["H", {"sub", [], ["2"], %{}}, "O or a", {"sup", [], ["n"], %{}}, " + b", {"sup", [], ["n"], %{}}, " = c", {"sup", [], ["n"], %{}}], %{}}], []} 171 ``` 172 173 ### Github Flavored Markdown 174 175 GFM is supported by default, however as GFM is a moving target and all GFM extension do not make sense in a general context, EarmarkParser does not support all of it, here is a list of what is supported: 176 177 #### Strike Through 178 179 ```elixir 180 iex(11)> EarmarkParser.as_ast("~~hello~~") 181 {:ok, [{"p", [], [{"del", [], ["hello"], %{}}], %{}}], []} 182 ``` 183 184 #### GFM Tables 185 186 Are not enabled by default 187 188 ```elixir 189 iex(12)> as_ast("a|b\\n-|-\\nc|d\\n") 190 {:ok, [{"p", [], ["a|b\\n-|-\\nc|d\\n"], %{}}], []} 191 ``` 192 193 But can be enabled with `gfm_tables: true` 194 195 ```elixir 196 iex(13)> as_ast("a|b\n-|-\nc|d\n", gfm_tables: true) 197 {:ok, 198 [ 199 { 200 "table", 201 [], 202 [ 203 {"thead", [], [{"tr", [], [{"th", [{"style", "text-align: left;"}], ["a"], %{}}, {"th", [{"style", "text-align: left;"}], ["b"], %{}}], %{}}], %{}}, 204 {"tbody", [], [{"tr", [], [{"td", [{"style", "text-align: left;"}], ["c"], %{}}, {"td", [{"style", "text-align: left;"}], ["d"], %{}}], %{}}], %{}} 205 ], 206 %{} 207 } 208 ], 209 []} 210 ``` 211 212 #### Syntax Highlighting 213 214 All backquoted or fenced code blocks with a language string are rendered with the given 215 language as a _class_ attribute of the _code_ tag. 216 217 For example: 218 219 ```elixir 220 iex(14)> [ 221 ...(14)> "```elixir", 222 ...(14)> " @tag :hello", 223 ...(14)> "```" 224 ...(14)> ] |> as_ast() 225 {:ok, [{"pre", [], [{"code", [{"class", "elixir"}], [" @tag :hello"], %{}}], %{}}], []} 226 ``` 227 228 will be rendered as shown in the doctest above. 229 230 If you want to integrate with a syntax highlighter with different conventions you can add more classes by specifying prefixes that will be 231 put before the language string. 232 233 Prism.js for example needs a class `language-elixir`. In order to achieve that goal you can add `language-` 234 as a `code_class_prefix` to `EarmarkParser.Options`. 235 236 In the following example we want more than one additional class, so we add more prefixes. 237 238 ```elixir 239 iex(15)> [ 240 ...(15)> "```elixir", 241 ...(15)> " @tag :hello", 242 ...(15)> "```" 243 ...(15)> ] |> as_ast(%EarmarkParser.Options{code_class_prefix: "lang- language-"}) 244 {:ok, [{"pre", [], [{"code", [{"class", "elixir lang-elixir language-elixir"}], [" @tag :hello"], %{}}], %{}}], []} 245 ``` 246 247 248 #### Footnotes 249 250 **N.B.** Footnotes are disabled by default, use `as_ast(..., footnotes: true)` to enable them 251 252 Footnotes are now a **superset** of GFM Footnotes. This implies some changes 253 254 - Footnote definitions (`[^footnote_id]`) must come at the end of your document (_GFM_) 255 - Footnotes that are not referenced are not rendered anymore (_GFM_) 256 - Footnote definitions can contain any markup with the exception of footnote definitions 257 258 ```elixir 259 iex(16)> markdown = [ 260 ...(16)> "My reference[^to_footnote]", 261 ...(16)> "", 262 ...(16)> "[^1]: I am not rendered", 263 ...(16)> "[^to_footnote]: Important information"] 264 ...(16)> {:ok, ast, []} = as_ast(markdown, footnotes: true) 265 ...(16)> ast 266 [ 267 {"p", [], ["My reference", 268 {"a", 269 [{"href", "#fn:to_footnote"}, {"id", "fnref:to_footnote"}, {"class", "footnote"}, {"title", "see footnote"}], 270 ["to_footnote"], %{}} 271 ], %{}}, 272 {"div", 273 [{"class", "footnotes"}], 274 [{"hr", [], [], %{}}, 275 {"ol", [], 276 [{"li", [{"id", "fn:to_footnote"}], 277 [{"a", [{"class", "reversefootnote"}, {"href", "#fnref:to_footnote"}, {"title", "return to article"}], ["↩"], %{}}, 278 {"p", [], ["Important information"], %{}}], %{}} 279 ], %{}}], %{}} 280 ] 281 ``` 282 283 For more complex examples of footnotes, please refer to 284 [these tests](https://github.com/RobertDober/earmark_parser/tree/master/test/acceptance/ast/footnotes/multiple_fn_test.exs) 285 286 #### Breaks 287 288 Hard linebreaks are disabled by default 289 290 ```elixir 291 iex(17)> ["* a"," b", "c"] 292 ...(17)> |> as_ast() 293 {:ok, 294 [{"ul", [], [{"li", [], ["a\nb\nc"], %{}}], %{}}], 295 []} 296 ``` 297 298 But can be enabled with `breaks: true` 299 300 ```elixir 301 iex(18)> ["* a"," b", "c"] 302 ...(18)> |> as_ast(breaks: true) 303 {:ok, [{"ul", [], [{"li", [], ["a", {"br", [], [], %{}}, "b", {"br", [], [], %{}}, "c"], %{}}], %{}}], []} 304 ``` 305 306 #### Enabling **all** options that are disabled by default 307 308 Can be achieved with the `all: true` option 309 310 ```elixir 311 iex(19)> [ 312 ...(19)> "a^n^", 313 ...(19)> "b~2~", 314 ...(19)> "[[wikilink]]"] 315 ...(19)> |> as_ast(all: true) 316 {:ok, [ 317 {"p", [], ["a", {"sup", [], ["n"], %{}}, {"br", [], [], %{}}, "b", {"sub", [], ["2"], %{}}, {"br", [], [], %{}}, {"a", [{"href", "wikilink"}], ["wikilink"], %{wikilink: true}}], %{}} 318 ], 319 []} 320 ``` 321 322 #### Tables 323 324 Are supported as long as they are preceded by an empty line. 325 326 State | Abbrev | Capital 327 ----: | :----: | ------- 328 Texas | TX | Austin 329 Maine | ME | Augusta 330 331 Tables may have leading and trailing vertical bars on each line 332 333 | State | Abbrev | Capital | 334 | ----: | :----: | ------- | 335 | Texas | TX | Austin | 336 | Maine | ME | Augusta | 337 338 Tables need not have headers, in which case all column alignments 339 default to left. 340 341 | Texas | TX | Austin | 342 | Maine | ME | Augusta | 343 344 Currently we assume there are always spaces around interior vertical unless 345 there are exterior bars. 346 347 However in order to be more GFM compatible the `gfm_tables: true` option 348 can be used to interpret only interior vertical bars as a table if a separation 349 line is given, therefore 350 351 Language|Rating 352 --------|------ 353 Elixir | awesome 354 355 is a table (if and only if `gfm_tables: true`) while 356 357 Language|Rating 358 Elixir | awesome 359 360 never is. 361 362 #### HTML Blocks 363 364 HTML is not parsed recursively or detected in all conditions right now, though GFM compliance 365 is a goal. 366 367 But for now the following holds: 368 369 A HTML Block defined by a tag starting a line and the same tag starting a different line is parsed 370 as one HTML AST node, marked with %{verbatim: true} 371 372 E.g. 373 374 ```elixir 375 iex(20)> lines = [ "<div><span>", "some</span><text>", "</div>more text" ] 376 ...(20)> EarmarkParser.as_ast(lines) 377 {:ok, [{"div", [], ["<span>", "some</span><text>"], %{verbatim: true}}, "more text"], []} 378 ``` 379 380 And a line starting with an opening tag and ending with the corresponding closing tag is parsed in similar 381 fashion 382 383 ```elixir 384 iex(21)> EarmarkParser.as_ast(["<span class=\"superspan\">spaniel</span>"]) 385 {:ok, [{"span", [{"class", "superspan"}], ["spaniel"], %{verbatim: true}}], []} 386 ``` 387 388 What is HTML? 389 390 We differ from strict GFM by allowing **all** tags not only HTML5 tags this holds for one liners.... 391 392 ```elixir 393 iex(22)> {:ok, ast, []} = EarmarkParser.as_ast(["<stupid />", "<not>better</not>"]) 394 ...(22)> ast 395 [ 396 {"stupid", [], [], %{verbatim: true}}, 397 {"not", [], ["better"], %{verbatim: true}}] 398 ``` 399 400 and for multi line blocks 401 402 ```elixir 403 iex(23)> {:ok, ast, []} = EarmarkParser.as_ast([ "<hello>", "world", "</hello>"]) 404 ...(23)> ast 405 [{"hello", [], ["world"], %{verbatim: true}}] 406 ``` 407 408 #### HTML Comments 409 410 Are recognized if they start a line (after ws and are parsed until the next `-->` is found 411 all text after the next '-->' is ignored 412 413 E.g. 414 415 ```elixir 416 iex(24)> EarmarkParser.as_ast(" <!-- Comment\ncomment line\ncomment --> text -->\nafter") 417 {:ok, [{:comment, [], [" Comment", "comment line", "comment "], %{comment: true}}, {"p", [], ["after"], %{}}], []} 418 ``` 419 420 421 #### Lists 422 423 Lists are pretty much GFM compliant, but some behaviors concerning the interpreation of the markdown inside a List Item's first 424 paragraph seem not worth to be interpreted, examples are blockquote in a tight [list item](ttps://babelmark.github.io/?text=*+aa%0A++%3E+Second) 425 which we can only have in a [loose one](https://babelmark.github.io/?text=*+aa%0A++%0A++%3E+Second) 426 427 Or a headline in a [tight list item](https://babelmark.github.io/?text=*+bb%0A++%23+Headline) which, again is only available in the 428 [loose version](https://babelmark.github.io/?text=*+bb%0A%0A++%23+Headline) in EarmarkParser. 429 430 furthermore [this example](https://babelmark.github.io/?text=*+aa%0A++%60%60%60%0ASecond%0A++%60%60%60) demonstrates how weird 431 and definitely not useful GFM's own interpretation can get. 432 433 Therefore we stick to a more predictable approach. 434 435 ```elixir 436 iex(25)> markdown = [ 437 ...(25)> "* aa", 438 ...(25)> " ```", 439 ...(25)> "Second", 440 ...(25)> " ```" ] 441 ...(25)> as_ast(markdown) 442 {:ok, [{"ul", [], [{"li", [], ["aa", {"pre", [], [{"code", [], ["Second"], %{}}], %{}}], %{}}], %{}}], []} 443 ``` 444 445 Also we do support the immediate style of block content inside lists 446 447 ```elixir 448 iex(26)> as_ast("* > Nota Bene!") 449 {:ok, [{"ul", [], [{"li", [], [{"blockquote", [], [{"p", [], ["Nota Bene!"], %{}}], %{}}], %{}}], %{}}], []} 450 ``` 451 452 or 453 454 ```elixir 455 iex(27)> as_ast("1. # Breaking...") 456 {:ok, [{"ol", [], [{"li", [], [{"h1", [], ["Breaking..."], %{}}], %{}}], %{}}], []} 457 ``` 458 459 460 ### Adding Attributes with the IAL extension 461 462 #### To block elements 463 464 HTML attributes can be added to any block-level element. We use 465 the Kramdown syntax: add the line `{:` _attrs_ `}` following the block. 466 467 ```elixir 468 iex(28)> markdown = ["# Headline", "{:.from-next-line}"] 469 ...(28)> as_ast(markdown) 470 {:ok, [{"h1", [{"class", "from-next-line"}], ["Headline"], %{}}], []} 471 ``` 472 473 Headers can also have the IAL string at the end of the line 474 475 ```elixir 476 iex(29)> markdown = ["# Headline{:.from-same-line}"] 477 ...(29)> as_ast(markdown) 478 {:ok, [{"h1", [{"class", "from-same-line"}], ["Headline"], %{}}], []} 479 ``` 480 481 A special use case is headers inside blockquotes which allow for some nifty styling in `ex_doc`* 482 see [this PR](https://github.com/elixir-lang/ex_doc/pull/1400) if you are interested in the technical 483 details 484 485 ```elixir 486 iex(30)> markdown = ["> # Headline{:.warning}"] 487 ...(30)> as_ast(markdown) 488 {:ok, [{"blockquote", [], [{"h1", [{"class", "warning"}], ["Headline"], %{}}], %{}}], []} 489 ``` 490 491 This also works for headers inside lists 492 493 ```elixir 494 iex(31)> markdown = ["- # Headline{:.warning}"] 495 ...(31)> as_ast(markdown) 496 {:ok, [{"ul", [], [{"li", [], [{"h1", [{"class", "warning"}], ["Headline"], %{}}], %{}}], %{}}], []} 497 ``` 498 499 It still works for inline code, as it did before 500 501 ```elixir 502 iex(32)> markdown = "`Enum.map`{:lang=elixir}" 503 ...(32)> as_ast(markdown) 504 {:ok, [{"p", [], [{"code", [{"class", "inline"}, {"lang", "elixir"}], ["Enum.map"], %{}}], %{}}], []} 505 ``` 506 507 508 _attrs_ can be one or more of: 509 510 * `.className` 511 * `#id` 512 * name=value, name="value", or name='value' 513 514 For example: 515 516 # Warning 517 {: .red} 518 519 Do not turn off the engine 520 if you are at altitude. 521 {: .boxed #warning spellcheck="true"} 522 523 #### To links or images 524 525 It is possible to add IAL attributes to generated links or images in the following 526 format. 527 528 ```elixir 529 iex(33)> markdown = "[link](url) {: .classy}" 530 ...(33)> EarmarkParser.as_ast(markdown) 531 { :ok, [{"p", [], [{"a", [{"class", "classy"}, {"href", "url"}], ["link"], %{}}], %{}}], []} 532 ``` 533 534 For both cases, malformed attributes are ignored and warnings are issued. 535 536 ```elixir 537 iex(34)> [ "Some text", "{:hello}" ] |> Enum.join("\n") |> EarmarkParser.as_ast() 538 {:error, [{"p", [], ["Some text"], %{}}], [{:warning, 2,"Illegal attributes [\"hello\"] ignored in IAL"}]} 539 ``` 540 541 It is possible to escape the IAL in both forms if necessary 542 543 ```elixir 544 iex(35)> markdown = "[link](url)\\{: .classy}" 545 ...(35)> EarmarkParser.as_ast(markdown) 546 {:ok, [{"p", [], [{"a", [{"href", "url"}], ["link"], %{}}, "{: .classy}"], %{}}], []} 547 ``` 548 549 This of course is not necessary in code blocks or text lines 550 containing an IAL-like string, as in the following example 551 552 ```elixir 553 iex(36)> markdown = "hello {:world}" 554 ...(36)> EarmarkParser.as_ast(markdown) 555 {:ok, [{"p", [], ["hello {:world}"], %{}}], []} 556 ``` 557 558 ## Limitations 559 560 * Block-level HTML is correctly handled only if each HTML 561 tag appears on its own line. So 562 563 <div> 564 <div> 565 hello 566 </div> 567 </div> 568 569 will work. However. the following won't 570 571 <div> 572 hello</div> 573 574 * John Gruber's tests contain an ambiguity when it comes to 575 lines that might be the start of a list inside paragraphs. 576 577 One test says that 578 579 This is the text 580 * of a paragraph 581 that I wrote 582 583 is a single paragraph. The "*" is not significant. However, another 584 test has 585 586 * A list item 587 * an another 588 589 and expects this to be a nested list. But, in reality, the second could just 590 be the continuation of a paragraph. 591 592 I've chosen always to use the second interpretation—a line that looks like 593 a list item will always be a list item. 594 595 * Rendering of block and inline elements. 596 597 Block or void HTML elements that are at the absolute beginning of a line end 598 the preceding paragraph. 599 600 Thusly 601 602 mypara 603 <hr /> 604 605 Becomes 606 607 <p>mypara</p> 608 <hr /> 609 610 While 611 612 mypara 613 <hr /> 614 615 will be transformed into 616 617 <p>mypara 618 <hr /></p> 619 620 ## Annotations 621 622 **N.B.** this is an experimental feature from v1.4.16-pre on and might change or be removed again 623 624 The idea is that each markdown line can be annotated, as such annotations change the semantics of Markdown 625 they have to be enabled with the `annotations` option. 626 627 If the `annotations` option is set to a string (only one string is supported right now, but a list might 628 be implemented later on, hence the name), the last occurrence of that string in a line and all text following 629 it will be added to the line as an annotation. 630 631 Depending on how that line will eventually be parsed, this annotation will be added to the meta map (the 4th element 632 in an AST quadruple) with the key `:annotation` 633 634 In the current version the annotation will only be applied to verbatim HTML tags and paragraphs 635 636 Let us show some examples now: 637 638 ### Annotated Paragraphs 639 640 ```elixir 641 iex(37)> as_ast("hello %> annotated", annotations: "%>") 642 {:ok, [{"p", [], ["hello "], %{annotation: "%> annotated"}}], []} 643 ``` 644 645 If we annotate more than one line in a para the first annotation takes precedence 646 647 ```elixir 648 iex(38)> as_ast("hello %> annotated\nworld %> discarded", annotations: "%>") 649 {:ok, [{"p", [], ["hello \nworld "], %{annotation: "%> annotated"}}], []} 650 ``` 651 652 ### Annotated HTML elements 653 654 In one line 655 656 ```elixir 657 iex(39)> as_ast("<span>One Line</span> // a span", annotations: "//") 658 {:ok, [{"span", [], ["One Line"], %{annotation: "// a span", verbatim: true}}], []} 659 ``` 660 661 or block elements 662 663 ```elixir 664 iex(40)> [ 665 ...(40)> "<div> : annotation", 666 ...(40)> " <span>text</span>", 667 ...(40)> "</div> : discarded" 668 ...(40)> ] |> as_ast(annotations: " : ") 669 {:ok, [{"div", [], [" <span>text</span>"], %{annotation: " : annotation", verbatim: true}}], []} 670 ``` 671 672 ### Commenting your Markdown 673 674 Although many markdown elements do not support annotations yet, they can be used to comment your markdown, w/o cluttering 675 the generated AST with comments 676 677 ```elixir 678 iex(41)> [ 679 ...(41)> "# Headline --> first line", 680 ...(41)> "- item1 --> a list item", 681 ...(41)> "- item2 --> another list item", 682 ...(41)> "", 683 ...(41)> "<http://somewhere/to/go> --> do not go there" 684 ...(41)> ] |> as_ast(annotations: "-->") 685 {:ok, [ 686 {"h1", [], ["Headline"], %{}}, 687 {"ul", [], [{"li", [], ["item1 "], %{}}, {"li", [], ["item2 "], %{}}], %{}}, 688 {"p", [], [{"a", [{"href", "http://somewhere/to/go"}], ["http://somewhere/to/go"], %{}}, " "], %{annotation: "--> do not go there"}} 689 ], [] 690 } 691 ``` 692 693 694 ### EarmarkParser.as_ast/2 695 696 iex(42)> markdown = "My `code` is **best**" 697 ...(42)> {:ok, ast, []} = EarmarkParser.as_ast(markdown) 698 ...(42)> ast 699 [{"p", [], ["My ", {"code", [{"class", "inline"}], ["code"], %{}}, " is ", {"strong", [], ["best"], %{}}], %{}}] 700 701 702 703 ```elixir 704 iex(43)> markdown = "```elixir\nIO.puts 42\n```" 705 ...(43)> {:ok, ast, []} = EarmarkParser.as_ast(markdown, code_class_prefix: "lang-") 706 ...(43)> ast 707 [{"pre", [], [{"code", [{"class", "elixir lang-elixir"}], ["IO.puts 42"], %{}}], %{}}] 708 ``` 709 710 **Rationale**: 711 712 The AST is exposed in the spirit of [Floki's](https://hex.pm/packages/floki). 713 714 ### EarmarkParser.version/0 715 716 Accesses current hex version of the `EarmarkParser` application. Convenience for 717 `iex` usage. 718 719 720 721 ## Contributing 722 723 Pull Requests are happily accepted. 724 725 Please be aware of one _caveat_ when correcting/improving `README.md`. 726 727 The `README.md` is generated by the mix task `readme` from `README.template` and 728 docstrings by means of `%moduledoc` or `%functiondoc` directives. 729 730 Please identify the origin of the generated text you want to correct and then 731 apply your changes there. 732 733 Then issue the mix task `readme`, this is important to have a correctly updated `README.md` after the merge of 734 your PR. 735 736 Thank you all who have already helped with Earmark/EarmarkParser, your names are duely noted in [RELEASE.md](RELEASE.md). 737 738 ## Author 739 740 Copyright © 2014,5,6,7,8,9;2020 Dave Thomas, The Pragmatic Programmers 741 @/+pragdave, dave@pragprog.com 742 Copyright © 2020 Robert Dober 743 robert.dober@gmail.com 744 745 ## LICENSE 746 747 Same as Elixir, which is Apache License v2.0. Please refer to [LICENSE](LICENSE) for details. 748 749 <!-- SPDX-License-Identifier: Apache-2.0 -->