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