Current section

Files

Jump to
ephp src ephp_array.erl
Raw

src/ephp_array.erl

%% @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:
%%
%% <pre lang="erlang"><![CDATA[
%% Array0 = ephp_array:new().
%% Array1 = ephp_array:store(auto, <<"hello world!">>, Array0).
%% Array2 = ephp_array:store(auto, <<"bye!">>, Array1).
%% ArrayN = ephp_array:from_list([1, 2, 3, 4, 5]).
%% ]]></pre>
%% @end
-module(ephp_array).
-author('manuel@altenwald.com').
-compile([warnings_as_errors]).
-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
]).
-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 elemnts 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 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 poped 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).