lib/doex/config.ex

defmodule Doex.Config do
  use FnExpr

  @moduledoc """
  There are a few ways to configure access to your Digital Ocean
  account.  First, you should go and find your [API TOKEN](https://www.digitalocean.com/community/tutorials/how-to-use-the-digitalocean-api-v2#how-to-generate-a-personal-access-token)

  Let's say your token is ABC123, then you can configure you application
  through Mix Tasks, as follows:

      mix doex.init
      mix doex.config token ABC123

  And to confirm it's set, run

      mix doex.config

  And the output should look similar to:

      ssh_keys: []
      token: "ABC123"
      url: "https://api.digitalocean.com/v2"

  You can achieve similar behvarious through an iEX session `iex -S mix`

      Doex.Config.init
      "/Users/aforward/.doex"

      Doex.Config.put(:token, "ABC123")
      :ok

      Doex.Config.read
      %{ssh_keys: [], token: "ABC123", url: "https://api.digitalocean.com/v2"}

  The information above is cached in the Doex.Worker, so if you are making changes
  in iEX, you can reload your configs using

      iex> Doex.reload
      :ok

  And you can see the currently cached values with

      Doex.config
      %{ssh_keys: [], token: "ABC123", url: "https://api.digitalocean.com/v2"}

  The order of preference for locating the appropriate configs are

      #1 Environment variable storing the path to the file
      DOEX_CONFIG=/tmp/my.doex

      #2 Elixir built in Mix.Config
      use Mix.Config
      config :doex, config:  %{token: "SHHHHH"}

      #3 A file within "myproject" called .doex
      /src/myproject/.doex

      # A file within the home directory called .doex
      ~/.doex

  You could overwrite the location, but that's mostly for testing, so unless you have
  a really valid reason do to so, please don't.
  """

  @default_filename "~/.doex"

  def filename do
    case Application.get_env(:doex, :config) do
      nil ->
        case System.get_env("DOEX_CONFIG") do
          nil -> lookup_filename()
          f -> f
        end
        |> Path.expand()

      _ ->
        :config
    end
  end

  def init(), do: filename() |> init
  def init(:config), do: :config

  def init(filename) do
    :ok = filename |> Path.dirname() |> File.mkdir_p!()

    unless File.exists?(filename) do
      filename |> reinit
    end

    filename
  end

  def reinit(), do: filename() |> reinit
  def reinit(:config), do: :config

  def reinit(filename) do
    :ok = write(filename, default_config())
    filename
  end

  def get(key), do: filename() |> get(key)
  def get(:config, key), do: read() |> Map.get(key)

  def get(filename, key) do
    filename
    |> Path.expand()
    |> init
    |> read
    |> Map.get(key)
  end

  def put(key, value), do: filename() |> put(key, value)
  def put(:config, _key, _value), do: {:error, :readonly}

  def put(filename, key, value) do
    filename
    |> Path.expand()
    |> init
    |> read
    |> Map.merge(%{key => value})
    |> invoke(fn map -> write(filename, map) end)
  end

  def remove(key), do: filename() |> remove(key)
  def remove(:config, _key), do: {:error, :readonly}

  def remove(filename, key) do
    filename
    |> Path.expand()
    |> init
    |> read
    |> Map.delete(key)
    |> invoke(fn map -> write(filename, map) end)
  end

  def read(), do: filename() |> read
  def read(:config), do: Application.get_env(:doex, :config)

  def read(filename) do
    filename
    |> Path.expand()
    |> File.read()
    |> invoke(fn result ->
      case result do
        {:ok, content} -> :erlang.binary_to_term(content)
        {:error, _} -> default_config()
      end
    end)
  end

  defp lookup_filename do
    [
      ".doex"
    ]
    |> Enum.filter(&File.exists?/1)
    |> Enum.fetch(0)
    |> case do
      :error -> @default_filename
      {:ok, f} -> f
    end
  end

  defp write(filename, map) do
    filename
    |> Path.expand()
    |> File.write!(:erlang.term_to_binary(map))
  end

  defp default_config do
    %{
      token: "FILL_ME_IN",
      ssh_keys: [],
      url: "https://api.digitalocean.com/v2"
    }
  end
end