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').
-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).