/home/runner/work/klsn/klsn/_build/test/cover/ct/klsn_map.html

1 -module(klsn_map).
2
3 -export([
4 lookup/2
5 , get/2
6 , get/3
7 , exists/2
8 , upsert/3
9 , remove/2
10 , filter/1
11 , invert/1
12 ]).
13
14 -export_type([
15 path/0
16 , key/0
17 ]).
18
19 %% A path into a nested map, represented as a list of keys. Example:
20 %% [foo, bar, baz] first looks up foo in the outer map, then bar in
21 %% the returned map, and finally baz.
22 -type path() :: [term()].
23
24 %% Deprecated. I should have named it path.
25 -type key() :: path().
26
27
28 %% @doc
29 %% Fetch the value at Key inside Map or raise error:not_found when
30 %% the path cannot be resolved.
31 -spec get(key(), map()) -> term().
32 get(Key, Map) ->
33
:-(
case lookup(Key, Map) of
34 {value, Value} ->
35
:-(
Value;
36 none ->
37
:-(
erlang:error(not_found, [Key, Map])
38 end.
39
40 %% @doc
41 %% Same as get/2 but returns Default instead of throwing.
42 -spec get(key(), map(), term()) -> term().
43 get(Key, Map, Default) ->
44
:-(
klsn_maybe:get_value(lookup(Key, Map), Default).
45
46 %% @doc
47 %% Variant of get/2 that never throws. Returns {value, V} when the
48 %% path exists or none otherwise.
49 -spec lookup(key(), map()) -> klsn:'maybe'(term()).
50 lookup([], Value) ->
51 1198 {value, Value};
52 lookup(_, Value) when not is_map(Value) ->
53
:-(
none;
54 lookup([H|T], Map) ->
55 34903 case maps:find(H, Map) of
56 {ok, Value} ->
57 1198 lookup(T, Value);
58 error ->
59 33705 none
60 end.
61
62 %% @doc
63 %% Return true when the path exists within the map, false otherwise.
64 %% This is a boolean convenience wrapper around lookup/2.
65 -spec exists(key(), map()) -> boolean().
66 exists(Key, Map) ->
67
:-(
lookup(Key, Map) =/= none.
68
69 %% @doc
70 %% Insert or replace the element located at *Key* with *Value* inside
71 %% *Map*. Missing intermediary maps are created on-the-fly.
72 -spec upsert(key(), term(), map()) -> map().
73 upsert(Key, Value, Map) ->
74
:-(
upsert_(Key, Value, {value, Map}, [], []).
75 upsert_([], Value, _, [], []) ->
76
:-(
Value;
77 upsert_([], Value, _, [{value, Map}|Maps], [Key|Keys]) ->
78
:-(
upsert_([], Map#{Key=>Value}, none, Maps, Keys);
79 upsert_([], Value, _, [none|Maps], [Key|Keys]) ->
80
:-(
upsert_([], #{Key=>Value}, none, Maps, Keys);
81 upsert_([H|T], Value, none, Maps, Keys) ->
82
:-(
upsert_(T, Value, none, [none|Maps], [H|Keys]);
83 upsert_([H|T], Value, {value, Map}, Maps, Keys) ->
84
:-(
Elem = lookup([H], Map),
85
:-(
upsert_(T, Value, Elem, [{value, Map}|Maps], [H|Keys]).
86
87
88 %% @doc
89 %% Remove the element at *Path* inside *Map*. Returns Map unchanged when the
90 %% path cannot be resolved. An empty path clears the map.
91 -spec remove(key(), map()) -> map().
92 remove([], _Map) ->
93
:-(
#{};
94 remove(Path, Map) ->
95
:-(
[KeyToDelete|PathToKeepRev] = lists:reverse(Path),
96
:-(
PathToKeep = lists:reverse(PathToKeepRev),
97
:-(
case lookup(PathToKeep, Map) of
98 {value, MapToDelete} when is_map(MapToDelete) ->
99
:-(
upsert(PathToKeep, maps:remove(KeyToDelete, MapToDelete), Map);
100 _ ->
101
:-(
Map
102 end.
103
104
105 %% @doc
106 %% Remove entries whose value is none and unwrap {value, V}.
107 -spec filter(maps:map(Key, klsn:'maybe'(Value))) -> maps:map(Key, Value).
108 filter(Map) ->
109
:-(
maps:filtermap(fun
110 (_, {value, Value}) ->
111
:-(
{true, Value};
112 (_, none) ->
113
:-(
false
114 end, Map).
115
116 %% @doc
117 %% Produce a new map where keys become values and vice-versa.
118 -spec invert(maps:map(Key, Value)) -> maps:map(Value, Key).
119 invert(Map) ->
120
:-(
maps:from_list(lists:map(fun({Key, Value})->
121
:-(
{Value, Key}
122 end, maps:to_list(Map))).
Line Hits Source