149 lines
4.1 KiB
Elixir
149 lines
4.1 KiB
Elixir
|
defmodule Mix.Ecto do
|
||
|
@moduledoc """
|
||
|
Conveniences for writing Ecto related Mix tasks.
|
||
|
"""
|
||
|
|
||
|
@doc """
|
||
|
Parses the repository option from the given command line args list.
|
||
|
|
||
|
If no repo option is given, it is retrieved from the application environment.
|
||
|
"""
|
||
|
@spec parse_repo([term]) :: [Ecto.Repo.t]
|
||
|
def parse_repo(args) do
|
||
|
parse_repo(args, [])
|
||
|
end
|
||
|
|
||
|
defp parse_repo([key, value|t], acc) when key in ~w(--repo -r) do
|
||
|
parse_repo t, [Module.concat([value])|acc]
|
||
|
end
|
||
|
|
||
|
defp parse_repo([_|t], acc) do
|
||
|
parse_repo t, acc
|
||
|
end
|
||
|
|
||
|
defp parse_repo([], []) do
|
||
|
apps =
|
||
|
if apps_paths = Mix.Project.apps_paths() do
|
||
|
Enum.filter(Mix.Project.deps_apps(), &is_map_key(apps_paths, &1))
|
||
|
else
|
||
|
[Mix.Project.config()[:app]]
|
||
|
end
|
||
|
|
||
|
apps
|
||
|
|> Enum.flat_map(fn app ->
|
||
|
Application.load(app)
|
||
|
Application.get_env(app, :ecto_repos, [])
|
||
|
end)
|
||
|
|> Enum.uniq()
|
||
|
|> case do
|
||
|
[] ->
|
||
|
Mix.shell().error """
|
||
|
warning: could not find Ecto repos in any of the apps: #{inspect apps}.
|
||
|
|
||
|
You can avoid this warning by passing the -r flag or by setting the
|
||
|
repositories managed by those applications in your config/config.exs:
|
||
|
|
||
|
config #{inspect hd(apps)}, ecto_repos: [...]
|
||
|
"""
|
||
|
[]
|
||
|
repos ->
|
||
|
repos
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp parse_repo([], acc) do
|
||
|
Enum.reverse(acc)
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Ensures the given module is an Ecto.Repo.
|
||
|
"""
|
||
|
@spec ensure_repo(module, list) :: Ecto.Repo.t
|
||
|
def ensure_repo(repo, args) do
|
||
|
# Do not pass the --force switch used by some tasks downstream
|
||
|
args = List.delete(args, "--force")
|
||
|
Mix.Task.run("app.config", args)
|
||
|
|
||
|
case Code.ensure_compiled(repo) do
|
||
|
{:module, _} ->
|
||
|
if function_exported?(repo, :__adapter__, 0) do
|
||
|
repo
|
||
|
else
|
||
|
Mix.raise "Module #{inspect repo} is not an Ecto.Repo. " <>
|
||
|
"Please configure your app accordingly or pass a repo with the -r option."
|
||
|
end
|
||
|
|
||
|
{:error, error} ->
|
||
|
Mix.raise "Could not load #{inspect repo}, error: #{inspect error}. " <>
|
||
|
"Please configure your app accordingly or pass a repo with the -r option."
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Asks if the user wants to open a file based on ECTO_EDITOR.
|
||
|
|
||
|
By default, it attempts to open the file and line using the
|
||
|
`file:line` notation. For example, if your editor is called
|
||
|
`subl`, it will open the file as:
|
||
|
|
||
|
subl path/to/file:line
|
||
|
|
||
|
It is important that you choose an editor command that does
|
||
|
not block nor that attempts to run an editor directly in the
|
||
|
terminal. Command-line based editors likely need extra
|
||
|
configuration so they open up the given file and line in a
|
||
|
separate window.
|
||
|
|
||
|
Custom editors are supported by using the `__FILE__` and
|
||
|
`__LINE__` notations, for example:
|
||
|
|
||
|
ECTO_EDITOR="my_editor +__LINE__ __FILE__"
|
||
|
|
||
|
and Elixir will properly interpolate values.
|
||
|
|
||
|
"""
|
||
|
@spec open?(binary, non_neg_integer) :: boolean
|
||
|
def open?(file, line \\ 1) do
|
||
|
editor = System.get_env("ECTO_EDITOR") || ""
|
||
|
|
||
|
if editor != "" do
|
||
|
command =
|
||
|
if editor =~ "__FILE__" or editor =~ "__LINE__" do
|
||
|
editor
|
||
|
|> String.replace("__FILE__", inspect(file))
|
||
|
|> String.replace("__LINE__", Integer.to_string(line))
|
||
|
else
|
||
|
"#{editor} #{inspect(file)}:#{line}"
|
||
|
end
|
||
|
|
||
|
Mix.shell().cmd(command)
|
||
|
true
|
||
|
else
|
||
|
false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Gets a path relative to the application path.
|
||
|
|
||
|
Raises on umbrella application.
|
||
|
"""
|
||
|
def no_umbrella!(task) do
|
||
|
if Mix.Project.umbrella?() do
|
||
|
Mix.raise "Cannot run task #{inspect task} from umbrella project root. " <>
|
||
|
"Change directory to one of the umbrella applications and try again"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Returns `true` if module implements behaviour.
|
||
|
"""
|
||
|
def ensure_implements(module, behaviour, message) do
|
||
|
all = Keyword.take(module.__info__(:attributes), [:behaviour])
|
||
|
unless [behaviour] in Keyword.values(all) do
|
||
|
Mix.raise "Expected #{inspect module} to implement #{inspect behaviour} " <>
|
||
|
"in order to #{message}"
|
||
|
end
|
||
|
end
|
||
|
end
|