From c234c302f53b08e2ee1d3cce26fad6739f64cb5c Mon Sep 17 00:00:00 2001 From: Dan Schultzer Date: Sat, 25 Aug 2018 20:47:28 -0700 Subject: [PATCH] Add :otp_app configuration support for extension mix tasks --- lib/mix/pow/extension.ex | 19 +++++- .../ecto/pow.extension.ecto.gen.migrations.ex | 29 ++++++++-- .../pow.extension.phoenix.gen.templates.ex | 29 +++++++--- ....extension.phoenix.mailer.gen.templates.ex | 26 ++++++--- ...pow.extension.ecto.gen.migrations_test.exs | 27 +++++++++ ...w.extension.phoenix.gen.templates_test.exs | 46 ++++++++++----- ...sion.phoenix.mailer.gen.templates_test.exs | 58 ++++++++++++------- 7 files changed, 177 insertions(+), 57 deletions(-) diff --git a/lib/mix/pow/extension.ex b/lib/mix/pow/extension.ex index 673558d3..ef8823b9 100644 --- a/lib/mix/pow/extension.ex +++ b/lib/mix/pow/extension.ex @@ -2,12 +2,27 @@ defmodule Mix.Pow.Extension do @moduledoc """ Utilities module for mix extension tasks. """ + alias Pow.Config - @spec extensions(map()) :: [atom()] - def extensions(config) do + @spec extensions(map(), atom()) :: [atom()] + def extensions(config, otp_app) do config |> Map.get(:extension, []) |> List.wrap() |> Enum.map(&Module.concat(Elixir, &1)) + |> maybe_fetch_otp_app_extensions(otp_app) + end + + defp maybe_fetch_otp_app_extensions([], otp_app) do + Config.get([otp_app: otp_app], :extensions, []) + end + defp maybe_fetch_otp_app_extensions(extensions, _otp_app), do: extensions + + @spec no_extensions_error(atom()) :: :ok + def no_extensions_error(otp_app) do + Mix.shell.error( + """ + No extensions was provided as arguments, or found in `config :#{otp_app}, :pow` configuration. + """) end end diff --git a/lib/mix/tasks/extension/ecto/pow.extension.ecto.gen.migrations.ex b/lib/mix/tasks/extension/ecto/pow.extension.ecto.gen.migrations.ex index c008ae5b..bee69441 100644 --- a/lib/mix/tasks/extension/ecto/pow.extension.ecto.gen.migrations.ex +++ b/lib/mix/tasks/extension/ecto/pow.extension.ecto.gen.migrations.ex @@ -4,6 +4,16 @@ defmodule Mix.Tasks.Pow.Extension.Ecto.Gen.Migrations do @moduledoc """ Generates a migration files for extensions. + ## Usage + + Install migration files for explicit extensions: + + mix pow.extension.ecto.gen.migrations -r MyApp.Repo --extension PowEmailConfirmation + + mix pow.extension.ecto.gen.migrations -r MyApp.Repo Accounts.Organization organizations --extension PowEmailConfirmation + + Use the context app configuration environment for extensions: + mix pow.extension.ecto.gen.migrations -r MyApp.Repo mix pow.extension.ecto.gen.migrations -r MyApp.Repo Accounts.Organization organizations @@ -24,6 +34,7 @@ defmodule Mix.Tasks.Pow.Extension.Ecto.Gen.Migrations do |> Pow.parse_options(@switches, @default_opts) |> parse() |> create_migrations_files(args) + |> print_shell_instructions() end defp parse({config, parsed, _invalid}) do @@ -37,17 +48,20 @@ defmodule Mix.Tasks.Pow.Extension.Ecto.Gen.Migrations do end defp create_migrations_files(config, args) do + context_base = Pow.context_base(Pow.context_app()) + otp_app = String.to_atom(Macro.underscore(context_base)) + extensions = Extension.extensions(config, otp_app) + args |> Ecto.parse_repo() |> Enum.map(&Ecto.ensure_repo(&1, args)) |> Enum.map(&Map.put(config, :repo, &1)) - |> Enum.each(&create_extension_migration_files/1) - end + |> Enum.each(&create_extension_migration_files(&1, extensions, context_base)) - defp create_extension_migration_files(config) do - extensions = Extension.extensions(config) - context_base = Pow.context_base(Pow.context_app()) + %{extensions: extensions, otp_app: otp_app} + end + defp create_extension_migration_files(config, extensions, context_base) do for extension <- extensions, do: create_migration_files(config, extension, context_base) end @@ -66,4 +80,9 @@ defmodule Mix.Tasks.Pow.Extension.Ecto.Gen.Migrations do defp empty?(%{assocs: [], attrs: [], indexes: []}), do: true defp empty?(_schema), do: false + + defp print_shell_instructions(%{extensions: [], otp_app: otp_app}) do + Extension.no_extensions_error(otp_app) + end + defp print_shell_instructions(config), do: config end diff --git a/lib/mix/tasks/extension/phoenix/pow.extension.phoenix.gen.templates.ex b/lib/mix/tasks/extension/phoenix/pow.extension.phoenix.gen.templates.ex index 295ade66..d0379322 100644 --- a/lib/mix/tasks/extension/phoenix/pow.extension.phoenix.gen.templates.ex +++ b/lib/mix/tasks/extension/phoenix/pow.extension.phoenix.gen.templates.ex @@ -4,7 +4,15 @@ defmodule Mix.Tasks.Pow.Extension.Phoenix.Gen.Templates do @moduledoc """ Generates pow extension templates for Phoenix. - mix pow.extension.phoenix.gen.templates + ## Usage + + Install extension templates explicitly: + + mix pow.extension.phoenix.gen.templates --extension PowEmailConfirmation + + Use the context app configuration environment for extensions: + + mix pow.extension.phoenix.gen.templates --context-app my_app """ use Mix.Task @@ -20,6 +28,7 @@ defmodule Mix.Tasks.Pow.Extension.Phoenix.Gen.Templates do args |> Pow.parse_options(@switches, @default_opts) |> create_template_files() + |> print_shell_instructions() end @extension_templates [ @@ -28,12 +37,13 @@ defmodule Mix.Tasks.Pow.Extension.Phoenix.Gen.Templates do ]} ] defp create_template_files({config, _parsed, _invalid}) do - structure = Phoenix.parse_structure(config) - web_module = structure[:web_module] - web_prefix = structure[:web_prefix] - extensions = + structure = Phoenix.parse_structure(config) + web_module = structure[:web_module] + web_prefix = structure[:web_prefix] + otp_app = String.to_atom(Macro.underscore(structure[:context_base])) + extensions = config - |> Extension.extensions() + |> Extension.extensions(otp_app) |> Enum.filter(&Keyword.has_key?(@extension_templates, &1)) |> Enum.map(&{&1, @extension_templates[&1]}) @@ -44,6 +54,11 @@ defmodule Mix.Tasks.Pow.Extension.Phoenix.Gen.Templates do end) end) - %{structure: structure} + %{extensions: extensions, otp_app: otp_app, structure: structure} + end + + defp print_shell_instructions(%{extensions: [], otp_app: otp_app}) do + Extension.no_extensions_error(otp_app) end + defp print_shell_instructions(config), do: config end diff --git a/lib/mix/tasks/extension/phoenix/pow.extension.phoenix.mailer.gen.templates.ex b/lib/mix/tasks/extension/phoenix/pow.extension.phoenix.mailer.gen.templates.ex index ffc1529e..6f73f742 100644 --- a/lib/mix/tasks/extension/phoenix/pow.extension.phoenix.mailer.gen.templates.ex +++ b/lib/mix/tasks/extension/phoenix/pow.extension.phoenix.mailer.gen.templates.ex @@ -4,7 +4,15 @@ defmodule Mix.Tasks.Pow.Extension.Phoenix.Mailer.Gen.Templates do @moduledoc """ Generates Pow mailer extension templates for Phoenix. - mix pow.extension.phoenix.mailer.gen.templates + ## Usage + + Install extension mailer templates explicitly: + + mix pow.extension.phoenix.mailer.gen.templates --extension PowEmailConfirmation + + Use the context app configuration environment for extensions: + + mix pow.extension.phoenix.mailer.gen.templates --context-app my_app """ use Mix.Task @@ -32,12 +40,13 @@ defmodule Mix.Tasks.Pow.Extension.Phoenix.Mailer.Gen.Templates do ]} ] defp create_template_files({config, _parsed, _invalid}) do - structure = Phoenix.parse_structure(config) - web_module = structure[:web_module] - web_prefix = structure[:web_prefix] - extensions = + structure = Phoenix.parse_structure(config) + web_module = structure[:web_module] + web_prefix = structure[:web_prefix] + otp_app = String.to_atom(Macro.underscore(structure[:context_base])) + extensions = config - |> Extension.extensions() + |> Extension.extensions(otp_app) |> Enum.filter(&Keyword.has_key?(@extension_templates, &1)) |> Enum.map(&{&1, @extension_templates[&1]}) @@ -49,9 +58,12 @@ defmodule Mix.Tasks.Pow.Extension.Phoenix.Mailer.Gen.Templates do end) end) - %{structure: structure} + %{extensions: extensions, otp_app: otp_app, structure: structure} end + defp print_shell_instructions(%{extensions: [], otp_app: otp_app}) do + Extension.no_extensions_error(otp_app) + end defp print_shell_instructions(%{structure: structure}) do web_base = structure[:web_module] web_prefix = structure[:web_prefix] diff --git a/test/mix/tasks/extension/ecto/pow.extension.ecto.gen.migrations_test.exs b/test/mix/tasks/extension/ecto/pow.extension.ecto.gen.migrations_test.exs index 127ec79e..f073afc0 100644 --- a/test/mix/tasks/extension/ecto/pow.extension.ecto.gen.migrations_test.exs +++ b/test/mix/tasks/extension/ecto/pow.extension.ecto.gen.migrations_test.exs @@ -47,6 +47,15 @@ defmodule Mix.Tasks.Pow.Extension.Ecto.Gen.MigrationsTest do end) end + test "warns if no extensions" do + File.cd!(@tmp_path, fn -> + Migrations.run(["-r", inspect(Repo)]) + + assert_received {:mix_shell, :error, [msg]} + assert msg =~ "No extensions was provided as arguments, or found in `config :pow, :pow` configuration." + end) + end + test "generates with :binary_id" do options = @options ++ ~w(--binary-id) @@ -61,6 +70,24 @@ defmodule Mix.Tasks.Pow.Extension.Ecto.Gen.MigrationsTest do end) end + describe "with :otp_app configuration" do + setup do + Application.put_env(:pow, :pow, extensions: [__MODULE__]) + on_exit(fn -> + Application.delete_env(:pow, :pow) + end) + end + + test "generates migrations" do + File.cd!(@tmp_path, fn -> + Application.put_env(:pow, :pow, extensions: [__MODULE__]) + Migrations.run(["-r", inspect(Repo)]) + + assert [_migration_file] = File.ls!(@migrations_path) + end) + end + end + test "doesn't make duplicate migrations" do options = @options ++ ["--extension", __MODULE__] diff --git a/test/mix/tasks/extension/phoenix/pow.extension.phoenix.gen.templates_test.exs b/test/mix/tasks/extension/phoenix/pow.extension.phoenix.gen.templates_test.exs index 2ffbb103..b552777f 100644 --- a/test/mix/tasks/extension/phoenix/pow.extension.phoenix.gen.templates_test.exs +++ b/test/mix/tasks/extension/phoenix/pow.extension.phoenix.gen.templates_test.exs @@ -48,27 +48,43 @@ defmodule Mix.Tasks.Pow.Extension.Phoenix.Gen.TemplatesTest do end) end - test "generates with :context_app" do - options = @options ++ ~w(--context-app test) - + test "warns if no extensions" do File.cd!(@tmp_path, fn -> - Templates.run(options) + Templates.run([]) - for {module, expected_templates} <- @expected_template_files do - templates_path = Path.join(["lib", "test_web", "templates", Macro.underscore(module)]) - dirs = templates_path |> File.ls!() |> Enum.sort() + assert_received {:mix_shell, :error, [msg]} + assert msg =~ "No extensions was provided as arguments, or found in `config :pow, :pow` configuration." + end) + end - assert dirs == Map.keys(expected_templates) + describe "with :context_app configuration" do + setup do + Application.put_env(:test, :pow, extensions: [PowResetPassword, PowEmailConfirmation]) + on_exit(fn -> + Application.delete_env(:test, :pow) + end) + end - views_path = Path.join(["lib", "test_web", "views", Macro.underscore(module)]) + test "generates templates" do + File.cd!(@tmp_path, fn -> + Templates.run(~w(--context-app test)) - [base_name | _rest] = expected_templates |> Map.keys() - view_content = views_path |> Path.join(base_name <> "_view.ex") |> File.read!() + for {module, expected_templates} <- @expected_template_files do + templates_path = Path.join(["lib", "test_web", "templates", Macro.underscore(module)]) + dirs = templates_path |> File.ls!() |> Enum.sort() - assert view_content =~ "defmodule TestWeb.#{inspect(module)}.#{Macro.camelize(base_name)}View do" - assert view_content =~ "use TestWeb, :view" - end - end) + assert dirs == Map.keys(expected_templates) + + views_path = Path.join(["lib", "test_web", "views", Macro.underscore(module)]) + + [base_name | _rest] = expected_templates |> Map.keys() + view_content = views_path |> Path.join(base_name <> "_view.ex") |> File.read!() + + assert view_content =~ "defmodule TestWeb.#{inspect(module)}.#{Macro.camelize(base_name)}View do" + assert view_content =~ "use TestWeb, :view" + end + end) + end end defp ls(path), do: path |> File.ls!() |> Enum.sort() diff --git a/test/mix/tasks/extension/phoenix/pow.extension.phoenix.mailer.gen.templates_test.exs b/test/mix/tasks/extension/phoenix/pow.extension.phoenix.mailer.gen.templates_test.exs index 9fc54585..0ce65d76 100644 --- a/test/mix/tasks/extension/phoenix/pow.extension.phoenix.mailer.gen.templates_test.exs +++ b/test/mix/tasks/extension/phoenix/pow.extension.phoenix.mailer.gen.templates_test.exs @@ -60,33 +60,49 @@ defmodule Mix.Tasks.Pow.Extension.Phoenix.Mailer.Gen.TemplatesTest do end) end - test "generates with :context_app" do - options = @options ++ ~w(--context-app test) - + test "warns if no extensions" do File.cd!(@tmp_path, fn -> - Templates.run(options) + Templates.run([]) - for {module, expected_templates} <- @expected_template_files do - templates_path = Path.join(["lib", "test_web", "templates", Macro.underscore(module)]) - dirs = templates_path |> File.ls!() |> Enum.sort() + assert_received {:mix_shell, :error, [msg]} + assert msg =~ "No extensions was provided as arguments, or found in `config :pow, :pow` configuration." + end) + end - assert dirs == Map.keys(expected_templates) + describe "with :context_app configuration" do + setup do + Application.put_env(:test, :pow, extensions: [PowResetPassword, PowEmailConfirmation]) + on_exit(fn -> + Application.delete_env(:test, :pow) + end) + end - views_path = Path.join(["lib", "test_web", "views", Macro.underscore(module)]) - [base_name | _rest] = expected_templates |> Map.keys() - view_content = views_path |> Path.join(base_name <> "_view.ex") |> File.read!() + test "generates mailer templates" do + File.cd!(@tmp_path, fn -> + Templates.run(~w(--context-app test)) - assert view_content =~ "defmodule TestWeb.#{inspect(module)}.#{Macro.camelize(base_name)}View do" - assert view_content =~ "use TestWeb, :mailer_view" - end + for {module, expected_templates} <- @expected_template_files do + templates_path = Path.join(["lib", "test_web", "templates", Macro.underscore(module)]) + dirs = templates_path |> File.ls!() |> Enum.sort() - for _ <- 1..6, do: assert_received({:mix_shell, :info, [_msg]}) - assert_received {:mix_shell, :info, [msg]} - assert msg =~ "lib/test_web.ex" - assert msg =~ ":mailer_view" - assert msg =~ "def mailer_view" - assert msg =~ "use Phoenix.View, root: \"lib/test_web/templates\"" - end) + assert dirs == Map.keys(expected_templates) + + views_path = Path.join(["lib", "test_web", "views", Macro.underscore(module)]) + [base_name | _rest] = expected_templates |> Map.keys() + view_content = views_path |> Path.join(base_name <> "_view.ex") |> File.read!() + + assert view_content =~ "defmodule TestWeb.#{inspect(module)}.#{Macro.camelize(base_name)}View do" + assert view_content =~ "use TestWeb, :mailer_view" + end + + for _ <- 1..6, do: assert_received({:mix_shell, :info, [_msg]}) + assert_received {:mix_shell, :info, [msg]} + assert msg =~ "lib/test_web.ex" + assert msg =~ ":mailer_view" + assert msg =~ "def mailer_view" + assert msg =~ "use Phoenix.View, root: \"lib/test_web/templates\"" + end) + end end defp ls(path), do: path |> File.ls!() |> Enum.sort()