swift-argument-parser icon indicating copy to clipboard operation
swift-argument-parser copied to clipboard

Proposal: Surface clearer help documentation around the default subcommand

Open bdrelling opened this issue 5 years ago • 8 comments

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 avatar May 14 '20 06:05 bdrelling

@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 the sync subcommand. Does that sound right?
  • With proposal 2, would you still want to include the (default) note at the beginning of the sync abstract?

natecook1000 avatar May 14 '20 14:05 natecook1000

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

kylemacomber avatar May 14 '20 21:05 kylemacomber

@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 avatar May 15 '20 03:05 bdrelling

@bdrelling Makes sense!

natecook1000 avatar May 16 '20 17:05 natecook1000

Looks like this was addressed by #183.

natecook1000 avatar Oct 01 '20 14:10 natecook1000

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

natecook1000 avatar Oct 01 '20 14:10 natecook1000

@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 @Argument and 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.

KS1019 avatar Jul 26 '21 10:07 KS1019

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

KS1019 avatar Nov 13 '21 01:11 KS1019