Packages
ex_doc
0.40.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/extras.ex
defmodule ExDoc.Extras do
@moduledoc false
alias ExDoc.{Config, Markdown, Utils}
@doc """
Build a list of `ExDoc.ExtraNode` and `ExDoc.URLNode`.
"""
def build(extras_input, config) do
groups = config.groups_for_extras
extras =
extras_input
|> Enum.map(&build_extra(&1, groups, config))
ids_count = Enum.reduce(extras, %{}, &Map.update(&2, &1.id, 1, fn c -> c + 1 end))
extras
|> Enum.map_reduce(1, fn extra, idx ->
if ids_count[extra.id] > 1, do: {disambiguate_id(extra, idx), idx + 1}, else: {extra, idx}
end)
|> elem(0)
|> Enum.sort_by(fn extra -> Config.index(groups, extra.group) end)
end
defp disambiguate_id(extra, discriminator) do
Map.put(extra, :id, "#{extra.id}-#{discriminator}")
end
defp build_extra(input, groups, config) when is_binary(input) do
build_extra({input, %{}}, groups, config)
end
defp build_extra({input, opts}, groups, config) when is_list(opts) do
build_extra({input, Map.new(opts)}, groups, config)
end
defp build_extra({input, %{url: _} = input_options}, groups, _config) do
input = to_string(input)
title = validate_extra_string!(input_options, :title) || input
url = validate_extra_string!(input_options, :url)
group = Config.match_extra(groups, url)
%ExDoc.URLNode{
group: group,
id: Utils.text_to_id(title),
title: title,
url: url
}
end
defp build_extra({input, input_options}, groups, config) do
input = to_string(input)
id =
validate_extra_string!(input_options, :filename) ||
input |> filename_to_title() |> Utils.text_to_id()
source_file = validate_extra_string!(input_options, :source) || input
opts = [file: source_file, line: 1, markdown_processor: config.markdown_processor]
{extension, source, ast} =
case extension_name(input) do
extension when extension in ["", ".txt"] ->
source = File.read!(input)
ast = [{:pre, [], ["\n" <> source], %{}}]
{extension, source, ast}
extension when extension in [".md", ".livemd", ".cheatmd"] ->
source = File.read!(input)
ast =
source
|> Markdown.to_ast(opts)
|> ExDoc.DocAST.add_ids_to_headers([:h2, :h3])
{extension, source, ast}
_ ->
raise ArgumentError,
"file extension not recognized, allowed extension is either .cheatmd, .livemd, .md, .txt or no extension"
end
{title_doc, title_text, ast} =
case ExDoc.DocAST.extract_title(ast) do
{:ok, title_doc, ast} -> {title_doc, ExDoc.DocAST.text(title_doc), ast}
:error -> {nil, nil, ast}
end
title =
validate_extra_string!(input_options, :title) || title_text || filename_to_title(input)
group = Config.match_extra(groups, input)
source_path = source_file |> Path.relative_to(File.cwd!()) |> String.replace_leading("./", "")
source_url = config.source_url_pattern.(source_path, 1)
search_data = validate_search_data!(input_options[:search_data])
%ExDoc.ExtraNode{
type: extra_type(extension),
source_doc: source,
group: group,
id: id,
doc: ast,
source_path: source_path,
source_url: source_url,
search_data: search_data,
title: title,
title_doc: title_doc || title
}
end
defp validate_extra_string!(input_options, key) do
case input_options[key] do
nil ->
nil
binary when is_binary(binary) ->
binary
other ->
raise ArgumentError,
"extra field #{inspect(key)} must be a string, got: #{inspect(other)}"
end
end
@search_data_keys [:anchor, :body, :title, :type]
defp validate_search_data!(nil), do: nil
defp validate_search_data!(search_data) when is_list(search_data) do
Enum.each(search_data, fn search_data ->
has_keys = Map.keys(search_data)
if Enum.sort(has_keys) != @search_data_keys do
raise ArgumentError,
"expected search data to be a list of maps with the keys: #{inspect(@search_data_keys)}, " <>
"found keys: #{inspect(has_keys)}"
end
end)
search_data
end
defp validate_search_data!(search_data) do
raise ArgumentError,
"expected search data to be a list of maps with the keys: #{inspect(@search_data_keys)}, " <>
"found: #{inspect(search_data)}"
end
defp extension_name(input) do
input
|> Path.extname()
|> String.downcase()
end
defp filename_to_title(input) do
input |> Path.basename() |> Path.rootname()
end
defp extra_type(".cheatmd"), do: :cheatmd
defp extra_type(".livemd"), do: :livemd
defp extra_type(_), do: :extra
end