Proposal: Surface clearer help documentation around the default subcommand
Commands with default subcommands do not have discoverable usage information without digging into the subcommand itself. Below, I'll outline the structure of a simple project as well as two proposals for updating the help information for commands that have a default subcommand.
Simply put, a command with a default subcommand likely doesn't have any information it runs itself and, as such, should specify the information that is available by running the default subcommand.
Structure
For ease of clarity, repeating the structure of my project here:
Command: zinc
Subcommands: lint, sync
Default Subcommand: sync
Proposal 1 - Highlighting the Default Subcommand
Problem Statement
The output of zinc --help is such that the default subcommand is not made clear.
This is what prints:
SUBCOMMANDS:
lint Performs basic linting against a Zincfile to identify issues and errors.
sync Syncs local files with remote files as defined by a Zincfile.
Proposed Solution
I'm proposing we highlight the default subcommand somehow:
SUBCOMMANDS:
lint Performs basic linting against a Zincfile to identify issues and errors.
sync (default) Syncs local files with remote files as defined by a Zincfile.
Proposal 2 - Prefer Default Subcommand Usage
Problem Statement
If I specify a default subcommand in my project, while I would allow someone to run zinc sync <args>, specifying a default subcommand likely implies that I'm happy with zinc <args>. As such, I think that the output of <command> --help should be refined.
Right now, here's what prints out when I run zinc --help:
OVERVIEW: Zinc is a command-line tool for keeping local files in sync with files hosted outside of your folder or
repository.
USAGE: zinc <subcommand>
OPTIONS:
-h, --help Show help information.
SUBCOMMANDS:
lint Performs basic linting against a Zincfile to identify issues and errors.
sync Syncs local files with remote files as defined by a Zincfile.
Here is the output of zinc sync --help:
OVERVIEW: Syncs local files with remote files as defined by a Zincfile.
USAGE: zinc sync [--file <file>] [--verbose]
OPTIONS:
-f, --file <file> The Zincfile to parse and use for syncing. (default: Zincfile)
--verbose Logs additional debug messages if enabled.
-h, --help Show help information.
Proposed Solution
I'm not sure what the right answer is here, but I imagine we should see an example of the default command being run, along with additional subcommands still.
I'm not sold on this, but here's an example:
OVERVIEW: Zinc is a command-line tool for keeping local files in sync with files hosted outside of your folder or
repository.
USAGE:
zinc --file <file>
zinc <subcommand>
OPTIONS:
-f, --file <file> The Zincfile to parse and use for syncing. (default: Zincfile)
--verbose Logs additional debug messages if enabled.
-h, --help Show help information.
SUBCOMMANDS:
lint Performs basic linting against a Zincfile to identify issues and errors.
sync (default) Syncs local files with remote files as defined by a Zincfile.
Additional Notes
I'd like some additional feedback on the proposed solutions. I'm poking around at a variety of commands that I use day-to-day in my Swift projects as well as some outside of the Swift ecosystem and there's a variety of answers, but almost all at least provide some help documentation for a default subcommand within the help documentation of the command itself if requested.
@bdrelling Thanks for identifying this issue and writing up the proposals! I definitely agree that this is a hole we need to address. I really like the hybrid help screen that you’ve put together in proposal 2 — it looks like it accurately describes what you’re able to do when calling the command, which is how I’d evaluate a solution.
Two notes/questions:
- I think the first usage line would match that of
zinc sync --help, just without showing thesyncsubcommand. Does that sound right? - With proposal 2, would you still want to include the
(default)note at the beginning of thesyncabstract?
it looks like it accurately describes what you’re able to do when calling the command, which is how I’d evaluate a solution.
I agree this is a nice property.
- I think the first usage line would match that of
zinc sync --help, just without showing thesyncsubcommand. Does that sound right?
+1
- With proposal 2, would you still want to include the
(default)note at the beginning of thesyncabstract?
+1
@natecook1000 thanks for the quick feedback!
I think the first usage line would match that of zinc sync --help, just without showing the sync subcommand. Does that sound right?
Agreed with that, yep!
With proposal 2, would you still want to include the (default) note at the beginning of the sync abstract?
Definitely! I'm editing the initial description to add this in there as well. I was initially treating this as two independent proposals because I wasn't sure if both would be valuable. I can merge them together if that's easier to track?
@bdrelling Makes sense!
Looks like this was addressed by #183.
Oh wait, only the (default) tag was added there — the rest is still tbd. Re-opening!
@natecook1000 Hi, I was working on the second proposal and I got it somewhat working but I have some questions which I would like some help with.
Basically, my questions are:
- Do we have to consider a command with nested default subcommands or not?
- If yes, what should help text look like in such cases?
- Do we need to expect a command to have
@Argumentand default subcommand at the same depth?
Below are description of the status of my implementaion.
Currently, math command outputs help that looks like this with my changes.
OVERVIEW: A utility for performing maths.
USAGE:
math [--hex-output] [<values> ...]
math <subcommand>
ARGUMENTS:
<values> A group of integers to operate on.
OPTIONS:
-x, --hex-output Use hexadecimal notation for the result.
--version Show the version.
-h, --help Show help information.
SUBCOMMANDS:
add (default) Print the sum of the values.
multiply Print the product of the values.
stats Calculate descriptive statistics.
See 'math help <subcommand>' for detailed help.
In the USAGE, there are both math [--hex-output] [<values> ...] for default add subcommand and math <subcommand> for the other subcommands. There is additional -x, --hex-output Use hexadecimal notation for the result. for default add subcommand in the OPTIONS
I also replicate zinc command like this:
struct Zinc: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "zinc",
abstract: "Zinc is a command-line tool for keeping local files in sync with files hosted outside of your folder or repository.",
subcommands: [Lint.self, Sync.self],
defaultSubcommand: Sync.self)
struct Lint: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "lint",
abstract: "Performs basic linting against a Zincfile to identify issues and errors.")
}
struct Sync: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "sync",
abstract: "Syncs local files with remote files as defined by a Zincfile.")
@Option(name: [.short, .long], help: "The Zincfile to parse and use for syncing.")
var file: String = "Zincfile"
@Flag(help: "Logs additional debug messages if enabled.")
var verbose: Bool = false
}
}
and zinc --help outputs
OVERVIEW: Zinc is a command-line tool for keeping local files in sync with
files hosted outside of your folder or repository.
USAGE:
zinc [--file <file>] [--verbose]
zinc <subcommand>
OPTIONS:
-f, --file <file> The Zincfile to parse and use for syncing. (default:
Zincfile)
--verbose Logs additional debug messages if enabled.
-h, --help Show help information.
SUBCOMMANDS:
lint Performs basic linting against a Zincfile to identify
issues and errors.
sync (default) Syncs local files with remote files as defined by a
Zincfile.
See 'zinc help <subcommand>' for detailed help.
However, if a user has nested default subcommands, the help text becomes a bit crazy with my current chanages. Given this command:
struct SuperCommand: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "super",
subcommands: [Sub.self],
defaultSubcommand: Sub.self)
@Argument(help: "Required argument")
var requiredArgument: String
@Argument(help: "Non-required argument")
var nonRequiredArgument: String?
struct Sub: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "sub",
subcommands: [SubSub.self],
defaultSubcommand: SubSub.self)
@Argument(help: "Required argument")
var requiredArgumentSub: String
@Argument(help: "Non-required argument")
var nonRequiredArgumentSub: String?
struct SubSub: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "subsub")
@Argument(help: "Required argument")
var requiredArgumentSubSub: String
@Argument(help: "Non-required argument")
var nonRequiredArgumentSubSub: String?
}
}
}
the help text when running super --help becomes
USAGE:
super <required-argument-sub> [<non-required-argument-sub>]
super <required-argument> [<non-required-argument>] <subcommand>
ARGUMENTS:
<required-argument-sub> Required argument
<non-required-argument-sub>
Non-required argument
OPTIONS:
-h, --help Show help information.
SUBCOMMANDS:
sub (default)
subsub (default)
See 'super help <subcommand>' for detailed help.
and super sub --help outputs
USAGE:
super sub <required-argument-sub-sub> [<non-required-argument-sub-sub>]
super sub <required-argument-sub> [<non-required-argument-sub>] <subcommand>
ARGUMENTS:
<required-argument-sub-sub>
Required argument
<non-required-argument-sub-sub>
Non-required argument
OPTIONS:
-h, --help Show help information.
SUBCOMMANDS:
subsub (default)
See 'super help sub <subcommand>' for detailed help.
The USAGE lines are not useful because parsing does not work as expected. This might be an edge case that SAP does not support.
@natecook1000 Hi! I appreciate it if you could help me with my question above. Thank you in advance!