Skip to content

Commit

Permalink
Introduce ExDoc.normalize_options/3 and new helpers.
Browse files Browse the repository at this point in the history
Introduce:
- ExDoc.normalize_source_url_pattern/2
- ExDoc.normalize_directory/2
  • Loading branch information
eksperimental committed May 20, 2017
1 parent 29399a9 commit c0d192d
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 58 deletions.
49 changes: 40 additions & 9 deletions lib/ex_doc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,22 @@ defmodule ExDoc do
find_formatter(config.formatter).run(docs, config)
end

@doc false
# 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
def 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

# Helpers

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

Expand All @@ -129,14 +131,43 @@ defmodule ExDoc do
modname
end

# Helpers
@doc false
# Removes trailing slash from paths
def normalize_paths(options, fields) when is_list(fields) do
Enum.reduce(fields, options, fn(field, options_acc) ->
if is_binary(options_acc[field]) do
Keyword.put(options_acc, field, String.trim_trailing(options_acc[field], "/"))
else
options_acc
end
end)
end

@doc false
def normalize_source_url_pattern(options, default_source_ref \\ ExDoc.Config.default(: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
end

@doc false
def normalize_options(options) do
options = normalize_options(options, :source_ref, ExDoc.Config.default(:source_ref))

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)
options
|> normalize_paths([:assets, :output, :source_root])
|> normalize_source_url_pattern(options[:source_ref] || ExDoc.Config.default(:source_ref))
end

if is_bitstring(options[:output]) do
Keyword.put(options, :output, String.trim_trailing(options[:output], "/"))
@doc false
# Updates `field` in `options` with `default` value if current value is `nil` or hasn't been set,
# and if `default` is not `nil`.
def normalize_options(options, field, default) do
if is_nil(options[field]) and not is_nil(default) do
Keyword.put(options, field, default)
else
options
end
Expand Down
23 changes: 5 additions & 18 deletions lib/mix/tasks/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,15 @@ defmodule Mix.Tasks.Docs do
options =
config
|> get_docs_opts()
# CLI options have precedence over config
|> Keyword.merge(cli_opts)
|> normalize_source_url(config) # accepted at root level config
|> normalize_homepage_url(config) # accepted at root level config
# :source_url & :homepage_url accepted at root level config
|> ExDoc.normalize_options(:source_url, config[:source_url])
|> ExDoc.normalize_options(:homepage_url, config[:homepage_url])
|> normalize_source_beam(config)
|> normalize_main()
|> normalize_deps()
|> ExDoc.normalize_options()

for formatter <- get_formatters(options) do
index = generator.(project, version, Keyword.put(options, :formatter, formatter))
Expand Down Expand Up @@ -161,22 +164,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_homepage_url(options, config) do
if homepage_url = config[:homepage_url] do
Keyword.put(options, :homepage_url, homepage_url)
else
options
end
end

defp normalize_source_beam(options, config) do
compile_path =
if Mix.Project.umbrella?(config) do
Expand Down
2 changes: 1 addition & 1 deletion test/ex_doc_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule ExDocTest do
project = "Elixir"
version = "1"
options = [formatter: IdentityFormatter, retriever: IdentityRetriever,
source_root: "root_dir", source_beam: "beam_dir",]
source_root: "root_dir", source_beam: "beam_dir", source_url_pattern: "https://github.com/USER/APP/blob/master/%{path}#L%{line}"]

{_, config} = ExDoc.generate_docs project, version, Keyword.merge(options, [output: "test/tmp/ex_doc"])
assert config.output == "test/tmp/ex_doc"
Expand Down
84 changes: 54 additions & 30 deletions test/mix/tasks/docs_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,91 +6,115 @@ defmodule Mix.Tasks.DocsTest do
use ExUnit.Case

def run(args, opts) do
Mix.Tasks.Docs.run(args, opts, &{&1, &2, &3})
Mix.Tasks.Docs.run(args, opts, &generate_docs/3)
end

def generate_docs(project, vsn, options) when is_binary(project) and is_binary(vsn) and is_list(options) do
config = ExDoc.build_config(project, vsn, options)
{project, vsn, config}
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", %ExDoc.Config{formatter: "html"}}] =
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", %ExDoc.Config{formatter: "html"}},
{"ex_doc", "0.1.0", %ExDoc.Config{formatter: "epub"}}] =
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", %ExDoc.Config{formatter: "html"}},
{"ex_doc", "0.1.0", %ExDoc.Config{formatter: "epub"}}] =
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", %ExDoc.Config{formatter: "html"}}] =
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", %ExDoc.Config{formatter: "html", main: "Sample"}}] =
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", %ExDoc.Config{formatter: "html", 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", %ExDoc.Config{formatter: "html", 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", %ExDoc.Config{formatter: "html", 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", %ExDoc.Config{formatter: "html", deps: deps}}] =
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", %ExDoc.Config{formatter: "html", deps: deps}}] =
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", %ExDoc.Config{formatter: "html", main: "another"}}] =
run([], [app: :ex_doc, docs: fn -> [main: "another"] end])
end

test "accepts options from root" do
describe "accepts options from root" do
# accepted options are: `app`, `name`, `source_url`, `homepage_url`, `version`
assert [{"ExDoc", "1.2.3-dev",
[formatter: "html",
deps: _, source_beam: _,
homepage_url: "http://elixir-lang.org",
source_url: "https://github.com/elixir-lang/ex_doc",
]}] =
run([], [app: :ex_doc,
name: "ExDoc",
source_url: "https://github.com/elixir-lang/ex_doc",
homepage_url: "http://elixir-lang.org",
version: "1.2.3-dev",
])

assert [{"ex_doc", "dev", _}] = run([], [app: :ex_doc])

test "all options" do
assert [{"ExDoc", "1.2.3-dev",
%ExDoc.Config{
formatter: "html",
homepage_url: "http://elixir-lang.org",
project: "ExDoc",
source_url: "https://github.com/elixir-lang/ex_doc",
source_url_pattern: "https://github.com/elixir-lang/ex_doc/blob/master/%{path}#L%{line}",
version: "1.2.3-dev",
}
}] =
run([], [app: :ex_doc,
name: "ExDoc",
source_url: "https://github.com/elixir-lang/ex_doc",
homepage_url: "http://elixir-lang.org",
version: "1.2.3-dev",
])
end

test "app only" do
assert [{"ex_doc", "dev", %ExDoc.Config{}}] = run([], [app: :ex_doc])
end

test "source_url" do
assert [{"ex_doc", "dev",
%ExDoc.Config{
formatter: "html",
source_url_pattern: "https://github.com/elixir-lang/ex_doc/blob/master/%{path}#L%{line}",
source_url: "https://github.com/elixir-lang/ex_doc",
}}] =
run([], [app: :ex_doc, source_url: "https://github.com/elixir-lang/ex_doc"])
end
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", %ExDoc.Config{formatter: "html", deps: deps}}] =
run([], [app: :umbrella, apps_path: "apps/"])

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

0 comments on commit c0d192d

Please sign in to comment.