lib/providers/dot_env.ex

defmodule ExSecrets.Providers.DotEnv do
  @moduledoc """
  DotEnv provider provides secrets from a .env file.
  """
  use ExSecrets.Providers.Base
  alias ExSecrets.Cache
  alias ExSecrets.Utils.Config

  def get(key) do
    case GenServer.whereis(process_name()) do
      pid when is_pid(pid) ->
        Cache.get(key)

      _ ->
        get_scripted(key)
    end
  end

  def set(name, value) do
    path = Config.provider_config_value(:dot_env, :path)

    with existing when is_nil(existing) <- get(name),
         true <- is_binary(path),
         :ok <- File.write(path, "#{name}=#{value}\n", [:append]) do
      Cache.save(name, value)
      :ok
    else
      _ -> {:error, "Failed to write to #{path}"}
    end
  end

  def init(_) do
    read_env()
    {:ok, %{}}
  end

  def reset() do
    read_env()
    :ok
  end

  defp read_env() do
    path = Config.provider_config_value(:dot_env, :path)

    with true <- is_binary(path),
         true <- File.exists?(path),
         {:ok, s} <- File.read(path),
         [_ | _] = envs <- String.split(s, ~r{(\r\n|\r|\n|\\n)}, trim: true) do
      Enum.each(envs, &put_env/1)
    else
      _ -> raise(raise(ExSecrets.Exceptions.InvalidConfiguration, ".env is not found"))
    end
  end

  defp get_scripted(key) do
    path = Config.provider_config_value(:dot_env, :path)

    with true <- is_binary(path),
         true <- File.exists?(path),
         {:ok, s} <- File.read(path),
         [_ | _] = envs <- String.split(s, ~r{(\r\n|\r|\n|\\n)}, trim: true) do
      Enum.find(envs, &(get_k_v(&1) |> is_value(key))) |> get_v()
    else
      _ -> nil
    end
  end

  defp put_env(s) do
    case get_k_v(s) do
      {k, v} when is_nil(k) or is_nil(v) -> nil
      {k, v} -> Cache.save(k, v)
    end
  end

  defp get_v(s) do
    {_k, v} = get_k_v(s)
    v
  end

  defp get_k_v(nil), do: {nil, nil}

  defp get_k_v(s) do
    [k | rest] = String.split(s, "=")

    v = Enum.join(rest, "=")

    {k, v}
  end

  defp is_value({k, _v}, key), do: k == key

  def process_name() do
    :ex_secrets_dot_env
  end
end