Packages

Nested data structures library

Current section

Files

Jump to
subtree src subtree.erl
Raw

src/subtree.erl

-module(subtree).
% API
-export([find/2]).
-export([get/2]).
-export([get/3]).
-export([set/3]).
-export([del/2]).
-export([deep_merge/1]).
-export([deep_merge/2]).
%--- API -----------------------------------------------------------------------
find(Path, Struct) when is_list(Path) ->
search(Struct, Path, fun() -> undefined end);
find(Key, Struct) ->
find([Key], Struct).
get(Path, Struct) when is_list(Path) ->
search(Struct, Path, fun() -> error({key_not_found, Path}) end);
get(Key, Struct) ->
get([Key], Struct).
get(Path, Struct, Default) when is_list(Path) ->
search(Struct, Path, fun() -> Default end);
get(Key, Struct, Default) ->
get([Key], Struct, Default).
set(Path, Value, Struct) when is_list(Path) ->
recursive(Struct, Path, {set, Value});
set(Key, Value, Struct) ->
set([Key], Value, Struct).
del(Path, Struct) when is_list(Path) ->
recursive(Struct, Path, delete);
del(Key, Struct) ->
del([Key], Struct).
deep_merge([Map|Maps]) -> deep_merge(Map, Maps).
deep_merge(Target, []) ->
Target;
deep_merge(Target, [From|Maps]) ->
deep_merge(deep_merge(Target, From), Maps);
deep_merge(Target, Map) when is_map(Map) ->
% Key collisions prefer maps over normal values. If a map is to be written
% to a key, a existing normal value is overwritten and an existing map is
% merged.
maps:fold(
fun(K, V, T) ->
case maps:find(K, T) of
{ok, Value} when is_map(Value), is_map(V) ->
maps:put(K, deep_merge(Value, V), T);
{ok, Value} when is_map(Value) ->
T;
{ok, _Value} ->
maps:put(K, V, T);
error ->
maps:put(K, V, T)
end
end,
Target,
Map
).
%--- Internal ------------------------------------------------------------------
search(Struct, [], _Default) ->
Struct;
search([Item|Struct], [{Field, Key}|Path] = All, Default) when is_map(Item) ->
case maps:find(Field, Item) of
{ok, Key} -> search(Item, Path, Default);
{ok, _} -> search(Struct, All, Default);
error -> search(Struct, All, Default)
end;
search(Struct, [Item|Path], Default) when is_map(Struct) ->
case maps:find(Item, Struct) of
{ok, Value} -> search(Value, Path, Default);
error -> Default()
end;
search({Struct}, Path, Default) ->
search(Struct, Path, Default);
search([{Fields} = Item|Struct], [{Field, Key}|Path], Default) ->
case proplists:get_value(Field, Fields) of
Key -> search(Item, Path, Default);
_Else -> search(Struct, [{Field, Key}|Path], Default)
end;
search(Struct, [Key|Path], Default) when is_list(Struct) ->
case lists:keyfind(Key, 1, Struct) of
false -> Default();
{Key, Other} -> search(Other, Path, Default)
end;
search(_Struct, _Path, Default) ->
Default().
recursive(Struct, Path, Action) ->
try
rec(Struct, Path, Action)
catch
throw:not_found ->
error({key_not_found, Path});
throw:exists ->
error({incompatible_path, Path})
end.
rec(Struct, [Key], Action) when is_map(Struct) ->
case maps:is_key(Key, Struct) of
true ->
case Action of
delete -> maps:remove(Key, Struct);
{set, Value} -> maps:update(Key, Value, Struct)
end;
false ->
case Action of
delete -> throw(not_found);
{set, Value} -> maps:put(Key, Value, Struct)
end
end;
rec(Struct, [Key|Path], Action) when is_map(Struct) ->
case maps:find(Key, Struct) of
{ok, Value} ->
maps:update(Key, rec(Value, Path, Action), Struct);
error ->
case Action of
delete -> throw(not_found);
{set, _Value} -> maps:put(Key, rec(#{}, Path, Action), Struct)
end
end;
rec({Struct}, Path, Action) ->
{rec(Struct, Path, Action)};
rec([{Item}|Struct], [{Field, Key}] = Path, Action) ->
case lists:keyfind(Field, 1, Item) of
{Field, Key} ->
case Action of
delete -> Struct;
{set, Value} -> [Value|Struct]
end;
_ ->
[{Item}|rec(Struct, Path, Action)]
end;
rec([{Item}|Struct], [{Field, Key}|Rest] = Path, Action) ->
case lists:keyfind(Field, 1, Item) of
{Field, Key} -> [{rec(Item, Rest, Action)}|Struct];
_ -> [{Item}|rec(Struct, Path, Action)]
end;
rec([{Key, _Data}|Struct], [Key], Action) ->
case Action of
delete -> Struct;
{set, Value} -> [{Key, Value}|Struct]
end;
rec([{Key, Data}|Struct], [Key|Path], Action) ->
[{Key, rec(Data, Path, Action)}|Struct];
rec([Item|Struct], [{Field, Key}|Rest] = Path, Action) when is_map(Item) ->
case {maps:find(Field, Item), Rest, Action} of
{{ok, Key}, [], delete} -> Struct;
{{ok, Key}, [], {set, Value}} -> [Value|Struct];
{{ok, Key}, _, _} -> [rec(Item, Rest, Action)|Struct];
{error, _, _} -> [Item|rec(Struct, Path, Action)]
end;
rec([Item|Struct], Path, Action) ->
[Item|rec(Struct, Path, Action)];
rec([], Path, Action) ->
case {Path, Action} of
{_, delete} -> throw(not_found);
{[Key|Rest], {set, _Value}} -> [{Key, rec([], Rest, Action)}];
{[], {set, Value}} -> Value
end;
rec(_Struct, _Path, _Action) ->
throw(exists).