zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

doc_ast.ex (5339B)


      1 defmodule ExDoc.DocAST do
      2   @moduledoc false
      3 
      4   @type t :: term()
      5 
      6   alias ExDoc.Markdown
      7 
      8   @doc """
      9   Parses given `doc_content` according to `doc_format`.
     10   """
     11   def parse!(doc_content, doc_format, options \\ [])
     12 
     13   def parse!(markdown, "text/markdown", opts) do
     14     parse_markdown(markdown, opts)
     15   end
     16 
     17   def parse!(ast, "application/erlang+html", _options) do
     18     parse_erl_ast(ast)
     19   end
     20 
     21   def parse!(_ast, other, _opts) do
     22     raise "content type #{inspect(other)} is not supported"
     23   end
     24 
     25   # https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-element
     26   @void_elements ~W(area base br col command embed hr img input keygen link
     27     meta param source track wbr)a
     28 
     29   @doc """
     30   Transform AST into string.
     31   """
     32   def to_string(ast, fun \\ fn _ast, string -> string end)
     33 
     34   def to_string(binary, _fun) when is_binary(binary) do
     35     ExDoc.Utils.h(binary)
     36   end
     37 
     38   def to_string(list, fun) when is_list(list) do
     39     result = Enum.map_join(list, "", &to_string(&1, fun))
     40     fun.(list, result)
     41   end
     42 
     43   def to_string({tag, attrs, _inner, _meta} = ast, fun) when tag in @void_elements do
     44     result = "<#{tag}#{ast_attributes_to_string(attrs)}/>"
     45     fun.(ast, result)
     46   end
     47 
     48   def to_string({tag, attrs, inner, %{verbatim: true}} = ast, fun) do
     49     inner = Enum.join(inner, "")
     50     result = "<#{tag}#{ast_attributes_to_string(attrs)}>" <> inner <> "</#{tag}>"
     51     fun.(ast, result)
     52   end
     53 
     54   def to_string({tag, attrs, inner, _meta} = ast, fun) do
     55     result = "<#{tag}#{ast_attributes_to_string(attrs)}>" <> to_string(inner, fun) <> "</#{tag}>"
     56     fun.(ast, result)
     57   end
     58 
     59   defp ast_attributes_to_string(attrs) do
     60     Enum.map(attrs, fn {key, val} -> " #{key}=\"#{val}\"" end)
     61   end
     62 
     63   ## parse markdown
     64 
     65   defp parse_markdown(markdown, opts) do
     66     Markdown.to_ast(markdown, opts)
     67   end
     68 
     69   ## parse erlang+html
     70 
     71   defp parse_erl_ast(binary) when is_binary(binary) do
     72     binary
     73   end
     74 
     75   defp parse_erl_ast(list) when is_list(list) do
     76     Enum.map(list, &parse_erl_ast/1)
     77   end
     78 
     79   defp parse_erl_ast({:pre, attrs, content}) do
     80     case content do
     81       # if we already have <pre><code>...</code></pre>, carry on
     82       [{:code, _, _}] ->
     83         {:pre, attrs, parse_erl_ast(content), %{}}
     84 
     85       # otherwise, turn <pre>...</pre> into <pre><code>...</code></pre>
     86       _ ->
     87         content = [{:code, [], parse_erl_ast(content), %{}}]
     88         {:pre, attrs, content, %{}}
     89     end
     90   end
     91 
     92   defp parse_erl_ast({tag, attrs, content}) when is_atom(tag) do
     93     {tag, attrs, parse_erl_ast(content), %{}}
     94   end
     95 
     96   @doc """
     97   Extracts leading title element from the given AST.
     98 
     99   If found, the title element is stripped from the resulting AST.
    100   """
    101   def extract_title(ast)
    102 
    103   def extract_title([{:h1, _attrs, inner, _meta} | ast]) do
    104     {:ok, inner, ast}
    105   end
    106 
    107   def extract_title(_ast) do
    108     :error
    109   end
    110 
    111   @doc """
    112   Returns text content from the given AST.
    113   """
    114   def text_from_ast(ast) do
    115     ast
    116     |> do_text_from_ast()
    117     |> IO.iodata_to_binary()
    118     |> String.trim()
    119   end
    120 
    121   def do_text_from_ast(ast) when is_list(ast) do
    122     Enum.map(ast, &do_text_from_ast/1)
    123   end
    124 
    125   def do_text_from_ast(ast) when is_binary(ast), do: ast
    126   def do_text_from_ast({_tag, _attr, ast, _meta}), do: text_from_ast(ast)
    127 
    128   @doc """
    129   Highlights a DocAST converted to string.
    130   """
    131   def highlight(html, language, opts \\ []) do
    132     highlight_info = language.highlight_info()
    133 
    134     Regex.replace(
    135       ~r/<pre(\s+class="\w*")?><code(?:\s+class="(\w*)")?>([^<]*)<\/code><\/pre>/,
    136       html,
    137       &highlight_code_block(&1, &2, &3, &4, highlight_info, opts)
    138     )
    139   end
    140 
    141   defp highlight_code_block(full_block, pre_attr, lang, code, highlight_info, outer_opts) do
    142     case pick_language_and_lexer(lang, highlight_info, code) do
    143       {_language, nil, _opts} -> full_block
    144       {lang, lexer, opts} -> render_code(pre_attr, lang, lexer, opts, code, outer_opts)
    145     end
    146   end
    147 
    148   defp pick_language_and_lexer("", _highlight_info, "$ " <> _) do
    149     {"shell", ExDoc.ShellLexer, []}
    150   end
    151 
    152   defp pick_language_and_lexer("output", highlight_info, _code) do
    153     {"output", highlight_info.lexer, highlight_info.opts}
    154   end
    155 
    156   defp pick_language_and_lexer("", highlight_info, _code) do
    157     {highlight_info.language_name, highlight_info.lexer, highlight_info.opts}
    158   end
    159 
    160   defp pick_language_and_lexer(lang, _highlight_info, _code) do
    161     case Makeup.Registry.fetch_lexer_by_name(lang) do
    162       {:ok, {lexer, opts}} -> {lang, lexer, opts}
    163       :error -> {lang, nil, []}
    164     end
    165   end
    166 
    167   defp render_code(pre_attr, lang, lexer, lexer_opts, code, opts) do
    168     highlight_tag = Keyword.get(opts, :highlight_tag, "span")
    169 
    170     highlighted =
    171       code
    172       |> unescape_html()
    173       |> IO.iodata_to_binary()
    174       |> Makeup.highlight_inner_html(
    175         lexer: lexer,
    176         lexer_options: lexer_opts,
    177         formatter_options: [highlight_tag: highlight_tag]
    178       )
    179 
    180     ~s(<pre#{pre_attr}><code class="makeup #{lang}" translate="no">#{highlighted}</code></pre>)
    181   end
    182 
    183   entities = [{"&amp;", ?&}, {"&lt;", ?<}, {"&gt;", ?>}, {"&quot;", ?"}, {"&#39;", ?'}]
    184 
    185   for {encoded, decoded} <- entities do
    186     defp unescape_html(unquote(encoded) <> rest) do
    187       [unquote(decoded) | unescape_html(rest)]
    188     end
    189   end
    190 
    191   defp unescape_html(<<c, rest::binary>>) do
    192     [c | unescape_html(rest)]
    193   end
    194 
    195   defp unescape_html(<<>>) do
    196     []
    197   end
    198 end