Skip to content

Commit

Permalink
Merge pull request #31 from inaka/jfacorro.3.cli.tool
Browse files Browse the repository at this point in the history
[#3] Implemented a command line interface for Elvis.
  • Loading branch information
elbrujohalcon committed Jun 27, 2014
2 parents 390375a + 04b43ff commit 3a40696
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 13 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ ebin
*.plt
erl_crash.dump
logs

# Ignore elvis escript
elvis
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
PROJECT = elvis

DEPS = lager sync
DEPS = lager sync getopt

dep_lager = https://github.com/basho/lager.git master
dep_lager = https://github.com/basho/lager.git 2.0.3
dep_sync = https://github.com/rustyio/sync.git master
dep_getopt = https://github.com/jcomellas/getopt v0.8.2

include erlang.mk

Expand All @@ -17,3 +18,8 @@ ERLC_OPTS += +warn_export_vars +warn_exported_vars +warn_missing_spec +warn_unty
TEST_ERLC_OPTS += +'{parse_transform, lager_transform}'
CT_SUITES = elvis
CT_OPTS = -cover test/elvis.coverspec -erl_args -config config/test

# Builds the elvis escript.
escript: all
rebar escriptize
./elvis help
9 changes: 9 additions & 0 deletions config/elvis-test.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{src_dirs, ["../../src"]},
{rules,
[
{elvis_style, line_length, [80]},
{elvis_style, no_tabs, []}
]
}
].
9 changes: 9 additions & 0 deletions config/elvis.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{src_dirs, ["src", "test"]},
{rules,
[
{elvis_style, line_length, [80]},
{elvis_style, no_tabs, []}
]
}
].
5 changes: 4 additions & 1 deletion rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
{deps_dir, "deps"}.
{deps,
[
{lager, "2.*", {git, "git@github.com:basho/lager.git", "2.0.0"}}
{lager, "2.*", {git, "git@github.com:basho/lager.git", "2.0.0"}},
{getopt, "0.*", {git, "git@github.com:jcomellas/getopt.git", "v0.8.2"}}
]
}.
{escript_name, "elvis"}.
{escript_incl_apps, [getopt]}.
114 changes: 111 additions & 3 deletions src/elvis.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,43 @@

%% Public API

-export([
main/1
]).

-export([
rock/0,
rock/1
]).

-define(APP_NAME, "elvis").

-export_type([
config/0
]).

-type config() :: [{atom(), term()}].

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Public API
%%% Public API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec main([string()]) -> ok.
main(Args) ->
OptSpecList = option_spec_list(),
case getopt:parse(OptSpecList, Args) of
{ok, {[], []}} ->
help();
{ok, {Options, Commands}} ->
process_options(Options, Commands);
{error, {Reason, Data}} ->
io:format("Error: ~s ~p~n~n", [Reason, Data]),
help()
end.

-spec rock() -> ok.
rock() ->
Config = application:get_all_env(elvis),
Config = default_config(),
rock(Config).

-spec rock(config()) -> ok.
Expand All @@ -32,9 +51,11 @@ rock(Config) ->
end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Private
%%% Private
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%% Rocking it hard

-spec run(config()) -> ok.
run(Config) ->
SrcDirs = elvis_utils:source_dirs(Config),
Expand All @@ -54,3 +75,90 @@ apply_rule({Module, Function, Args}, {Result, Config, FilePath}) ->
Results = Module:Function(Config, FilePath, Args),
RuleResult = elvis_result:new(rule, Function, Results),
{[RuleResult | Result], Config, FilePath}.


%%% Command Line Interface

-spec option_spec_list() -> [getopt:option_spec()].
option_spec_list() ->
Commands = "Provide the path to the configuration file.",
[
{help, $h, "help", undefined, "Show this help information."},
{config, $c, "config", string, Commands},
{commands, undefined, "commands", undefined, "Show available commands."}
].

-spec process_options([atom()], [string()]) -> ok.
process_options(Options, Commands) ->
try
Config = default_config(),
process_options(Options, Commands, Config)
catch
throw:Exception ->
io:format("Error: ~p.~n", [Exception])
end.

-spec process_options([atom()], [string()], config()) -> ok.
process_options([help | Opts], Cmds, Config) ->
help(),
process_options(Opts, Cmds, Config);
process_options([{config, Path} | Opts], Cmds, _) ->
Config = config(Path),
process_options(Opts, Cmds, Config);
process_options([commands | Opts], Cmds, Config) ->
commands(),
process_options(Opts, Cmds, Config);
process_options([], Cmds, Config) ->
process_commands(Cmds, Config).

-spec process_commands([string()], config()) -> ok.
process_commands(["rock" | Cmds], Config) ->
rock(Config),
process_commands(Cmds, Config);
process_commands(["help" | Cmds], Config) ->
Config = help(Config),
process_commands(Cmds, Config);
process_commands([], _Config) ->
ok;
process_commands([_Cmd | _Cmds], _Config) ->
throw(unrecognized_or_unimplemened_command).

-spec default_config() -> config().
default_config() ->
case file:consult("./elvis.config") of
{ok, [Config]} ->
Config;
{error, enoent} ->
application:get_all_env(elvis);
{error, Reason} ->
throw(Reason)
end.

%%% Options

-spec help() -> ok.
help() ->
OptSpecList = option_spec_list(),
getopt:usage(OptSpecList, ?APP_NAME, standard_io).

-spec help(config()) -> config().
help(Config) ->
help(),
Config.

-spec config(string()) -> config().
config(Path) ->
case file:consult(Path) of
{ok, [Config]} ->
Config;
{error, Reason} ->
throw(Reason)
end.

-spec commands() -> ok.
commands() ->
Commands = <<"Elvis will do the following things for you when asked nicely:
rock Rock your socks off by running all rules to your source files.
">>,
io:put_chars(Commands).
136 changes: 129 additions & 7 deletions test/elvis_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
check_configuration/1,
find_file_and_check_src/1,
verify_line_length_rule/1,
verify_no_tabs_rule/1
verify_no_tabs_rule/1,
main_help/1,
main_commands/1,
main_config/1,
main_rock/1,
main_default_config/1,
main_unexistent/1
]).

-define(EXCLUDED_FUNS,
Expand All @@ -34,8 +40,7 @@
-spec all() -> [atom()].
all() ->
Exports = elvis_SUITE:module_info(exports),
[F || {F, _} <- Exports,
lists:all(fun(E) -> E /= F end, ?EXCLUDED_FUNS)].
[F || {F, _} <- Exports, not lists:member(F, ?EXCLUDED_FUNS)].

-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
Expand All @@ -51,6 +56,9 @@ end_per_suite(Config) ->
%% Test Cases
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%
%%% Rocking

-spec rock_with_empty_config(config()) -> any().
rock_with_empty_config(_Config) ->
ok = try
Expand All @@ -72,7 +80,13 @@ rock_with_incomplete_config(_Config) ->

-spec rock_with_file_config(config()) -> ok.
rock_with_file_config(_Config) ->
ok = elvis:rock().
Fun = fun() -> elvis:rock() end,
Expected = "# ../../test/examples/fail_line_length.erl [FAIL]\n",
check_first_line_output(Fun, Expected),
ok.

%%%%%%%%%%%%%%%
%%% Utils

-spec check_configuration(config()) -> any().
check_configuration(_Config) ->
Expand All @@ -93,9 +107,8 @@ find_file_and_check_src(_Config) ->
{ok, <<"-module(small).\n">>} = elvis_utils:src([], Path),
{error, enoent} = elvis_utils:src([], "doesnt_exist.erl").

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Rules
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%
%%% Rules

-spec verify_line_length_rule(config()) -> any().
verify_line_length_rule(_Config) ->
Expand Down Expand Up @@ -124,3 +137,112 @@ verify_no_tabs_rule(_Config) ->
2 -> ok;
_ -> tabs_undetected
end.

%%%%%%%%%%%%%%%
%%% CLI

-spec main_help(config()) -> any().
main_help(_Config) ->
Expected = "Usage: elvis",

ShortOptFun = fun() -> elvis:main("-h") end,
check_first_line_output(ShortOptFun, Expected, fun starts_with/2),

LongOptFun = fun() -> elvis:main("--help") end,
check_first_line_output(LongOptFun, Expected, fun starts_with/2),

EmptyFun = fun() -> elvis:main("") end,
check_first_line_output(EmptyFun, Expected, fun starts_with/2),

CmdFun = fun() -> elvis:main("help") end,
check_first_line_output(CmdFun, Expected, fun starts_with/2),

ok.

-spec main_commands(config()) -> any().
main_commands(_Config) ->
Expected = "Elvis will do the following things",

OptFun = fun() -> elvis:main("--commands") end,
check_first_line_output(OptFun, Expected, fun starts_with/2),

ok.

-spec main_config(config()) -> any().
main_config(_Config) ->
Expected = "Error: missing_option_arg config",

OptFun = fun() -> elvis:main("-c") end,
check_first_line_output(OptFun, Expected, fun starts_with/2),

EnoentExpected = "Error: enoent.\n",
OptEnoentFun = fun() -> elvis:main("-c missing") end,
check_first_line_output(OptEnoentFun, EnoentExpected),

ConfigFileFun = fun() -> elvis:main("-c ../../config/elvis.config") end,
check_first_line_output(ConfigFileFun, ""),

ok.

-spec main_rock(config()) -> any().
main_rock(_Config) ->
ExpectedFail = "# ../../test/examples/fail_line_length.erl [FAIL]\n",

NoConfigArgs = "rock",
NoConfigFun = fun() -> elvis:main(NoConfigArgs) end,
check_first_line_output(NoConfigFun, ExpectedFail, fun starts_with/2),

Expected = "# ../../src/elvis.erl [OK]",

ConfigArgs = "rock -c ../../config/elvis-test.config",
ConfigFun = fun() -> elvis:main(ConfigArgs) end,
check_first_line_output(ConfigFun, Expected, fun starts_with/2),

ok.

-spec main_default_config(config()) -> any().
main_default_config(_Config) ->
Src = "../../config/elvis-test.config",
Dest = "./elvis.config",
file:copy(Src, Dest),

Expected = "# ../../src/elvis.erl [OK]",
RockFun = fun() -> elvis:main("rock") end,
check_first_line_output(RockFun, Expected, fun starts_with/2),

file:delete(Dest),

ok.

-spec main_unexistent(config()) -> any().
main_unexistent(_Config) ->
Expected = "Error: unrecognized_or_unimplemened_command.\n",

UnexistentFun = fun() -> elvis:main("aaarrrghh") end,
check_first_line_output(UnexistentFun, Expected),

ok.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Private
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

check_first_line_output(Fun, Expected) ->
Equals = fun(Result, Exp) ->
Result = Exp
end,
check_first_line_output(Fun, Expected, Equals).

check_first_line_output(Fun, Expected, CheckFun) ->
ct:capture_start(),
Fun(),
ct:capture_stop(),
Result = case ct:capture_get([]) of
[] -> "";
[Head | _] -> Head
end,

CheckFun(Result, Expected).

starts_with(Result, Expected) ->
1 = string:str(Result, Expected).

0 comments on commit 3a40696

Please sign in to comment.