%% @doc The PHP Array is a collection that could be used as a simple array %% or a hash. This module helps to create an structure to handle the %% PHP Array. %% %% An example for the use of this module: %% %%
>, Array0).
%%      Array2 = ephp_array:store(auto, <<"bye!">>, Array1).
%%      ArrayN = ephp_array:from_list([1, 2, 3, 4, 5]).
%%      ]]>
%% @end -module(ephp_array). -author('manuel@altenwald.com'). -include("ephp.hrl"). -export([new/0, size/1, find/2, find/3, store/3, erase/2, map/2, fold/3, from_list/1, to_list/1, first/1, last/1, next/1, prev/1, current/1, cursor/2, ksort/2, keys/1, values/1, pop/1, update_counter/3, from_map/1]). -include("ephp_array.hrl"). -type ephp_array() :: #ephp_array{}. -export_type([ephp_array/0]). -spec new() -> ephp_array(). %% @doc creates an empty PHP Array structure. new() -> #ephp_array{}. -spec size(ephp_array()) -> non_neg_integer(). %% @doc retrieve the size of the array. size(#ephp_array{size = Size}) -> Size. -spec find(mixed(), ephp_array()) -> {ok, mixed()} | error. %% @doc finds an element by the key passed as a param. find(Key, #ephp_array{values = Values}) -> case lists:keyfind(Key, 1, Values) of {Key, Value} -> {ok, Value}; false -> error end. -spec find(mixed(), ephp_array(), any()) -> any(). %% @doc finds an element by the passed as a param. If the value isn't found the %% default value passed as param is returned. %% @end find(Key, Array, Default) -> case find(Key, Array) of {ok, Value} -> Value; error -> Default end. -spec store(auto | non_neg_integer() | mixed(), mixed(), ephp_array()) -> ephp_array(). %% @doc stores a new element given a key and a value. If the key passed is %% `auto' the key is autogenerated based on the last numeric index %% used. %% @end store(auto, Value, #ephp_array{last_num_index = Key, values = Values} = Array) -> Array#ephp_array{last_num_index = Key + 1, values = Values ++ [{Key, Value}], size = Array#ephp_array.size + 1}; store(Key, Value, #ephp_array{last_num_index = Last, values = Values} = Array) when is_integer(Key) andalso Key >= 0 andalso Last =< Key -> Array#ephp_array{last_num_index = Key + 1, values = ordsets:add_element({Key, Value}, Values), size = Array#ephp_array.size + 1}; store(Key, Value, #ephp_array{values = Values} = Array) -> case lists:keyfind(Key, 1, Values) =/= false of true -> NewValues = lists:keyreplace(Key, 1, Values, {Key, Value}), Size = Array#ephp_array.size; false -> NewValues = Values ++ [{Key, Value}], Size = Array#ephp_array.size + 1 end, Array#ephp_array{values = NewValues, size = Size}. -spec erase(mixed(), ephp_array()) -> ephp_array(). %% @doc removes an element from the array given the index. erase(Key, #ephp_array{values = Values} = Array) -> NewValues = lists:keydelete(Key, 1, Values), Array#ephp_array{values = NewValues, size = length(NewValues)}. -spec map(function(), ephp_array()) -> ephp_array(). %% @doc performs a map action on all of the elements in the array. map(Fun, #ephp_array{values = Values} = Array) -> NewValues = lists:map(fun({K, V}) -> Fun(K, V) end, Values), Array#ephp_array{values = NewValues}. -spec fold(function(), any(), ephp_array()) -> any(). %% @doc performs a fold on all of the elements in the array given an initial %% value and changing that value in each element. %% @end fold(Fun, Initial, #ephp_array{values = Values}) -> lists:foldl(fun({K, V}, Acc) -> Fun(K, V, Acc) end, Initial, Values). -spec from_list([any()]) -> ephp_array(). %% @doc transform the list passed as param in a PHP Array. from_list(List) when is_list(List) -> lists:foldl(fun ({K, _} = E, #ephp_array{values = V, size = S} = A) when is_binary(K) -> A#ephp_array{size = S + 1, values = V ++ [E]}; ({K, _} = E, #ephp_array{values = V, size = S} = A) when is_number(K) -> LastIndex = if is_integer(K) >= 0 andalso K >= A#ephp_array.last_num_index -> K + 1; true -> A#ephp_array.last_num_index end, A#ephp_array{size = S + 1, values = V ++ [E], last_num_index = LastIndex}; (E, #ephp_array{values = V, last_num_index = K, size = S} = A) -> A#ephp_array{size = S + 1, values = V ++ [{K, E}], last_num_index = K + 1} end, #ephp_array{}, List). -spec from_map(map()) -> ephp_array(). %% @doc transform the map passed as param in a PHP Array. from_map(Map) when is_map(Map) -> from_list(maps:to_list(Map)). -spec to_list(ephp_array()) -> [{mixed(), mixed()}]. %% @doc transform a PHP Array to a property list. to_list(#ephp_array{values = Values}) -> Values. -spec first(ephp_array()) -> {ok, {mixed(), mixed()}, ephp_array()} | {error, empty}. %% @doc moves the cursor to the begin of the array and retrieves that element. first(#ephp_array{size = 0}) -> {error, empty}; first(#ephp_array{values = [Head | _]} = Array) -> {ok, Head, Array#ephp_array{cursor = 1}}. -spec last(ephp_array()) -> {ok, {mixed(), mixed()}, ephp_array()} | {error, empty}. %% @doc moves the cursor to the end of the array and retrieves that element. last(#ephp_array{size = 0}) -> {error, empty}; last(#ephp_array{size = Size, values = Values} = Array) -> {ok, lists:last(Values), Array#ephp_array{cursor = Size}}. -spec next(ephp_array()) -> {ok, {mixed(), mixed()}, ephp_array()} | {error, eof | empty | enocursor}. %% @doc moves the cursor the to next element and retrieves that element. next(#ephp_array{size = 0}) -> {error, empty}; next(#ephp_array{cursor = false}) -> {error, enocursor}; next(#ephp_array{cursor = Cursor, size = Size}) when Size =:= Cursor -> {error, eof}; next(#ephp_array{cursor = Cursor, values = Values} = Array) -> {ok, lists:nth(Cursor + 1, Values), Array#ephp_array{cursor = Cursor + 1}}. -spec prev(ephp_array()) -> {ok, {mixed(), mixed()}, ephp_array()} | {error, bof | empty | enocursor}. %% @doc moves the cursor to the previous element and retrieves that element. prev(#ephp_array{size = 0}) -> {error, empty}; prev(#ephp_array{cursor = 1}) -> {error, bof}; prev(#ephp_array{cursor = false}) -> {error, enocursor}; prev(#ephp_array{cursor = Cursor, values = Values} = Array) -> {ok, lists:nth(Cursor - 1, Values), Array#ephp_array{cursor = Cursor - 1}}. -spec current(ephp_array()) -> {ok, {mixed(), mixed()}} | {error, empty | enocursor}. %% @doc retrieves the element under the cursor. current(#ephp_array{size = 0}) -> {error, empty}; current(#ephp_array{cursor = false}) -> {error, enocursor}; current(#ephp_array{cursor = Cursor, values = Values}) -> {ok, lists:nth(Cursor, Values)}. -spec cursor(ephp_array(), pos_integer() | false) -> ephp_array(). %% @doc set the cursor for an array. cursor(#ephp_array{} = Array, Cursor) -> Array#ephp_array{cursor = Cursor}. -spec ksort(ephp_array(), Flags :: pos_integer()) -> ephp_array(). %% @doc sort an array based on incoming flags (as a second param). ksort(#ephp_array{values = Values} = Array, ?SORT_REGULAR) -> Array#ephp_array{values = lists:keysort(1, Values)}; ksort(#ephp_array{values = Values} = Array, ?SORT_NUMERIC) -> TempValues = [{ephp_data:to_number(K), {K, V}} || {K, V} <- Values], SortValues = [{K, V} || {_, {K, V}} <- lists:keysort(1, TempValues)], Array#ephp_array{values = SortValues}; ksort(#ephp_array{values = Values} = Array, ?SORT_STRING) -> TempValues = [{ephp_data:to_bin(K), {K, V}} || {K, V} <- Values], SortValues = [{K, V} || {_, {K, V}} <- lists:keysort(1, TempValues)], Array#ephp_array{values = SortValues}; ksort(#ephp_array{values = Values} = Array, Sort) when Sort =:= ?SORT_STRING bor ?SORT_FLAG_CASE -> TempValues = [{str2bin2low(K), {K, V}} || {K, V} <- Values], SortValues = [{K, V} || {_, {K, V}} <- lists:keysort(1, TempValues)], Array#ephp_array{values = SortValues}. %% FIXME: check how to do the rest... SORT_LOCALE_STRING and SORT_NATURAL. str2bin2low(Key) -> ephp_string:to_lower( ephp_data:to_bin(Key)). -spec keys(ephp_array()) -> ephp_array(). %% @doc returns an array only with the keys. keys(#ephp_array{values = Values}) -> ephp_array:from_list([K || {K, _} <- Values]). -spec values(ephp_array()) -> [mixed()]. %% @doc returns list only with the values (no keys). values(#ephp_array{values = Values}) -> [V || {_, V} <- Values]. -spec pop(ephp_array()) -> {mixed(), ephp_array()}. %% @doc returns a tuple with the value popped from the array (last element) and %% the rest of the array. pop(#ephp_array{size = 0} = Array) -> {undefined, Array}; pop(#ephp_array{values = Values, size = Size} = Array) -> {_, Value} = lists:last(Values), HeadValues = lists:droplast(Values), {Value, Array#ephp_array{values = HeadValues, size = Size - 1}}. -spec update_counter(mixed(), integer(), ephp_array()) -> ephp_array(). %% @doc creates or updates an element increasing the number passed as second %% parameter and return the modified array. update_counter(Key, Increase, Array) -> Value = ephp_data:to_int( ephp_array:find(Key, Array, 0)), ephp_array:store(Key, Value + Increase, Array).