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