-module(bucs). -include_lib("eunit/include/eunit.hrl"). -include("../include/bucs.hrl"). -export([ type/1, is_type/2, to_atom/1, to_list/1, to_string/1, to_binary/1, to_integer/1, to_float/1, to_float/2, to_term/1, to/2, as/2, module_exist/1, module_exists/1, function_exist/3, function_exists/3, apply/4, apply/3, apply/2, is_string/1, is_list_of_lists/1, is_kw_list/1, is_tuple_of/2, compare_as_list/2, compare_as_string/2, compare_as_atom/2, compare_as_integer/2, compare_as_binary/2, compare_as_float/2, pipecall/1, match/2, call/3, blank/1, present/1, default_to/2, eval/1, eval/2 ]). -type type() :: binary | list | string | atom | float | integer | pid | reference | port | tuple | map | function | boolean. % @doc % Convert Data in type Type. % @end -spec to(Type :: type(), Data :: term()) -> term(). to(atom, Data) -> to_atom(Data); to(list, Data) -> to_list(Data); to(string, Data) -> to_string(Data); to(binary, Data) -> to_binary(Data); to(integer, Data) -> to_integer(Data); to(float, Data) -> to_float(Data); to(term, Data) -> to_term(Data); to(_, Data) -> Data. % @doc % Return Data in the same type than Type. % %
% bucs:as("this is a string", <<"this is a binary">>).
% % => "this is a binary"
%
% @end
-spec as(Type :: term(), Data :: term()) -> term().
as(Type, Data) -> to(type(Type), Data).
% @doc Return the type (atom, string, float, ...) of Data
-spec type(Data :: term()) -> type().
type(Data) when is_atom(Data) -> atom;
type(Data) when is_list(Data) ->
case is_string(Data) of
true -> string;
false -> list
end;
type(Data) when is_binary(Data) -> binary;
type(Data) when is_float(Data) -> float;
type(Data) when is_integer(Data) -> integer;
type(Data) when is_pid(Data) -> pid;
type(Data) when is_reference(Data) -> reference;
type(Data) when is_port(Data) -> port;
type(Data) when is_tuple(Data) -> tuple;
type(Data) when is_map(Data) -> map;
type(Data) when is_function(Data) -> function;
type(Data) when is_boolean(Data) -> boolean;
type(_) -> undefined.
% @doc
% Return true if Data is of type Type
% @end
-spec is_type(Data :: term(), Type :: type()) -> true | false.
is_type(Data, Type) when Type == binary;
Type == list;
Type == string;
Type == atom;
Type == float;
Type == integer;
Type == pid;
Type == reference;
Type == port;
Type == tuple;
Type == map;
Type == function;
Type == boolean ->
type(Data) == Type;
is_type(Data, Type) when is_atom(Type) ->
is_type(Data, lists:map(fun bucs:to_atom/1, string:split(bucs:to_string(Type), "_or_", all)));
is_type(_Data, []) ->
false;
is_type(Data, [Type|Types]) ->
case is_type(Data, Type) of
true ->
true;
false ->
is_type(Data, Types)
end.
% @doc
% Convert the given term to atom
%
% Example:
%
% atom = bucs:to_atom(atom).
% atom = bucs:to_atom(<<"atom">>).
% atom = bucs:to_atom("atom").
%
% @end
-spec to_atom(Data :: term()) -> atom().
to_atom(X) when is_atom(X) ->
X;
to_atom(X) when is_binary(X); is_bitstring(X) ->
binary_to_atom(X, utf8);
to_atom(X) when is_list(X) ->
list_to_atom(X);
to_atom(X) ->
to_atom(to_list(X)).
% @doc
% Convert the given term to list
%
% Example:
%
% "list" = bucs:to_list(list).
% "list" = bucs:to_list("list").
% "list" = bucs:to_list(<<"list">>).
% "123" = bucs:to_list(123).
% "1.20000000000000000000e+01" = bucs:to_list(12.0).
% "true" = bucs:to_list(true).
% "false" = bucs:to_list(false).
%
% @end
-spec to_list(Data :: term()) -> list().
to_list(true) ->
"true";
to_list(false) ->
"false";
to_list(V) when is_atom(V) ->
atom_to_list(V);
to_list(V) when is_list(V) ->
V;
to_list(V) when is_integer(V) ->
integer_to_list(V);
to_list(V) when is_float(V) ->
float_to_list(V);
to_list(V) when is_binary(V); is_bitstring(V) ->
binary_to_list(V);
to_list(V) when is_tuple(V) ->
[element(I, V) || I <- lists:seq(1, tuple_size(V))];
to_list(V) when is_pid(V) ->
pid_to_list(V);
to_list(V) when is_reference(V) ->
erlang:ref_to_list(V).
% @doc
% Convert the given term to string
% @end
-spec to_string(Data :: term()) -> string().
to_string(V) when is_binary(V);
is_bitstring(V);
is_atom(V);
is_pid(V);
is_reference(V)->
lists:flatten(to_list(V));
to_string(V) ->
case is_string(V) of
true -> V;
false ->
lists:flatten(io_lib:format("~p", [V]))
end.
% @doc
% Convert the given term to binary
%
% Example:
%
% <<"list">> = bucs:to_binary(list).
% <<"list">> = bucs:to_binary("list").
% <<"list">> = bucs:to_binary(<<"list">>).
% <<"123">> = bucs:to_binary(123).
% <<"1.20000000000000000000e+01">> = bucs:to_binary(12.0).
% <<"true">> = bucs:to_binary(true).
% <<"false">> = bucs:to_binary(false).
%
% @end
-spec to_binary(Data :: term()) -> binary().
to_binary(V) when is_binary(V); is_bitstring(V) ->
V;
to_binary(V) when is_float(V) ->
iolist_to_binary(to_string(V));
to_binary(V) ->
iolist_to_binary(to_list(V)).
% @doc
% Convert the given term to integer
%
% Example
%
% 123 = bucs:to_integer(123).
% 123 = bucs:to_integer("123").
% 123 = bucs:to_integer(<<"123">>).
% 123 = bucs:to_integer('123').
% 123 = bucs:to_integer(123.456).
%
% @end
-spec to_integer(Data :: term()) -> integer().
to_integer(I) when is_integer(I) ->
I;
to_integer(I) when is_list(I) ->
try
list_to_integer(I)
catch
_:_ -> to_integer(to_float(I))
end;
to_integer(I) when is_binary(I); is_bitstring(I) ->
try
binary_to_integer(I)
catch
_:_ -> to_integer(to_float(I))
end;
to_integer(I) when is_atom(I) ->
to_integer(atom_to_list(I));
to_integer(I) when is_float(I) ->
to_integer(float_to_list(I, [{decimals, 0}])).
% @doc
% Convert the given term to float
%
% Example
%
% 123.45 = bucs:to_float(123.45).
% 123.45 = bucs:to_float("123.45").
% 123.45 = bucs:to_float(<<"123.45">>).
% 123.45 = bucs:to_float('123.45').
% 123.0 = bucs:to_float(123).
%
% @end
-spec to_float(Data :: term()) -> float().
to_float(Value) when is_integer(Value) ->
float(Value);
to_float(Value) when is_float(Value) ->
Value;
to_float(Value) when is_list(Value) ->
case string:to_float(Value) of
{error, no_float} -> float(list_to_integer(Value));
{F, _} -> F
end;
to_float(Value) when is_binary(Value) ->
to_float(binary_to_list(Value));
to_float(Value) when is_atom(Value) ->
to_float(atom_to_list(Value)).
% @doc
% Convert the given term to float, with the given precision
%
% Example
%
% 123.457 = bucs:to_float(123.45678i, 3).
% 123.457 = bucs:to_float("123.45678", 3).
% 123.457 = bucs:to_float(<<"123.45678">>, 3).
% 123.457 = bucs:to_float('123.45678', 3).
% 123.0 = bucs:to_float(123, 3).
%
% @end
-spec to_float(Data :: term(), Precision :: integer()) -> float().
to_float(Value, Precision) ->
to_float(float_to_list(to_float(Value), [{decimals, Precision}])).
% @doc
% Convert the given value to term
%
% Example
%
% bucs:to_term("{hello, 1}").
% % => {hello, 1}
%
% @end
-spec to_term(Data :: term()) -> term().
to_term(Value) ->
try
Value0 = to_string(Value),
Value1 = case lists:reverse(Value0) of
[$.|_] -> Value0;
_ -> Value0 ++ "."
end,
{ok, Tokens, _} = erl_scan:string(Value1),
case erl_parse:parse_term(Tokens) of
{ok, Term} -> {ok, Term};
{error, {_, _, Reason}} -> {error, Reason}
end
catch
_:Error ->
{error, Error}
end.
% @deprecated use module_exists/1
module_exist(Module) ->
module_exists(Module).
% @doc
% Check if the given module exist
% @end
-spec module_exists(Module :: module()) -> true | false.
module_exists(Module) ->
case is_atom(Module) of
true ->
try Module:module_info() of
_InfoList ->
true
catch
_:_ ->
false
end;
false ->
false
end.
% @deprecated use function_exists/3
function_exist(Module, Function, Arity) ->
function_exists(Module, Function, Arity).
% @doc
% Check if the given function exist
% @end
-spec function_exists(Module :: module(), Function :: atom(), Arity :: integer()) -> true | false.
function_exists(Module, Function, Arity) ->
case code:ensure_loaded(Module) of
{module, Module} ->
erlang:function_exported(Module, Function, Arity);
_ ->
false
end.
% @doc
% Execute the given function and return the result or Default if the function
% does not exists
% @end
-spec apply(Module :: module(), Function :: atom(), Args :: [term()], Default :: term()) -> term().
apply(Module, Function, Args, Default) ->
case function_exists(Module, Function, length(Args)) of
true ->
erlang:apply(Module, Function, Args);
false ->
Default
end.
% @doc
% Execute the given function and return the result or Default if the function
% does not exists
% @end
-spec apply(FunOrModule :: function() | module(), ArgsOrFunction :: [term()] | atom(), DefaultOrArgs :: term() | [term()]) -> {ok, term() | error}.
apply(Fun, Args, Default) when is_function(Fun),
is_list(Args) ->
case bucs:apply(Fun, Args) of
{ok, Result} -> Result;
error -> Default
end;
apply(Module, Function, Args) when is_atom(Module),
is_atom(Function),
is_list(Args) ->
try
{ok, erlang:apply(Module, Function, Args)}
catch
_:_ ->
error
end.
% @doc
% Execute the given function and return its result.
% @end
-spec apply(Fun :: function(), Args :: [term()]) -> {ok, term()} | error.
apply(Fun, Args) ->
try
{ok, erlang:apply(Fun, Args)}
catch
_:_ ->
error
end.
% @doc
% Check if the given value is a string
% @end
-spec is_string(Data :: term()) -> true | false.
is_string(V) when is_list(V) ->
io_lib:printable_list(V) orelse io_lib:printable_latin1_list(V) orelse io_lib:printable_unicode_list(V);
is_string(_) -> false.
% @doc
% Check if the given value is a keyword list
% @end
-spec is_kw_list(Data :: term()) -> true | false.
is_kw_list(V) when is_list(V) ->
lists:all(fun
({_, _}) -> true;
(_) -> false
end, V);
is_kw_list(_) -> false.
% @doc
% Check if the given value is a list of lists
% @end
-spec is_list_of_lists(Data :: term()) -> true | false.
is_list_of_lists(L) ->
is_list(L) andalso
lists:all(fun(E) ->
is_list(E)
end, L).
% @doc
% Return true if A match B. false otherwise.
% @end
-spec match(A :: term(), B :: term()) -> true | false.
match(A, B) ->
case A of
B -> true;
_ -> false
end.
% @doc
% Compare A and B as if they were lists
% @end
-spec compare_as_list(A :: term(), B :: term()) -> true | false.
compare_as_list(V1, V2) ->
compare_as(fun to_list/1, V1, V2).
% @doc
% Compare A and B as if they were strings
% @end
-spec compare_as_string(A :: term(), B :: term()) -> true | false.
compare_as_string(V1, V2) ->
compare_as(fun to_string/1, V1, V2).
% @doc
% Compare A and B as if they were atoms
% @end
-spec compare_as_atom(A :: term(), B :: term()) -> true | false.
compare_as_atom(V1, V2) ->
compare_as(fun to_atom/1, V1, V2).
% @doc
% Compare A and B as if they were integers
% @end
-spec compare_as_integer(A :: term(), B :: term()) -> true | false.
compare_as_integer(V1, V2) ->
compare_as(fun to_integer/1, V1, V2).
% @doc
% Compare A and B as if they were binaries
% @end
-spec compare_as_binary(A :: term(), B :: term()) -> true | false.
compare_as_binary(V1, V2) ->
compare_as(fun to_binary/1, V1, V2).
% @doc
% Compare A and B as if they were floats
% @end
-spec compare_as_float(A :: term(), B :: term()) -> true | false.
compare_as_float(V1, V2) ->
compare_as(fun to_float/1, V1, V2).
compare_as(Fun, V1, V2) ->
V11 = Fun(V1),
V21 = Fun(V2),
if
V11 < V21 -> -1;
V11 =:= V21 -> 0;
true -> 1
end.
% @doc
% Pipe fun call
%
% Example:
%
% Add = math:pow(7, 3),
% Log = math:log(Add),
% Mul = multiplication(Log, 7),
% Res = addition(Mul, 7).
%
% % With bucs:pipecall/1 :
% Res = bucs:pipecall([
% {fun math:pow/2, [7, 3]},
% fun math:log/1,
% {fun multiplication/2, [7]},
% {fun addition/2, [7]}
% ]).
%
% @end
-spec pipecall([{function(), [term()]}]) -> term().
pipecall([{Call, Args}|Rest]) ->
pipecall(Rest, erlang:apply(Call, Args));
pipecall([Call|Rest]) ->
pipecall(Rest, erlang:apply(Call, [])).
pipecall([], Out) ->
Out;
pipecall([{Call, Args}|Rest], Out) ->
pipecall(Rest, erlang:apply(Call, [Out|Args]));
pipecall([Call|Rest], Out) ->
pipecall([{Call, []}|Rest], Out).
% @doc
% Returns the result of applying Function in Module to Args. The applied
% function must be exported from Module. The arity of the function is the length of Args.
%
% Return {error, undefined_function} if the applied function is not exported.
% @end
-spec call(Module :: module(), Function :: atom(), Args :: [term()]) -> term() | {error, undefined_function}.
call(Module, Function, Args) ->
case erlang:function_exposted(Module, Function, length(Args)) of
true ->
erlang:apply(Module, Function, Args);
false ->
{error, undefined_function}
end.
% @doc
% Return true if Data is blank
% @end
-spec blank(Data :: term()) -> true | false.
blank([]) -> true;
blank({}) -> true;
blank(<<>>) -> true;
blank(M) when is_map(M) -> maps:size(M) == 0;
blank(nil) -> true;
blank(undefined) -> true;
blank(false) -> true;
blank(X) ->
try
binary:replace(bucs:to_binary(X),
[<<" ">>, <<"\r">>, <<"\n">>, <<"\t">>],
<<>>,
[global]) == <<>>
catch
_:_ -> false
end.
% @doc
% Return true if Data is not blank
% @end
-spec present(Data :: term()) -> true | false.
present(X) ->
not blank(X).
% @doc
% Return Default if Data is black, Data otherwise.
% @end
-spec default_to(Data :: term(), Default :: term()) -> term().
default_to(X, Default) ->
case blank(X) of
true ->
Default;
_ ->
X
end.
% @equiv eval(Expression, [])
-spec eval(Expression :: term()) -> {value, term(), list()} | {error, erl_scan:error_info(), erl_anno:location()} | {error, erl_parse:error_info()}.
eval(Value) ->
eval(Value, []).
% @doc
% Evaluate the given Expression with the given Environment.
%
%
% bucs:eval({toto, 1}).
% % => {value,{toto,1},[]}
%
% bucs:eval("{toto, 1}").
% % => {value,{toto,1},[]}
%
% bucs:eval(<<"bucs:eval({toto, 1})">>).
% % => {value,{value,{toto,1},[]},[]}
%
% @end
-spec eval(Expression :: term(), Environment :: list()) -> {value, term(), list()} | {error, erl_scan:error_info(), erl_anno:location()} | {error, erl_parse:error_info()}.
eval(Value, Environ) ->
Value0 = to_string(Value),
Value1 = case lists:reverse(Value0) of
[$.|_] -> Value0;
_ -> Value0 ++ "."
end,
case erl_scan:string(Value1) of
{ok, Scanned, _} ->
case erl_parse:parse_exprs(Scanned) of
{ok, Parsed} ->
erl_eval:exprs(Parsed, Environ);
Error ->
Error
end;
Error ->
Error
end.
% @doc
% Return true if Tuple is a tuple matching the tuple Pattern
%
% Pattern can contains types().
%
%
% bucs:is_tuple_of({1, "hello", world}, {integer, string, atom}).
% % => true
%
% bucs:is_tuple_of({1, "hello", world}, {integer, string, binary}).
% % => false
%
% @end
-spec is_tuple_of(Tuple :: tuple(), Pattern :: tuple()) -> true | false.
is_tuple_of(Tuple, Pattern) when is_tuple(Tuple), is_tuple(Pattern), size(Tuple) == size(Pattern) ->
check_tuple_pattern(to_list(Tuple), to_list(Pattern));
is_tuple_of(_Tuple, _Pattern) ->
false.
check_tuple_pattern([], []) ->
true;
check_tuple_pattern([Element|RestTuple], [Pattern|RestPattern]) ->
case is_type(Element, Pattern) of
true ->
check_tuple_pattern(RestTuple, RestPattern);
false ->
false
end.