diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/rcl_cmd_args.erl | 125 | ||||
-rw-r--r-- | src/rcl_goal_utils.erl | 4 | ||||
-rw-r--r-- | src/rcl_log.erl | 87 | ||||
-rw-r--r-- | src/rcl_state.erl | 56 | ||||
-rw-r--r-- | src/rcl_util.erl | 9 | ||||
-rw-r--r-- | src/relcool.erl | 102 |
6 files changed, 340 insertions, 43 deletions
diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl new file mode 100644 index 0000000..454e762 --- /dev/null +++ b/src/rcl_cmd_args.erl @@ -0,0 +1,125 @@ +%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%%% 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 <[email protected]> +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc Trivial utility file to help handle common tasks +-module(rcl_cmd_args). + +-export([args2state/1]). + +%%============================================================================ +%% types +%%============================================================================ + + +%%============================================================================ +%% API +%%============================================================================ +-spec args2state({error, Reason::term()} | {[getopt:option()], [string()]}) -> + {error, Reason::term()} | + {ok, {rcl_state:t(), [string()]}}. +args2state(Error={error, _}) -> + Error; +args2state({ok, {Opts, Targets}}) -> + RelName = proplists:get_value(relname, Opts, undefined), + RelVsn = proplists:get_value(relvsn, Opts, undefined), + case create_log(Opts, + [{relname, RelName}, + {relvsn, RelVsn}]) of + Error = {error, _} -> + Error; + {ok, CommandLineConfig} -> + {ok, {rcl_state:new(CommandLineConfig), Targets}} + end. + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== + +-spec create_log([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | {error, Reason::term()}. +create_log(Opts, Acc) -> + LogLevel = proplists:get_value(log_level, Opts, 0), + if + LogLevel >= 0, LogLevel =< 2 -> + create_goals(Opts, [{log, rcl_log:new(LogLevel)} | Acc]); + true -> + {error, {invalid_log_level, LogLevel}} + end. + +-spec create_goals([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | {error, Reason::term()}. +create_goals(Opts, Acc) -> + case convert_goals(proplists:get_all_values(goals, Opts), []) of + Error={error, {failed_to_parse, _Spec}} -> + Error; + {ok, Specs} -> + create_output_dir(Opts, [{goals, Specs} | Acc]) + end. + +-spec convert_goals([string()], [depsolver:constraint()]) -> + {error,{failed_to_parse, string()}} | + {ok,[depsolver:constraint()]}. +convert_goals([], Specs) -> + %% Reverse the specs because order matters to depsolver + {ok, lists:reverse(Specs)}; +convert_goals([RawSpec | Rest], Acc) -> + case rcl_goal:parse(RawSpec) of + {ok, Spec} -> + convert_goals(Rest, [Spec | Acc]); + {fail, _} -> + {error, {failed_to_parse, RawSpec}} + end. +-spec create_output_dir([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | {error, Reason::term()}. +create_output_dir(Opts, Acc) -> + OutputDir = proplists:get_value(output_dir, Opts, "./relcool_output"), + case filelib:is_dir(OutputDir) of + false -> + case rcl_util:mkdir_p(OutputDir) of + ok -> + create_lib_dirs(Opts, [{output_dir, OutputDir} | Acc]); + {error, _} -> + {error, {unable_to_create_output_dir, OutputDir}} + end; + true -> + create_lib_dirs(Opts, [{output_dir, OutputDir} | Acc]) + end. + +-spec create_lib_dirs([getopt:option()], rcl_state:cmd_args()) -> + {ok, rcl_state:cmd_args()} | {error, Reason::term()}. +create_lib_dirs(Opts, Acc) -> + Dirs = proplists:get_all_values(lib_dir, Opts), + case check_lib_dirs(Dirs) of + Error = {error, _} -> + Error; + ok -> + {ok, [{lib_dirs, Dirs} | Acc]} + end. + +-spec check_lib_dirs([string()]) -> ok | {error, {Reason::atom(), Dir::string()}}. +check_lib_dirs([]) -> + ok; +check_lib_dirs([Dir | Rest]) -> + case filelib:is_dir(Dir) of + false -> + {error, {not_directory, Dir}}; + true -> + check_lib_dirs(Rest) + end. diff --git a/src/rcl_goal_utils.erl b/src/rcl_goal_utils.erl index 75fcda4..213a87d 100644 --- a/src/rcl_goal_utils.erl +++ b/src/rcl_goal_utils.erl @@ -3,7 +3,7 @@ %%% %%% 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 +%%% except in compliance with the License. You may obtain %%% a copy of the License at %%% %%% http://www.apache.org/licenses/LICENSE-2.0 @@ -29,7 +29,6 @@ %% types %%============================================================================ - %%============================================================================ %% API %%============================================================================ @@ -68,7 +67,6 @@ to_op(<<":btwn:">>) -> to_op(<<":between:">>) -> 'between'. - to_vsn(Version) when erlang:is_list(Version) -> to_vsn(erlang:iolist_to_binary(Version)); to_vsn(Vsn) -> diff --git a/src/rcl_log.erl b/src/rcl_log.erl index 63eee14..a5fb43a 100644 --- a/src/rcl_log.erl +++ b/src/rcl_log.erl @@ -32,11 +32,13 @@ error/2, error/3, log_level/1, - atom_log_level/1]). + atom_log_level/1, + format/1]). -export_type([int_log_level/0, log_level/0, - state/0]). + log_fun/0, + t/0]). -include_lib("relcool/include/relcool.hrl"). @@ -48,13 +50,15 @@ %% Why no warn? because for our purposes there is no difference between error %% and warn -type log_level() :: error | info | debug. --opaque state() :: {?MODULE, log_level()}. +-opaque t() :: {?MODULE, int_log_level()}. + +-type log_fun() :: fun(() -> iolist()). %%============================================================================ %% API %%============================================================================ %% @doc Create a new 'log level' for the system --spec new(int_log_level() | log_level()) -> state(). +-spec new(int_log_level() | log_level()) -> t(). new(LogLevel) when LogLevel >= 0, LogLevel =< 2 -> {?MODULE, LogLevel}; new(AtomLogLevel) @@ -68,44 +72,66 @@ new(AtomLogLevel) end, new(LogLevel). -%% @doc log at the debug level given the current log state with a string --spec debug(state(), string()) -> none(). + +%% @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, ?RCL_DEBUG, Fun); 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(state(), string(), [any()]) -> none(). +-spec debug(t(), string(), [any()]) -> ok. debug(LogState, FormatString, Args) -> log(LogState, ?RCL_DEBUG, FormatString, Args). -%% @doc log at the info level given the current log state with a string --spec info(state(), string()) -> none(). +%% @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, ?RCL_INFO, Fun); 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(state(), string(), [any()]) -> none(). +-spec info(t(), string(), [any()]) -> ok. info(LogState, FormatString, Args) -> log(LogState, ?RCL_INFO, FormatString, Args). -%% @doc log at the error level given the current log state with a string --spec error(state(), string()) -> none(). +%% @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, ?RCL_ERROR, Fun); 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(state(), string(), [any()]) -> none(). +-spec error(t(), string(), [any()]) -> ok. error(LogState, FormatString, Args) -> log(LogState, ?RCL_ERROR, FormatString, Args). +%% @doc Execute the fun passed in if log level is as expected. +-spec log(t(), int_log_level(), log_fun()) -> ok. +log({?MODULE, 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(state(), int_log_level(), string(), [any()]) -> ok. +-spec log(t(), int_log_level(), string(), [any()]) -> ok. log({?MODULE, DetailLogLevel}, LogLevel, FormatString, Args) - when DetailLogLevel =< LogLevel, + when DetailLogLevel >= LogLevel, erlang:is_list(Args) -> io:format(FormatString, Args); log(_, _, _, _) -> @@ -113,7 +139,7 @@ log(_, _, _, _) -> %% @doc return a boolean indicating if the system should log for the specified %% levelg --spec should(state(), int_log_level()) -> ok. +-spec should(t(), int_log_level() | any()) -> boolean(). should({?MODULE, DetailLogLevel}, LogLevel) when DetailLogLevel >= LogLevel -> true; @@ -121,12 +147,12 @@ should(_, _) -> false. %% @doc get the current log level as an integer --spec log_level(state()) -> int_log_level(). +-spec log_level(t()) -> int_log_level(). log_level({?MODULE, DetailLogLevel}) -> DetailLogLevel. %% @doc get the current log level as an atom --spec atom_log_level(state()) -> log_level(). +-spec atom_log_level(t()) -> log_level(). atom_log_level({?MODULE, ?RCL_ERROR}) -> error; atom_log_level({?MODULE, ?RCL_INFO}) -> @@ -134,6 +160,13 @@ atom_log_level({?MODULE, ?RCL_INFO}) -> atom_log_level({?MODULE, ?RCL_DEBUG}) -> debug. +-spec format(t()) -> iolist(). +format(Log) -> + [<<"(">>, + erlang:integer_to_list(log_level(Log)), <<":">>, + erlang:atom_to_list(atom_log_level(Log)), + <<")">>]. + %%%=================================================================== %%% Test Functions %%%=================================================================== @@ -143,23 +176,23 @@ atom_log_level({?MODULE, ?RCL_DEBUG}) -> should_test() -> ErrorLogState = new(error), - ?assert(should(ErrorLogState, ?RCL_ERROR)), - ?assert(not should(ErrorLogState, ?RCL_INFO)), - ?assert(not should(ErrorLogState, ?RCL_DEBUG)), + ?assertMatch(true, should(ErrorLogState, ?RCL_ERROR)), + ?assertMatch(true, not should(ErrorLogState, ?RCL_INFO)), + ?assertMatch(true, not should(ErrorLogState, ?RCL_DEBUG)), ?assertEqual(?RCL_ERROR, log_level(ErrorLogState)), ?assertEqual(error, atom_log_level(ErrorLogState)), InfoLogState = new(info), - ?assert(should(InfoLogState, ?RCL_ERROR)), - ?assert(should(InfoLogState, ?RCL_INFO)), - ?assert(not should(InfoLogState, ?RCL_DEBUG)), + ?assertMatch(true, should(InfoLogState, ?RCL_ERROR)), + ?assertMatch(true, should(InfoLogState, ?RCL_INFO)), + ?assertMatch(true, not should(InfoLogState, ?RCL_DEBUG)), ?assertEqual(?RCL_INFO, log_level(InfoLogState)), ?assertEqual(info, atom_log_level(InfoLogState)), DebugLogState = new(debug), - ?assert(should(DebugLogState, ?RCL_ERROR)), - ?assert(should(DebugLogState, ?RCL_INFO)), - ?assert(should(DebugLogState, ?RCL_DEBUG)), + ?assertMatch(true, should(DebugLogState, ?RCL_ERROR)), + ?assertMatch(true, should(DebugLogState, ?RCL_INFO)), + ?assertMatch(true, should(DebugLogState, ?RCL_DEBUG)), ?assertEqual(?RCL_DEBUG, log_level(DebugLogState)), ?assertEqual(debug, atom_log_level(DebugLogState)). diff --git a/src/rcl_state.erl b/src/rcl_state.erl index 7175d1f..3a61e58 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -24,30 +24,72 @@ -module(rcl_state). -export([new/1, - log/1]). + log/1, + output_dir/1, + lib_dirs/1, + goals/1, + format/1, + format/2]). --export_type([state/0]). +-export_type([t/0, + cmd_args/0]). --record(?MODULE, {log :: rcl_log:state()}). +-record(?MODULE, {log :: rcl_log:t(), + output_dir :: file:name(), + lib_dirs=[] :: [file:name()], + goals=[] :: [depsolver:constraint()]}). %%============================================================================ %% types %%============================================================================ --opaque state() :: record(?MODULE). + +-type cmd_args() :: proplists:proplist(). +-opaque t() :: record(?MODULE). %%============================================================================ %% API %%============================================================================ %% @doc Create a new 'log level' for the system --spec new(proplists:proplist()) -> state(). +-spec new(proplists:proplist()) -> t(). new(PropList) when erlang:is_list(PropList) -> - #?MODULE{log = proplists:get_value(log, PropList, rcl_log:new(error))}. + #?MODULE{log = proplists:get_value(log, PropList, rcl_log:new(error)), + output_dir=proplists:get_value(output_dir, PropList, ""), + lib_dirs=proplists:get_value(lib_dirs, PropList, []), + goals=proplists:get_value(goals, PropList, [])}. %% @doc get the current log state for the system --spec log(state()) -> rc_log:state(). +-spec log(t()) -> rcl_log:t(). log(#?MODULE{log=LogState}) -> LogState. +-spec output_dir(t()) -> file:name(). +output_dir(#?MODULE{output_dir=OutDir}) -> + OutDir. + +-spec lib_dirs(t()) -> [file:name()]. +lib_dirs(#?MODULE{lib_dirs=LibDir}) -> + LibDir. + +-spec goals(t()) -> [depsolver:constraints()]. +goals(#?MODULE{goals=TS}) -> + TS. + +-spec format(t()) -> iolist(). +format(Mod) -> + format(Mod, 0). + +-spec format(t(), non_neg_integer()) -> iolist(). +format(#?MODULE{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, goals=Goals}, + Indent) -> + [rcl_util:indent(Indent), + <<"state:\n">>, + rcl_util:indent(Indent + 1), <<"log: ">>, rcl_log:format(LogState), <<",\n">>, + rcl_util:indent(Indent + 1), "goals: \n", + [[rcl_util:indent(Indent + 2), depsolver:format_constraint(Goal), ",\n"] || Goal <- Goals], + rcl_util:indent(Indent + 1), "output_dir: ", OutDir, "\n", + rcl_util:indent(Indent + 1), "lib_dirs: \n", + [[rcl_util:indent(Indent + 2), LibDir, ",\n"] || LibDir <- LibDirs]]. + %%%=================================================================== %%% Test Functions %%%=================================================================== diff --git a/src/rcl_util.erl b/src/rcl_util.erl index c0b5c5d..be20302 100644 --- a/src/rcl_util.erl +++ b/src/rcl_util.erl @@ -21,8 +21,10 @@ %%% @doc Trivial utility file to help handle common tasks -module(rcl_util). --export([mkdir_p/1]). +-export([mkdir_p/1, + indent/1]). +-define(ONE_LEVEL_INDENT, " "). %%============================================================================ %% types %%============================================================================ @@ -41,6 +43,11 @@ mkdir_p(Path) -> DirName = filename:join([filename:absname(Path), "tmp"]), filelib:ensure_dir(DirName). +%% @doc ident to the level specified +-spec indent(non_neg_integer()) -> iolist(). +indent(Amount) when erlang:is_integer(Amount) -> + [?ONE_LEVEL_INDENT || _ <- lists:seq(1, Amount)]. + %%%=================================================================== %%% Test Functions %%%=================================================================== diff --git a/src/relcool.erl b/src/relcool.erl index 7702207..f8a9bbd 100644 --- a/src/relcool.erl +++ b/src/relcool.erl @@ -1,12 +1,104 @@ -%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- +%%% 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 <[email protected]> +%%% @copyright (C) 2012 Erlware, LLC. %%% @doc -%%% @copyright (C) 2007-2012 Erlware, Inc. -%%%--------------------------------------------------------------------------- -module(relcool). --export([main/1]). +-export([main/1, opt_spec_list/0]). + +%%============================================================================ +%% types +%%============================================================================ + +%%============================================================================ +%% API +%%============================================================================ +-spec main([string()]) -> ok. +main(Args) -> + io:format("~p~n", [Args]), + OptSpecList = opt_spec_list(), + case rcl_cmd_args:args2state(getopt:parse(OptSpecList, Args)) of + {ok, {State, _Target}} -> + run_relcool_process(State); + Error={error, _} -> + report_error(Error) + end. + +-spec opt_spec_list() -> [getopt:option_spec()]. +opt_spec_list() -> + [ + {relname, $n, "relname", string, "Specify the name for the release that will be generated"}, + {relvsn, $v, "relvsn", string, "Specify the version for the release"}, + {goals, $g, "goal", string, "Specify a target constraint on the system. These are " + "usually the OTP"}, + {output_dir, $o, "output-dir", string, "The output directory for the release. This is `./` by default."}, + {lib_dir, $l, "lib-dir", string, "Additional dirs that should be searched for OTP Apps"}, + {log_level, $V, "verbose", {integer, 2}, "Verbosity level, maybe between 0 and 2"} + ]. -main(_Args) -> +%%============================================================================ +%% internal api +%%============================================================================ +run_relcool_process(State) -> + rcl_log:info(rcl_state:log(State), "Starting relcool build process ..."), + rcl_log:debug(rcl_state:log(State), + fun() -> + rcl_state:format(State) + end), ok. + +-spec usage() -> ok. +usage() -> + getopt:usage(opt_spec_list(), "relcool", "[*release-specification-file*]"). + + +-spec report_error(term()) -> none(). +report_error(Error) -> + io:format("~s~n", [to_error(Error)]), + usage(), + erlang:halt(127). + +-spec to_error({error, Reason::term()}) -> string(). +to_error({error,{invalid_option_arg, Arg}}) -> + case Arg of + {goals, Goal} -> + io_lib:format("Invalid Goal argument -g ~p~n", [Goal]); + {relname, RelName} -> + io_lib:format("Invalid Release Name argument -n ~p~n", [RelName]); + {relvsn, RelVsn} -> + io_lib:format("Invalid Release Version argument -n ~p~n", [RelVsn]); + {output_dir, Outdir} -> + io_lib:format("Invalid Output Directory argument -n ~p~n", [Outdir]); + {lib_dir, LibDir} -> + io_lib:format("Invalid Library Directory argument -n ~p~n", [LibDir]); + {log_level, LogLevel} -> + io_lib:format("Invalid Library Directory argument -n ~p~n", [LogLevel]) + end; +to_error({error, {failed_to_parse, Spec}}) -> + io_lib:format("Unable to parse spec ~s", [Spec]); +to_error({error, {unable_to_create_output_dir, OutputDir}}) -> + io_lib:format("Unable to create output directory (possible permissions issue): ~s", + [OutputDir]); +to_error({error, {not_directory, Dir}}) -> + io_lib:format("Library directory does not exist: ~s", [Dir]); +to_error({error, {invalid_log_level, LogLevel}}) -> + io_lib:format("Invalid log level specified -V ~p, log level must be in the" + " range 0..2", [LogLevel]); +to_error({error, Error}) -> + io_lib:format("~p~n", [Error]). |