From 81b6cfe787ec50130fc8c617b0978e585b58958b Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 29 Jan 2025 12:45:57 +0000 Subject: [PATCH 1/3] Add ability to specify multiple apps from CLI --- README.md | 6 ++++++ lib/ex_doc/cli.ex | 16 +++++----------- test/ex_doc/cli_test.exs | 20 ++++++++------------ 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 777084fde..63aa06617 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,12 @@ You can use ExDoc via the command line. GITHUB_REPO => ecto ``` +It is also possible to specify multiple `ebin` directories in the case of _umbrella_ projects: + + ```bash + $ ex_doc "PROJECT_NAME" "PROJECT_VERSION" _build/dev/lib/app1/ebin _build/dev/lib/app2/ebin -m "PROJECT_MODULE" -u "https://github.com/GITHUB_USER/GITHUB_REPO" -l path/to/logo.png + ``` + You can specify a config file via the `--config` option, both Elixir and Erlang formats are supported. Invoke `ex_doc` without arguments to learn more. diff --git a/lib/ex_doc/cli.ex b/lib/ex_doc/cli.ex index 0d6c7f196..5a13e5d67 100644 --- a/lib/ex_doc/cli.ex +++ b/lib/ex_doc/cli.ex @@ -69,9 +69,9 @@ defmodule ExDoc.CLI do end defp generate(args, opts, generator) do - [project, version, source_beam] = parse_args(args) + [project, version | source_beams] = parse_args(args) - Code.prepend_path(source_beam) + Code.prepend_paths(source_beams) for path <- Keyword.get_values(opts, :paths), path <- Path.wildcard(path) do @@ -80,8 +80,8 @@ defmodule ExDoc.CLI do opts = opts - |> Keyword.put(:source_beam, source_beam) - |> Keyword.put(:apps, [app(source_beam)]) + |> Keyword.put(:source_beam, source_beams) + |> Keyword.put(:apps, Enum.map(source_beams, &app/1)) |> merge_config() |> normalize_formatters() @@ -166,13 +166,7 @@ defmodule ExDoc.CLI do end end - defp parse_args([_project, _version, _source_beam] = args), do: args - - defp parse_args([_, _, _ | _]) do - IO.puts("Too many arguments.\n") - print_usage() - exit({:shutdown, 1}) - end + defp parse_args([_project, _version | _source_beams] = args), do: args defp parse_args(_) do IO.puts("Too few arguments.\n") diff --git a/test/ex_doc/cli_test.exs b/test/ex_doc/cli_test.exs index 1b351a2f7..9ea54ead4 100644 --- a/test/ex_doc/cli_test.exs +++ b/test/ex_doc/cli_test.exs @@ -17,7 +17,7 @@ defmodule ExDoc.CLITest do formatter: "html", formatters: ["html", "epub"], apps: [:ex_doc], - source_beam: @ebin + source_beam: [@ebin] ]} assert epub == @@ -26,7 +26,7 @@ defmodule ExDoc.CLITest do formatter: "epub", formatters: ["html", "epub"], apps: [:ex_doc], - source_beam: @ebin + source_beam: [@ebin] ]} end @@ -39,7 +39,7 @@ defmodule ExDoc.CLITest do formatter: "epub", formatters: ["epub", "html"], apps: [:ex_doc], - source_beam: @ebin + source_beam: [@ebin] ]} assert html == @@ -48,7 +48,7 @@ defmodule ExDoc.CLITest do formatter: "html", formatters: ["epub", "html"], apps: [:ex_doc], - source_beam: @ebin + source_beam: [@ebin] ]} end @@ -60,10 +60,6 @@ defmodule ExDoc.CLITest do assert io == "ExDoc v#{ExDoc.version()}\n" end - test "too many arguments" do - assert catch_exit(run(["ExDoc", "1.2.3", "/", "kaboom"])) == {:shutdown, 1} - end - test "too few arguments" do assert catch_exit(run(["ExDoc"])) == {:shutdown, 1} end @@ -98,7 +94,7 @@ defmodule ExDoc.CLITest do logo: "logo.png", main: "Main", output: "html", - source_beam: "#{@ebin}", + source_beam: ["#{@ebin}"], source_ref: "abcdefg", source_url: "http://example.com/username/project" ] @@ -127,7 +123,7 @@ defmodule ExDoc.CLITest do extras: ["README.md"], formatter: "html", formatters: ["html"], - source_beam: @ebin + source_beam: [@ebin] ] after File.rm!("test.exs") @@ -155,7 +151,7 @@ defmodule ExDoc.CLITest do formatter: "html", formatters: ["html"], logo: "opts_logo.png", - source_beam: @ebin + source_beam: [@ebin] ] after File.rm!("test.exs") @@ -192,7 +188,7 @@ defmodule ExDoc.CLITest do extras: ["README.md"], formatter: "html", formatters: ["html"], - source_beam: @ebin + source_beam: [@ebin] ] after File.rm!("test.config") From af3028523d922d1e62e9e9e2b95b63d2d02f2fa9 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 30 Jan 2025 09:37:06 +0000 Subject: [PATCH 2/3] Group modules by app for umbrella projects by default --- README.md | 2 ++ lib/ex_doc/config.ex | 16 +++++++++++++++- test/ex_doc/cli_test.exs | 9 +++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 63aa06617..1fed7d715 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,8 @@ It is also possible to specify multiple `ebin` directories in the case of _umbre $ ex_doc "PROJECT_NAME" "PROJECT_VERSION" _build/dev/lib/app1/ebin _build/dev/lib/app2/ebin -m "PROJECT_MODULE" -u "https://github.com/GITHUB_USER/GITHUB_REPO" -l path/to/logo.png ``` +If multiple `ebin` directories are specified, modules are grouped by application by default. It is possible to override this behaviour by providing a custom `groups_per_modules` option. + You can specify a config file via the `--config` option, both Elixir and Erlang formats are supported. Invoke `ex_doc` without arguments to learn more. diff --git a/lib/ex_doc/config.ex b/lib/ex_doc/config.ex index f79a66e72..de4ba82da 100644 --- a/lib/ex_doc/config.ex +++ b/lib/ex_doc/config.ex @@ -115,7 +115,10 @@ defmodule ExDoc.Config do {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, []) + apps = Keyword.get(options, :apps, []) + + {groups_for_modules, options} = + Keyword.pop(options, :groups_for_modules, default_groups_for_modules(apps)) {skip_undefined_reference_warnings_on, options} = Keyword.pop( @@ -278,4 +281,15 @@ defmodule ExDoc.Config do 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 diff --git a/test/ex_doc/cli_test.exs b/test/ex_doc/cli_test.exs index 9ea54ead4..72dfb28d2 100644 --- a/test/ex_doc/cli_test.exs +++ b/test/ex_doc/cli_test.exs @@ -3,6 +3,7 @@ defmodule ExDoc.CLITest do import ExUnit.CaptureIO @ebin "_build/test/lib/ex_doc/ebin" + @ebin2 "_build/test/lib/makeup/ebin" defp run(args) do with_io(fn -> ExDoc.CLI.main(args, &{&1, &2, &3}) end) @@ -64,6 +65,14 @@ defmodule ExDoc.CLITest do assert catch_exit(run(["ExDoc"])) == {:shutdown, 1} end + test "multiple apps" do + {[{"ExDoc", "1.2.3", html}, {"ExDoc", "1.2.3", epub}], _io} = + run(["ExDoc", "1.2.3", @ebin, @ebin2]) + + assert [:ex_doc, :makeup] = Enum.sort(Keyword.get(html, :apps)) + assert [:ex_doc, :makeup] = Enum.sort(Keyword.get(epub, :apps)) + end + test "arguments that are not aliased" do File.write!("not_aliased.exs", ~s([key: "val"])) From eee74077042f446f99a11c396d4485017108ffea Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Thu, 6 Feb 2025 09:15:14 +0100 Subject: [PATCH 3/3] Add config test for groups_for_modules --- test/ex_doc/config_test.exs | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/ex_doc/config_test.exs b/test/ex_doc/config_test.exs index e2939cb83..c24733d65 100644 --- a/test/ex_doc/config_test.exs +++ b/test/ex_doc/config_test.exs @@ -113,5 +113,43 @@ defmodule ExDoc.ConfigTest do config = build(source_url_pattern: "a%{line}b%{path}c") assert config.source_url_pattern.("foo.ex", 123) == "a123bfoo.exc" end + + test "groups_for_modules" do + # Using real applications, since we load them to extract the corresponding list of modules + stdlib = :stdlib + kernel = :kernel + custom_group = :custom_group + + groups_for_modules = fn config, key -> + List.keyfind(config.groups_for_modules, to_string(key), 0) + end + + # Single app, no custom grouping + config = build(apps: [stdlib]) + assert groups_for_modules.(config, stdlib) == nil + assert groups_for_modules.(config, custom_group) == nil + + # Single app, custom grouping + config = build(apps: [stdlib], groups_for_modules: [{"custom_group", ["module_1"]}]) + assert groups_for_modules.(config, stdlib) == nil + assert groups_for_modules.(config, custom_group) == {"custom_group", ["module_1"]} + + # Multiple apps, no custom grouping + config = build(apps: [stdlib, kernel]) + stdlib_groups = groups_for_modules.(config, stdlib) + kernel_groups = groups_for_modules.(config, kernel) + assert match?({"stdlib", _}, stdlib_groups) + assert match?({"kernel", _}, kernel_groups) + {"stdlib", stdlib_modules} = stdlib_groups + {"kernel", kernel_modules} = kernel_groups + assert Enum.member?(stdlib_modules, :gen_server) + assert Enum.member?(kernel_modules, :file) + + # Multiple apps, custom grouping + config = build(apps: [stdlib, kernel], groups_for_modules: [{"custom_group", ["module_1"]}]) + assert groups_for_modules.(config, stdlib) == nil + assert groups_for_modules.(config, kernel) == nil + assert groups_for_modules.(config, custom_group) == {"custom_group", ["module_1"]} + end end end