Current section
Files
Jump to
Current section
Files
src/elvis.erl
-module(elvis).
%% Public API
-export([main/1, default_config/0]).
-elvis([{elvis_style, no_debug_call, disable}]).
-export([start/0]).
-define(APP_NAME, "elvis").
-define(DEFAULT_CONFIG_PATH, "./elvis.config").
-define(DEFAULT_REBAR_CONFIG_PATH, "./rebar.config").
-type option() ::
commands
| help
| keep_rocking
| quiet
| verbose
| version
| {code_path, [any()]}
| {config, [any()]}
| {output_format, [any()]}
| {parallel, [any()]}.
-export_type([option/0]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Public API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% @doc Used when starting the application on the shell.
-spec start() -> ok.
start() ->
{ok, _} = application:ensure_all_started(elvis),
ok.
-spec main(string()) -> ok.
main(Args) ->
%% Load the application to be able to access its information
%% (e.g. --version option)
ok =
case application:load(elvis) of
ok ->
ok;
{error, {already_loaded, elvis}} ->
ok
end,
ok =
case application:load(elvis_core) of
ok ->
ok;
{error, {already_loaded, elvis_core}} ->
ok
end,
OptSpecList = option_spec_list(),
case getopt:parse(OptSpecList, Args) of
{ok, {[], []}} ->
help();
{ok, {Options, Commands}} ->
process_options(Options, Commands);
{error, {Reason, Data}} ->
elvis_utils:error("~s ~p~n", [Reason, Data]),
help(),
elvis_utils:erlang_halt(1)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Private
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Command Line Interface
%% @private
-spec option_spec_list() -> [getopt:option_spec()].
option_spec_list() ->
Commands =
"Provide the path to the configuration file. "
"When none is provided elvis checks if there's "
"an elvis.config file.",
OutputFormat =
"It allows you to display the results in plain text. When "
"none is provided elvis displays the results in colors. "
"The options allowed are (plain | colors | parsable).",
KeepRocking =
"Won't stop rocking on first error"
" when given a list of files",
Parallel =
"Allows to analyze files concurrently. Provide max number of"
" concurrent workers, or specify \"auto\" to peek default value"
" based on the number of schedulers.",
[
{help, $h, "help", undefined, "Show this help information."},
{config, $c, "config", string, Commands},
{commands, undefined, "commands", undefined, "Show available commands."},
{output_format, undefined, "output-format", string, OutputFormat},
{parallel, $P, "parallel", string, Parallel},
{quiet, $q, "quiet", undefined, "Suppress all output."},
{verbose, $V, "verbose", undefined, "Enable verbose output."},
{version, $v, "version", undefined, "Output the current elvis version."},
{code_path, $p, "code-path", string, "Add the directory in the code path."},
{keep_rocking, $k, "keep-rocking", undefined, KeepRocking}
].
%% @private
-spec process_options([option()], [string()]) -> ok.
process_options(Options, Commands) ->
try
Config = default_config(),
AtomCommands = lists:map(fun list_to_atom/1, Commands),
process_options(Options, AtomCommands, Config)
catch
_:Reason:Stack ->
elvis_utils:error("Error processing options: ~p\nStack: ~p", [Reason, Stack]),
elvis_utils:erlang_halt(1)
end.
%% @private
-spec process_options([option()], [string()], [elvis_config:t()]) -> ok.
process_options([help | Opts], Cmds, Config) ->
help(),
process_options(Opts, Cmds, Config);
process_options([{config, Path} | Opts], Cmds, _) ->
Config = elvis_config:from_file(Path),
process_options(Opts, Cmds, Config);
process_options([commands | Opts], Cmds, Config) ->
commands(),
process_options(Opts, Cmds, Config);
process_options([{output_format, Format} | Opts], Cmds, Config) ->
ok = application:set_env(elvis_core, output_format, list_to_atom(Format)),
process_options(Opts, Cmds, Config);
process_options([keep_rocking | Opts], Cmds, Config) ->
ok = application:set_env(elvis_core, keep_rocking, true),
process_options(Opts, Cmds, Config);
process_options([quiet | Opts], Cmds, Config) ->
ok = application:set_env(elvis_core, no_output, true),
process_options(Opts, Cmds, Config);
process_options([verbose | Opts], Cmds, Config) ->
ok = application:set_env(elvis_core, verbose, true),
process_options(Opts, Cmds, Config);
process_options([version | Opts], Cmds, Config) ->
version(),
process_options(Opts, Cmds, Config);
process_options([{code_path, Path} | Opts], Cmds, Config) ->
true = code:add_path(Path),
process_options(Opts, Cmds, Config);
process_options([{parallel, Num} | Opts], Cmds, Config) ->
N =
case Num of
"auto" ->
erlang:system_info(schedulers);
_ ->
erlang:list_to_integer(Num)
end,
ok = application:set_env(elvis_core, parallel, N),
process_options(Opts, Cmds, Config);
process_options([], Cmds, Config) ->
process_commands(Cmds, Config).
%% @private
-spec process_commands(
[
rock
| help
| string()
],
[elvis_config:t()]
) ->
ok.
process_commands([rock | Files], Config) ->
case Files of
[] ->
case elvis_core:rock(Config) of
{fail, [{throw, {invalid_config, Reason}}]} ->
io:format("Invalid config: ~ts\n", [Reason]),
elvis_utils:erlang_halt(1);
{fail, [{throw, Error}]} ->
io:format("FATAL ERROR while running Elvis:\n\t~p\n", [Error]),
elvis_utils:erlang_halt(1);
{fail, _} ->
elvis_utils:erlang_halt(1);
ok ->
ok
end;
_ ->
lists:map(fun(F) -> rock_one_song(F, Config) end, Files)
end;
process_commands([help | Cmds], Config) ->
Config = help(Config),
process_commands(Cmds, Config);
process_commands([], _Config) ->
ok;
process_commands([_ | _] = Cmds, _Config) ->
error({unrecognized_or_unimplemented_command, Cmds}).
%%% Options
%% @private
-spec help() -> ok.
help() ->
OptSpecList = option_spec_list(),
getopt:usage(OptSpecList, ?APP_NAME, standard_io).
%% @private
-spec help([elvis_config:t()]) -> [elvis_config:t()].
help(Config) ->
help(),
Config.
%% @private
-spec commands() -> ok.
commands() ->
Commands =
<<
"Elvis will do the following things for you when asked nicely:\n"
"\n"
"rock [file...] Rock your socks off by running all rules to your source files.\n"
>>,
io:put_chars(Commands).
%% @private
-spec version() -> ok.
version() ->
{ok, ElvisCoreAppConfig} = application:get_all_key(elvis_core),
{ok, ElvisShellAppConfig} = application:get_all_key(elvis),
ElvisCoreVsn = proplists:get_value(vsn, ElvisCoreAppConfig),
ElvisShellVsn = proplists:get_value(vsn, ElvisShellAppConfig),
Version =
" ______ _ \n"
" / __/ / __(_)__\n"
" / _// / |/ / (_-<\n"
"/___/_/|___/_/___/\n"
"Version: ~s\n"
"Elvis Core Version: ~s\n",
io:format(Version, [ElvisShellVsn, ElvisCoreVsn]).
%% @private
rock_one_song(Filename, Config) ->
F = atom_to_list(Filename),
case elvis_core:rock_this(F, Config) of
{fail, _} ->
case application:get_env(elvis_core, keep_rocking, false) of
false ->
elvis_utils:erlang_halt(1);
true ->
ok
end;
ok ->
ok
end.
%% @private
-spec default_config() -> [elvis_config:t()].
default_config() ->
default_config([
{?DEFAULT_CONFIG_PATH, fun() -> elvis_config:from_file(?DEFAULT_CONFIG_PATH) end},
{?DEFAULT_REBAR_CONFIG_PATH, fun() ->
elvis_config:from_rebar(?DEFAULT_REBAR_CONFIG_PATH)
end}
]).
-spec default_config([{Filename, Fun}]) -> [elvis_config:t()] when
Filename :: file:name_all(),
Fun :: fun(() -> elvis_config:t()).
default_config([{Filename, Fun} | Funs]) ->
Config =
try Fun() of
{fail, Errors} ->
io:format(
standard_error,
"Failed to read config from ~ts:\n\t~p\nUsing default Elvis configuration.\n",
[
filename:absname(Filename), Errors
]
),
[];
Configs ->
Configs
catch
_:_ ->
[]
end,
case Config of
[] ->
default_config(Funs);
Config ->
Config
end;
default_config([]) ->
application:get_env(elvis, config, elvis_config:default()).