Current section

Files

Jump to
pow lib mix pow.ex
Raw

lib/mix/pow.ex

defmodule Mix.Pow do
@moduledoc """
Utilities module for mix tasks.
"""
alias Mix.{Dep, Project}
@doc """
Raises an exception if the project is an umbrella app.
"""
@spec no_umbrella!(binary()) :: :ok | no_return
def no_umbrella!(task) do
if Project.umbrella?() do
Mix.raise("mix #{task} can only be run inside an application directory")
end
:ok
end
@doc """
Raises an exception if the application doesn't have the dependency.
"""
@spec ensure_dep!(binary(), atom(), OptionParser.argv()) :: :ok | no_return
def ensure_dep!(task, dep, _args) do
fetch_deps()
|> dep_in_deps?(dep)
|> case do
true ->
:ok
false ->
Mix.raise("mix #{task} can only be run inside an application directory that has #{inspect dep} as dependency")
end
end
# TODO: Remove by 1.1.0 and only support Elixir 1.7
defp fetch_deps do
System.version()
|> Version.match?("~> 1.6.0")
|> case do
true -> apply(Dep, :loaded, [[]])
false -> apply(Dep, :load_on_environment, [[]])
end
end
defp dep_in_deps?(deps, dep) do
Enum.any?(deps, fn
%Mix.Dep{app: ^dep} -> true
_any -> false
end)
end
@doc """
Raises an exception if application doesn't have Ecto as dependency.
"""
def ensure_ecto!(task, args), do: ensure_dep!(task, :ecto, args)
@doc """
Raises an exception if application doesn't have Phoenix as dependency.
"""
def ensure_phoenix!(task, args), do: ensure_dep!(task, :phoenix, args)
@doc """
Parses argument options into a map.
"""
@spec parse_options(OptionParser.argv(), Keyword.t(), Keyword.t()) :: {map(), OptionParser.argv(), OptionParser.errors()}
def parse_options(args, switches, default_opts) do
{opts, parsed, invalid} = OptionParser.parse(args, switches: switches)
default_opts = to_map(default_opts)
opts = to_map(opts)
config =
default_opts
|> Map.merge(opts)
|> context_app_to_atom()
{config, parsed, invalid}
end
defp to_map(keyword) do
Enum.reduce(keyword, %{}, fn {key, value}, map ->
case Map.get(map, key) do
nil ->
Map.put(map, key, value)
existing_value ->
value = List.wrap(existing_value) ++ [value]
Map.put(map, key, value)
end
end)
end
defp context_app_to_atom(%{context_app: context_app} = config),
do: Map.put(config, :context_app, String.to_atom(context_app))
defp context_app_to_atom(config),
do: config
@doc """
Fetches context app for the project.
"""
@spec context_app :: atom() | no_return
def context_app do
this_app = otp_app()
this_app
|> Application.get_env(:generators, [])
|> Keyword.get(:context_app)
|> case do
nil -> this_app
false -> Mix.raise("No context_app configured for current application")
{app, _path} -> app
app -> app
end
end
defp otp_app do
Keyword.fetch!(Mix.Project.config(), :app)
end
@doc """
Fetches the context base module for the app.
"""
@spec context_base(atom()) :: atom()
def context_base(app) do
case Application.get_env(app, :namespace, app) do
^app ->
app
|> to_string()
|> Macro.camelize()
|> List.wrap()
|> Module.concat()
mod ->
mod
end
end
@doc """
Fetches the library path for the context app.
"""
@spec context_lib_path(atom(), Path.t()) :: Path.t()
def context_lib_path(ctx_app, rel_path) do
context_app_path(ctx_app, Path.join(["lib", to_string(ctx_app), rel_path]))
end
defp context_app_path(ctx_app, rel_path) when is_atom(ctx_app) do
this_app = otp_app()
if ctx_app == this_app do
rel_path
else
app_path =
case Application.get_env(this_app, :generators)[:context_app] do
{^ctx_app, path} -> Path.relative_to_cwd(path)
_ -> mix_app_path(ctx_app, this_app)
end
Path.join(app_path, rel_path)
end
end
defp mix_app_path(app, this_otp_app) do
case Mix.Project.deps_paths() do
%{^app => path} ->
Path.relative_to_cwd(path)
_deps ->
Mix.raise("No directory for context_app #{inspect(app)} found in #{this_otp_app}'s deps.")
end
end
end