Current section

Files

Jump to
ex_doc lib ex_doc.ex
Raw

lib/ex_doc.ex

defmodule ExDoc do
@moduledoc false
@ex_doc_version Mix.Project.config()[:version]
alias ExDoc.Config
@doc """
Returns the ExDoc version (used in templates).
"""
@spec version :: String.t()
def version, do: @ex_doc_version
@doc """
Generates documentation for the given `project`, `vsn` (version)
and `options`.
"""
@spec generate_docs(String.t(), String.t(), Keyword.t()) :: atom
def generate_docs(project, vsn, options)
when is_binary(project) and is_binary(vsn) and is_list(options) do
config = build_config(project, vsn, options)
if processor = options[:markdown_processor] do
ExDoc.Markdown.put_markdown_processor(processor)
end
if markdown_processor_options = options[:markdown_processor_options] do
ExDoc.Markdown.configure_processor(markdown_processor_options)
end
docs = config.retriever.docs_from_dir(config.source_beam, config)
find_formatter(config.formatter).run(docs, config)
end
# Builds configuration by merging `options`, and normalizing the options.
@spec build_config(String.t(), String.t(), Keyword.t()) :: ExDoc.Config.t()
defp build_config(project, vsn, options) do
options = normalize_options(options)
preconfig = %Config{
project: project,
version: vsn,
main: options[:main],
homepage_url: options[:homepage_url],
source_root: options[:source_root] || File.cwd!()
}
struct(preconfig, options)
end
# Short path for programmatic interface
defp find_formatter(modname) when is_atom(modname), do: modname
defp find_formatter("ExDoc.Formatter." <> _ = name) do
[name]
|> Module.concat()
|> check_formatter_module(name)
end
defp find_formatter(name) do
[ExDoc.Formatter, String.upcase(name)]
|> Module.concat()
|> check_formatter_module(name)
end
defp check_formatter_module(modname, argname) do
if Code.ensure_loaded?(modname) do
modname
else
raise "formatter module #{inspect argname} not found"
end
end
# Helpers
defp normalize_options(options) do
pattern =
options[:source_url_pattern] ||
guess_url(options[:source_url], options[:source_ref] || ExDoc.Config.default_source_ref())
options = Keyword.put(options, :source_url_pattern, pattern)
if is_binary(options[:output]) do
Keyword.put(options, :output, String.trim_trailing(options[:output], "/"))
else
options
end
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
end