defmodule Mix.Tasks.Exzeitable.Gen.Migration do
use Mix.Task
import Macro, only: [camelize: 1, underscore: 1]
import Mix.Generator
import Mix.Ecto
import Mix.EctoSQL
@shortdoc "Generates a new migration for trgm"
@name "exzeitable_add_pg_trgm"
@aliases [
r: :repo
]
@switches [
repo: [:string, :keep],
no_compile: :boolean,
no_deps_check: :boolean,
migrations_path: :string
]
@moduledoc """
Generates a migration file adding the pg_trgm extension to postgres.
The repository must be set under `:ecto_repos` in the
current app configuration or given via the `-r` option.
## Example
$ mix exzeitable.gen.migration
$ mix exzeitable.gen.migration -r Custom.Repo
By default, the migration will be generated to the
"priv/YOUR_REPO/migrations" directory of the current application
but it can be configured to be any subdirectory of `priv` by
specifying the `:priv` key under the repository configuration.
This generator will automatically open the generated file if
you have `ECTO_EDITOR` set in your environment variable.
## Command line options
* `-r`, `--repo` - the repo to generate migration for
* `--no-compile` - does not compile applications before running
* `--no-deps-check` - does not check dependencies before running
* `--migrations-path` - the path to run the migrations from, defaults to `priv/repo/migrations`
## Configuration
If the current app configuration specifies a custom migration module
the generated migration code will use that rather than the default
`Ecto.Migration`:
config :ecto_sql, migration_module: MyApplication.CustomMigrationModule
"""
@impl true
def run(args) do
repos = parse_repo(args)
Enum.map(repos, fn repo ->
{opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
ensure_repo(repo, args)
path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations")
base_name = "#{underscore(@name)}.exs"
file = Path.join(path, "#{timestamp()}_#{base_name}")
unless File.dir?(path), do: create_directory(path)
fuzzy_path = Path.join(path, "*_#{base_name}")
if Path.wildcard(fuzzy_path) !== [] do
Mix.raise(
"migration can't be created, there is already a migration file with name #{@name}."
)
end
# The :change option may be used by other tasks but not the CLI
assigns = [mod: Module.concat([repo, Migrations, camelize(@name)])]
create_file(file, migration_template(assigns))
if open?(file) and Mix.shell().yes?("Do you want to run this migration?") do
Mix.Task.run("ecto.migrate", ["-r", inspect(repo), "--migrations-path", path])
end
file
end)
end
defp timestamp do
{{y, m, d}, {hh, mm, ss}} = :calendar.universal_time()
"#{y}#{pad(m)}#{pad(d)}#{pad(hh)}#{pad(mm)}#{pad(ss)}"
end
defp pad(i) when i < 10, do: <<?0, ?0 + i>>
defp pad(i), do: to_string(i)
defp migration_module do
case Application.get_env(:ecto_sql, :migration_module, Ecto.Migration) do
migration_module when is_atom(migration_module) -> migration_module
other -> Mix.raise("Expected :migration_module to be a module, got: #{inspect(other)}")
end
end
embed_template(:migration, """
defmodule <%= inspect @mod %> do
use <%= inspect migration_module() %>
def change do
execute("CREATE EXTENSION pg_trgm", "DROP EXTENSION pg_trgm")
end
end
""")
end