diff --git a/include/ec_cmd_log.hrl b/include/ec_cmd_log.hrl new file mode 100644 index 0000000..170d399 --- /dev/null +++ b/include/ec_cmd_log.hrl @@ -0,0 +1,24 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. + +-define(EC_ERROR, 0). +-define(EC_WARN, 1). +-define(EC_INFO, 2). +-define(EC_DEBUG, 3). diff --git a/src/ec_cmd_log.erl b/src/ec_cmd_log.erl new file mode 100644 index 0000000..0be4143 --- /dev/null +++ b/src/ec_cmd_log.erl @@ -0,0 +1,255 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc This provides simple output functions for command line apps. You should +%%% use this to talk to the users if you are wrting code for the system +-module(ec_cmd_log). + +-export([new/1, + new/2, + log/4, + should/2, + debug/2, + debug/3, + info/2, + info/3, + error/2, + error/3, + warn/2, + warn/3, + log_level/1, + atom_log_level/1, + format/1]). + +-include_lib("erlware_commons/include/ec_cmd_log.hrl"). + +-define(RED, 31). +-define(GREEN, 32). +-define(YELLOW, 33). +-define(BLUE, 34). +-define(MAGENTA, 35). +-define(CYAN, 36). + +-define(PREFIX, "===> "). + +-record(state_t, {mod=?MODULE :: ec_log, + log_level=0 :: int_log_level(), + caller=api :: api | command_line}). + +%%============================================================================ +%% types +%%============================================================================ +-export_type([t/0, + int_log_level/0, + atom_log_level/0, + log_level/0, + log_fun/0]). + +-type log_level() :: int_log_level() | atom_log_level(). + +-type int_log_level() :: 0..3. + +-type atom_log_level() :: error | warn | info | debug. + +-type log_fun() :: fun(() -> iolist()). + +-type color() :: 31..36. + +-opaque t() :: record(state_t). + +%%============================================================================ +%% API +%%============================================================================ +%% @doc Create a new 'log level' for the system +-spec new(log_level()) -> t(). +new(LogLevel) -> + new(LogLevel, api). + +new(LogLevel, Caller) when LogLevel >= 0, LogLevel =< 3 -> + #state_t{mod=?MODULE, log_level=LogLevel, caller=Caller}; +new(AtomLogLevel, Caller) + when AtomLogLevel =:= error; + AtomLogLevel =:= warn; + AtomLogLevel =:= info; + AtomLogLevel =:= debug -> + LogLevel = case AtomLogLevel of + error -> 0; + warn -> 1; + info -> 2; + debug -> 3 + end, + new(LogLevel, Caller). + +%% @doc log at the debug level given the current log state with a string or +%% function that returns a string +-spec debug(t(), string() | log_fun()) -> ok. +debug(LogState, Fun) + when erlang:is_function(Fun) -> + log(LogState, ?EC_DEBUG, fun() -> + colorize(LogState, ?CYAN, false, Fun()) + end); +debug(LogState, String) -> + debug(LogState, "~s~n", [String]). + +%% @doc log at the debug level given the current log state with a format string +%% and argements @see io:format/2 +-spec debug(t(), string(), [any()]) -> ok. +debug(LogState, FormatString, Args) -> + log(LogState, ?EC_DEBUG, colorize(LogState, ?CYAN, false, FormatString), Args). + +%% @doc log at the info level given the current log state with a string or +%% function that returns a string +-spec info(t(), string() | log_fun()) -> ok. +info(LogState, Fun) + when erlang:is_function(Fun) -> + log(LogState, ?EC_INFO, fun() -> + colorize(LogState, ?GREEN, false, Fun()) + end); +info(LogState, String) -> + info(LogState, "~s~n", [String]). + +%% @doc log at the info level given the current log state with a format string +%% and argements @see io:format/2 +-spec info(t(), string(), [any()]) -> ok. +info(LogState, FormatString, Args) -> + log(LogState, ?EC_INFO, colorize(LogState, ?GREEN, false, FormatString), Args). + +%% @doc log at the error level given the current log state with a string or +%% format string that returns a function +-spec error(t(), string() | log_fun()) -> ok. +error(LogState, Fun) + when erlang:is_function(Fun) -> + log(LogState, ?EC_ERROR, fun() -> + colorize(LogState, ?RED, false, Fun()) + end); +error(LogState, String) -> + error(LogState, "~s~n", [String]). + +%% @doc log at the error level given the current log state with a format string +%% and argements @see io:format/2 +-spec error(t(), string(), [any()]) -> ok. +error(LogState, FormatString, Args) -> + log(LogState, ?EC_ERROR, colorize(LogState, ?GREEN, false, FormatString), Args). + +%% @doc log at the warn level given the current log state with a string or +%% format string that returns a function +-spec warn(t(), string() | log_fun()) -> ok. +warn(LogState, Fun) + when erlang:is_function(Fun) -> + log(LogState, ?EC_WARN, fun() -> colorize(LogState, ?MAGENTA, false, Fun()) end); +warn(LogState, String) -> + warn(LogState, "~s~n", [String]). + +%% @doc log at the warn level given the current log state with a format string +%% and argements @see io:format/2 +-spec warn(t(), string(), [any()]) -> ok. +warn(LogState, FormatString, Args) -> + log(LogState, ?EC_WARN, colorize(LogState, ?MAGENTA, false, FormatString), Args). + +%% @doc Execute the fun passed in if log level is as expected. +-spec log(t(), int_log_level(), log_fun()) -> ok. +log(#state_t{mod=?MODULE, log_level=DetailLogLevel}, LogLevel, Fun) + when DetailLogLevel >= LogLevel -> + io:format("~s~n", [Fun()]); +log(_, _, _) -> + ok. + +%% @doc when the module log level is less then or equal to the log level for the +%% call then write the log info out. When its not then ignore the call. +-spec log(t(), int_log_level(), string(), [any()]) -> ok. +log(#state_t{mod=?MODULE, log_level=DetailLogLevel}, LogLevel, FormatString, Args) + when DetailLogLevel >= LogLevel, + erlang:is_list(Args) -> + io:format(FormatString, Args); +log(_, _, _, _) -> + ok. + +%% @doc return a boolean indicating if the system should log for the specified +%% levelg +-spec should(t(), int_log_level() | any()) -> boolean(). +should(#state_t{mod=?MODULE, log_level=DetailLogLevel}, LogLevel) + when DetailLogLevel >= LogLevel -> + true; +should(_, _) -> + false. + +%% @doc get the current log level as an integer +-spec log_level(t()) -> int_log_level(). +log_level(#state_t{mod=?MODULE, log_level=DetailLogLevel}) -> + DetailLogLevel. + +%% @doc get the current log level as an atom +-spec atom_log_level(t()) -> atom_log_level(). +atom_log_level(#state_t{mod=?MODULE, log_level=?EC_ERROR}) -> + error; +atom_log_level(#state_t{mod=?MODULE, log_level=?EC_WARN}) -> + warn; +atom_log_level(#state_t{mod=?MODULE, log_level=?EC_INFO}) -> + info; +atom_log_level(#state_t{mod=?MODULE, log_level=?EC_DEBUG}) -> + debug. + +-spec format(t()) -> iolist(). +format(Log) -> + [<<"(">>, + ec_cnv:to_binary(log_level(Log)), <<":">>, + ec_cnv:to_binary(atom_log_level(Log)), + <<")">>]. + +-spec colorize(t(), color(), boolean(), string()) -> string(). +colorize(#state_t{caller=command_line}, Color, false, Msg) when is_integer(Color) -> + colorize_(Color, 0, Msg); +colorize(_LogState, _Color, _Bold, Msg) -> + Msg. + +-spec colorize_(color(), integer(), string()) -> string(). +colorize_(Color, Bold, Msg) when is_integer(Color), is_integer(Bold)-> + lists:flatten(io_lib:format("\033[~B;~Bm~s~s\033[0m", [Bold, Color, ?PREFIX, Msg])). + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== + +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +should_test() -> + ErrorLogState = new(error), + ?assertMatch(true, should(ErrorLogState, ?EC_ERROR)), + ?assertMatch(true, not should(ErrorLogState, ?EC_INFO)), + ?assertMatch(true, not should(ErrorLogState, ?EC_DEBUG)), + ?assertEqual(?EC_ERROR, log_level(ErrorLogState)), + ?assertEqual(error, atom_log_level(ErrorLogState)), + + InfoLogState = new(info), + ?assertMatch(true, should(InfoLogState, ?EC_ERROR)), + ?assertMatch(true, should(InfoLogState, ?EC_INFO)), + ?assertMatch(true, not should(InfoLogState, ?EC_DEBUG)), + ?assertEqual(?EC_INFO, log_level(InfoLogState)), + ?assertEqual(info, atom_log_level(InfoLogState)), + + DebugLogState = new(debug), + ?assertMatch(true, should(DebugLogState, ?EC_ERROR)), + ?assertMatch(true, should(DebugLogState, ?EC_INFO)), + ?assertMatch(true, should(DebugLogState, ?EC_DEBUG)), + ?assertEqual(?EC_DEBUG, log_level(DebugLogState)), + ?assertEqual(debug, atom_log_level(DebugLogState)). + +-endif. diff --git a/src/ec_cnv.erl b/src/ec_cnv.erl new file mode 100644 index 0000000..a063e2a --- /dev/null +++ b/src/ec_cnv.erl @@ -0,0 +1,309 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt +%%% @copyright (C) 2012 Erlware, LLC. +%%% +-module(ec_cnv). + +%% API +-export([to_integer/1, + to_integer/2, + to_float/1, + to_float/2, + to_number/1, + to_list/1, + to_binary/1, + to_atom/1, + to_boolean/1, + is_true/1, + is_false/1]). + +-ifndef(NOTEST). +-include_lib("proper/include/proper.hrl"). +-endif. + +%%%=================================================================== +%%% API +%%%=================================================================== + +%% @doc +%% Automatic conversion of a term into integer type. The conversion +%% will round a float value if nonstrict is specified otherwise badarg +-spec to_integer(string() | binary() | integer() | float()) -> + integer(). +to_integer(X)-> + to_integer(X, nonstrict). + +-spec to_integer(string() | binary() | integer() | float(), + strict | nonstrict) -> + integer(). +to_integer(X, strict) + when erlang:is_float(X) -> + erlang:error(badarg); +to_integer(X, nonstrict) + when erlang:is_float(X) -> + erlang:round(X); +to_integer(X, S) + when erlang:is_binary(X) -> + to_integer(erlang:binary_to_list(X), S); +to_integer(X, S) + when erlang:is_list(X) -> + try erlang:list_to_integer(X) of + Result -> + Result + catch + error:badarg when S =:= nonstrict -> + erlang:round(erlang:list_to_float(X)) + end; +to_integer(X, _) + when erlang:is_integer(X) -> + X. + +%% @doc +%% Automatic conversion of a term into float type. badarg if strict +%% is defined and an integer value is passed. +-spec to_float(string() | binary() | integer() | float()) -> + float(). +to_float(X) -> + to_float(X, nonstrict). + +-spec to_float(string() | binary() | integer() | float(), + strict | nonstrict) -> + float(). +to_float(X, S) when is_binary(X) -> + to_float(erlang:binary_to_list(X), S); +to_float(X, S) + when erlang:is_list(X) -> + try erlang:list_to_float(X) of + Result -> + Result + catch + error:badarg when S =:= nonstrict -> + erlang:list_to_integer(X) * 1.0 + end; +to_float(X, strict) when + erlang:is_integer(X) -> + erlang:error(badarg); +to_float(X, nonstrict) + when erlang:is_integer(X) -> + X * 1.0; +to_float(X, _) when erlang:is_float(X) -> + X. + +%% @doc +%% Automatic conversion of a term into number type. +-spec to_number(binary() | string() | number()) -> + number(). +to_number(X) + when erlang:is_number(X) -> + X; +to_number(X) + when erlang:is_binary(X) -> + to_number(to_list(X)); +to_number(X) + when erlang:is_list(X) -> + try list_to_integer(X) of + Int -> Int + catch + error:badarg -> + list_to_float(X) + end. + +%% @doc +%% Automatic conversion of a term into Erlang list +-spec to_list(atom() | list() | binary() | integer() | float()) -> + list(). +to_list(X) + when erlang:is_float(X) -> + erlang:float_to_list(X); +to_list(X) + when erlang:is_integer(X) -> + erlang:integer_to_list(X); +to_list(X) + when erlang:is_binary(X) -> + erlang:binary_to_list(X); +to_list(X) + when erlang:is_atom(X) -> + erlang:atom_to_list(X); +to_list(X) + when erlang:is_list(X) -> + X. + +%% @doc +%% Known limitations: +%% Converting [256 | _], lists with integers > 255 +-spec to_binary(atom() | string() | binary() | integer() | float()) -> + binary(). +to_binary(X) + when erlang:is_float(X) -> + to_binary(to_list(X)); +to_binary(X) + when erlang:is_integer(X) -> + erlang:iolist_to_binary(integer_to_list(X)); +to_binary(X) + when erlang:is_atom(X) -> + erlang:list_to_binary(erlang:atom_to_list(X)); +to_binary(X) + when erlang:is_list(X) -> + erlang:iolist_to_binary(X); +to_binary(X) + when erlang:is_binary(X) -> + X. + +-spec to_boolean(binary() | string() | atom()) -> + boolean(). +to_boolean(<<"true">>) -> + true; +to_boolean("true") -> + true; +to_boolean(true) -> + true; +to_boolean(<<"false">>) -> + false; +to_boolean("false") -> + false; +to_boolean(false) -> + false. + +-spec is_true(binary() | string() | atom()) -> + boolean(). +is_true(<<"true">>) -> + true; +is_true("true") -> + true; +is_true(true) -> + true; +is_true(_) -> + false. + +-spec is_false(binary() | string() | atom()) -> + boolean(). +is_false(<<"false">>) -> + true; +is_false("false") -> + true; +is_false(false) -> + true; +is_false(_) -> + false. + +%% @doc +%% Automation conversion a term to an existing atom. badarg is +%% returned if the atom doesn't exist. the safer version, won't let +%% you leak atoms +-spec to_atom(atom() | list() | binary() | integer() | float()) -> + atom(). +to_atom(X) + when erlang:is_atom(X) -> + X; +to_atom(X) + when erlang:is_list(X) -> + erlang:list_to_existing_atom(X); +to_atom(X) -> + to_atom(to_list(X)). + +%%%=================================================================== +%%% API +%%%=================================================================== + +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +force_proper_test_() -> + {"Runs PropEr test during EUnit phase", + {timeout, 15000, [?_assertEqual([], proper:module(?MODULE))]}}. + +to_integer_test() -> + ?assertError(badarg, to_integer(1.5, strict)). + +to_float_test() -> + ?assertError(badarg, to_float(10, strict)). + +to_atom_test() -> + ?assertMatch(true, to_atom("true")), + ?assertMatch(true, to_atom(<<"true">>)), + ?assertMatch(false, to_atom(<<"false">>)), + ?assertMatch(false, to_atom(false)), + ?assertError(badarg, to_atom("hello_foo_bar_baz")), + + S = erlang:list_to_atom("1"), + ?assertMatch(S, to_atom(1)). + +to_boolean_test()-> + ?assertMatch(true, to_boolean(<<"true">>)), + ?assertMatch(true, to_boolean("true")), + ?assertMatch(true, to_boolean(true)), + ?assertMatch(false, to_boolean(<<"false">>)), + ?assertMatch(false, to_boolean("false")), + ?assertMatch(false, to_boolean(false)). + +%%% PropEr testing + +prop_to_integer() -> + ?FORALL({F, I}, {float(), integer()}, + begin + Is = [[Fun(N), N] || + Fun <- [fun to_list/1, + fun to_binary/1], + N <- [F, I]], + lists:all(fun([FN, N]) -> + erlang:is_integer(to_integer(N)) andalso + erlang:is_integer(to_integer(FN)) + end, Is) + end). + +prop_to_list() -> + ?FORALL({A, L, B, I, F}, {atom(), list(), binary(), integer(), float()}, + lists:all(fun(X) -> + erlang:is_list(to_list(X)) + end, [A, L, B, I, F])). + +prop_to_binary() -> + ?FORALL({A, L, B, I, F, IO}, {atom(), list(range(0,255)), binary(), + integer(), float(), iolist()}, + lists:all(fun(X) -> + erlang:is_binary(to_binary(X)) + end, [A, L, B, I, F, IO])). + +prop_iolist_t() -> + ?FORALL(IO, iolist(), erlang:is_binary(to_binary(IO))). + +prop_to_float() -> + ?FORALL({F, I}, {float(), integer()}, + begin + Fs = [[Fun(N), N] || + Fun <- [fun to_list/1, fun to_binary/1], + N <- [F, I]], + lists:all(fun([FN, N]) -> + erlang:is_float(to_float(N)) andalso + erlang:is_float(to_float(FN)) + end, Fs) + end). + +prop_to_number() -> + ?FORALL({F, I}, {float(), integer()}, + begin + Is = [[Fun(N), N] || + Fun <- [fun to_list/1, fun to_binary/1], + N <- [F, I] ], + lists:all(fun([FN, N]) -> + erlang:is_number(to_number(N)) andalso + erlang:is_number(to_number(FN)) + end, Is) + end). +-endif.