apple / swift-argument-parser

Straightforward, type-safe argument parsing for Swift
Apache License 2.0
3.34k stars 321 forks source link

Proposal: Surface clearer help documentation around the default subcommand #143

Open bdrelling opened 4 years ago

bdrelling commented 4 years ago

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.

natecook1000 commented 4 years ago

@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:

kylemacomber commented 4 years ago

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 the sync subcommand. Does that sound right?

+1

  • With proposal 2, would you still want to include the (default) note at the beginning of the sync abstract?

+1

bdrelling commented 4 years ago

@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?

natecook1000 commented 4 years ago

@bdrelling Makes sense!

natecook1000 commented 4 years ago

Looks like this was addressed by #183.

natecook1000 commented 4 years ago

Oh wait, only the (default) tag was added there — the rest is still tbd. Re-opening!

KS1019 commented 3 years ago

@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:


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] [ ...] math ARGUMENTS: 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 ' 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:

```swift 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 ] [--verbose] zinc OPTIONS: -f, --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 ' 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:

```swift 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 [] super [] ARGUMENTS: Required argument Non-required argument OPTIONS: -h, --help Show help information. SUBCOMMANDS: sub (default) subsub (default) See 'super help ' for detailed help. ```

and super sub --help outputs

``` USAGE: super sub [] super sub [] ARGUMENTS: Required argument Non-required argument OPTIONS: -h, --help Show help information. SUBCOMMANDS: subsub (default) See 'super help sub ' 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.

KS1019 commented 2 years ago

@natecook1000 Hi! I appreciate it if you could help me with my question above. Thank you in advance!