defmodule Envar do @moduledoc """ Docs for `Envar`; Environment Variable checker/getter. Variable names are logged to improve developer experience. The _values_ of Environment Variables should _never_ be logged here. If an App needs to debug a variable, it can log it locally. """ require Logger @doc """ `get/2` gets an environment variable by name with an _optional_ second argument `default_value` which, as it's name suggests, defines the default value for the evironment variable if it is not set. ## Examples iex> System.put_env("HELLO", "world") iex> Envar.get("HELLO") "world" iex> Envar.get("FOO", "bar") "bar" """ @spec get(binary, binary | nil) :: binary | nil def get(varname, default \\ nil) do val = System.get_env(varname, default) if is_nil(val) do Logger.error("ERROR: #{varname} Environment Variable is not set") end val end @doc """ `is_set/1` binary check that an environment variable is defined by name e.g: `Envar.is_set?("HEROKU")` will return `false` if the `HEROKU` environment variable is not set. When a particular variable is set, it will return `true`. ## Examples iex> Envar.is_set?("HEROKU") false iex> System.put_env("HELLO", "world") iex> Envar.is_set?("HELLO") true """ @spec is_set?(binary) :: boolean def is_set?(varname) do case System.get_env(varname) do nil -> Logger.debug("#{varname} Environment Variable is not set") false _ -> true end end @doc """ `is_set_all/1` binary check that ***ALL*** environment variable in a `List` are defined. e.g: `Envar.is_set_all?(~w/HEROKU FLYIO/)` will return `false` if _both_ the `HEROKU` and `FLYIO` environment variables are _not_ set. When _all_ of the environment variables in the list are set, it will return `true`. It's the equivalent of writing: `Envar.is_set?("HEROKU") && Envar.is_set?("FLYIO")`. ## Examples iex> Envar.is_set_all?(["HEROKU", "AWS"]) false iex> Envar.set("HELLO", "world") iex> Envar.set("GOODBYE", "au revoir") iex> Envar.is_set_all?(["HELLO", "GOODBYE"]) true """ @spec is_set_all?(list) :: boolean def is_set_all?(list) do Enum.all?(list, fn var -> is_set?(var) end) end @doc """ `is_set_any/1` binary check that any environment variable in a `List` is defined. e.g: `Envar.is_set_any?(["HEROKU", "FLYIO"])` will return `false` if _both_ the `HEROKU` and `FLYIO` environment variables are _not_ set. When any of the environment variables in the list are set, it will return `true`. It's the equivalent of writing: `Envar.is_set?("HEROKU") || Envar.is_set?("FLYIO")`. ## Examples iex> Envar.is_set_any?(["HEROKU", "AWS"]) false iex> System.put_env("HELLO", "world") iex> Envar.is_set_any?(["HELLO", "GOODBYE"]) true """ @spec is_set_any?(list) :: boolean def is_set_any?(list) do Enum.any?(list, fn var -> is_set?(var) end) end @doc """ `set/2` set the `value` of an environment variable `varname`. Accepts two `String` parameters: `varname` and `value`. ## Examples iex> Envar.set("API_KEY", "YourSuperLongAPIKey") :ok """ @spec set(binary, binary) :: :ok def set(varname, value) do System.put_env(varname, value) end @doc """ `load/1` load a file containing a line-separated list of environment variables e.g: `.env` Set the `value` of each environment variable. ## Examples iex> Envar.load(".env") :ok """ @spec load(binary) :: :ok def load(filename) do read(filename) |> Enum.each(fn {k, v} -> set(k, v) end) :ok end @doc """ `require_env_file/1` load a file containing a line-separated list of environment variables e.g: `.env` Set the `value` of each environment variable. Log an Error if the file is not available ## Examples iex> Envar.require_env_file(".env") :ok iex> Envar.require_env_file(".env_not_there") :error """ @spec require_env_file(binary) :: :ok def require_env_file(filename) do # check if the file exists: path = Path.join(File.cwd!(), filename) case File.exists?(path) do true -> load(filename) false -> Logger.error("Required .env file does not exist at path: #{path}") :error end end @spec keys(binary) :: list def keys(filename) do read(filename) |> Map.keys() end @spec values(binary) :: list def values(filename) do read(filename) |> Map.values() end @doc """ `read/1` reads a file containing a line-separated list of environment variables e.g: `.env` Returns a Map in the form %{ KEY: value, MYVAR: value2 } ## Examples iex> Envar.read(".env") %{ "ADMIN_EMAIL" => "alex@gmail.com", "EVERYTHING" => "awesome!", "SECRET" => "master plan" } """ @spec read(binary) :: map def read(filename) do path = Path.join(File.cwd!(), filename) Logger.debug(".env file path: #{path}") data = File.read!(path) data |> String.trim() |> String.split("\n") |> Enum.reduce(%{}, fn line, acc -> line = String.trim(line) with line <- String.replace(line, ["export ", "'"], ""), [key | rest] <- String.split(line, "="), value <- Enum.join(rest, "=") do if String.length(value) > 0 do Map.put(acc, key, value) else acc end end end) end end