Packages
elvis_core
4.2.0
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
retired
4.2.3
4.2.2
4.2.1
4.2.0
4.1.1
4.1.0
4.0.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.0
3.0.1
3.0.0
2.0.1
2.0.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.2
1.1.1
1.1.0
1.0.0
0.7.0
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
Core library for the Erlang style reviewer
Current section
Files
Jump to
Current section
Files
src/elvis_config.erl
-module(elvis_config).
-feature(maybe_expr, enable).
-export([from_rebar/1, from_file/1, validate_config/1, default/0]).
%% Getters
-export([dirs/1, ignore/1, filter/1, files/1, rules/1, ruleset/1]).
%% Files
-export([resolve_files/1, resolve_files/2, apply_to_files/2]).
%% Rules
-export([merge_rules/2]).
%% Options
-export([config/0, output_format/0, verbose/0, no_output/0, parallel/0]).
-export([set_output_format/1, set_verbose/1, set_no_output/1, set_parallel/1]).
% Corresponds to the 'config' key.
-opaque t() :: map().
-export_type([t/0]).
-type output_format() :: plain | colors | parsable.
-export_type([output_format/0]).
-type fail_validation() :: {fail, [{throw, {invalid_config, Message :: string()}}]}.
-export_type([fail_validation/0]).
% API exports, not consumed locally.
-ignore_xref([from_rebar/1, from_file/1, default/0, resolve_files/2, apply_to_files/2]).
-ignore_xref([set_output_format/1, set_verbose/1, set_no_output/1, set_parallel/1]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Public
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
fetch_elvis_config(Key, AppConfig) ->
Elvis = from_static(elvis, {app, AppConfig}),
_ =
case lists:member(Key, elvis_control_opts()) of
true ->
ok;
_ ->
do_validate({elvis, Elvis})
end,
_ = elvis_ruleset:load_custom(from_static(rulesets, {elvis, Elvis})),
Elvis.
from_static(Key, {Type, Config}) ->
elvis_utils:debug("fetching key '~s' from '~s' configuration", [Key, Type]),
case proplists:get_value(Key, Config) of
undefined ->
elvis_utils:debug(
"no value for key '~s' found in '~s' configuration; going with default", [
Key, Type
]
),
default(Key);
Value ->
elvis_utils:debug("value for key '~s' found in '~s' configuration", [Key, Type]),
Value
end.
-spec config() -> [t()] | fail_validation().
config() ->
try
for(config)
catch
{invalid_config, _} = Caught ->
{fail, [{throw, Caught}]}
end.
-spec output_format() -> output_format().
output_format() ->
for(output_format).
-spec verbose() -> boolean().
verbose() ->
for(verbose).
-spec no_output() -> boolean().
no_output() ->
for(no_output).
-spec parallel() -> pos_integer().
parallel() ->
for(parallel).
-spec set_output_format(output_format()) -> ok.
set_output_format(OutputFormat) ->
set_env(output_format, OutputFormat).
-spec set_verbose(boolean()) -> ok.
set_verbose(Verbose) ->
set_env(verbose, Verbose).
-spec set_no_output(boolean()) -> ok.
set_no_output(NoOutput) ->
set_env(no_output, NoOutput).
-spec set_parallel(pos_integer()) -> ok.
set_parallel(Parallel) ->
set_env(parallel, Parallel).
set_env(Key, Value) ->
application:set_env(elvis_core, Key, Value).
default(Key) ->
case application:get_env(elvis_core, Key) of
undefined ->
elvis_utils:debug(
"no value for key '~s' found in application environment; going with default",
[Key]
),
default_for(Key);
{ok, Value} ->
elvis_utils:debug("value for key '~s' (~p) found in application environment", [
Key, Value
]),
Value
end.
for(Key) ->
AppDefault = default_for(app),
AppConfig =
case consult_elvis_config("elvis.config") of
AppDefault ->
% This might happen whether we fail to parse the file or it actually is []
elvis_utils:debug("elvis.config unusable; falling back to rebar.config", []),
consult_rebar_config("rebar.config");
AppConfig0 ->
AppConfig0
end,
% If we got this far, the configuration is valid...
Elvis = fetch_elvis_config(Key, AppConfig),
from_static(Key, {elvis, Elvis}).
consult_elvis_config(File) ->
case file:consult(File) of
{ok, [AppConfig]} when is_list(AppConfig) ->
elvis_utils:debug("elvis.config consultable; using it", []),
AppConfig;
{error, {Line, Mod, Term}} ->
% In this very specific case we prefer to throw, since we make efforts
% to provide a valid config., but we also need to make sure the file
% is readable
exit(
lists:flatten(
io_lib:format("elvis.config unconsultable: ~p, ~p, ~p", [Line, Mod, Term])
)
);
_ ->
elvis_utils:debug("elvis.config unconsultable", []),
default_for(app)
end.
consult_rebar_config(File) ->
case file:consult(File) of
{ok, AppConfig0} when is_list(AppConfig0) ->
elvis_utils:debug("rebar.config consultable; using it", []),
AppConfig0;
_ ->
elvis_utils:debug("rebar.config unconsultable", []),
default_for(app)
end.
-spec from_rebar(File :: string()) -> [t()] | fail_validation().
from_rebar(File) ->
AppConfig = consult_rebar_config(File),
fetch_elvis_config_from(AppConfig).
-spec from_file(File :: string()) -> [t()] | fail_validation().
from_file(File) ->
AppConfig = consult_elvis_config(File),
fetch_elvis_config_from(AppConfig).
fetch_elvis_config_from(AppConfig) ->
try fetch_elvis_config(undefined, AppConfig) of
Elvis ->
from_static(config, {elvis, Elvis})
catch
{invalid_config, Message} ->
{fail, [{throw, {invalid_config, lists:flatten(Message)}}]}
end.
default_for(app) ->
% This is the top-level element, before 'elvis'
[];
default_for(elvis) ->
[];
default_for(config) ->
[];
default_for(output_format) ->
colors;
default_for(verbose) ->
false;
default_for(no_output) ->
false;
default_for(parallel) ->
1;
default_for(rulesets) ->
#{};
default_for([config, dirs]) ->
[];
default_for([config, filter]) ->
"";
default_for([config, ignore]) ->
[];
default_for([config, ruleset]) ->
undefined;
default_for([config, rules]) ->
[].
-spec default() -> [t()].
default() ->
[
#{
dirs => [
"apps/**/src",
"src"
],
filter => "*.erl",
ruleset => erl_files
},
#{
dirs => [
"apps/**/src",
"src",
"apps/**/include",
"include"
],
filter => "*.hrl",
ruleset => hrl_files
},
#{
dirs => ["."],
filter => "rebar.config",
ruleset => rebar_config
},
#{
dirs => ["."],
filter => ".gitignore",
ruleset => gitignore
}
].
-spec dirs(Config :: [t()] | t()) -> [string()].
dirs(Config) when is_list(Config) ->
lists:flatmap(fun dirs/1, Config);
dirs(#{dirs := Dirs}) ->
Dirs;
dirs(#{}) ->
[].
-spec ignore([t()] | t()) -> [string()].
ignore(Config) when is_list(Config) ->
lists:flatmap(fun ignore/1, Config);
ignore(#{ignore := Ignore}) ->
lists:map(fun ignore_to_regexp/1, Ignore);
ignore(#{}) ->
[].
-spec filter([t()] | t()) -> [string()].
filter(Config) when is_list(Config) ->
lists:flatmap(fun filter/1, Config);
filter(#{filter := Filter}) ->
Filter;
filter(#{}) ->
"*.erl".
-spec files(RuleGroup :: [t()] | t()) -> [elvis_file:t()].
files(RuleGroup) when is_list(RuleGroup) ->
lists:map(fun files/1, RuleGroup);
files(#{files := Files}) ->
Files;
files(#{}) ->
[].
-spec rules
(RulesL :: [t()]) -> [[elvis_rule:t()]];
(Rules :: t()) -> [elvis_rule:t()].
rules(Rules) when is_list(Rules) ->
lists:map(fun rules/1, Rules);
rules(#{rules := UserRules, ruleset := Ruleset}) ->
DefaultRules = elvis_ruleset:rules(Ruleset),
merge_rules(UserRules, DefaultRules);
rules(#{rules := Rules}) ->
Rules;
rules(#{ruleset := Ruleset}) ->
elvis_ruleset:rules(Ruleset);
rules(#{}) ->
[].
-spec ruleset(t()) -> Ruleset :: atom() | undefined.
ruleset(ElvisConfig) ->
maps:get(ruleset, ElvisConfig, undefined).
%% @doc Takes a configuration and a list of files, filtering some
%% of them according to the 'filter' key, or if not specified
%% uses '*.erl'.
%% @end
%% resolve_files/2 with a [t()] type is used in elvis project
-spec resolve_files(Config :: [t()] | t(), Files :: [elvis_file:t()]) ->
[t()] | t().
resolve_files(Config, Files) when is_list(Config) ->
Fun = fun(RuleGroup) -> resolve_files(RuleGroup, Files) end,
lists:map(Fun, Config);
resolve_files(RuleGroup, Files) ->
Filter = filter(RuleGroup),
Dirs = dirs(RuleGroup),
Ignore = ignore(RuleGroup),
FilteredFiles = elvis_file:filter_files(Files, Dirs, Filter, Ignore),
RuleGroup#{files => FilteredFiles}.
%% @doc Takes a configuration and finds all files according to its 'dirs'
%% end 'filter' key, or if not specified uses '*.erl'.
%% @end
-spec resolve_files(t()) -> t().
resolve_files(#{files := _Files} = RuleGroup) ->
RuleGroup;
resolve_files(#{dirs := Dirs} = RuleGroup) ->
Filter = filter(RuleGroup),
Files = elvis_file:find_files(Dirs, Filter),
resolve_files(RuleGroup, Files);
resolve_files(#{}) ->
[].
%% @doc Takes a function and configuration and applies the function to all
%% file in the configuration.
%% @end
-spec apply_to_files(Fun :: fun(), Config :: [t()] | t()) ->
[t()] | t().
apply_to_files(Fun, Config) when is_list(Config) ->
ApplyFun = fun(RuleGroup) -> apply_to_files(Fun, RuleGroup) end,
lists:map(ApplyFun, Config);
apply_to_files(Fun, #{files := Files} = RuleGroup) ->
NewFiles = lists:map(Fun, Files),
RuleGroup#{files => NewFiles}.
%% @doc Ensures the ignore is a regexp, this is used
%% to allow using 'module name' atoms in the ignore
%% list by taking advantage of the fact that erlang
%% enforces the module and the file name to be the
%% same.
%% @end
-spec ignore_to_regexp(string() | atom()) -> string().
ignore_to_regexp(R) when is_list(R) ->
R;
ignore_to_regexp(A) when is_atom(A) ->
"/" ++ atom_to_list(A) ++ "\\.erl$".
%% @doc Merge user rules (override) with elvis default rules.
-spec merge_rules(UserRules :: list(), DefaultRules :: list()) -> [elvis_rule:t()].
merge_rules(UserRules, DefaultRules) ->
UnduplicatedRules =
% Drops repeated rules
% If any default rule is in UserRules it means the user
lists:filtermap(
% wants to override the rule.
fun(Tuple) ->
Rule = elvis_rule:from_tuple(Tuple),
case not is_rule_override(Rule, UserRules) andalso not elvis_rule:disabled(Rule) of
true ->
{true, Rule};
false ->
false
end
end,
DefaultRules
),
OverrideRules =
% Remove the rules that the user wants to "disable" and after that,
% remains just the rules the user wants to override.
lists:filtermap(
fun(Tuple) ->
Rule = elvis_rule:from_tuple(Tuple),
case elvis_rule:disabled(Rule) of
false ->
{true, Rule};
true ->
false
end
end,
UserRules
),
UnduplicatedRules ++ OverrideRules.
-spec is_rule_override(
Rule :: elvis_rule:t(),
UserRules :: [{NS :: module(), Name :: atom()}]
) ->
boolean().
is_rule_override(Rule, UserRules) ->
lists:any(
fun(UserRule) ->
elvis_rule:same(Rule, elvis_rule:from_tuple(UserRule))
end,
UserRules
).
-spec validate_config(term()) -> ok.
validate_config(ElvisConfig) ->
do_validate({config, ElvisConfig}).
get_elvis_opt(OptName, Elvis) ->
proplists:get_value(OptName, Elvis, default_for(OptName)).
elvis_control_opts() ->
[output_format, verbose, no_output, parallel].
do_validate({elvis = Option, Elvis}) ->
case check_flag(validation_started(Option)) of
true ->
ok;
_ ->
maybe
ok = flag(validation_started(Option)),
ok ?= is_nonempty_list(elvis, Elvis),
ok ?=
proplist_keys_are_in(
elvis, Elvis, elvis_control_opts() ++ [rulesets, config]
),
OutputFormat = get_elvis_opt(output_format, Elvis),
ok ?= is_one_of('elvis.output_format', OutputFormat, [colors, plain, parsable]),
Verbose = get_elvis_opt(verbose, Elvis),
ok ?= is_boolean('elvis.verbose', Verbose),
NoOutput = get_elvis_opt(no_output, Elvis),
ok ?= is_boolean('elvis.no_output', NoOutput),
Parallel = get_elvis_opt(parallel, Elvis),
ok ?= is_pos_integer('elvis.parallel', Parallel),
CustomRulesets = get_elvis_opt(rulesets, Elvis),
ok ?= are_valid_rulesets('elvis.rulesets', CustomRulesets),
ElvisConfig = get_elvis_opt(config, Elvis),
ok ?= is_valid_config('elvis.config', maps:keys(CustomRulesets), ElvisConfig)
else
{error, FormatData} ->
do_validate_throw(FormatData)
end
end;
do_validate({config = Option, ElvisConfig}) ->
case check_flag(validation_started(Option)) of
true ->
ok;
_ ->
maybe
ok ?= is_valid_config('elvis.config', elvis_ruleset:custom_names(), ElvisConfig)
else
{error, FormatData} ->
do_validate_throw(FormatData)
end
end.
-spec do_validate_throw(_) -> no_return().
do_validate_throw(FormatData) ->
{Format, Data} =
case is_list(FormatData) of
false ->
FormatData;
true ->
% Get only first result, for now
% If we wanna be smarter we need to start concatenating readable strings
[{Format0, Data0} | _] = FormatData,
{Format0, Data0}
end,
throw({invalid_config, io_lib:format(Format, Data)}).
is_nonempty_list(What, List) when not is_list(List) orelse List =:= [] ->
{error, {"'~s' is expected to exist and be a non-empty list.", [What]}};
is_nonempty_list(_What, _List) ->
ok.
proplist_keys_are_in(What, List, Keys) ->
Filtered = [Element || {Element, _} <- List, not lists:member(Element, Keys)],
case Filtered of
[] ->
ok;
_ ->
{error,
{"in '~s', the following keys are unknown: ~s.", [
What, elvis_utils:list_to_str(Filtered)
]}}
end.
is_one_of(What, Value, Possibilities) ->
case lists:member(Value, Possibilities) of
true ->
ok;
_ ->
{error,
{"'~s' is expected to be one of the following: ~s.", [
What, elvis_utils:list_to_str(Possibilities)
]}}
end.
is_boolean(_What, Value) when is_boolean(Value) ->
ok;
is_boolean(What, _Value) ->
{error, {"'~s' is expected to be a boolean.", [What]}}.
is_pos_integer(_What, Value) when is_integer(Value) andalso Value > 0 ->
ok;
is_pos_integer(What, _Value) ->
{error, {"'~s' is expected to be a positive integer.", [What]}}.
are_valid_rulesets(What, CustomRulesets) ->
maybe
ok ?= is_map(What, CustomRulesets),
ok ?= all_map_keys_are_atoms(What, CustomRulesets),
ok ?= all_custom_rulesets_have_valid_rules(What, CustomRulesets),
ok ?= no_default_ruleset_override(What, CustomRulesets)
else
{error, FormatData} ->
{error, FormatData}
end.
is_map(_What, Value) when is_map(Value) ->
ok;
is_map(What, _Value) ->
{error, {"'~s' is expected to be a map.", [What]}}.
all_map_keys_are_atoms(What, Map) ->
Filtered = [Key || Key <- maps:keys(Map), not is_atom(Key)],
case Filtered of
[] ->
ok;
_ ->
{error, {"in '~s', keys are expected to be atoms.", [What]}}
end.
all_custom_rulesets_have_valid_rules(What, CustomRulesets) ->
AccOut = maps:fold(
fun(CustomRuleset, RuleTuples, AccInO) ->
lists:foldl(
fun(RuleTuple, AccInI) ->
case elvis_rule:is_valid_from_tuple(RuleTuple) of
{true, _Rule} ->
AccInI;
{false, ValidError} ->
[
{"in '~s', in ruleset '~s', " ++ ValidError, [What, CustomRuleset]}
| AccInI
]
end
end,
AccInO,
RuleTuples
)
end,
[],
CustomRulesets
),
case AccOut of
[] ->
ok;
_ ->
{error, lists:reverse(AccOut)}
end.
no_default_ruleset_override(What, CustomRulesets) ->
Filtered = [
CustomRuleset
|| CustomRuleset <- maps:keys(CustomRulesets), elvis_ruleset:is_defined(CustomRuleset)
],
case Filtered of
[] ->
ok;
_ ->
{error,
{
"in '~s', the following rulesets are not expected to be "
"named after a default ruleset: ~s.",
[
What, elvis_utils:list_to_str(Filtered)
]
}}
end.
is_valid_config(What, CustomRulesetNames, Configset0) ->
maybe
ok ?= is_nonempty_list(What, Configset0),
Configset = wrap_in_list(Configset0),
ok ?= all_configs_are_valid(What, CustomRulesetNames, Configset)
else
{error, FormatData} ->
{error, FormatData}
end.
wrap_in_list(Term) when is_list(Term) ->
Term;
wrap_in_list(Term) ->
[Term].
all_configs_are_valid(What, CustomRulesetNames, Configset) ->
{_PosNumber, ValidErrors} = lists:foldl(
fun(Config, {PosNumber, AccIn}) ->
AccOut =
case config_is_valid(CustomRulesetNames, Config) of
ok ->
AccIn;
{error, ValidError} ->
[
{"in '~s', at list position number ~w, " ++ ValidError, [
What, PosNumber
]}
| AccIn
]
end,
{PosNumber + 1, AccOut}
end,
{1, []},
Configset
),
case ValidErrors of
[] ->
ok;
_ ->
{error, lists:reverse(ValidErrors)}
end.
get_config_opt(OptName, Config) ->
maps:get(OptName, Config, default_for([config, OptName])).
config_is_valid(CustomRulesetNames, Config) ->
maybe
% We're keeping files, for the time being, because of elvis
% and the fact it knows about elvis_core's internals, but we
% should shortly revisit this
ok ?= map_keys_are_in(Config, [dirs, filter, ignore, ruleset, rules, files]),
Dirs = get_config_opt(dirs, Config),
ok ?= is_nonempty_list_of_dirs(dirs, Dirs),
Filter = get_config_opt(filter, Config),
ok ?= is_nonempty_string(filter, Filter),
ok ?= all_dirs_filter_combos_are_valid(Dirs, Filter),
Ignore = get_config_opt(ignore, Config),
ok ?= is_list_of_ignorables(ignore, Ignore),
Ruleset = get_config_opt(ruleset, Config),
ok ?= defined_ruleset_is_custom_or_default(CustomRulesetNames, Ruleset),
Rules = get_config_opt(rules, Config),
ok ?= all_rules_are_valid(rules, Rules),
ok ?= either_rules_is_nonempty_or_ruleset_is_defined(Rules, Ruleset)
else
{error, ValidError} ->
{error, ValidError}
end.
map_keys_are_in(Map, _Keys) when not is_map(Map) ->
{error, "element is expected to be a map."};
map_keys_are_in(Map, Keys) ->
Filtered = [Key || Key <- maps:keys(Map), not lists:member(Key, Keys)],
case Filtered of
[] ->
ok;
_ ->
{error,
io_lib:format("the following keys are unknown: ~s.", [
elvis_utils:list_to_str(Filtered)
])}
end.
is_nonempty_list_of_dirs(What, List) when not is_list(List) orelse List =:= [] ->
{error, io_lib:format("'~s' is expected to be a non-empty list.", [What])};
is_nonempty_list_of_dirs(What, List) ->
Filtered = [
Element
|| Element <- List, not io_lib:char_list(Element) orelse not holds_dir(Element)
],
case Filtered of
[] ->
ok;
_ ->
{error,
io_lib:format(
"in '~s', the following elements are not (or don't contain) directories: ~s.",
[What, elvis_utils:list_to_str(Filtered)]
)}
end.
holds_dir(Element) ->
Dirs = filelib:wildcard(Element),
Filtered = [Dir || Dir <- Dirs, filelib:is_dir(Dir)],
Filtered =/= [].
is_nonempty_string(What, String) ->
case io_lib:char_list(String) andalso length(String) > 0 of
true ->
ok;
_ ->
{error, io_lib:format("'~s' is expected to be a non-empty string.", [What])}
end.
all_dirs_filter_combos_are_valid(Dirs, Filter) ->
AccOut = lists:foldl(
fun(Dir, AccIn) ->
case filelib:wildcard(filename:join(Dir, Filter)) of
[_ | _] ->
AccIn;
_ ->
[
io_lib:format(
"'<dir>' + '<filter>' combo '~s' + '~s' yielded no files to analyse.", [
Dir, Filter
]
)
| AccIn
]
end
end,
[],
Dirs
),
case AccOut of
[] ->
ok;
_ ->
{error, lists:reverse(AccOut)}
end.
is_list_of_ignorables(What, List) when not is_list(List) ->
{error, io_lib:format("'~s' is expected to be a list.", [What])};
is_list_of_ignorables(What, List) ->
Filtered = [Element || Element <- List, not elvis_rule:is_ignorable(Element)],
case Filtered of
[] ->
ok;
_ ->
{error,
io_lib:format("in '~s', the following elements are not ignorable: ~s.", [
What, elvis_utils:list_to_str(Filtered)
])}
end.
defined_ruleset_is_custom_or_default(_CustomRulesetNames, undefined = _Ruleset) ->
ok;
defined_ruleset_is_custom_or_default(CustomRulesetNames, Ruleset) ->
case lists:member(Ruleset, CustomRulesetNames) orelse elvis_ruleset:is_defined(Ruleset) of
true ->
ok;
_ ->
{error,
io_lib:format("'~s' is expected to be either a custom or a default ruleset.", [
Ruleset
])}
end.
all_rules_are_valid(What, RuleTuples) when not is_list(RuleTuples) ->
{error, io_lib:format("'~s' is expected to be a list.", [What])};
all_rules_are_valid(What, RuleTuples) ->
AccOut = lists:foldl(
fun(RuleTuple, AccInI) ->
case elvis_rule:is_valid_from_tuple(RuleTuple) of
{true, Rule} ->
check_rule_for_options(Rule, AccInI);
{false, ValidError} ->
[io_lib:format("in '~s', " ++ ValidError, [What]) | AccInI]
end
end,
[],
RuleTuples
),
case AccOut of
[] ->
ok;
_ ->
{error, lists:reverse(AccOut)}
end.
either_rules_is_nonempty_or_ruleset_is_defined([_ | _] = _Rules, _Ruleset) ->
ok;
either_rules_is_nonempty_or_ruleset_is_defined(_Rules, Ruleset) when Ruleset =/= undefined ->
ok;
either_rules_is_nonempty_or_ruleset_is_defined(_Rules, _Ruleset) ->
{error,
io_lib:format(
"each config section is expected to have either"
" a non-empty list of 'rules' or a 'ruleset'.",
[]
)}.
check_rule_for_options(Rule, AccInI) ->
case elvis_rule:defkeys(Rule) of
[] ->
% No further validation possible.
AccInI;
DefKeysInput ->
NS = elvis_rule:ns(Rule),
Name = elvis_rule:name(Rule),
% Bypass new/ constraints.
DefKeys = [ignore | maps:keys(NS:default(Name))],
case DefKeysInput -- DefKeys of
[] ->
AccInI;
Extra ->
[
io_lib:format(
"in rule ~w/~w, the following options are unknown: ~s.",
[NS, Name, elvis_utils:list_to_str(Extra)]
),
AccInI
]
end
end.
flag({_Option, _What} = Obj) ->
_ = ensure_ets_table(),
true = ets:insert(elvis_config, Obj),
ok.
check_flag({Option, _What} = Obj) ->
try ets:lookup(elvis_config, Option) of
Found -> [Obj] =:= Found
catch
_:badarg -> false
end.
validation_started(Option) ->
{Option, validation_started}.
ensure_ets_table() ->
case ets:info(elvis_config) of
undefined ->
_ = ets:new(elvis_config, [public, named_table]);
_ ->
ok
end.