diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index b9b0f8ee7..00120fcb3 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -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) -> diff --git a/src/gen_mod.erl b/src/gen_mod.erl index 5aeb1df25..9ff851ed5 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -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.