%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @copyright 2000-2003 Richard Carlsson
%% @author Richard Carlsson Property lists are ordinary lists containing entries in the form
%% of either tuples, whose first elements are keys used for lookup and
%% insertion, or atoms, which work as shorthand for tuples Property lists are useful for representing inherited properties,
%% such as options passed to a function where a user may specify options
%% overriding the default settings, object properties, annotations,
%% etc. Example: Example: For example, the following expressions all return {Atom,
%% true}. (Other terms are allowed in the lists, but are ignored
%% by this module.) If there is more than one entry in a list for a
%% certain key, the first occurrence normally overrides any later
%% (irrespective of the arity of the tuples).PropertyIn is {Key, true} where
%% Key is an atom, this returns Key, otherwise
%% the whole term PropertyIn is returned.
%%
%% @see property/2
-spec property(PropertyIn) -> PropertyOut when
PropertyIn :: property(),
PropertyOut :: property().
property({Key, true}) when is_atom(Key) ->
Key;
property(Property) ->
Property.
%% @doc Creates a normal form (minimal) representation of a simple
%% key/value property. Returns Key if Value is
%% true and Key is an atom, otherwise a tuple
%% {Key, Value} is returned.
%%
%% @see property/1
-spec property(Key, Value) -> Property when
Key :: term(),
Value :: term(),
Property :: atom() | {term(), term()}.
property(Key, true) when is_atom(Key) ->
Key;
property(Key, Value) ->
{Key, Value}.
%% ---------------------------------------------------------------------
%% @doc Unfolds all occurrences of atoms in ListIn to tuples
%% {Atom, true}.
%%
%% @see compact/1
-spec unfold(ListIn) -> ListOut when
ListIn :: [term()],
ListOut :: [term()].
unfold([P | Ps]) ->
if is_atom(P) ->
[{P, true} | unfold(Ps)];
true ->
[P | unfold(Ps)]
end;
unfold([]) ->
[].
%% @doc Minimizes the representation of all entries in the list. This is
%% equivalent to [property(P) || P <- ListIn].
%%
%% @see unfold/1
%% @see property/1
-spec compact(ListIn) -> ListOut when
ListIn :: [property()],
ListOut :: [property()].
compact(ListIn) ->
[property(P) || P <- ListIn].
%% ---------------------------------------------------------------------
%% @doc Returns the first entry associated with Key in
%% List, if one exists, otherwise returns
%% none. For an atom A in the list, the tuple
%% {A, true} is the entry associated with A.
%%
%% @see lookup_all/2
%% @see get_value/2
%% @see get_bool/2
-spec lookup(Key, List) -> 'none' | tuple() when
Key :: term(),
List :: [term()].
lookup(Key, [P | Ps]) ->
if is_atom(P), P =:= Key ->
{Key, true};
tuple_size(P) >= 1, element(1, P) =:= Key ->
%% Note that Key does not have to be an atom in this case.
P;
true ->
lookup(Key, Ps)
end;
lookup(_Key, []) ->
none.
%% @doc Returns the list of all entries associated with Key
%% in List. If no such entry exists, the result is the
%% empty list.
%%
%% @see lookup/2
-spec lookup_all(Key, List) -> [tuple()] when
Key :: term(),
List :: [term()].
lookup_all(Key, [P | Ps]) ->
if is_atom(P), P =:= Key ->
[{Key, true} | lookup_all(Key, Ps)];
tuple_size(P) >= 1, element(1, P) =:= Key ->
[P | lookup_all(Key, Ps)];
true ->
lookup_all(Key, Ps)
end;
lookup_all(_Key, []) ->
[].
%% ---------------------------------------------------------------------
%% @doc Returns true if List contains at least
%% one entry associated with Key, otherwise
%% false is returned.
-spec is_defined(Key, List) -> boolean() when
Key :: term(),
List :: [term()].
is_defined(Key, [P | Ps]) ->
if is_atom(P), P =:= Key ->
true;
tuple_size(P) >= 1, element(1, P) =:= Key ->
true;
true ->
is_defined(Key, Ps)
end;
is_defined(_Key, []) ->
false.
%% ---------------------------------------------------------------------
%% @equiv get_value(Key, List, undefined)
-spec get_value(Key, List) -> term() when
Key :: term(),
List :: [term()].
get_value(Key, List) ->
get_value(Key, List, undefined).
%% @doc Returns the value of a simple key/value property in
%% List. If lookup(Key, List) would yield
%% {Key, Value}, this function returns the corresponding
%% Value, otherwise Default is returned.
%%
%% @see lookup/2
%% @see get_value/2
%% @see get_all_values/2
%% @see get_bool/2
-spec get_value(Key, List, Default) -> term() when
Key :: term(),
List :: [term()],
Default :: term().
get_value(Key, [P | Ps], Default) ->
if is_atom(P), P =:= Key ->
true;
tuple_size(P) >= 1, element(1, P) =:= Key ->
case P of
{_, Value} ->
Value;
_ ->
%% Dont continue the search!
Default
end;
true ->
get_value(Key, Ps, Default)
end;
get_value(_Key, [], Default) ->
Default.
%% @doc Similar to get_value/2, but returns the list of
%% values for all entries {Key, Value} in
%% List. If no such entry exists, the result is the empty
%% list.
%%
%% @see get_value/2
-spec get_all_values(Key, List) -> [term()] when
Key :: term(),
List :: [term()].
get_all_values(Key, [P | Ps]) ->
if is_atom(P), P =:= Key ->
[true | get_all_values(Key, Ps)];
tuple_size(P) >= 1, element(1, P) =:= Key ->
case P of
{_, Value} ->
[Value | get_all_values(Key, Ps)];
_ ->
get_all_values(Key, Ps)
end;
true ->
get_all_values(Key, Ps)
end;
get_all_values(_Key, []) ->
[].
%% @doc Similar to get_all_values/2, but each value is
%% wrapped in a list unless it is already itself a list, and the
%% resulting list of lists is concatenated. This is often useful for
%% "incremental" options; e.g., append_values(a, [{a, [1,2]}, {b,
%% 0}, {a, 3}, {c, -1}, {a, [4]}]) will return the list
%% [1,2,3,4].
%%
%% @see get_all_values/2
-spec append_values(Key, ListIn) -> ListOut when
Key :: term(),
ListIn :: [term()],
ListOut :: [term()].
append_values(Key, [P | Ps]) ->
if is_atom(P), P =:= Key ->
[true | append_values(Key, Ps)];
tuple_size(P) >= 1, element(1, P) =:= Key ->
case P of
{_, Value} when is_list(Value) ->
Value ++ append_values(Key, Ps);
{_, Value} ->
[Value | append_values(Key, Ps)];
_ ->
append_values(Key, Ps)
end;
true ->
append_values(Key, Ps)
end;
append_values(_Key, []) ->
[].
%% ---------------------------------------------------------------------
%% @doc Returns the value of a boolean key/value option. If
%% lookup(Key, List) would yield {Key, true},
%% this function returns true; otherwise false
%% is returned.
%%
%% @see lookup/2
%% @see get_value/2
-spec get_bool(Key, List) -> boolean() when
Key :: term(),
List :: [term()].
get_bool(Key, [P | Ps]) ->
if is_atom(P), P =:= Key ->
true;
tuple_size(P) >= 1, element(1, P) =:= Key ->
case P of
{_, true} ->
true;
_ ->
%% Don't continue the search!
false
end;
true ->
get_bool(Key, Ps)
end;
get_bool(_Key, []) ->
false.
%% ---------------------------------------------------------------------
%% @doc Returns an unordered list of the keys used in List,
%% not containing duplicates.
-spec get_keys(List) -> [term()] when
List :: [term()].
get_keys(Ps) ->
sets:to_list(get_keys(Ps, sets:new())).
get_keys([P | Ps], Keys) ->
if is_atom(P) ->
get_keys(Ps, sets:add_element(P, Keys));
tuple_size(P) >= 1 ->
get_keys(Ps, sets:add_element(element(1, P), Keys));
true ->
get_keys(Ps, Keys)
end;
get_keys([], Keys) ->
Keys.
%% ---------------------------------------------------------------------
%% @doc Deletes all entries associated with Key from
%% List.
-spec delete(Key, List) -> List when
Key :: term(),
List :: [term()].
delete(Key, [P | Ps]) ->
if is_atom(P), P =:= Key ->
delete(Key, Ps);
tuple_size(P) >= 1, element(1, P) =:= Key ->
delete(Key, Ps);
true ->
[P | delete(Key, Ps)]
end;
delete(_, []) ->
[].
%% ---------------------------------------------------------------------
%% @doc Substitutes keys of properties. For each entry in
%% ListIn, if it is associated with some key K1
%% such that {K1, K2} occurs in Aliases, the
%% key of the entry is changed to Key2. If the same
%% K1 occurs more than once in Aliases, only
%% the first occurrence is used.
%%
%% substitute_aliases([{color, colour}], L)
%% will replace all tuples {color, ...} in L
%% with {colour, ...}, and all atoms color
%% with colour.ListIn, if it is
%% associated with some key K1 such that {K1,
%% K2} occurs in Negations, then if the entry was
%% {K1, true} it will be replaced with {K2,
%% false}, otherwise it will be replaced with {K2,
%% true}, thus changing the name of the option and simultaneously
%% negating the value given by get_bool(ListIn). If the same
%% K1 occurs more than once in Negations, only
%% the first occurrence is used.
%%
%% substitute_negations([{no_foo, foo}], L)
%% will replace any atom no_foo or tuple {no_foo,
%% true} in L with {foo, false}, and
%% any other tuple {no_foo, ...} with {foo,
%% true}.{Property,
%% Expansion} in Expansions, if E is
%% the first entry in ListIn with the same key as
%% Property, and E and Property
%% have equivalent normal forms, then E is replaced with
%% the terms in Expansion, and any following entries with
%% the same key are deleted from ListIn.
%%
%% [fie, bar,
%% baz, fum]:
%%
%%
%% However, no expansion is done in the following call:
%% expand([{foo, [bar, baz]}],
%% [fie, foo, fum])expand([{{foo, true}, [bar, baz]}],
%% [fie, foo, fum])expand([{{foo, false}, [bar, baz]}],
%% [fie, {foo, false}, fum])
%%
%% because expand([{{foo, true}, [bar, baz]}],
%% [{foo, false}, fie, foo, fum]){foo, false} shadows foo.
Note that if the original property term is to be preserved in the
%% result when expanded, it must be included in the expansion list. The
%% inserted terms are not expanded recursively. If
%% Expansions contains more than one property with the same
%% key, only the first occurrance is used.
List through a sequence of
%% substitution/expansion stages. For an aliases operation,
%% the function substitute_aliases/2 is applied using the
%% given list of aliases; for a negations operation,
%% substitute_negations/2 is applied using the given
%% negation list; for an expand operation, the function
%% expand/2 is applied using the given list of expansions.
%% The final result is automatically compacted (cf.
%% compact/1).
%%
%% Typically you want to substitute negations first, then aliases, %% then perform one or more expansions (sometimes you want to pre-expand %% particular entries before doing the main expansion). You might want %% to substitute negations and/or aliases repeatedly, to allow such %% forms in the right-hand side of aliases and expansion lists.
%% %% @see substitute_aliases/2 %% @see substitute_negations/2 %% @see expand/2 %% @see compact/1 -spec normalize(ListIn, Stages) -> ListOut when ListIn :: [term()], Stages :: [Operation], Operation :: {'aliases', Aliases} | {'negations', Negations} | {'expand', Expansions}, Aliases :: [{Key, Key}], Negations :: [{Key, Key}], Expansions :: [{Property :: property(), Expansion :: [term()]}], ListOut :: [term()]. normalize(L, [{aliases, As} | Xs]) -> normalize(substitute_aliases(As, L), Xs); normalize(L, [{expand, Es} | Xs]) -> normalize(expand(Es, L), Xs); normalize(L, [{negations, Ns} | Xs]) -> normalize(substitute_negations(Ns, L), Xs); normalize(L, []) -> compact(L). %% --------------------------------------------------------------------- %% @doc PartitionsList into a list of sublists and a
%% remainder. Lists contains one sublist for each key in
%% Keys, in the corresponding order. The relative order of
%% the elements in each sublist is preserved from the original
%% List. Rest contains the elements in
%% List that are not associated with any of the given keys,
%% also with their original relative order preserved.
%%
%% Example:
%% split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])
%% returns
%% {[[a], [{b, 5}, b],[{c, 2}, {c, 3, 4}]], [{e, 1}, d]}
%%
-spec split(List, Keys) -> {Lists, Rest} when
List :: [term()],
Keys :: [term()],
Lists :: [[term()]],
Rest :: [term()].
split(List, Keys) ->
{Store, Rest} = split(List, dict:from_list([{K, []} || K <- Keys]), []),
{[lists:reverse(dict:fetch(K, Store)) || K <- Keys],
lists:reverse(Rest)}.
split([P | Ps], Store, Rest) ->
if is_atom(P) ->
case dict:is_key(P, Store) of
true ->
split(Ps, dict_prepend(P, P, Store), Rest);
false ->
split(Ps, Store, [P | Rest])
end;
tuple_size(P) >= 1 ->
%% Note that Key does not have to be an atom in this case.
Key = element(1, P),
case dict:is_key(Key, Store) of
true ->
split(Ps, dict_prepend(Key, P, Store), Rest);
false ->
split(Ps, Store, [P | Rest])
end;
true ->
split(Ps, Store, [P | Rest])
end;
split([], Store, Rest) ->
{Store, Rest}.
dict_prepend(Key, Val, Dict) ->
dict:store(Key, [Val | dict:fetch(Key, Dict)], Dict).