ecto.ex (4517B)
1 defmodule Mix.Ecto do 2 @moduledoc """ 3 Conveniences for writing Ecto related Mix tasks. 4 """ 5 6 @doc """ 7 Parses the repository option from the given command line args list. 8 9 If no repo option is given, it is retrieved from the application environment. 10 """ 11 @spec parse_repo([term]) :: [Ecto.Repo.t] 12 def parse_repo(args) do 13 parse_repo(args, []) 14 end 15 16 defp parse_repo([key, value|t], acc) when key in ~w(--repo -r) do 17 parse_repo t, [Module.concat([value])|acc] 18 end 19 20 defp parse_repo([_|t], acc) do 21 parse_repo t, acc 22 end 23 24 defp parse_repo([], []) do 25 apps = 26 if apps_paths = Mix.Project.apps_paths() do 27 # TODO: Use the proper ordering from Mix.Project.deps_apps 28 # when we depend on Elixir v1.11+. 29 apps_paths |> Map.keys() |> Enum.sort() 30 else 31 [Mix.Project.config()[:app]] 32 end 33 34 apps 35 |> Enum.flat_map(fn app -> 36 Application.load(app) 37 Application.get_env(app, :ecto_repos, []) 38 end) 39 |> Enum.uniq() 40 |> case do 41 [] -> 42 Mix.shell().error """ 43 warning: could not find Ecto repos in any of the apps: #{inspect apps}. 44 45 You can avoid this warning by passing the -r flag or by setting the 46 repositories managed by those applications in your config/config.exs: 47 48 config #{inspect hd(apps)}, ecto_repos: [...] 49 """ 50 [] 51 repos -> 52 repos 53 end 54 end 55 56 defp parse_repo([], acc) do 57 Enum.reverse(acc) 58 end 59 60 @doc """ 61 Ensures the given module is an Ecto.Repo. 62 """ 63 @spec ensure_repo(module, list) :: Ecto.Repo.t 64 def ensure_repo(repo, args) do 65 # Do not pass the --force switch used by some tasks downstream 66 args = List.delete(args, "--force") 67 68 # TODO: Use only app.config when we depend on Elixir v1.11+. 69 if Code.ensure_loaded?(Mix.Tasks.App.Config) do 70 Mix.Task.run("app.config", args) 71 else 72 Mix.Task.run("loadpaths", args) 73 "--no-compile" not in args && Mix.Task.run("compile", args) 74 end 75 76 case Code.ensure_compiled(repo) do 77 {:module, _} -> 78 if function_exported?(repo, :__adapter__, 0) do 79 repo 80 else 81 Mix.raise "Module #{inspect repo} is not an Ecto.Repo. " <> 82 "Please configure your app accordingly or pass a repo with the -r option." 83 end 84 85 {:error, error} -> 86 Mix.raise "Could not load #{inspect repo}, error: #{inspect error}. " <> 87 "Please configure your app accordingly or pass a repo with the -r option." 88 end 89 end 90 91 @doc """ 92 Asks if the user wants to open a file based on ECTO_EDITOR. 93 94 By default, it attempts to open the file and line using the 95 `file:line` notation. For example, if your editor is called 96 `subl`, it will open the file as: 97 98 subl path/to/file:line 99 100 It is important that you choose an editor command that does 101 not block nor that attempts to run an editor directly in the 102 terminal. Command-line based editors likely need extra 103 configuration so they open up the given file and line in a 104 separate window. 105 106 Custom editors are supported by using the `__FILE__` and 107 `__LINE__` notations, for example: 108 109 ECTO_EDITOR="my_editor +__LINE__ __FILE__" 110 111 and Elixir will properly interpolate values. 112 113 """ 114 @spec open?(binary, non_neg_integer) :: boolean 115 def open?(file, line \\ 1) do 116 editor = System.get_env("ECTO_EDITOR") || "" 117 118 if editor != "" do 119 command = 120 if editor =~ "__FILE__" or editor =~ "__LINE__" do 121 editor 122 |> String.replace("__FILE__", inspect(file)) 123 |> String.replace("__LINE__", Integer.to_string(line)) 124 else 125 "#{editor} #{inspect(file)}:#{line}" 126 end 127 128 Mix.shell().cmd(command) 129 true 130 else 131 false 132 end 133 end 134 135 @doc """ 136 Gets a path relative to the application path. 137 138 Raises on umbrella application. 139 """ 140 def no_umbrella!(task) do 141 if Mix.Project.umbrella?() do 142 Mix.raise "Cannot run task #{inspect task} from umbrella project root. " <> 143 "Change directory to one of the umbrella applications and try again" 144 end 145 end 146 147 @doc """ 148 Returns `true` if module implements behaviour. 149 """ 150 def ensure_implements(module, behaviour, message) do 151 all = Keyword.take(module.__info__(:attributes), [:behaviour]) 152 unless [behaviour] in Keyword.values(all) do 153 Mix.raise "Expected #{inspect module} to implement #{inspect behaviour} " <> 154 "in order to #{message}" 155 end 156 end 157 end