diff options
Diffstat (limited to 'deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/help_command.ex')
-rw-r--r-- | deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/help_command.ex | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/help_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/help_command.ex new file mode 100644 index 0000000000..8f459cc83f --- /dev/null +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/help_command.ex @@ -0,0 +1,343 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +alias RabbitMQ.CLI.CommandBehaviour + +defmodule RabbitMQ.CLI.Ctl.Commands.HelpCommand do + alias RabbitMQ.CLI.Core.{CommandModules, Config, ExitCodes} + alias RabbitMQ.CLI.Core.CommandModules + + import RabbitMQ.CLI.Core.ANSI + + @behaviour RabbitMQ.CLI.CommandBehaviour + + def scopes(), do: [:ctl, :diagnostics, :plugins, :queues, :upgrade] + def switches(), do: [list_commands: :boolean] + + def distribution(_), do: :none + use RabbitMQ.CLI.Core.MergesNoDefaults + + def validate([], _), do: :ok + def validate([_command], _), do: :ok + def validate(args, _) when length(args) > 1 do + {:validation_failure, :too_many_args} + end + + def run([command_name | _], opts) do + CommandModules.load(opts) + + module_map = CommandModules.module_map() + case module_map[command_name] do + nil -> + # command not found + # {:error, all_usage(opts)} + case RabbitMQ.CLI.AutoComplete.suggest_command(command_name, module_map) do + {:suggest, suggested} -> + suggest_message = "\nCommand '#{command_name}' not found. \n" <> + "Did you mean '#{suggested}'? \n" + {:error, ExitCodes.exit_usage(), suggest_message} + nil -> + {:error, ExitCodes.exit_usage(), "\nCommand '#{command_name}' not found."} + end + + command -> + {:ok, command_usage(command, opts)} + end + end + + def run([], opts) do + CommandModules.load(opts) + + case opts[:list_commands] do + true -> + {:ok, commands_description()} + _ -> + {:ok, all_usage(opts)} + end + end + + def output({:ok, result}, _) do + {:ok, result} + end + def output({:error, result}, _) do + {:error, ExitCodes.exit_usage(), result} + end + use RabbitMQ.CLI.DefaultOutput + + def banner(_, _), do: nil + + def help_section(), do: :help + + def description(), do: "Displays usage information for a command" + + def usage(), do: "help (<command> | [--list-commands])" + + def usage_additional() do + [ + ["--list-commands", "only output a list of discovered commands"] + ] + end + + + # + # Implementation + # + + def all_usage(opts) do + tool_name = program_name(opts) + tool_usage(tool_name) ++ + ["\n\nAvailable commands:\n"] ++ commands_description() ++ + help_footer(tool_name) + end + + def command_usage(command, opts) do + Enum.join([base_usage(command, opts)] ++ + command_description(command) ++ + additional_usage(command) ++ + relevant_doc_guides(command) ++ + general_options_usage(), + "\n\n") <> "\n" + end + + defp tool_usage(tool_name) do + [ + "\n#{bright("Usage")}\n\n" <> + "#{tool_name} [--node <node>] [--timeout <timeout>] [--longnames] [--quiet] <command> [<command options>]" + ] + end + + def base_usage(command, opts) do + tool_name = program_name(opts) + + maybe_timeout = + case command_supports_timeout(command) do + true -> " [--timeout <timeout>]" + false -> "" + end + + Enum.join([ + "\n#{bright("Usage")}\n\n", + "#{tool_name} [--node <node>] [--longnames] [--quiet] " <> + flatten_string(command.usage(), maybe_timeout) + ]) + end + + defp flatten_string(list, additional) when is_list(list) do + list + |> Enum.map(fn line -> line <> additional end) + |> Enum.join("\n") + end + + defp flatten_string(str, additional) when is_binary(str) do + str <> additional + end + + defp general_options_usage() do + [ + "#{bright("General Options")} + +The following options are accepted by most or all commands. + +short | long | description +-----------------|---------------|-------------------------------- +-? | --help | displays command help +-n <node> | --node <node> | connect to node <node> +-l | --longnames | use long host names +-t | --timeout <n> | for commands that support it, operation timeout in seconds +-q | --quiet | suppress informational messages +-s | --silent | suppress informational messages + | and table header row +-p | --vhost | for commands that are scoped to a virtual host, + | | virtual host to use + | --formatter | alternative result formatter to use + | if supported: json, pretty_table, table, csv, erlang + not all commands support all (or any) alternative formatters."] + end + + defp command_description(command) do + case CommandBehaviour.description(command) do + "" -> [] + other -> [other <> ".\n"] + end + end + + defp list_item_formatter([option, description]) do + "#{option}\n\t#{description}\n" + end + defp list_item_formatter({option, description}) do + "#{option}\n\t#{description}\n" + end + defp list_item_formatter(line) do + "#{line}\n" + end + + defp additional_usage(command) do + command_usage = + case CommandBehaviour.usage_additional(command) do + list when is_list(list) -> list |> Enum.map(&list_item_formatter/1) + bin when is_binary(bin) -> ["#{bin}\n"] + end + case command_usage do + [] -> [] + usage -> + [flatten_string(["#{bright("Arguments and Options")}\n" | usage], "")] + end + end + + defp relevant_doc_guides(command) do + guide_list = + case CommandBehaviour.usage_doc_guides(command) do + list when is_list(list) -> list |> Enum.map(fn ln -> " * #{ln}\n" end) + bin when is_binary(bin) -> [" * #{bin}\n"] + end + case guide_list do + [] -> [] + usage -> + [flatten_string(["#{bright("Relevant Doc Guides")}\n" | usage], "")] + end + end + + defp help_footer(tool_name) do + ["Use '#{tool_name} help <command>' to learn more about a specific command"] + end + + defp command_supports_timeout(command) do + nil != CommandBehaviour.switches(command)[:timeout] + end + + defp commands_description() do + module_map = CommandModules.module_map() + + pad_commands_to = Enum.reduce(module_map, 0, + fn({name, _}, longest) -> + name_length = String.length(name) + case name_length > longest do + true -> name_length + false -> longest + end + end) + + lines = module_map + |> Enum.map( + fn({name, cmd}) -> + description = CommandBehaviour.description(cmd) + help_section = CommandBehaviour.help_section(cmd) + {name, {description, help_section}} + end) + |> Enum.group_by(fn({_, {_, help_section}}) -> help_section end) + |> Enum.sort_by( + fn({help_section, _}) -> + case help_section do + :deprecated -> 999 + :other -> 100 + {:plugin, _} -> 99 + :help -> 1 + :node_management -> 2 + :cluster_management -> 3 + :replication -> 3 + :user_management -> 4 + :access_control -> 5 + :observability_and_health_checks -> 6 + :parameters -> 7 + :policies -> 8 + :virtual_hosts -> 9 + _ -> 98 + end + end) + |> Enum.map( + fn({help_section, section_helps}) -> + [ + "\n" <> bright(section_head(help_section)) <> ":\n\n" | + Enum.sort(section_helps) + |> Enum.map( + fn({name, {description, _}}) -> + " #{String.pad_trailing(name, pad_commands_to)} #{description}\n" + end) + ] + + end) + |> Enum.concat() + + lines ++ ["\n"] + end + + defp section_head(help_section) do + case help_section do + :help -> + "Help" + :user_management -> + "Users" + :cluster_management -> + "Cluster" + :replication -> + "Replication" + :node_management -> + "Nodes" + :queues -> + "Queues" + :observability_and_health_checks -> + "Monitoring, observability and health checks" + :virtual_hosts -> + "Virtual hosts" + :access_control -> + "Access Control" + :parameters -> + "Parameters" + :policies -> + "Policies" + :configuration -> + "Configuration and Environment" + :feature_flags -> + "Feature flags" + :other -> + "Other" + {:plugin, plugin} -> + plugin_section(plugin) <> " plugin" + custom -> + snake_case_to_capitalized_string(custom) + end + end + + defp strip_rabbitmq_prefix(value, regex) do + Regex.replace(regex, value, "") + end + + defp format_known_plugin_name_fragments(value) do + case value do + ["amqp1.0"] -> "AMQP 1.0" + ["amqp1", "0"] -> "AMQP 1.0" + ["management"] -> "Management" + ["management", "agent"] -> "Management" + ["mqtt"] -> "MQTT" + ["stomp"] -> "STOMP" + ["web", "mqtt"] -> "Web MQTT" + ["web", "stomp"] -> "Web STOMP" + [other] -> snake_case_to_capitalized_string(other) + fragments -> snake_case_to_capitalized_string(Enum.join(fragments, "_")) + end + end + + defp plugin_section(plugin) do + regex = Regex.recompile!(~r/^rabbitmq_/) + + to_string(plugin) + # drop rabbitmq_ + |> strip_rabbitmq_prefix(regex) + |> String.split("_") + |> format_known_plugin_name_fragments() + end + + defp snake_case_to_capitalized_string(value) do + to_string(value) + |> String.split("_") + |> Enum.map(&String.capitalize/1) + |> Enum.join(" ") + end + + defp program_name(opts) do + Config.get_option(:script_name, opts) + end +end |