Packages

More proplists support functions.

Current section

Files

Jump to
epl src epl.erl
Raw

src/epl.erl

-module(epl).
-export([
get_path/2, get_path/3, set_path/3, set_paths/2,
is_plist/1, to_map/1, from_map/1
]).
-ifdef(EUNIT).
-compile(export_all).
-endif.
-type path() :: [atom() | pos_integer()].
-export_type([path/0]).
%% get_path(Plist, Keys) -> Value | undefined
%%
%% Given a Path into Plist0, return the value found; otherwise undefined
%% if not found.
%%
-spec get_path(Plist :: proplists:proplist(), Keys :: path()) -> any() | undefined.
get_path(Plist, Keys) ->
get_path(Plist, Keys, undefined).
%% get_path(Plist, Keys, Default) -> Value | Default
%%
%% Given a Path into Plist0, return the value found; otherwise a Default
%% value if not found.
%%
-spec get_path(Plist :: proplists:proplist(), Keys :: path(), Default :: any()) -> any().
get_path(undefined, _Keys, Default) ->
Default;
get_path(Plist, [], _Default) ->
Plist;
get_path(Plist, [Key | Keys], Default) when is_integer(Key) ->
get_path(lists:nth(Key, Plist), Keys, Default);
get_path(Plist, [Key | Keys], Default) ->
get_path(proplists:get_value(Key, Plist), Keys, Default).
%% set_path(Plist0, Path, Value) -> Plist1
%%
%% Given a Path into Plist0, insert or replace a value. If the Path
%% specified does not exist, it will be created.
%%
-spec set_path(Plist :: proplists:proplist(), Path :: path(), Value :: any()) -> proplists:proplist().
set_path(Plist, [Key], Value) when is_integer(Key) ->
list_replace(Plist, Key, Value);
set_path(Plist, [Key], Value) ->
% Last key, update its value.
[{Key, Value} | proplists:delete(Key, Plist)];
set_path(Plist, [Key | Keys], Value) when is_integer(Key) ->
list_replace(Plist, Key, set_path(lists:nth(Key, Plist), Keys, Value));
set_path(Plist, [Key | Keys], Value) ->
case proplists:get_value(Key, Plist) of
undefined ->
% Path not defined and more keys to go.
[{Key, set_path([], Keys, Value)} | Plist];
SubList ->
[{Key, set_path(SubList, Keys, Value)} | proplists:delete(Key, Plist)]
end.
-spec list_replace(List :: list(), N :: pos_integer(), New :: any()) -> list().
list_replace([_Curr | Rest], 1, New) ->
[New | Rest];
list_replace([Curr | Rest], N, New) ->
[Curr | list_replace(Rest, N-1, New)].
%% set_paths(Plist0, PathsValues) -> Plist1
%%
%% Given a list of paths and values, insert or replace one or
%% more values in Plist0. See set_path/3.
%%
-spec set_paths(Plist :: proplists:proplist(), PathsValues :: [{path(), any()}]) -> proplists:proplist().
set_paths(Plist, PathsValues) ->
lists:foldl(fun ({Path, Value}, Acc) ->
set_path(Acc, Path, Value)
end, Plist, PathsValues).
-spec is_plist_tuple(Thing :: term()) -> boolean().
is_plist_tuple(Thing) ->
is_atom(Thing) orelse (is_tuple(Thing) andalso tuple_size(Thing) == 2).
% X = [{a, 1}, {b, [{c, [{d, 2},{e,3}]}]}].
% epl:set_paths(X, [{[b,c,z],123},{[z,y,x],321}]).
% [{z,[{y,[{x,321}]}]},{b,[{c,[{z,123},{d,2},{e,3}]}]},{a,1}]
%% is_plist(Thing) -> Bool
%%
%% Return true if Thing adheres to a formal definition of a Plist
%% (suitable to convert to JSON), where each list element is either
%% an atom (short hand of `{Atom, true}`) or 2-tuple Key-Value.
%%
-spec is_plist(Thing :: term()) -> boolean().
is_plist(Thing) when is_list(Thing) ->
lists:all(fun is_plist_tuple/1, Thing);
is_plist(_Other) ->
false.
%% to_map(Plist) -> Map
%%
%% Convert the nested Plist into a nested Map.
%%
-spec to_map(Plist :: proplists:proplist()) -> map().
to_map(Plist) ->
lists:foldl(fun
({Key, Value}, Acc) when is_list(Value) ->
case is_plist(Value) of
true -> Acc#{Key => to_map(Value)};
false -> Acc#{Key => Value}
end;
({Key, Value}, Acc) ->
Acc#{Key => Value}
end, #{}, Plist).
%% from_map(Map) -> Plist
%%
%% Convert the nested Map into a nested Plist.
%%
-spec from_map(Map :: map()) -> proplists:proplist().
from_map(Map) ->
maps:fold(fun
(Key, Value, Acc) when is_map(Value) ->
[{Key, from_map(Value)} | Acc];
(Key, Value, Acc) ->
[{Key, Value} | Acc]
end, [], Map).