earmark.ex (2658B)
1 defmodule ExDoc.Markdown.Earmark do 2 @moduledoc """ 3 ExDoc extension for the EarmarkParser Markdown parser. 4 """ 5 6 @behaviour ExDoc.Markdown 7 8 @impl true 9 def available? do 10 match?({:ok, _}, Application.ensure_all_started(:earmark_parser)) and 11 Code.ensure_loaded?(EarmarkParser) 12 end 13 14 @doc """ 15 Generate HTML AST. 16 17 ## Options 18 19 * `:gfm` - (boolean) turns on Github Flavored Markdown extensions. Defaults to `true`. 20 21 * `:breaks` - (boolean) only applicable if `gfm` is enabled. Makes all line 22 breaks significant (so every line in the input is a new line in the output). 23 24 """ 25 @impl true 26 def to_ast(text, opts) do 27 options = [ 28 gfm: true, 29 line: 1, 30 file: "nofile", 31 breaks: false, 32 pure_links: true 33 ] 34 35 options = Keyword.merge(options, opts) 36 37 case EarmarkParser.as_ast(text, options) do 38 {:ok, ast, messages} -> 39 print_messages(messages, options) 40 fixup(ast) 41 42 {:error, ast, messages} -> 43 print_messages(messages, options) 44 fixup(ast) 45 end 46 end 47 48 defp print_messages(messages, options) do 49 for {severity, line, message} <- messages do 50 file = options[:file] 51 IO.warn("#{inspect(__MODULE__)} (#{severity}) #{file}:#{line} #{message}", []) 52 end 53 end 54 55 defp fixup(list) when is_list(list) do 56 fixup_list(list, []) 57 end 58 59 defp fixup(binary) when is_binary(binary) do 60 binary 61 end 62 63 defp fixup({tag, attrs, ast}) do 64 fixup({tag, attrs, ast, %{}}) 65 end 66 67 defp fixup({tag, attrs, ast, meta}) when is_binary(tag) and is_list(attrs) and is_map(meta) do 68 {fixup_tag(tag), Enum.map(attrs, &fixup_attr/1), fixup(ast), meta} 69 end 70 71 defp fixup({:comment, _, _, _}) do 72 [] 73 end 74 75 # We are matching on Livebook outputs here, because we prune comments at this point 76 defp fixup_list( 77 [ 78 {:comment, _, [~s/ livebook:{"output":true} /], %{comment: true}}, 79 {"pre", pre_attrs, [{"code", code_attrs, [source], code_meta}], pre_meta} 80 | ast 81 ], 82 acc 83 ) do 84 code_attrs = Enum.reject(code_attrs, &match?({"class", _}, &1)) 85 new_code = {"code", [{"class", "output"} | code_attrs], [source], code_meta} 86 fixup_list([{"pre", pre_attrs, [new_code], pre_meta} | ast], acc) 87 end 88 89 defp fixup_list([head | tail], acc) do 90 fixed = fixup(head) 91 92 if fixed == [] do 93 fixup_list(tail, acc) 94 else 95 fixup_list(tail, [fixed | acc]) 96 end 97 end 98 99 defp fixup_list([], acc) do 100 Enum.reverse(acc) 101 end 102 103 defp fixup_tag(tag) do 104 String.to_atom(tag) 105 end 106 107 defp fixup_attr({name, value}) do 108 {String.to_atom(name), value} 109 end 110 end