Current section
Files
Jump to
Current section
Files
lib/ex_doc/config.ex
defmodule Iris.ExDoc.Config do
@moduledoc false
# Defaults
@default_source_ref "main"
def default_group_for_doc(metadata), do: metadata[:group]
def filter_modules(_module, _metadata), do: true
def before_closing_head_tag(_), do: ""
def before_closing_footer_tag(_), do: ""
def before_closing_body_tag(_), do: ""
def annotations_for_docs(_), do: []
def skip_undefined_reference_warnings_on(_string), do: false
def skip_code_autolink_to(_string), do: false
def source_url_pattern(_, _), do: nil
defstruct annotations_for_docs: &__MODULE__.annotations_for_docs/1,
api_reference: true,
apps: [],
assets: %{},
authors: nil,
before_closing_body_tag: &__MODULE__.before_closing_body_tag/1,
before_closing_footer_tag: &__MODULE__.before_closing_footer_tag/1,
before_closing_head_tag: &__MODULE__.before_closing_head_tag/1,
canonical: nil,
cover: nil,
deps: [],
docs_groups: [],
extra_section: nil,
extras: [],
favicon: nil,
filter_modules: &__MODULE__.filter_modules/2,
formatter: "html",
formatters: [],
group_for_doc: &__MODULE__.default_group_for_doc/1,
groups_for_extras: [],
groups_for_modules: [],
homepage_url: nil,
language: "en",
logo: nil,
main: nil,
nest_modules_by_prefix: [],
output: "./doc",
package: nil,
proglang: :elixir,
project: nil,
redirects: %{},
retriever: ExDoc.Retriever,
skip_undefined_reference_warnings_on:
&__MODULE__.skip_undefined_reference_warnings_on/1,
skip_code_autolink_to: &__MODULE__.skip_code_autolink_to/1,
source_beam: nil,
source_ref: @default_source_ref,
source_url: nil,
source_url_pattern: &__MODULE__.source_url_pattern/2,
title: nil,
version: nil,
warnings_as_errors: false,
verbose: false
@type t :: %__MODULE__{
annotations_for_docs: (map() -> list()),
api_reference: boolean(),
apps: [atom()],
assets: %{binary() => binary()},
authors: nil | [String.t()],
before_closing_body_tag: (atom() -> String.t()) | mfa() | map(),
before_closing_footer_tag: (atom() -> String.t()) | mfa() | map(),
before_closing_head_tag: (atom() -> String.t()) | mfa() | map(),
canonical: nil | String.t(),
cover: nil | Path.t(),
deps: [{ebin_path :: String.t(), doc_url :: String.t()}],
docs_groups: [String.t()],
extra_section: nil | String.t(),
extras: list(),
favicon: nil | Path.t(),
filter_modules: (module, map -> boolean),
formatter: nil | String.t(),
formatters: [String.t()],
group_for_doc: (keyword() -> String.t() | nil),
groups_for_extras: [{binary(), term()}],
groups_for_modules: [{binary(), term()}],
homepage_url: nil | String.t(),
language: String.t(),
logo: nil | Path.t(),
main: nil | String.t(),
nest_modules_by_prefix: [String.t()],
output: nil | Path.t(),
package: :atom | nil,
project: nil | String.t(),
redirects: %{optional(String.t()) => String.t()} | [{String.t(), String.t()}],
retriever: atom(),
skip_undefined_reference_warnings_on: (String.t() -> boolean),
skip_code_autolink_to: (String.t() -> boolean),
source_beam: nil | String.t(),
source_ref: nil | String.t(),
source_url: nil | String.t(),
source_url_pattern: (String.t(), integer() -> String.t() | nil),
title: nil | String.t(),
version: nil | String.t(),
warnings_as_errors: boolean(),
verbose: boolean()
}
def build(project, vsn, options) do
{output, options} = Keyword.pop(options, :output, "./doc")
{nest_modules_by_prefix, options} = Keyword.pop(options, :nest_modules_by_prefix, [])
{proglang, options} = Keyword.pop(options, :proglang, :elixir)
{filter_modules, options} = Keyword.pop(options, :filter_modules, &filter_modules/2)
options =
if groups_for_functions = options[:groups_for_functions] do
IO.warn(":groups_for_functions is deprecated, please use :groups_for_docs instead")
Keyword.put_new(options, :groups_for_docs, groups_for_functions)
else
options
end
apps = Keyword.get(options, :apps, [])
{groups_for_docs, options} = Keyword.pop(options, :groups_for_docs, [])
{groups_for_extras, options} = Keyword.pop(options, :groups_for_extras, [])
{groups_for_modules, options} =
Keyword.pop(options, :groups_for_modules, default_groups_for_modules(apps))
{default_group_for_doc, options} =
Keyword.pop(options, :default_group_for_doc, &default_group_for_doc/1)
{skip_undefined_reference_warnings_on, options} =
Keyword.pop(
options,
:skip_undefined_reference_warnings_on,
&skip_undefined_reference_warnings_on/1
)
{skip_code_autolink_to, options} =
Keyword.pop(options, :skip_code_autolink_to, &skip_code_autolink_to/1)
{source_url_pattern, options} =
Keyword.pop_lazy(options, :source_url_pattern, fn ->
guess_url(options[:source_url], options[:source_ref] || @default_source_ref)
end)
preconfig = %__MODULE__{
filter_modules: normalize_filter_modules(filter_modules),
docs_groups: for({group, _} <- groups_for_docs, do: to_string(group)),
group_for_doc: normalize_groups_for_docs(groups_for_docs, default_group_for_doc),
groups_for_extras: normalize_groups(groups_for_extras),
groups_for_modules:
normalize_groups(
# TODO: The default module groups must be returned by the language
groups_for_modules ++ [Deprecated: &deprecated?/1, Exceptions: &exception?/1]
),
homepage_url: options[:homepage_url],
main: options[:main],
nest_modules_by_prefix: normalize_nest_modules_by_prefix(nest_modules_by_prefix),
output: normalize_output(output),
proglang: normalize_proglang(proglang),
project: project,
skip_undefined_reference_warnings_on:
normalize_skip_list_function(skip_undefined_reference_warnings_on),
skip_code_autolink_to: normalize_skip_list_function(skip_code_autolink_to),
source_url_pattern: normalize_source_url_pattern(source_url_pattern),
version: vsn
}
struct(preconfig, options)
end
defp normalize_output(output) do
String.trim_trailing(output, "/")
end
defp normalize_proglang(binary) when is_binary(binary) do
binary |> String.to_atom() |> normalize_proglang()
end
defp normalize_proglang(proglang) when proglang in [:elixir, :erlang] do
proglang
end
defp normalize_proglang(proglang) do
raise ArgumentError, "#{inspect(proglang)} is not supported"
end
defp normalize_groups_for_docs(groups, default) do
groups = normalize_groups(groups)
fn metadata ->
Enum.find_value(groups, fn {group, function} ->
function.(metadata) && group
end) || default.(metadata)
end
end
defp normalize_groups(groups) do
for {k, v} <- groups, do: {to_string(k), v}
end
defp deprecated?(metadata), do: metadata[:deprecated] != nil
defp exception?(metadata), do: metadata[:kind] == :exception
defp normalize_nest_modules_by_prefix(nest_modules_by_prefix) do
nest_modules_by_prefix
|> Enum.map(&inspect_atoms/1)
|> Enum.sort()
|> Enum.reverse()
end
defp inspect_atoms(atom) when is_atom(atom), do: inspect(atom)
defp inspect_atoms(binary) when is_binary(binary), do: binary
defp normalize_filter_modules(string) when is_binary(string),
do: normalize_filter_modules(Regex.compile!(string))
defp normalize_filter_modules(%Regex{} = regex),
do: fn module, _ -> Atom.to_string(module) =~ regex end
defp normalize_filter_modules(fun) when is_function(fun, 2),
do: fun
defp normalize_skip_list_function(strings) when is_list(strings),
do: &(&1 in strings)
defp normalize_skip_list_function(fun) when is_function(fun, 1),
do: fun
defp normalize_source_url_pattern(function) when is_function(function, 2), do: function
defp normalize_source_url_pattern(nil), do: &source_url_pattern/2
defp normalize_source_url_pattern(binary) when is_binary(binary) do
case :binary.split(binary, "%{path}") do
[left, right] ->
case :binary.split(left, "%{line}") do
[line_left, line_right] ->
fn path, line ->
line_left <> Integer.to_string(line) <> line_right <> path <> right
end
[_] ->
case :binary.split(right, "%{line}") do
[line_left, line_right] ->
fn path, line ->
left <> path <> line_left <> Integer.to_string(line) <> line_right
end
[_] ->
fn path, _ -> left <> path <> right end
end
end
[_] ->
case :binary.split(binary, "%{line}") do
[left, right] ->
fn _, line -> left <> Integer.to_string(line) <> right end
[_] ->
fn _, _ -> binary end
end
end
end
defp normalize_source_url_pattern(other) do
raise ArgumentError,
":source_url_pattern must be a string, a two-arity function or nil, got: #{inspect(other)}"
end
defp guess_url(url, ref) do
with {:ok, host_with_path} <- http_or_https(url),
{:ok, pattern} <- known_pattern(host_with_path, ref) do
"https://" <> append_slash(host_with_path) <> pattern
else
_ -> url
end
end
defp http_or_https("http://" <> rest),
do: {:ok, rest}
defp http_or_https("https://" <> rest),
do: {:ok, rest}
defp http_or_https(_),
do: :error
defp known_pattern("github.com/" <> _, ref),
do: {:ok, "blob/#{ref}/%{path}#L%{line}"}
defp known_pattern("gitlab.com/" <> _, ref),
do: {:ok, "blob/#{ref}/%{path}#L%{line}"}
defp known_pattern("bitbucket.org/" <> _, ref),
do: {:ok, "src/#{ref}/%{path}#cl-%{line}"}
defp known_pattern(_host_with_path, _ref),
do: :error
defp append_slash(url) do
if :binary.last(url) == ?/, do: url, else: url <> "/"
end
defp default_groups_for_modules([_app]) do
[]
end
defp default_groups_for_modules(apps) do
Enum.map(apps, fn app ->
Application.load(app)
{app, Application.spec(app, :modules)}
end)
end
end