%%%---------------------------------------------------------------------------- %%% Created: 2012-09-21 %%%---------------------------------------------------------------------------- -module(env). -moduledoc """ Environment utils Author: Serge Aleynikov Copyright: 2012 Serge Aleynikov """. -author('saleyn@gmail.com'). %% API -export([subst_env_path/1, subst_env_path/2, replace_env_vars/1, replace_env_vars/2, get_env/3, home_dir/0, normalize_path/1]). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. -deprecated([{subst_env_path,1,"use replace_env_vars/1 instead"}]). -deprecated([{subst_env_path,2,"use replace_env_vars/2 instead"}]). %%%---------------------------------------------------------------------------- %%% External API %%%---------------------------------------------------------------------------- -spec subst_env_path(list() | binary()) -> list() | binary(). subst_env_path(OsPath) -> replace_env_vars(OsPath, []). -spec subst_env_path(list() | binary(), [{atom() | string(), string()}]) -> list() | binary(). subst_env_path(OsPath, Bindings) when is_list(Bindings) -> replace_env_vars(OsPath, Bindings). %%------------------------------------------------------------------------ -doc """ Perform replacement of environment variable values in the OsPath. ``` Example: env:replace_env_vars("~/app") -> "/home/cuser/app" env:replace_env_vars("${HOME}/app") -> "/home/cuser/app" env:replace_env_vars("$USER/app") -> "cuser/app" ``` See: `os:getenv/1`. """. %%------------------------------------------------------------------------ -spec replace_env_vars(list() | binary()) -> list() | binary(). replace_env_vars(OsPath) -> replace_env_vars(OsPath, []). %%------------------------------------------------------------------------ -doc """ Perform replacement of environment variable values in the OsPath. This function also allows to provide a list of `Bindings` that override the environment (they are checked before environment variables are looked up). ``` Example: env:replace_env_vars("~/", [{"HOME", "/home/cu"}]) -> "/home/cu/" env:replace_env_vars("~/", [{home, "/home/cu"}]) -> "/home/cu/" env:replace_env_vars("$A/", [{a, "/aaa"}]) -> "/aaa/" env:replace_env_vars("${A}/",[{a, "/aaa"}]) -> "/aaa/" ``` See: `os:getenv/1`. """. %%------------------------------------------------------------------------ -spec replace_env_vars(list() | binary(), [{atom() | string(), string()}]) -> list() | binary(). replace_env_vars(OsPath, Bindings) when is_list(Bindings) -> element(2, env_subst(OsPath, Bindings)). %%----------------------------------------------------------------------------- -doc "Get application configuration". %%----------------------------------------------------------------------------- -spec get_env(atom(), atom(), any()) -> any(). get_env(App, Key, Default) -> case application:get_env(App, Key) of {ok, Val} -> Val; _ -> Default end. %%%---------------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------------- internal_var("RELEASES") -> {ok, get_rels_dir()}; internal_var("ROOTDIR") -> {ok, get_root_dir()}; internal_var("RELPATH") -> {ok, get_rel_path()}; % Releases/Version internal_var(_) -> undefined. env_var(OS, "$$", _) when OS==unix; OS==win -> "$"; env_var(OS, [$~] = Key, Bindings) when OS==unix; OS==win -> case get_var(OS, "HOME", Bindings) of {ok, Value} -> {Key, Value}; _ -> Key end; env_var(OS, [$~ | User] = Word, Bindings) when OS==unix; OS==win -> case get_var(OS, "HOME", Bindings) of {ok, Value} -> {[$~], filename:join(filename:dirname(Value), User)}; _ -> Word end; env_var(OS, [$$, ${ | Env] = Word, Bindings) when OS==unix; OS==win -> Key = string:strip(Env, right, $}), case get_var(OS, Key, Bindings) of {ok, Value} -> {Key, Value}; _ -> Word end; env_var(OS, [$$ | Key] = Word, Bindings) when OS==unix; OS==win -> case get_var(OS, Key, Bindings) of {ok, Value} -> {Key, Value}; _ -> Word end; env_var(win, "%%", _) -> "%"; env_var(win, [$% | Env] = Word, Bindings) -> Key = string:strip(Env, right, $%), case get_var(win, Key, Bindings) of {ok, Value} -> {Key, Value}; _ -> Word end; env_var(_, Other, _Bindings) -> Other. get_var(OS, Name, Bindings) -> case try_get_binding(Name, Bindings) of undefined when Name == "HOME" -> {ok, home_dir(OS)}; undefined -> case os:getenv(Name) of false -> internal_var(Name); Value -> {ok, Value} end; Value -> {ok, Value} end. try_get_binding(Name, Bindings) -> case proplists:get_value(Name, Bindings) of undefined when is_list(Name) -> try A = list_to_existing_atom(string:to_lower(Name)), try_get_binding(A, Bindings) catch _:_ -> undefined end; Value -> Value end. env_subst(Bin, Bindings) when is_binary(Bin) -> {VarList, NewText} = env_subst(binary_to_list(Bin), Bindings), {VarList, list_to_binary(NewText)}; env_subst(Text, Bindings) when is_list(Text) -> env_subst(Text, os:type(), Bindings). env_subst(Text, {unix, _}, Bindings) -> env_subst(Text, unix, "(?|(?:\\$\\$)|(?:~[^/$]*)|(?:\\${[A-Za-z][A-Za-z_0-9]*})|(?:\\$[A-Za-z][A-Za-z_0-9]*))", Bindings); env_subst(Text, {win32, _}, Bindings) -> env_subst(Text, win, "(?|(?:\\%\\%)|(?:%[A-Za-z][A-Za-z_0-9]*%)|(?:~[^/$]*)|(?:\\${[A-Za-z][A-Za-z_0-9]*})|(?:\\$[A-Za-z][A-Za-z_0-9]*))", Bindings). env_subst(Text, OsType, Pattern, Bindings) -> case re:run(Text, Pattern, [global,{capture, all}]) of {match, Matches} -> {Vars, TextList, Last} = lists:foldl(fun ([{Start, Length}], {Dict, List, Prev}) when Start >= 0 -> Pos = Start+1, Match = string:substr(Text, Pos, Length), case env_var(OsType, Match, Bindings) of {Key, Val} -> case orddict:is_key(Key, Dict) of true -> NewDict = Dict; false -> NewDict = orddict:append(Key, Val, Dict) end; Val -> NewDict = Dict end, NewList = [Val, string:substr(Text, Prev, Pos - Prev) | List], NewPrev = Pos + Length, {NewDict, NewList, NewPrev}; (_, Acc) -> Acc end, {orddict:new(), [], 1}, Matches), VarList = [{K, V} || {K, [V|_]} <- orddict:to_list(Vars)], NewText = lists:concat(lists:reverse([string:substr(Text, Last) | TextList])), {VarList, NewText}; nomatch -> {[], Text} end. get_rel_ver() -> % release and version try [T || T <- release_handler:which_releases(), element(4, T) =:= permanent] of [{Name, Vsn, _Apps, permanent} | _] -> {Name, Vsn}; _Other -> throw catch _:_ -> false end. get_root_dir() -> case os:getenv("ROOTDIR") of Str when is_list(Str) -> Str; _ -> "" end. get_rels_dir() -> case application:get_env(sasl, releases_dir) of {ok, Path} -> Path; _ -> filename:join(get_root_dir(), "releases") end. get_rel_path() -> % version {_, Ver} = get_rel_ver(), % current release dir filename:join(get_rels_dir(), Ver). home_dir() -> case os:type() of {win32,_} -> home_dir(win); {_,_} -> home_dir(linux) end. home_dir(win) -> normalize_path(os:getenv("USERPROFILE")); home_dir(_) -> os:getenv("HOME"). normalize_path(Path) -> case os:type() of {win32,_} -> F = fun Norm([$\\ | T]) -> [$/ | Norm(T)]; Norm([H | T]) -> [H | Norm(T)]; Norm([] ) -> [] end, F(Path); {_,_} -> Path end. %%%---------------------------------------------------------------------------- %%% Test Cases %%%---------------------------------------------------------------------------- -ifdef(EUNIT). run_test_() -> [ ?_assertEqual("/abc/$/efg", replace_env_vars("/abc/$$/efg")), ?_assertEqual(true, os:putenv("X", "x")), ?_assertEqual("/" ++ os:getenv("X") ++ "/dir", replace_env_vars("/$X/dir")), ?_assertEqual(os:getenv("X") ++ "/dir", replace_env_vars("${X}/dir")), ?_assertEqual(os:getenv("HOME") ++ "/dir", replace_env_vars("~/dir")), ?_assertEqual("/aaa/dir", replace_env_vars("/$X/dir", [{"X", "aaa"}])), ?_assertEqual("/aaa/dir", replace_env_vars("/$X/dir", [{x, "aaa"}])), ?_assertEqual("/xxx/dir", replace_env_vars("$HOME/dir", [{"HOME", "/xxx"}])), ?_assertEqual("/xxx/dir", replace_env_vars("~/dir", [{"HOME", "/xxx"}])), ?_assertEqual("/xxx/dir", replace_env_vars("~/dir", [{home, "/xxx"}])) ]. -endif.