src/gourd.erl

-module(gourd).

-export([load/0, load/1, load/2]).

-type maybe_binary() :: nomatch | binary().

-spec load() -> ok | {error, any()}.
load() ->
    case application:get_application() of
        {ok, App} ->
            load(App);
        undefined ->
            {error, application_undefined}
    end.

-spec load(atom()) -> ok | {error, any()}.
load(App) ->
    DefaultPath =
        filename:join(
            code:priv_dir(App), <<"gourd.toml">>),
    load(App, DefaultPath).

-spec load(atom(), binary()) -> ok | {error, any()}.
load(AppName, Path) ->
    case tomerl:read_file(Path) of
        {error, _Err} = E ->
            E;
        {ok, Data} ->
            maps:foreach(fun(K, V) ->
                            application:set_env(AppName, binary_to_atom(K), replace_env_variable(V))
                         end,
                         Data),
            ok
    end.

-spec replace_env_variable(binary() | list() | map() | integer() | float()) ->
                              binary() | list() | map() | integer() | float().
replace_env_variable(S) when is_binary(S) ->
    case env_variable(S) of
        nomatch ->
            S;
        S2 ->
            S2
    end;
replace_env_variable(L) when is_list(L) ->
    lists:map(fun replace_env_variable/1, L);
replace_env_variable(M) when is_map(M) ->
    maps:map(fun replace_env_variable/2, M);
replace_env_variable(V) ->
    V.

-spec replace_env_variable(any(), any()) -> any().
replace_env_variable(_K, V) ->
    replace_env_variable(V).

-spec env_variable(binary()) -> maybe_binary().
env_variable(S) ->
    case string:split(S, "${", all) of
        [_] ->
            nomatch;
        L ->
            list_to_binary(lists:map(fun handle_env_var/1, L))
    end.

-spec handle_env_var(binary()) -> list() | binary().
handle_env_var(S) ->
    case string:split(S, "}") of
        [_] ->
            S;
        [ReplaceMe, KeepMe] ->
            case gourd_util:os_getenv(binary_to_list(ReplaceMe)) of
                false ->
                    S;
                ReplaceText ->
                    [ReplaceText, KeepMe]
            end
    end.

-ifdef(TEST).

-include_lib("eunit/include/eunit.hrl").

replace_env_variable_test() ->
    meck:new(gourd_util),
    meck:expect(gourd_util, os_getenv, fun(X) -> X end),
    ?assertEqual(<<"foobar">>, replace_env_variable(<<"${foobar}">>)),
    ?assertEqual(<<"barfoo">>, replace_env_variable(<<"barfoo">>)),
    ?assertEqual([<<"foobar">>, <<"stuff">>, <<"barfoo">>],
                 replace_env_variable([<<"${foobar}">>, <<"stuff">>, <<"${barfoo}">>])),

    ?assertEqual(#{<<"hello">> => <<"world">>},
                 replace_env_variable(#{<<"hello">> => <<"${world}">>})),

    ?assertEqual(<<"this is something and not something else">>,
                 replace_env_variable(<<"this is ${something} and not ${something} else">>)).

-endif.