Suggest commands by Levenshtein distance

This commit is contained in:
Daniil Fedotov 2016-12-05 18:59:58 +00:00
parent f82a274902
commit 80dc953ee5
3 changed files with 36 additions and 17 deletions

View File

@ -16,7 +16,8 @@
alias RabbitMQ.CLI.Core.CommandModules, as: CommandModules
defmodule RabbitMQ.CLI.Core.Parser do
alias RabbitMQ.CLI.Core.Helpers, as: Helpers
@levenshtein_distance_limit 5
@spec parse(String.t) :: {command :: :no_command | atom(),
command_name :: String.t,
@ -33,6 +34,8 @@ defmodule RabbitMQ.CLI.Core.Parser do
case command_module do
nil ->
{:no_command, command_name, arguments, options, invalid};
{:suggest, _} = suggest ->
{suggest, command_name, arguments, options, invalid};
command_module when is_atom(command_module) ->
{[^command_name | cmd_arguments], cmd_options, cmd_invalid} =
parse_command_specific(input, command_module)
@ -43,12 +46,35 @@ defmodule RabbitMQ.CLI.Core.Parser do
defp look_up_command(parsed_args) do
case parsed_args do
[cmd_name | arguments] ->
{cmd_name, CommandModules.module_map[cmd_name], arguments}
module_map = CommandModules.module_map
command = case module_map[cmd_name] do
nil -> closest_similar_command(cmd_name, module_map)
command -> command
end
{cmd_name, command, arguments}
[] ->
{"", nil, []}
end
end
defp closest_similar_command(_cmd_name, empty) when empty == %{} do
nil
end
defp closest_similar_command(cmd_name, module_map) do
suggestion = module_map
|> Map.keys
|> Enum.map(fn(cmd) ->
{cmd, Simetric.Levenshtein.compare(cmd, cmd_name)}
end)
|> Enum.min_by(fn({_,distance}) -> distance end)
case suggestion do
{cmd, distance} when distance < @levenshtein_distance_limit ->
{:suggest, cmd};
_ ->
nil
end
end
def parse_command_specific(input, command) do
switches = build_switches(default_switches(), command)
aliases = build_aliases(default_aliases(), command)

View File

@ -36,11 +36,14 @@ defmodule RabbitMQCtl do
end
def main(unparsed_command) do
{command, command_name, arguments, parsed_options, invalid} = parse(unparsed_command)
case {command, invalid} do
{:no_command, _} ->
usage_string = HelpCommand.all_usage()
{:error, ExitCodes.exit_usage, usage_string};
{{:suggest, suggested}, _} ->
suggest_message = "Command '#{command_name}' not found."<>
" Did you mean '#{suggested}'?"
{:error, ExitCodes.exit_usage, suggest_message};
{_, [_|_]} ->
validation_error({:bad_option, invalid}, command_name, unparsed_command);
_ ->
@ -225,10 +228,6 @@ defmodule RabbitMQCtl do
end
defp format_validation_error(err, _), do: inspect err
defp command_flags(command) do
apply_if_exported(command, :switches, [], []) |> Keyword.keys
end
defp exit_program(code) do
:net_kernel.stop
exit({:shutdown, code})

View File

@ -110,16 +110,10 @@ defmodule RabbitMQCtl.MixfileBase do
compile: make,
override: true
},
{
:amqp, "~> 0.1.5",
only: :test
},
{
:json, "~> 1.0.0"
},
{
:csv, "~> 1.4.2"
}
{:amqp, "~> 0.1.5", only: :test},
{:json, "~> 1.0.0"},
{:csv, "~> 1.4.2"},
{:simetric, "~> 0.1.0"}
]
end