Skip to content
This repository has been archived by the owner on Oct 7, 2023. It is now read-only.

pouriya/cfg

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Simple and modern configuration management in Erlang

Features

  • Read config from different places:
~/cfg $ export FOO_KEY=VALUE
1> application:set_env(bar, key2, value2). 
ok

2> cfg:read([{shell, "FOO"}, {env, bar}]).
{
    ok,
    % Config from different places:
    [
        {shell, "FOO", [{key, <<"VALUE">>}]},
        {env, bar, [{key2,value2}]}
    ],
    % The whole config
    [
        {key2, value2, [{env, bar}]},
        {key, <<"VALUE">>, [{shell, "FOO"}]}
    ]
}
  • Override configuration from different places:
~/cfg $ echo {server, []}. > my.config
~/cfg $ export MY_SERVER__HOST=127.0.0.1
~/cfg $ export MY_SERVER__PORT=8080
1> application:set_env(my, server, [{port, 9090}]).
ok

2> cfg:read([{shell, "MY"}, {erl, "my.config"}, {env, my}]).
{
    ok,
    [
        {shell, "MY", [{server, [{host, <<"127.0.0.1">>}, {port, 8080}]}]},
        {erl, "my.config", [{server,[]}]},
        {env, my, [{server, [{port, 9090}]}]}
    ],
    [
        {
            server,
            [
                {port, 9090, [{env, my}]}, % This key comes from app env of 'my' 
                {host, <<"127.0.0.1">>, [{shell, "MY"}]} % This key comes from shell
            ],
            [{env, my}, {erl, "my.config"}, {shell, "MY"}] % 'server' key comes from three places
        }
    ]
}
  • Filter configuration parameters:
~/cfg $ export BAR_KEY=value
1> cfg:read_and_filter([{shell, "BAR"}], []). % no filter
{ok, []}

2> cfg:read_and_filter([{shell, "BAR"}], [{key, atom}]). % I want it be atom 
{ok, [{key, value}]}

3> cfg:read_and_filter([{shell, "BAR"}], [{key, binary}]). % I want it be binary
{
    error,
    {
        filter_config,
        #{
            allowed_type => binary,
            filter => {key, binary},
            filters => [{key, binary}],
            index => 1,
            key => key,
            key_filter => binary,
            readers => [{shell, "BAR"}],
            value => value
        }
    }
}

4> cfg:read_and_filter([{shell, "BAR"}], [{key, try_binary}]). % Tries to convert its value to binary
{ok, [{key, <<"value">>}]}

5> cfg:read_and_filter([{shell, "BAR"}], [{key, try_binary}, {undefined_key, try_integer}]). % 'undefined_key' does not exists in config
{
    error,
    {
        filter_config,
        #{
            filter => {undefined_key, try_integer},
            filters => [{key, try_binary}, {undefined_key, try_integer}],
            index => 2,
            key => undefined_key,
            key_filter => try_integer,
            reason => value_not_found
        }
    }
}

% Define default value as 3rd element of a filter tuple:
6> cfg:read_and_filter([{shell, "BAR"}], [{key, try_binary}, {undefined_key, try_integer, <<"1995">>}]).
{ok, [{key, <<"value">>}, {undefined_key, 1995}]}

cfg supports many ready-to-use filters and you can give your own filters to it. Also cfg can infer your wanted type from your default value!

  • Keep configuration:
1> Keeper = {ets, table_name}.
{ets,table_name}

2> cfg:init(Keeper).
ok

3> cfg:set(Keeper, [{k, v}, {k2, v2}]).
ok

4> ets:tab2list(table_name).
[{k, v}, {k2, v2}]

5> cfg:get(Keeper, k).
{ok, v}

6> cfg:get(Keeper, k3).
not_found

7> cfg:get(Keeper, k3, default_value).
{ok, default_value}

You can use above features in one function call:

1> cfg:load([{shell, "BAR"}], [{key, try_binary}, {undefined_key, try_integer, <<"1995">>}], {ets, tab_name}).
{ok, [{key, <<"value">>}, {undefined_key, 1995}]}

2> ets:tab2list(tab_name).
[{key, <<"value">>}, {undefined_key, 1995}]
  • cfg comes with a ready-to-use server process which supports reloading config on OS signals, reloading on timer and subscription to know changes in config during reload!
1> cfg:start_link(
    {local, server}, % Server name
    [{shell, "BAR"}], % Reader(s)
    [{key, try_binary}, {undefined_key, try_integer, <<"1995">>}], % Filter(s)
    {ets, tab_name}, % Keeper
    #{ % Server options
        reload_on_signal => sighup,
        signal_handler_post_reload => fun(ok) -> io:format("Config reloaded!\n") end
    }
).
{ok,<0.114.0>}

2> cfg:get({ets, tab_name}). % Get all configuration parameters
{ok, [{key, <<"value">>}, {undefined_key, 1995}]}

3> os:getpid().
"29867"

4> os:cmd("kill -1 29867"), ok
ok
Config reloaded!

5> cfg:subscribe(server, [{key, try_binary}]). % I want to know changes just for key
ok

6> os:cmd("kill -1 29867"), ok
ok
Config reloaded!

7> flush().
ok % value of 'key' did not change


8> os:putenv("BAR_KEY", "value2").            
true

9> os:cmd("kill -1 29867"), ok
ok
Config reloaded!

10> flush().
Shell got {
    config,
    {
        {value, [{key, <<"value">>}]}, % Old value 
        {value,[{key, <<"value2">>}]}  % New value
    }
}
ok

Todo

Complete documentation about readers, filters, keeper and server.

Author

pouriya.jahanbakhsh@gmail.com