lib/mix/tasks/uniform.init.ex

defmodule Mix.Tasks.Uniform.Init do
  @moduledoc """
  Initializes a [Base Project](how-it-works.html#the-base-project) repository
  with the bare minimum setup required for using Uniform.

  1. Adds a [Blueprint](Uniform.Blueprint.html) module
  2. Adds required configuration to `config/config.exs`

  The remainder of installation steps are listed in the [Getting
  Started](getting-started.html) guide.

  ## Usage

  ```bash
  $ mix uniform.init
  Created lib/base_app/uniform/blueprint.ex
  Added configuration in config/config.exs
  ```
  """

  use Mix.Task

  require Logger

  @doc false
  def run(_) do
    otp_app = Keyword.fetch!(Mix.Project.config(), :app)

    File.mkdir_p!("lib/#{otp_app}/uniform")
    create_blueprint(otp_app)
    add_config(otp_app)
  end

  defp create_blueprint(otp_app) do
    blueprint_path =
      __ENV__.file
      |> Path.dirname()
      |> Path.join("/../../../templates/blueprint.ex")
      |> Path.expand()

    blueprint =
      EEx.eval_file(blueprint_path,
        assigns: [
          app_module: app_module(otp_app),
          app_underscore: to_string(otp_app)
        ]
      )

    write_unless_exists("lib/#{otp_app}/uniform/blueprint.ex", blueprint)
  end

  defp add_config(otp_app) do
    case File.read("config/config.exs") do
      {:ok, config} ->
        do_add_config(config, otp_app)

      {:error, reason} ->
        Logger.warn("Could not patch config/config.exs – reason: #{reason}")
    end
  end

  defp do_add_config(config, otp_app) do
    case String.split(config, "import Config") do
      [before_import, after_import] ->
        blueprint_config = """
        import Config

        # uniform:remove
        config :#{otp_app}, Uniform, blueprint: #{app_module(otp_app)}.Uniform.Blueprint
        # /uniform:remove\
        """

        File.write!("config/config.exs", before_import <> blueprint_config <> after_import)
        IO.puts("Added configuration in config/config.exs")

      _ ->
        Logger.warn(
          "Could not patch config/config.exs because it does not contain `import Config`"
        )
    end
  end

  defp write_unless_exists(path, contents) do
    if File.exists?(path) do
      Logger.warn("Did not create #{path} because it already exists")
    else
      File.write!(path, contents)
      IO.puts("Created #{path}")
    end
  end

  defp app_module(otp_app) do
    otp_app
    |> to_string()
    |> Macro.camelize()
  end
end