DockerCLI/cli/required.go

114 lines
2.8 KiB
Go
Raw Normal View History

package cli
import (
"fmt"
"github.com/spf13/cobra"
)
// NoArgs validates args and returns an error if there are any args
func NoArgs(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return nil
}
if cmd.HasSubCommands() {
return fmt.Errorf(
cli: improve output and consistency for unknown (sub)commands Before this patch, output for invalid top-level and sub-commands differed. For top-level commands, the CLI would print an error-message and a suggestion to use `--help`. For missing *subcommands*, we would hit a different code-path, and different output, which includes full "usage" / "help" output. While it is a common convention to show usage output, and may have been a nice gesture when docker was still young and only had a few commands and options ("you did something wrong; here's an overview of what you can use"), that's no longer the case, and many commands have a _very_ long output. The result of this is that the error message, which is the relevant information in this case - "You mis-typed something" - is lost in the output, and hard to find (sometimes even requiring scrolling back). The output is also confusing, because it _looks_ like something ran successfully (most of the output is not about the error!). Even further; the suggested resolution (try `--help` to see the correct options) is rather redundant, because running teh command with `--help` produces _exactly_ the same output as was just showh, baring the error message. As a fun fact, due to the usage output being printed, the output even contains not one, but _two_ "call to actions"; - `See 'docker volume --help'.` (under the erro message) - `Run 'docker volume COMMAND --help' for more information on a command.` (under the usage output) In short; the output is too verbose, confusing, and doesn't provide a good UX. Let's reduce the output produced so that the focus is on the important information. This patch: - Changes the usage to the short-usage. - Changes the error-message to mention the _full_ command instead of only the command after `docker` (so `docker no-such-command` instead of `no-such-command`). - Prefixes the error message with the binary / root-command name (usually `docker:`); this is something we can still decide on, but it's a pattern we already use in some places. The motivation for this is that `docker` commands can often produce output that's a combination of output from the CLI itself, output from the daemon, and even output from the container. The `docker:` prefix helps to distinguish where the message originated from (the `docker` CLI in this case). - Adds an empty line between the error-message and the "call to action" (`Run 'docker volume --help'...` in the example below). This helps separating the error message ("unkown flag") from the call-to-action. Before this patch: Unknown top-level command: docker nosuchcommand foo docker: 'nosuchcommand' is not a docker command. See 'docker --help' Unknown sub-command: docker volume nosuchcommand foo Usage: docker volume COMMAND Manage volumes Commands: create Create a volume inspect Display detailed information on one or more volumes ls List volumes prune Remove unused local volumes rm Remove one or more volumes update Update a volume (cluster volumes only) Run 'docker volume COMMAND --help' for more information on a command. After this patch: Unknown top-level command: docker nosuchcommand foo docker: unknown command: docker nosuchcommand Run 'docker --help' for more information Unknown sub-command: docker volume nosuchcommand foo docker: unknown command: 'docker volume nosuchcommand' Usage: docker volume COMMAND Run 'docker volume --help' for more information Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-04 18:57:07 -04:00
"%[1]s: unknown command: %[2]s %[3]s\n\nUsage: %[4]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
args[0],
cmd.UseLine(),
)
}
return fmt.Errorf(
"%[1]s: '%[2]s' accepts no arguments\n\nUsage: %[3]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
cmd.UseLine(),
)
}
// RequiresMinArgs returns an error if there is not at least min args
func RequiresMinArgs(minArgs int) cobra.PositionalArgs {
return func(cmd *cobra.Command, args []string) error {
if len(args) >= minArgs {
return nil
}
return fmt.Errorf(
"%[1]s: '%[2]s' requires at least %[3]d %[4]s\n\nUsage: %[5]s\n\nSee '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
minArgs,
pluralize("argument", minArgs),
cmd.UseLine(),
)
}
}
// RequiresMaxArgs returns an error if there is not at most max args
func RequiresMaxArgs(maxArgs int) cobra.PositionalArgs {
return func(cmd *cobra.Command, args []string) error {
if len(args) <= maxArgs {
return nil
}
return fmt.Errorf(
"%[1]s: '%[2]s' requires at most %[3]d %[4]s\n\nUsage: %[5]s\n\nSRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
maxArgs,
pluralize("argument", maxArgs),
cmd.UseLine(),
)
}
}
// RequiresRangeArgs returns an error if there is not at least min args and at most max args
func RequiresRangeArgs(minArgs int, maxArgs int) cobra.PositionalArgs {
return func(cmd *cobra.Command, args []string) error {
if len(args) >= minArgs && len(args) <= maxArgs {
return nil
}
return fmt.Errorf(
"%[1]s: '%[2]s' requires at least %[3]d and at most %[4]d %[5]s\n\nUsage: %[6]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
minArgs,
maxArgs,
pluralize("argument", maxArgs),
cmd.UseLine(),
)
}
}
// ExactArgs returns an error if there is not the exact number of args
func ExactArgs(number int) cobra.PositionalArgs {
return func(cmd *cobra.Command, args []string) error {
if len(args) == number {
return nil
}
return fmt.Errorf(
"%[1]s: '%[2]s' requires %[3]d %[4]s\n\nUsage: %[5]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
number,
pluralize("argument", number),
cmd.UseLine(),
)
}
}
cli: improve output and consistency for unknown (sub)commands Before this patch, output for invalid top-level and sub-commands differed. For top-level commands, the CLI would print an error-message and a suggestion to use `--help`. For missing *subcommands*, we would hit a different code-path, and different output, which includes full "usage" / "help" output. While it is a common convention to show usage output, and may have been a nice gesture when docker was still young and only had a few commands and options ("you did something wrong; here's an overview of what you can use"), that's no longer the case, and many commands have a _very_ long output. The result of this is that the error message, which is the relevant information in this case - "You mis-typed something" - is lost in the output, and hard to find (sometimes even requiring scrolling back). The output is also confusing, because it _looks_ like something ran successfully (most of the output is not about the error!). Even further; the suggested resolution (try `--help` to see the correct options) is rather redundant, because running teh command with `--help` produces _exactly_ the same output as was just showh, baring the error message. As a fun fact, due to the usage output being printed, the output even contains not one, but _two_ "call to actions"; - `See 'docker volume --help'.` (under the erro message) - `Run 'docker volume COMMAND --help' for more information on a command.` (under the usage output) In short; the output is too verbose, confusing, and doesn't provide a good UX. Let's reduce the output produced so that the focus is on the important information. This patch: - Changes the usage to the short-usage. - Changes the error-message to mention the _full_ command instead of only the command after `docker` (so `docker no-such-command` instead of `no-such-command`). - Prefixes the error message with the binary / root-command name (usually `docker:`); this is something we can still decide on, but it's a pattern we already use in some places. The motivation for this is that `docker` commands can often produce output that's a combination of output from the CLI itself, output from the daemon, and even output from the container. The `docker:` prefix helps to distinguish where the message originated from (the `docker` CLI in this case). - Adds an empty line between the error-message and the "call to action" (`Run 'docker volume --help'...` in the example below). This helps separating the error message ("unkown flag") from the call-to-action. Before this patch: Unknown top-level command: docker nosuchcommand foo docker: 'nosuchcommand' is not a docker command. See 'docker --help' Unknown sub-command: docker volume nosuchcommand foo Usage: docker volume COMMAND Manage volumes Commands: create Create a volume inspect Display detailed information on one or more volumes ls List volumes prune Remove unused local volumes rm Remove one or more volumes update Update a volume (cluster volumes only) Run 'docker volume COMMAND --help' for more information on a command. After this patch: Unknown top-level command: docker nosuchcommand foo docker: unknown command: docker nosuchcommand Run 'docker --help' for more information Unknown sub-command: docker volume nosuchcommand foo docker: unknown command: 'docker volume nosuchcommand' Usage: docker volume COMMAND Run 'docker volume --help' for more information Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-04 18:57:07 -04:00
// binName returns the name of the binary / root command (usually 'docker').
func binName(cmd *cobra.Command) string {
return cmd.Root().Name()
}
//nolint:unparam
func pluralize(word string, number int) string {
if number == 1 {
return word
}
return word + "s"
}