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
 |