Skip to content

Instantly share code, notes, and snippets.

@seeekr
Forked from jmglov/config.ex
Last active August 14, 2017 14:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seeekr/9f781ee081836e081638df209ac60928 to your computer and use it in GitHub Desktop.
Save seeekr/9f781ee081836e081638df209ac60928 to your computer and use it in GitHub Desktop.
Elixir: override Mix config with environment variables
defmodule Confix do
defmacro __using__(app: app_name) do
quote do
import unquote(__MODULE__), only: :macros
@confix_app unquote(app_name)
end
end
defmacro cfg(key, type \\ :string) do
quote do
def unquote(key)() do
unquote(__MODULE__).get(@confix_app, __MODULE__, unquote(key), unquote(type))
end
end
end
def get(app_name, module, key, type \\ :string) do
case System.get_env(env_var(module, key)) do
nil -> Application.get_env(app_name, module)[key]
str -> parse_val(str, type)
end
end
def get_all(app_name) do
Application.get_all_env(app_name)
|> Enum.map(fn {module, _} -> {module_name(module), get_module(app_name, module)} end)
|> Enum.into(Map.new)
end
def get_module(app_name, module) do
Application.get_env(app_name, module)
|> Enum.map(fn {key, _} -> {key, get(app_name, module, key)} end)
|> Enum.into(Map.new)
end
defp env_var(module, key) do
String.upcase "#{stringify module}_#{stringify key}"
end
defp module_name(module) do
module
|> to_string
|> String.replace_leading("Elixir.", "")
end
defp stringify(atom) do
atom
|> module_name
|> String.replace(".", "_")
|> String.replace_trailing("?", "")
end
defp parse_val(str, type) do
fun =
case type do
:atom -> &String.to_atom/1
:boolean -> &to_boolean/1
:float -> &String.to_float/1
:integer -> &String.to_integer/1
{:list, element_type} -> &to_list(&1, element_type)
{:set, element_type} -> &to_set(&1, element_type)
:string -> &(&1)
end
fun.(str)
end
defp to_boolean(str) do
String.downcase(str) == "true"
end
defp to_list(str, element_type) do
str |> String.split(",") |> Enum.map(&parse_val(&1, element_type))
end
defp to_set(str, element_type) do
str |> to_list(element_type) |> MapSet.new
end
end
defmodule Foo do
use Config, app: :foo_app
cfg :disable_foo?, :boolean
cfg :bar_delay, :integer
cfg :baz_name
cfg :blahs, {:list, :float}
cfg :blerks, {:set, :atom}
def print_stuff do
IO.puts "Disable foo? #{disable_foo?}"
IO.puts "Bar delay: #{bar_delay}"
IO.puts "Baz name: #{baz_name}"
IO.puts "Blahs: #{inspect blahs}"
IO.puts "Blerks: #{inspect blerks}"
end
end
#####
# Example config.exs
use Mix.Config
config :foo_app, Foo,
disable_foo?: false,
bar_delay: 42,
baz_name: "Baz, James Baz",
blahs: [1.23, 4.56, 7.89],
blerks: MapSet.new([:green_eggs, :ham])
#####
# Example environment variables
# FOO_DISABLE_FOO=true
# FOO_BAR_DELAY=747
# FOO_BAZ_NAME='James T. Kirk'
# FOO_BLAHS=9.87,6.54,3.21
# FOO_BLERKS=spam,jam
@seeekr
Copy link
Author

seeekr commented Aug 14, 2017

Macro-defined functions in target module are also public now, since I find it more convenient to configure some top-level module with general stuff and then access that config from other modules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment