lib/couchx/migrator.ex

defmodule Couchx.Migrator do
  import Couchx.Support.ApplicationHelper

  def run(repo, :up) do
    repo
    |> index_path
    |> Path.wildcard
    |> require_files
    |> Enum.map(&fetch_modules/1)
    |> run_direction(:up)
  end

  def run(_repo, :down, migrations) do
    migrations
    |> require_files
    |> Enum.map(&fetch_modules/1)
    |> run_direction(:down)
  end

  defp base_module_atom(module) do
    String.to_existing_atom(module)
  end

  defp ensure_migration(migration) when is_atom(migration) do
    migration
  end

  defp ensure_migration(migration) when is_binary(migration) do
    String.to_existing_atom(migration)
  end

  defp fetch_modules(file_path) do
    migration = migration_code(file_path)

    ~r{defmodule \s+ (?<module>\S+) }x
    |> Regex.named_captures(migration)
    |> migration_to_module
  end

  defp index_path(repo, directory \\ "index") do
    repo
    |> base_repo_path(directory)
    |> Path.join("*.exs")
  end

  defp migration_code(file_path) do
    file_path
    |> to_string
    |> File.read!
  end

  defp migration_to_module(%{"module" => module}) do
    "Elixir."
    |> Kernel.<>(module)
    |> base_module_atom
  end
  defp require_files(files) do
    Enum.each(files, &Code.require_file/1)
    files
  end

  def run_direction(migrations, direction) do
    migrations
    |> Enum.map(&ensure_migration/1)
    |> Enum.filter(&with_direction?(&1, direction))
    |> Enum.map(&apply(&1, direction, []))
  end

  defp with_direction?(module, direction) do
    Keyword.has_key?(module.__info__(:functions), direction)
  end
end