Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 77 additions & 12 deletions lib/ex_doc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,51 @@ defmodule ExDoc do
Elixir Documentation System. ExDoc produces documentation for Elixir projects
"""

defmodule Default do
@moduledoc false

@config %{
:formatter => "html",
:output => "./doc",
:source_ref => "master",
:retriever => ExDoc.Retriever,
}

@spec config :: map
def config do
@config
end

@spec config(atom) :: term
def config(field) do
Map.get(config(), field)
end
end

defmodule Config do
@moduledoc """
Configuration structure that holds all the available options for ExDoc

You can find more details about these options in the `ExDoc.CLI` module.
"""

defstruct [
assets: nil,
canonical: nil,
deps: [],
extra_section: nil,
extras: [],
filter_prefix: nil,
formatter: "html",
formatter: ExDoc.Default.config(:formatter),
formatter_opts: [],
homepage_url: nil,
logo: nil,
main: nil,
output: "doc",
output: ExDoc.Default.config(:output),
project: nil,
retriever: ExDoc.Retriever,
retriever: ExDoc.Default.config(:retriever),
source_beam: nil,
source_ref: ExDoc.Default.config(:source_ref),
source_root: nil,
source_url: nil,
source_url_pattern: nil,
Expand All @@ -48,6 +71,7 @@ defmodule ExDoc do
project: nil | String.t,
retriever: :atom,
source_beam: nil | String.t,
source_ref: nil | String.t,
source_root: nil | String.t,
source_url: nil | String.t,
source_url_pattern: nil | String.t,
Expand Down Expand Up @@ -78,17 +102,46 @@ defmodule ExDoc do
# 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)
options =
options
|> normalize_directory([:assets, :output, :source_root])
|> normalize_source_url_pattern()

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

@doc """
Replaces all empty string values in `options` with `nil`.
"""
@spec nilify_options(keyword) :: keyword
def nilify_options(options) do
for {field, value} <- options do
case value do
"" -> {field, nil}
_ -> {field, value}
end
end
end

@doc """
Updates `field` in `options` if its value is `nil`.
"""
@spec normalize_options(keyword, atom, term) :: keyword
def normalize_options(options, field, default) do
if is_nil(options[field]) do
Keyword.put(options, field, default)
else
options
end
end

# Helpers

# Short path for programmatic interface
defp find_formatter(modname) when is_atom(modname), do: modname

Expand All @@ -111,14 +164,26 @@ defmodule ExDoc do
modname
end

# Helpers
@doc false
def normalize_directory(options, fields) when is_list(fields) do
Enum.reduce(fields, options, fn(field, options_acc) ->
normalize_directory(options_acc, field)
end)
end

defp normalize_options(options) do
pattern = options[:source_url_pattern] || guess_url(options[:source_url], options[:source_ref] || "master")
options = Keyword.put(options, :source_url_pattern, pattern)
def normalize_directory(options, field) when is_atom(field) do
if is_binary(options[field]) do
Keyword.put(options, field, String.trim_trailing(options[field], "/"))
else
options
end
end

if is_bitstring(options[:output]) do
Keyword.put(options, :output, String.trim_trailing(options[:output], "/"))
@doc false
def normalize_source_url_pattern(options, default_source_ref \\ ExDoc.Default.config(:source_ref)) do
if !options[:source_url_pattern] && options[:source_url] do
source_ref = options[:source_ref] || default_source_ref
Keyword.put(options, :source_url_pattern, guess_url(options[:source_url], source_ref))
else
options
end
Expand Down
1 change: 1 addition & 0 deletions lib/ex_doc/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ defmodule ExDoc.CLI do
opts =
opts
|> Keyword.put(:source_beam, source_beam)
|> ExDoc.nilify_options()
|> merge_config()
generator.(project, version, opts)
end
Expand Down
14 changes: 5 additions & 9 deletions lib/mix/tasks/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ defmodule Mix.Tasks.Docs do
config
|> get_docs_opts()
|> Keyword.merge(cli_opts)
|> normalize_source_url(config)
|> ExDoc.nilify_options()
|> ExDoc.normalize_options(:source_ref, config[:source_ref] || ExDoc.Default.config(:source_ref))
|> ExDoc.normalize_options(:source_url, config[:source_url])
|> ExDoc.normalize_options(:source_url_pattern, config[:source_url_pattern])
|> ExDoc.normalize_source_url_pattern(config[:source_ref] || ExDoc.Default.config(:source_ref))
|> normalize_source_beam(config)
|> normalize_main()
|> normalize_deps()
Expand Down Expand Up @@ -157,14 +161,6 @@ defmodule Mix.Tasks.Docs do
Mix.shell.info [:green, "View them at #{inspect index}."]
end

defp normalize_source_url(options, config) do
if source_url = config[:source_url] do
Keyword.put(options, :source_url, source_url)
else
options
end
end

defp normalize_source_beam(options, config) do
compile_path =
if Mix.Project.umbrella?(config) do
Expand Down
14 changes: 14 additions & 0 deletions test/ex_doc_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ defmodule ExDocTest do
fn -> ExDoc.generate_docs project, version, options end
end

test "default configuration" do
assert Map.get(%ExDoc.Config{}, :formatter) == "html"
assert ExDoc.Default.config(:formatter) == "html"

assert Map.get(%ExDoc.Config{}, :output) == "./doc"
assert ExDoc.Default.config(:output) == "./doc"

assert Map.get(%ExDoc.Config{}, :source_ref) == "master"
assert ExDoc.Default.config(:source_ref) == "master"

assert Map.get(%ExDoc.Config{}, :retriever) == ExDoc.Retriever
assert ExDoc.Default.config(:retriever) == ExDoc.Retriever
end

test "version" do
assert ExDoc.version =~ ~r{\d+\.\d+\.\d+}
end
Expand Down
32 changes: 16 additions & 16 deletions test/mix/tasks/docs_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,75 +10,75 @@ defmodule Mix.Tasks.DocsTest do
end

test "inflects values from app and version" do
assert [{"ex_doc", "0.1.0", [formatter: "html", deps: _, source_beam: _]}] =
assert [{"ex_doc", "0.1.0", [formatter: "html", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _]}] =
run([], [app: :ex_doc, version: "0.1.0"])
end

test "accepts multiple formatters from CLI" do
assert [{"ex_doc", "0.1.0", [formatter: "html", deps: _, source_beam: _]},
{"ex_doc", "0.1.0", [formatter: "epub", deps: _, source_beam: _]}] =
assert [{"ex_doc", "0.1.0", [formatter: "html", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _]},
{"ex_doc", "0.1.0", [formatter: "epub", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _]}] =
run(["-f", "html", "-f", "epub"], [app: :ex_doc, version: "0.1.0"])
end

test "accepts multiple formatters from config" do
assert [{"ex_doc", "0.1.0", [formatter: "html", deps: _, source_beam: _, formatters: _]},
{"ex_doc", "0.1.0", [formatter: "epub", deps: _, source_beam: _, formatters: _]}] =
assert [{"ex_doc", "0.1.0", [formatter: "html", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _ , formatters: _]},
{"ex_doc", "0.1.0", [formatter: "epub", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _ , formatters: _]}] =
run([], [app: :ex_doc, version: "0.1.0", docs: [formatters: ["html", "epub"]]])
end

test "uses the given name" do
assert [{"ExDoc", "0.1.0", [formatter: "html", deps: _, source_beam: _]}] =
assert [{"ExDoc", "0.1.0", [formatter: "html", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _]}] =
run([], [app: :ex_doc, version: "0.1.0", name: "ExDoc"])
end

test "accepts modules in :main" do
assert [{"ex_doc", "dev", [formatter: "html", deps: _, main: "Sample", source_beam: _, ]}] =
assert [{"ex_doc", "dev", [formatter: "html", deps: _, main: "Sample", source_beam: _, source_url_pattern: _, source_url: _, source_ref: _]}] =
run([], [app: :ex_doc, docs: [main: Sample]])
end

test "accepts files in :main" do
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _, main: "another"]}] =
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _ , main: "another"]}] =
run([], [app: :ex_doc, docs: [main: "another"]])
end

test "accepts output in :output" do
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _, output: "hello"]}] =
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _ , output: "hello"]}] =
run([], [app: :ex_doc, docs: [output: "hello"]])
end

test "parses output with lower preference than options" do
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _, output: "world"]}] =
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _ , output: "world"]}] =
run(~w(-o world), [app: :ex_doc, docs: [output: "world"]])
end

test "includes dependencies" do
assert [{"ex_doc", "dev", [formatter: "html", deps: deps, source_beam: _]}] =
assert [{"ex_doc", "dev", [formatter: "html", deps: deps, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _]}] =
run([], [app: :ex_doc, docs: []])
assert List.keyfind(deps, Application.app_dir(:earmark), 0) ==
{Application.app_dir(:earmark), "https://hexdocs.pm/earmark/#{Application.spec(:earmark, :vsn)}/"}
end

test "allows custom dependency paths" do
assert [{"ex_doc", "dev", [formatter: "html", deps: deps, source_beam: _]}] =
assert [{"ex_doc", "dev", [formatter: "html", deps: deps, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _]}] =
run([], [app: :ex_doc, docs: [deps: [earmark: "foo"]]])
assert List.keyfind(deps, Application.app_dir(:earmark), 0) ==
{Application.app_dir(:earmark), "foo"}
end

test "accepts lazy docs" do
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _, main: "another"]}] =
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _, main: "another"]}] =
run([], [app: :ex_doc, docs: fn -> [main: "another"] end])
end

test "accepts source_url from root" do
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _,
source_url: "http://github.com/elixir-lang/ex_doc"]}] =
assert [{"ex_doc", "dev", [formatter: "html", deps: _, source_beam: _, source_url_pattern: _,
source_url: "http://github.com/elixir-lang/ex_doc", source_ref: _]}] =
run([], [app: :ex_doc, source_url: "http://github.com/elixir-lang/ex_doc"])
end

test "supports umbrella project" do
Mix.Project.in_project(:umbrella, "test/fixtures/umbrella", fn _mod ->
assert [{"umbrella", "dev", [formatter: "html", deps: deps, source_beam: _]}] =
assert [{"umbrella", "dev", [formatter: "html", deps: deps, source_beam: _, source_url_pattern: _, source_url: _, source_ref: _]}] =
run([], [app: :umbrella, apps_path: "apps/"])

assert List.keyfind(deps, Application.app_dir(:foo), 0) ==
Expand Down