Packages
ex_doc
0.20.1
0.40.3
0.40.2
0.40.1
0.40.0
0.39.3
0.39.2
0.39.1
0.39.0
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.3
0.37.2
0.37.1
0.37.0
0.37.0-rc.2
0.37.0-rc.1
0.37.0-rc.0
0.36.1
0.36.0
0.35.1
0.35.0
0.34.2
0.34.1
0.34.0
0.33.0
0.32.2
0.32.1
0.32.0
0.31.2
0.31.1
0.31.0
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.4
0.29.3
0.29.2
0.29.1
0.29.0
0.28.6
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.3
0.27.2
0.27.1
0.27.0
0.26.0
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.2
0.24.1
0.24.0
0.23.0
0.22.6
0.22.5
0.22.4
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.19.0-rc
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
retired
0.17.1
0.17.0
retired
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
ExDoc is a documentation generation tool for Elixir
Current section
Files
Jump to
Current section
Files
lib/ex_doc/formatter/html/templates.ex
defmodule ExDoc.Formatter.HTML.Templates do
@moduledoc false
require EEx
alias ExDoc.Formatter.HTML
@doc """
Generate content from the module template for a given `node`
"""
def module_page(module_node, nodes_map, config) do
summary = module_summary(module_node)
module_template(config, module_node, summary, nodes_map)
end
@doc """
Get the full specs from a function, already in HTML form.
"""
def get_specs(%ExDoc.TypeNode{spec: spec}) do
[spec]
end
def get_specs(%ExDoc.FunctionNode{specs: specs}) when is_list(specs) do
presence(specs)
end
def get_specs(_node) do
nil
end
@doc """
Get defaults clauses.
"""
def get_defaults(%{defaults: defaults}) do
defaults
end
def get_defaults(_) do
[]
end
@doc """
Get the pretty name of a function node
"""
def pretty_type(%{type: t}) do
Atom.to_string(t)
end
@doc """
Returns the HTML formatted title for the module page.
"""
def module_title(%{type: :task, title: title}),
do: "mix " <> title
def module_title(%{type: :module, title: title}),
do: title
def module_title(%{type: type, title: title}),
do: title <> " <small>#{type}</small>"
@doc """
Gets the first paragraph of the documentation of a node. It strips
surrounding white-spaces and traling `:`.
If `doc` is `nil`, it returns `nil`.
"""
@spec synopsis(String.t()) :: String.t()
@spec synopsis(nil) :: nil
def synopsis(nil), do: nil
def synopsis(doc) when is_binary(doc) do
case :binary.split(doc, "</p>") do
[left, _] -> String.trim_trailing(left, ":") <> "</p>"
[all] -> all
end
end
defp presence([]), do: nil
defp presence(other), do: other
@doc false
def h(binary) do
escape_map = [{"&", "&"}, {"<", "<"}, {">", ">"}, {~S("), """}]
Enum.reduce(escape_map, binary, fn {pattern, escape}, acc ->
String.replace(acc, pattern, escape)
end)
end
@doc false
def enc_h(binary) do
binary
|> URI.encode()
|> h()
end
defp is_enc?(binary) do
h(binary) != enc_h(binary)
end
@doc """
Create a JS object which holds all the items displayed in the sidebar area
"""
def create_sidebar_items(nodes_map, extras) do
nodes_map = [sidebar_items_extras(extras) | Enum.map(nodes_map, &sidebar_items_keys/1)]
"sidebarNodes={#{Enum.join(nodes_map, ",")}}"
end
defp sidebar_items_extras(extras) do
keys =
extras
|> Enum.map(&sidebar_items_extra/1)
|> Enum.join(",")
~s/"extras":[#{keys}]/
end
defp sidebar_items_keys({id, value}) do
keys =
value
|> Enum.map(&sidebar_items_node/1)
|> Enum.join(",")
~s/"#{id}":[#{keys}]/
end
defp sidebar_items_extra(%{id: id, title: title, group: group, content: content}) do
headers =
content
|> extract_headers
|> Enum.map_join(",", fn {header, anchor} -> sidebar_items_object(header, anchor) end)
~s/{"id":"#{id}","title":"#{title}","group":"#{group}","headers":[#{headers}]}/
end
@h2_regex ~r/<h2.*?>(.*?)<\/h2>/m
defp extract_headers(content) do
@h2_regex
|> Regex.scan(content, capture: :all_but_first)
|> List.flatten()
|> Enum.filter(&(&1 != ""))
|> Enum.map(&HTML.strip_tags/1)
|> Enum.map(&{&1, HTML.text_to_id(&1)})
end
defp sidebar_items_node(module_node) do
items =
module_node
|> module_summary()
|> Enum.reject(fn {_type, nodes_map} -> nodes_map == [] end)
|> Enum.map_join(",", &sidebar_items_by_group/1)
sidebar_items_json_string(module_node, items)
end
defp sidebar_items_by_group({group, docs}) do
objects =
Enum.map_join(docs, ",", fn doc ->
sidebar_items_object(doc.id, HTML.link_id(doc))
end)
~s/{"key":"#{HTML.text_to_id(group)}","name":"#{group}","nodes":[#{objects}]}/
end
defp sidebar_items_object(id, anchor) do
~s/{"id":"#{id}","anchor":"#{URI.encode(anchor)}"}/
end
defp sidebar_items_json_string(module_node, items) do
json_attrs =
for key <- [:id, :title, :nested_title, :nested_context],
value = Map.get(module_node, key),
do: [json_kv(key, inspect(value)), ?,]
json_attrs = [json_attrs | ~s("group":"#{module_node.group}")]
json_attrs =
case items do
"" -> json_attrs
items -> [json_attrs, ?, | json_kv(:nodeGroups, "[#{items}]")]
end
IO.iodata_to_binary([?{, json_attrs, ?}])
end
defp json_kv(key, value), do: [?", Atom.to_string(key), ?", ?:, value]
def module_summary(module_node) do
[Types: module_node.typespecs] ++
function_groups(module_node.function_groups, module_node.docs) ++
[Callbacks: Enum.filter(module_node.docs, &(&1.type in [:callback, :macrocallback]))]
end
defp function_groups(groups, docs) do
for group <- groups, do: {group, Enum.filter(docs, &(&1.group == group))}
end
defp logo_path(%{logo: nil}), do: nil
defp logo_path(%{logo: logo}), do: "assets/logo#{Path.extname(logo)}"
defp sidebar_type(:exception), do: "exceptions"
defp sidebar_type(:extra), do: "extras"
defp sidebar_type(:module), do: "modules"
defp sidebar_type(:behaviour), do: "modules"
defp sidebar_type(:protocol), do: "modules"
defp sidebar_type(:task), do: "tasks"
defp sidebar_type(:search), do: "search"
def asset_rev(output, pattern) do
output = Path.expand(output)
output
|> Path.join(pattern)
|> Path.wildcard()
|> relative_asset(output, pattern)
end
defp relative_asset([], output, pattern),
do: raise("could not find matching #{output}/#{pattern}")
defp relative_asset([h | _], output, _pattern), do: Path.relative_to(h, output)
@doc """
Link headings found with `regex` with in the given `content`. IDs are
prefixed with `prefix`.
"""
@heading_regex ~r/<(h[23]).*?>(.*?)<\/\1>/m
@spec link_headings(String.t(), Regex.t(), String.t()) :: String.t()
def link_headings(content, regex \\ @heading_regex, prefix \\ "")
def link_headings(nil, _, _), do: nil
def link_headings(content, regex, prefix) do
Regex.replace(regex, content, fn match, tag, title ->
link_heading(match, tag, title, HTML.text_to_id(title), prefix)
end)
end
defp link_heading(match, _tag, _title, "", _prefix), do: match
defp link_heading(_match, tag, title, id, prefix) do
"""
<#{tag} id="#{prefix}#{id}" class="section-heading">
<a href="##{prefix}#{id}" class="hover-link"><span class="icon-link" aria-hidden="true"></span></a>
#{title}
</#{tag}>
"""
end
defp link_moduledoc_headings(content) do
link_headings(content, @heading_regex, "module-")
end
defp link_detail_headings(content, prefix) do
link_headings(content, @heading_regex, prefix <> "-")
end
templates = [
detail_template: [:node, :_module],
footer_template: [:config],
head_template: [:config, :page],
module_template: [:config, :module, :summary, :nodes_map],
not_found_template: [:config, :nodes_map],
api_reference_entry_template: [:module_node],
api_reference_template: [:config, :nodes_map],
extra_template: [:config, :title, :nodes_map, :content],
search_template: [:config, :nodes_map],
sidebar_template: [:config, :nodes_map],
summary_template: [:name, :nodes],
summary_entry_template: [:node],
redirect_template: [:config, :redirect_to]
]
Enum.each(templates, fn {name, args} ->
filename = Path.expand("templates/#{name}.eex", __DIR__)
@doc false
EEx.function_from_file(:def, name, filename, args, trim: true)
end)
end