markdown.ex (3115B)
1 defmodule ExDoc.Markdown do 2 @moduledoc """ 3 Adapter behaviour and conveniences for converting Markdown to HTML. 4 5 ExDoc is compatible with any markdown processor that implements the 6 functions defined in this module. The markdown processor can be changed 7 via the `:markdown_processor` option in your `mix.exs`. 8 9 ExDoc supports the following Markdown parsers out of the box: 10 11 * [EarmarkParser](https://github.com/robertdober/earmark_parser) 12 13 ExDoc uses EarmarkParser by default. 14 """ 15 16 @doc """ 17 Converts markdown into HTML. 18 """ 19 @callback to_ast(String.t(), Keyword.t()) :: term() 20 21 @doc """ 22 Returns true if all dependencies necessary are available. 23 """ 24 @callback available?() :: boolean() 25 26 @markdown_processors [ 27 ExDoc.Markdown.Earmark 28 ] 29 30 @markdown_processor_key :markdown_processor 31 32 @doc """ 33 Converts the given markdown document to HTML AST. 34 """ 35 def to_ast(text, opts \\ []) when is_binary(text) do 36 {processor, options} = get_markdown_processor() 37 processor.to_ast(text, options |> Keyword.merge(opts)) 38 end 39 40 @doc """ 41 Gets the current markdown processor set globally. 42 """ 43 def get_markdown_processor do 44 case Application.fetch_env(:ex_doc, @markdown_processor_key) do 45 {:ok, {processor, options}} -> 46 {processor, options} 47 48 :error -> 49 processor = find_markdown_processor() || raise_no_markdown_processor() 50 put_markdown_processor({processor, []}) 51 {processor, []} 52 end 53 end 54 55 @doc """ 56 Changes the markdown processor globally. 57 """ 58 def put_markdown_processor(processor) when is_atom(processor) do 59 put_markdown_processor({processor, []}) 60 end 61 62 def put_markdown_processor({processor, options}) do 63 Application.put_env(:ex_doc, @markdown_processor_key, {processor, options}) 64 end 65 66 defp find_markdown_processor do 67 Enum.find(@markdown_processors, fn module -> 68 Code.ensure_loaded?(module) && module.available? 69 end) 70 end 71 72 defp raise_no_markdown_processor do 73 raise """ 74 Could not find a markdown processor to be used by ex_doc. 75 You can either: 76 77 * Add {:earmark, ">= 0.0.0"} to your mix.exs deps 78 to use an Elixir-based markdown processor 79 """ 80 end 81 82 @doc """ 83 Wraps the desired tags in HTML in sections. 84 """ 85 def sectionize(list, matcher), do: sectionize(list, matcher, []) 86 87 defp sectionize(list, matcher, acc) do 88 case pivot(list, acc, matcher) do 89 {acc, {header_tag, header_attrs, _, _} = header, rest} -> 90 {inner, rest} = Enum.split_while(rest, ¬_tag?(&1, header_tag)) 91 class = String.trim_trailing("#{header_tag} #{header_attrs[:class]}") 92 section = {:section, [class: class], [header | sectionize(inner, matcher, [])], %{}} 93 sectionize(rest, matcher, [section | acc]) 94 95 acc -> 96 acc 97 end 98 end 99 100 defp not_tag?({tag, _, _, _}, tag), do: false 101 defp not_tag?(_, _tag), do: true 102 103 defp pivot([head | tail], acc, fun) do 104 case fun.(head) do 105 true -> {acc, head, tail} 106 false -> pivot(tail, [head | acc], fun) 107 end 108 end 109 110 defp pivot([], acc, _fun), do: Enum.reverse(acc) 111 end