]> granicus.if.org Git - ejabberd/commitdiff
Provide a suggestion when unknown option is detected
authorEvgeny Khramtsov <ekhramtsov@process-one.net>
Mon, 29 Apr 2019 17:57:59 +0000 (20:57 +0300)
committerEvgeny Khramtsov <ekhramtsov@process-one.net>
Mon, 29 Apr 2019 17:57:59 +0000 (20:57 +0300)
src/ejabberd_config.erl
src/gen_mod.erl

index b9b0f8ee7c87d703b43d528832cfd3d243539cff..00120fcb3e7d9a50128e61b8609b8e147cca0a44 100644 (file)
@@ -38,7 +38,8 @@
         default_db/1, default_db/2, default_ram_db/1, default_ram_db/2,
         default_queue_type/1, queue_dir/0, fsm_limit_opts/1,
         use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1,
-        codec_options/1, get_plain_terms_file/2, negotiation_timeout/0]).
+        codec_options/1, get_plain_terms_file/2, negotiation_timeout/0,
+        similar_option/2]).
 
 -export([start/2]).
 
@@ -1099,7 +1100,9 @@ validate_opts(#state{opts = Opts} = State, ModOpts) ->
                                            erlang:error(invalid_option)
                                    end;
                                _ ->
-                                   ?ERROR_MSG("Unknown option '~s'", [Opt]),
+                                   KnownOpts = dict:fetch_keys(ModOpts),
+                                   ?ERROR_MSG("Unknown option '~s', did you mean '~s'?",
+                                              [Opt, similar_option(Opt, KnownOpts)]),
                                    erlang:error(unknown_option)
                            end
                    end, Opts),
@@ -1123,6 +1126,34 @@ is_file_readable(Path) ->
            false
     end.
 
+-spec similar_option(atom(), [atom()]) -> atom().
+similar_option(Pattern, [_|_] = Opts) ->
+    String = atom_to_list(Pattern),
+    {Ds, _} = lists:mapfoldl(
+               fun(Opt, Cache) ->
+                       {Distance, Cache1} = ld(String, atom_to_list(Opt), Cache),
+                       {{Distance, Opt}, Cache1}
+               end, #{}, Opts),
+    element(2, lists:min(Ds)).
+
+%% Levenshtein distance
+-spec ld(string(), string(), map()) -> {non_neg_integer(), map()}.
+ld([] = S, T, Cache) ->
+    {length(T), maps:put({S, T}, length(T), Cache)};
+ld(S, [] = T, Cache) ->
+    {length(S), maps:put({S, T}, length(S), Cache)};
+ld([X|S], [X|T], Cache) ->
+    ld(S, T, Cache);
+ld([_|ST] = S, [_|TT] = T, Cache) ->
+    try {maps:get({S, T}, Cache), Cache}
+    catch _:{badkey, _} ->
+            {L1, C1} = ld(S, TT, Cache),
+            {L2, C2} = ld(ST, T, C1),
+            {L3, C3} = ld(ST, TT, C2),
+            L = 1 + lists:min([L1, L2, L3]),
+            {L, maps:put({S, T}, L, C3)}
+    end.
+
 get_version() ->
     case application:get_env(ejabberd, custom_vsn) of
        {ok, Vsn0} when is_list(Vsn0) ->
index 5aeb1df25afba664271e291c5571a5951fea8669..9ff851ed500553c92ae3eef35749be8e47d9f073 100644 (file)
@@ -582,8 +582,10 @@ validate_opts(Host, Module, Opts0) ->
            module_error(ErrTxt);
          _:{unknown_option, Opt, KnownOpts} ->
            ErrTxt = io_lib:format("Unknown option '~s' of module '~s',"
-                                  " available options are: ~s",
+                                  " did you mean '~s'?"
+                                  " Available options are: ~s",
                                   [Opt, Module,
+                                   ejabberd_config:similar_option(Opt, KnownOpts),
                                    misc:join_atoms(KnownOpts, <<", ">>)]),
            module_error(ErrTxt)
     end.