%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2001-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% ===================================================================== %% Support functions for property lists %% %% Copyright (C) 2000-2003 Richard Carlsson %% --------------------------------------------------------------------- %% %% @doc Support functions for property lists. %% %%
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 {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).
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.
%% %% % @type property() = atom() | tuple() -module(proplists). -export([property/1, property/2, unfold/1, compact/1, lookup/2, lookup_all/2, is_defined/2, get_value/2, get_value/3, get_all_values/2, append_values/2, get_bool/2, get_keys/1, delete/2, substitute_aliases/2, substitute_negations/2, expand/2, normalize/2, split/2]). %% --------------------------------------------------------------------- -export_type([property/0, proplist/0]). -type property() :: atom() | tuple(). -type proplist() :: [property()]. %% --------------------------------------------------------------------- %% @doc Creates a normal form (minimal) representation of a property. If %%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 occurences 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.
%%
%% Example: 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.
%%
%% Example: 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
.
%%
%% For example, the following expressions all return [fie, bar,
%% baz, fum]
:
%%
expand([{foo, [bar, baz]}],
%% [fie, foo, fum])
expand([{{foo, true}, [bar, baz]}],
%% [fie, foo, fum])
expand([{{foo, false}, [bar, baz]}],
%% [fie, {foo, false}, fum])
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).