defmodule Jetenv do @moduledoc """ Directly map environment variables into the application configuration at runtime including type conversion and complex type bundling. # Types supported * S - String * A - Atom * I - Integer * B - boolean (true or TRUE is true all else is false) * F - Float * M - Module name * C - charlist * J - JSON encoded string * G - Erlang term * T - Base64 encoded Erlang term * PEM - PEM string, see note below Type specifiers are required suffixes, two underscores and the type, e.g.: ``` je__my_app__some_string__S="a string value" ``` ### PEM handling PEM strings are decoded using :public_key, however the usual consumers of the results (private key or cert) expect different things than what :public_key returns. The PEM handler provides values appropriate for use in `ssl_opts` `cacert` and `key`. ### Modules in keys There is a special case for representing a module name in the key string. This often occurs for configuring ecto Repos. A module will be detected by starting with `Elixir`. Underscores will be effectively translated to dots. Example: ``` je__my_app__Elixir_MyApp_Repo__database__S=mydb ``` equivalent to: ``` config :my_app, MyApp.Repo, database: "mydb" ``` """ @typedoc """ Common run options are: * prefix: String.t - the string a name starts with for discovery """ @type run_options :: keyword() @doc """ The default is `je` but can be overridden by setting the environment variable `jetenv_prefix`. This function returns the prefix in effect. If options are not provided to load functions, this will provide the default. """ @spec prefix_env() :: String.t() def prefix_env do System.get_env("jetenv_prefix", "je") end @doc """ Processes values from the environment into a nested Keyword list suitable for use in runtime.exs or as confg to `Application.put_all_env/2`. This variant will filter out non-loaded applications. Prefix can be specified to override environment. See `t:run_options/0` """ @spec load_env(run_options() | []) :: keyword() def load_env(opts \\ []) do load_all_env(opts) |> filter_loaded end @doc """ Same as `load_env/1` except does not filter out config against loaded applications. See `t:run_options/0` """ @spec load_all_env(run_options() | []) :: keyword() def load_all_env(opts \\ []) do System.get_env() |> load(opts) end @doc """ Accepts a map (such as from `System.get_env/0`) and generates a config tree. See `t:run_options/0` """ @spec load(%{String.t() => String.t()}, run_options | []) :: keyword() def load(env_map, opts \\ []) do env_map |> prefix_filter(Keyword.get(opts, :prefix, prefix_env())) |> Jetenv.Decode.decode([]) end defp filter_loaded(config) do loaded = Application.loaded_applications() |> Enum.map(&elem(&1, 0)) Enum.filter( config, fn {app, _stuff} -> app in loaded end ) end defp prefix_filter(env_map, prefix) do ps = (prefix || prefix_env()) <> "__" Enum.reduce(env_map, [], fn {k, v}, acc -> if String.starts_with?(k, ps) do [{String.replace_prefix(k, ps, ""), v} | acc] else acc end end) end end