When designing CLI commands, a common scenario is that a command corresponding to some conceptual action may take different, largely non-overlapping arguments depending on the value of a particular parameter.
For instance, suppose we’re designing a new gcloud surface for managing images, and we need a command that formats an image as JPEG, GIF, or PNG. Regardless of the image format, the command will need arguments for the source and destination files, but otherwise depending on the format, an entirely different set of options will apply. JPEG, for example, might take arguments for smoothing, subsampling, and DCT method, which are only relevant for JPEG images. GIF, on the other hand, might take arguments for controlling animated gifs e.g. whether to loop forever, and a delay time in between frames. Finally, PNG might take arguments for color type and bits per channel.
When the parameter-specific arguments are numerous relative to the other
arguments, create a command group for the action, with separate subcommands
named after each of the parameter values. In the example above, we would thus
gcloud images format command group, with subcommands called
png. These subcommands can then take their own format-specific
$ gcloud images format jpeg --help $ gcloud images format jpeg --source-file=foo --destination-file=bar \ --dct-method=integer --smoothing=0.1 --subsampling=4:4:4 $ gcloud images format png --source-file=foo --destination-file=bar \ --color-type=0 --bits-per-channel=16 $ gcloud images format gif --source-file=foo --destination-file=bar \ --loop-forever --frame-delay=1ms
It’s possible that over time more and more arguments are added that are common
to all parameters. In that case, it may make sense to instead use a single
command that takes an argument for the parameter. This can be done without
breaking backward compatibility by making the parameter a positional argument.
In the example above, this would involve changing
format from a command group
to a command, and having it take a positional image format argument which can be
png. Note that all the example commands above would
still function identically (except for the first one, but since it only affects
help text it’s not considered a breaking change.)
There are several other possibilities for the design of such a command, outlined below:
Single command with an explicit flag corresponding to the parameter
In the example, this would involve a
--type flag to specify the image format:
$ gcloud images format --source-file=source --destination-file=dest \ --type=JPEG --dct-method=integer --smoothing=0.1 --subsampling=4:4:4 $ gcloud images format --source-file=source --destination-file=dest \ --type=GIF --loop-forever --frame-delay=1ms $ gcloud images format --source-file=source --destination-file=dest \ --type=PNG --color-type=0 --bits-per-channel=16
Conceptually it makes the most sense to just have a single format command. However, this approach has several drawbacks:
- Unnecessary help text. The user will see all of the format-specific options, most of which will be irrelevant since they apply to different formats. In graphical image editing programs such as Photoshop or GIMP, the UI can selectively show these format-specific options once the user chooses the desired format from a dropdown. On the CLI, however, we have no such capability because the help text is statically generated.
- Additional logic needed for validation. Since some arguments will be invalid depending on the format, the command author needs to ensure specifying invalid combinations returns an appropriate error. While this can be accomplished with appropriately nested mutex groups, the nesting has the potential to become overly deep and complex.
Multiple commands named after the action hyphenated with the parameter value
In the example, this would look like:
$ gcloud images format-jpeg ... $ gcloud images format-gif ... $ gcloud images format-png ...
This is similar to the recommended design in that each parameter value gets its own command. However, there are disadvantages:
- Backward compatibility. If in the future it becomes desirable to make the parameter value an argument to a single command, this would necessitate a breaking change.
- It’s less elegant from a command tree layout perspective. Grouping the parameter-specific commands into a command group allows for a natural decomposition of the command space, in keeping with gcloud’s CLI design philosophy, and allows for progressive disclosure in the help text and in autocompletion.