config.ex (6332B)
1 defmodule ExDoc.Config do 2 @moduledoc false 3 4 # Defaults 5 @default_source_ref "main" 6 def filter_modules(_module, _metadata), do: true 7 def before_closing_head_tag(_), do: "" 8 def before_closing_body_tag(_), do: "" 9 10 defstruct api_reference: true, 11 apps: [], 12 assets: nil, 13 authors: nil, 14 before_closing_body_tag: &__MODULE__.before_closing_body_tag/1, 15 before_closing_head_tag: &__MODULE__.before_closing_head_tag/1, 16 canonical: nil, 17 cover: nil, 18 deps: [], 19 extra_section: nil, 20 extras: [], 21 filter_modules: &__MODULE__.filter_modules/2, 22 formatter: "html", 23 groups_for_extras: [], 24 groups_for_functions: [], 25 groups_for_modules: [], 26 homepage_url: nil, 27 javascript_config_path: "docs_config.js", 28 language: "en", 29 logo: nil, 30 main: nil, 31 nest_modules_by_prefix: [], 32 output: "./doc", 33 package: nil, 34 proglang: :elixir, 35 project: nil, 36 retriever: ExDoc.Retriever, 37 skip_undefined_reference_warnings_on: [], 38 source_beam: nil, 39 source_ref: @default_source_ref, 40 source_url: nil, 41 source_url_pattern: nil, 42 title: nil, 43 version: nil 44 45 @type t :: %__MODULE__{ 46 api_reference: boolean(), 47 apps: [atom()], 48 assets: nil | String.t(), 49 authors: nil | [String.t()], 50 before_closing_body_tag: (atom() -> String.t()), 51 before_closing_head_tag: (atom() -> String.t()), 52 canonical: nil | String.t(), 53 cover: nil | Path.t(), 54 deps: [{ebin_path :: String.t(), doc_url :: String.t()}], 55 extra_section: nil | String.t(), 56 extras: list(), 57 filter_modules: (module, map -> boolean), 58 formatter: nil | String.t(), 59 groups_for_extras: keyword(), 60 groups_for_functions: keyword((keyword() -> boolean)), 61 groups_for_modules: keyword(), 62 homepage_url: nil | String.t(), 63 javascript_config_path: nil | String.t(), 64 language: String.t(), 65 logo: nil | Path.t(), 66 main: nil | String.t(), 67 nest_modules_by_prefix: [String.t()], 68 output: nil | Path.t(), 69 package: :atom | nil, 70 project: nil | String.t(), 71 retriever: atom(), 72 skip_undefined_reference_warnings_on: [String.t()], 73 source_beam: nil | String.t(), 74 source_ref: nil | String.t(), 75 source_url: nil | String.t(), 76 source_url_pattern: nil | String.t(), 77 title: nil | String.t(), 78 version: nil | String.t() 79 } 80 81 @spec build(String.t(), String.t(), Keyword.t()) :: ExDoc.Config.t() 82 def build(project, vsn, options) do 83 {output, options} = Keyword.pop(options, :output, "./doc") 84 {groups_for_modules, options} = Keyword.pop(options, :groups_for_modules, []) 85 {nest_modules_by_prefix, options} = Keyword.pop(options, :nest_modules_by_prefix, []) 86 {proglang, options} = Keyword.pop(options, :proglang, :elixir) 87 {filter_modules, options} = Keyword.pop(options, :filter_modules, &filter_modules/2) 88 89 {source_url_pattern, options} = 90 Keyword.pop_lazy(options, :source_url_pattern, fn -> 91 guess_url(options[:source_url], options[:source_ref] || @default_source_ref) 92 end) 93 94 preconfig = %__MODULE__{ 95 filter_modules: normalize_filter_modules(filter_modules), 96 groups_for_modules: normalize_groups_for_modules(groups_for_modules), 97 homepage_url: options[:homepage_url], 98 main: options[:main], 99 nest_modules_by_prefix: normalize_nest_modules_by_prefix(nest_modules_by_prefix), 100 output: normalize_output(output), 101 proglang: normalize_proglang(proglang), 102 project: project, 103 source_url_pattern: source_url_pattern, 104 version: vsn 105 } 106 107 struct(preconfig, options) 108 end 109 110 defp normalize_output(output) do 111 String.trim_trailing(output, "/") 112 end 113 114 defp normalize_proglang(binary) when is_binary(binary) do 115 binary |> String.to_atom() |> normalize_proglang() 116 end 117 118 defp normalize_proglang(proglang) when proglang in [:elixir, :erlang] do 119 proglang 120 end 121 122 defp normalize_proglang(proglang) do 123 raise ArgumentError, "#{inspect(proglang)} is not supported" 124 end 125 126 defp normalize_groups_for_modules(groups_for_modules) do 127 default_groups = [Deprecated: &deprecated?/1, Exceptions: &exception?/1] 128 129 groups_for_modules ++ 130 Enum.reject(default_groups, fn {k, _} -> Keyword.has_key?(groups_for_modules, k) end) 131 end 132 133 defp deprecated?(metadata), do: metadata[:deprecated] != nil 134 defp exception?(metadata), do: metadata[:__type__] == :exception 135 136 defp normalize_nest_modules_by_prefix(nest_modules_by_prefix) do 137 nest_modules_by_prefix 138 |> Enum.map(&inspect_atoms/1) 139 |> Enum.sort() 140 |> Enum.reverse() 141 end 142 143 defp inspect_atoms(atom) when is_atom(atom), do: inspect(atom) 144 defp inspect_atoms(binary) when is_binary(binary), do: binary 145 146 defp normalize_filter_modules(string) when is_binary(string), 147 do: normalize_filter_modules(Regex.compile!(string)) 148 149 defp normalize_filter_modules(%Regex{} = regex), 150 do: fn module, _ -> Atom.to_string(module) =~ regex end 151 152 defp normalize_filter_modules(fun) when is_function(fun, 2), 153 do: fun 154 155 defp guess_url(url, ref) do 156 with {:ok, host_with_path} <- http_or_https(url), 157 {:ok, pattern} <- known_pattern(host_with_path, ref) do 158 "https://" <> append_slash(host_with_path) <> pattern 159 else 160 _ -> url 161 end 162 end 163 164 defp http_or_https("http://" <> rest), 165 do: {:ok, rest} 166 167 defp http_or_https("https://" <> rest), 168 do: {:ok, rest} 169 170 defp http_or_https(_), 171 do: :error 172 173 defp known_pattern("github.com/" <> _, ref), 174 do: {:ok, "blob/#{ref}/%{path}#L%{line}"} 175 176 defp known_pattern("gitlab.com/" <> _, ref), 177 do: {:ok, "blob/#{ref}/%{path}#L%{line}"} 178 179 defp known_pattern("bitbucket.org/" <> _, ref), 180 do: {:ok, "src/#{ref}/%{path}#cl-%{line}"} 181 182 defp known_pattern(_host_with_path, _ref), 183 do: :error 184 185 defp append_slash(url) do 186 if :binary.last(url) == ?/, do: url, else: url <> "/" 187 end 188 end