1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
|
## This Source Code Form is subject to the terms of the Mozilla Public
## License, v. 2.0. If a copy of the MPL was not distributed with this
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
##
## Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
defmodule RabbitMQ.CLI.AutoComplete do
alias RabbitMQ.CLI.Core.{CommandModules, Parser}
# Use the same jaro distance limit as in Elixir's "did you mean?"
@jaro_distance_limit 0.77
@spec complete(String.t(), [String.t()]) :: [String.t()]
def complete(_, []) do
[]
end
def complete(script_name, args) do
case Parser.parse_global(args) do
{_, %{script_name: _args_script_name}, _} ->
complete(args)
_ ->
complete(["--script-name", script_name | args])
end
end
def suggest_command(_cmd_name, empty) when empty == %{} do
nil
end
def suggest_command(typed, module_map) do
suggestion =
module_map
|> Map.keys()
|> Enum.map(fn existing ->
{existing, String.jaro_distance(existing, typed)}
end)
|> Enum.max_by(fn {_, distance} -> distance end)
case suggestion do
{cmd, distance} when distance >= @jaro_distance_limit ->
{:suggest, cmd}
_ ->
nil
end
end
defp complete(tokens) do
{command, command_name, _, _, _} = Parser.parse(tokens)
last_token = List.last(tokens)
case {command, command_name} do
## No command provided
{_, ""} ->
complete_default_opts(last_token)
## Command is not found/incomplete
{:no_command, command_name} ->
complete_command_name(command_name)
{{:suggest, _}, command_name} ->
complete_command_name(command_name)
## Command is found
{command, _} ->
complete_command_opts(command, last_token)
end
|> Enum.sort()
end
defp complete_default_opts(opt) do
Parser.default_switches()
|> Keyword.keys()
|> Enum.map(fn sw -> "--" <> to_string(sw) end)
|> select_starts_with(opt)
|> format_options
end
defp complete_command_name(command_name) do
module_map = CommandModules.module_map()
case module_map[command_name] do
nil ->
module_map
|> Map.keys()
|> select_starts_with(command_name)
_ ->
command_name
end
end
defp complete_command_opts(command, <<"-", _::binary>> = opt) do
switches =
command.switches()
|> Keyword.keys()
|> Enum.map(fn sw -> "--" <> to_string(sw) end)
# aliases = command.aliases
# |> Keyword.keys
# |> Enum.map(fn(al) -> "-" <> to_string(al) end)
select_starts_with(switches, opt)
|> format_options
end
defp complete_command_opts(_, _) do
[]
end
defp select_starts_with(list, prefix) do
Enum.filter(list, fn el -> String.starts_with?(el, prefix) end)
end
defp format_options(options) do
options
|> Enum.map(fn opt -> String.replace(opt, "_", "-") end)
end
end
|