215 lines
5.9 KiB
Elixir
215 lines
5.9 KiB
Elixir
|
defmodule Mix.Tasks.Phx.Gen.Notifier do
|
||
|
@shortdoc "Generates a notifier that delivers emails by default"
|
||
|
|
||
|
@moduledoc """
|
||
|
Generates a notifier that delivers emails by default.
|
||
|
|
||
|
$ mix phx.gen.notifier Accounts User welcome_user reset_password confirmation_instructions
|
||
|
|
||
|
This task expects a context module name, followed by a
|
||
|
notifier name and one or more message names. Messages
|
||
|
are the functions that will be created prefixed by "deliver",
|
||
|
so the message name should be "snake_case" without punctuation.
|
||
|
|
||
|
Additionally a context app can be specified with the flag
|
||
|
`--context-app`, which is useful if the notifier is being
|
||
|
generated in a different app under an umbrella.
|
||
|
|
||
|
$ mix phx.gen.notifier Accounts User welcome_user --context-app marketing
|
||
|
|
||
|
The app "marketing" must exist before the command is executed.
|
||
|
"""
|
||
|
|
||
|
use Mix.Task
|
||
|
|
||
|
@switches [
|
||
|
context: :boolean,
|
||
|
context_app: :string,
|
||
|
prefix: :string
|
||
|
]
|
||
|
|
||
|
@default_opts [context: true]
|
||
|
|
||
|
alias Mix.Phoenix.Context
|
||
|
|
||
|
@doc false
|
||
|
def run(args) do
|
||
|
if Mix.Project.umbrella?() do
|
||
|
Mix.raise(
|
||
|
"mix phx.gen.notifier must be invoked from within your *_web application root directory"
|
||
|
)
|
||
|
end
|
||
|
|
||
|
{context, notifier_module, messages} = build(args)
|
||
|
|
||
|
inflections = Mix.Phoenix.inflect(notifier_module)
|
||
|
|
||
|
binding = [
|
||
|
context: context,
|
||
|
inflections: inflections,
|
||
|
notifier_messages: messages
|
||
|
]
|
||
|
|
||
|
paths = Mix.Phoenix.generator_paths()
|
||
|
|
||
|
prompt_for_conflicts(context)
|
||
|
|
||
|
if "--no-compile" not in args do
|
||
|
Mix.Task.run("compile")
|
||
|
end
|
||
|
|
||
|
context
|
||
|
|> copy_new_files(binding, paths)
|
||
|
|> maybe_print_mailer_installation_instructions()
|
||
|
end
|
||
|
|
||
|
@doc false
|
||
|
def build(args, help \\ __MODULE__) do
|
||
|
{opts, parsed, _} = parse_opts(args)
|
||
|
|
||
|
[context_name, notifier_name | notifier_messages] = validate_args!(parsed, help)
|
||
|
|
||
|
notifier_module = inspect(Module.concat(context_name, "#{notifier_name}Notifier"))
|
||
|
context = Context.new(notifier_module, opts)
|
||
|
|
||
|
{context, notifier_module, notifier_messages}
|
||
|
end
|
||
|
|
||
|
defp parse_opts(args) do
|
||
|
{opts, parsed, invalid} = OptionParser.parse(args, switches: @switches)
|
||
|
|
||
|
merged_opts =
|
||
|
@default_opts
|
||
|
|> Keyword.merge(opts)
|
||
|
|> put_context_app(opts[:context_app])
|
||
|
|
||
|
{merged_opts, parsed, invalid}
|
||
|
end
|
||
|
|
||
|
defp put_context_app(opts, nil), do: opts
|
||
|
|
||
|
defp put_context_app(opts, string) do
|
||
|
Keyword.put(opts, :context_app, String.to_atom(string))
|
||
|
end
|
||
|
|
||
|
defp validate_args!([context, notifier | messages] = args, help) do
|
||
|
cond do
|
||
|
not Context.valid?(context) ->
|
||
|
help.raise_with_help(
|
||
|
"Expected the context, #{inspect(context)}, to be a valid module name"
|
||
|
)
|
||
|
|
||
|
not valid_notifier?(notifier) ->
|
||
|
help.raise_with_help(
|
||
|
"Expected the notifier, #{inspect(notifier)}, to be a valid module name"
|
||
|
)
|
||
|
|
||
|
context == Mix.Phoenix.base() ->
|
||
|
help.raise_with_help(
|
||
|
"Cannot generate context #{context} because it has the same name as the application"
|
||
|
)
|
||
|
|
||
|
notifier == Mix.Phoenix.base() ->
|
||
|
help.raise_with_help(
|
||
|
"Cannot generate notifier #{notifier} because it has the same name as the application"
|
||
|
)
|
||
|
|
||
|
Enum.any?(messages, &(!valid_message?(&1))) ->
|
||
|
help.raise_with_help(
|
||
|
"Cannot generate notifier #{inspect(notifier)} because one of the messages is invalid: #{Enum.map_join(messages, ", ", &inspect/1)}"
|
||
|
)
|
||
|
|
||
|
true ->
|
||
|
args
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp validate_args!(_, help) do
|
||
|
help.raise_with_help("Invalid arguments")
|
||
|
end
|
||
|
|
||
|
defp valid_notifier?(notifier) do
|
||
|
notifier =~ ~r/^[A-Z]\w*(\.[A-Z]\w*)*$/
|
||
|
end
|
||
|
|
||
|
defp valid_message?(message_name) do
|
||
|
message_name =~ ~r/^[a-z]+(\_[a-z0-9]+)*$/
|
||
|
end
|
||
|
|
||
|
@doc false
|
||
|
@spec raise_with_help(String.t()) :: no_return()
|
||
|
def raise_with_help(msg) do
|
||
|
Mix.raise("""
|
||
|
#{msg}
|
||
|
|
||
|
mix phx.gen.notifier expects a context module name, followed by a
|
||
|
notifier name and one or more message names. Messages are the
|
||
|
functions that will be created prefixed by "deliver", so the message
|
||
|
name should be "snake_case" without punctuation.
|
||
|
For example:
|
||
|
|
||
|
mix phx.gen.notifier Accounts User welcome reset_password
|
||
|
|
||
|
In this example the notifier will be called `UserNotifier` inside
|
||
|
the Accounts context. The functions `deliver_welcome/1` and
|
||
|
`reset_password/1` will be created inside this notifier.
|
||
|
""")
|
||
|
end
|
||
|
|
||
|
defp copy_new_files(%Context{} = context, binding, paths) do
|
||
|
files = files_to_be_generated(context)
|
||
|
|
||
|
Mix.Phoenix.copy_from(paths, "priv/templates/phx.gen.notifier", binding, files)
|
||
|
|
||
|
context
|
||
|
end
|
||
|
|
||
|
defp files_to_be_generated(%Context{} = context) do
|
||
|
[
|
||
|
{:eex, "notifier.ex", context.file},
|
||
|
{:eex, "notifier_test.exs", context.test_file}
|
||
|
]
|
||
|
end
|
||
|
|
||
|
defp prompt_for_conflicts(context) do
|
||
|
context
|
||
|
|> files_to_be_generated()
|
||
|
|> Mix.Phoenix.prompt_for_conflicts()
|
||
|
end
|
||
|
|
||
|
@doc false
|
||
|
@spec maybe_print_mailer_installation_instructions(%Context{}) :: %Context{}
|
||
|
def maybe_print_mailer_installation_instructions(%Context{} = context) do
|
||
|
mailer_module = Module.concat([context.base_module, "Mailer"])
|
||
|
|
||
|
unless Code.ensure_loaded?(mailer_module) do
|
||
|
Mix.shell().info("""
|
||
|
Unable to find the "#{inspect(mailer_module)}" module defined.
|
||
|
|
||
|
A mailer module like the following is expected to be defined
|
||
|
in your application in order to send emails.
|
||
|
|
||
|
defmodule #{inspect(mailer_module)} do
|
||
|
use Swoosh.Mailer, otp_app: #{inspect(context.context_app)}
|
||
|
end
|
||
|
|
||
|
It is also necessary to add "swoosh" as a dependency in your
|
||
|
"mix.exs" file:
|
||
|
|
||
|
def deps do
|
||
|
[{:swoosh, "~> 1.4"}]
|
||
|
end
|
||
|
|
||
|
Finally, an adapter needs to be set in your configuration:
|
||
|
|
||
|
import Config
|
||
|
config #{inspect(context.context_app)}, #{inspect(mailer_module)}, adapter: Swoosh.Adapters.Local
|
||
|
|
||
|
Check https://hexdocs.pm/swoosh for more details.
|
||
|
""")
|
||
|
end
|
||
|
|
||
|
context
|
||
|
end
|
||
|
end
|