Skip to content

Navigation Menu

Sign in
Sign up

Should Document extend Command? #70

Answered by patriksvensson
boblail asked this question in Q&A
Discussion options

The root CLI is a command.

Note

Most CLI frameworks model it this way:

  • alecthomas/kong (Go): the root CLI and commands are all modeled as Go structs using Kong's grammar
  • oclif/oclif (TS): the root CLI and commands are all modeled as classes that extend Command
  • spf13/cobra (Go): the root command and subcommands are all instances of *cobra.Command
  • clap-rs/clap (Rust): the root and subcommands are all clap::Command values composed into a tree
  • remkop/picocli (Java): the root command and subcommands are all @Command-annotated classes/objects
  • commander-js/commander.js (JS): the "program" and its subcommands are all Command objects
  • CLIUtils/CLI11 (C++): the root app and subcommands are all CLI::App objects (subcommands are Apps too!)

There are exceptions —

  • urfave/cli (Go): the root is a cli.App that contains []cli.Command
  • dotnet/command-line-api (C#): the root is a RootCommand rather than just Command
  • pallets/click (Python): the root is typically a click.Group rather than a plain click.Command

— but even among these, it's typical for the root command to extend the branch command (System.CommandLine.RootCommand extends System.CommandLine.Command and click.Group extends click.Command)

Both Document and Command have fields for arguments, options, commands, examples, etc. However Document puts its name and description under info.

CliInfo subtly diverges from Command:

  • Why does CliInfo have a title but Command has a name?
    • Can we settle on name?
  • Why does CliInfo have a summary and a description but Command only has a description?
    • How do summary and description differ?
    • Is Command's description intended to be the one-line string displayed in a menu of commands?

Command's hidden flag is perhaps meaningless for the root command, but other than that, I think the spec would be strengthened by modeling Document as a superset of Command.

You must be logged in to vote

Replies: 4 comments 9 replies

Comment options

Here's how this change might look: #74

You must be logged in to vote
0 replies
Comment options

I agree that the current form is awkward (and I've implemented code against the spec). But the solution I would propose would be:

  • Still have a Document Object with its info, conventions, etc.
  • But the Document Object should have a required command field that is a single Command Object and this Command Object should be the root of the specification.

It think this will allow for important meta information for the spec as a whole document but remove the awkwardness of have two object that basically need to understood identically in processing code.

You must be logged in to vote
1 reply
Comment options

I'm OK with this approach too.

To be clear, you're proposing something like moving arguments, options, commands, exitCodes, examples, interactive, and metadata off of Document and replacing them with a field, root, of type Command?

Would you pull title, summary, and description out of CliInfo as well, in favor of name, summary, description on Command?

Comment options

Yes, that is it:

Document would be come:

Field Name Type Default Value Description
opencli string - REQUIRED The OpenCLI version number
info CliInfo Object - REQUIRED Information about the CLI
conventions Conventions Object - The conventions used by the CLI
command Command Object - REQUIRED Root command
metadata [Metadata Object] - Custom metadata

And all the removed parts would be put in that root Command Object.

As for the overlap between between the data in the Info Object and the meta fields in that root Command Object could be discussed. I could see an argument that they are different. The Info Object services to provide info about the CLI as a wholistic application, where as the Command Object are there for the root level command. But it might seem to be splitting hairs. So here is a proposal.

CliInfo Object

Field Name Type Default Value Description
title string - REQUIRED The application title
summary string - A short summary of the application
contact Contact Object - The contact information
license License Object - The application license
version string - REQUIRED The application version

changes:

  • removed description, seem redundant to summary.

semantics:

  • title is the given title to the CLI tool. The title don't have to be the sequence of characters that are entered into the terminal to execute the command. Sometimes I see a tool that has a long title but they use a shorter sequence for the actual command (the most well known example that I know of is Maven vs its command mvn)
  • summary is a short description of the tool could differ from the description in the root command or could be the same. I don't think it really matters. Arg parse tool makers could just have them bee the same.

@boblail does that makes sense?

Another thing we could consider is the remove the Info Object and inline them into the Document Object since we have change it shape and it is now mostly meta fields container.

You must be logged in to vote
8 replies
Comment options

I have nothing against this, but would the command name be required then? Is there some way that we can express that all commands must have a name, unless it's the root command?

Comment options

@patriksvensson, yes the command name would still be required. The root would basically be the root command. So for example, the tool is kubectl the root command name is kubectl, it would then have a list of sub-commands like get, apply, delete, etc. The info object title would be a separate bit of metadata. It would not have to actually match the in terminal command. For example maven could be used in the title since it is the title of the software package (eg. sudo yum install maven) but the in terminal command is mvn (that is what you type in your shell). So the root command name would be mvn.

Like I said, I'm writing TUI that uses opencli and this change would make things much more ergonomic. (I'm basically doing this under the hood in my code. I convert the Document object to a Command Object and process just the Command Object and it works very well).

Here is an example:

---
"$schema": https://json-schema.org/draft/2020-12/schema
"$id": OpenCLI_kubectl.json
opencli: '0.1'
info:
 title: kubectl
 version: 1.29.0
 summary: Command-line tool for controlling Kubernetes clusters
 description: kubectl controls Kubernetes clusters. You can deploy applications,
 inspect and manage cluster resources, and view logs.
 contact:
 name: Kubernetes Contributors
 url: https://kubernetes.io/docs/reference/kubectl/
 email: kubernetes-dev@googlegroups.com
 license:
 name: Apache License 2.0
 identifier: Apache-2.0
conventions:
 groupOptions: true
 optionSeparator: " "
command:
 name: kubectl
 options:
 - name: "--kubeconfig"
 description: Path to the kubeconfig file to use
 - name: "--context"
 description: The name of the kubeconfig context to use
 commands:
 - name: get
 description: Display one or many resources
 commands:
 - name: send
 description: Test Sub-Sub-Command
 arguments:
 - name: resource
 required: true
 description: The RESOURCE type argument tells kubectl get what kind of Kubernetes objects to get (list and/or describe), such as pods, services, or deployments.
 - name: name
 required: false
 options:
 - name: "--namespace"
 description: If present, the namespace scope for this CLI request
 arguments:
 - name: "NAMESPACE"
 description: the namespace scope for this CLI request
 - name: "--output"
 aliases:
 - "-o"
 description: 'Output format. One of: json|yaml|wide'
 arguments:
 - name: "FORMAT"
 acceptedValues:
 - "json"
 - "yaml"
 - "wide"
 - name: "--output"
 aliases:
 - "-o"
 description: 'Output format. One of: json|yaml|wide'
 - name: "--output2"
 aliases:
 - "-o"
 description: 'Output format. One of: json|yaml|wide'
 - name: "--output3"
 aliases:
 - "-o"
 description: 'Output format. One of: json|yaml|wide'
 - name: "--output4"
 aliases:
 - "-o"
 description: 'Output format. One of: json|yaml|wide'
 - name: "--output5"
 aliases:
 - "-o"
 description: 'Output format. One of: json|yaml|wide'
 - name: "--output6"
 aliases:
 - "-o"
 description: 'Output format. One of: json|yaml|wide'
 - name: apply
 description: Apply a configuration to a resource by filename or stdin
 options:
 - name: "-f"
 description: Filename or directory to apply
 required: true
 - name: delete
 description: Delete resources by filenames, stdin, resources and names, or by label
 selector
 arguments:
 - name: resource
 required: true
 - name: name
 required: false
 examples:
 - kubectl get pods
 - kubectl apply -f deployment.yaml
 - kubectl delete pod nginx
 interactive: false
Comment options

@patriksvensson, does @devin-fisher's response sound reasonable to you?

It feels like we're 90% aligned here and can land this change with only a little more effort!!

Comment options

@boblail @devin-fisher Sorry for the radio silence. It has been some hectic weeks for me, and I wanted to respond when I had some dedicated time.

Yes, I think that this approach sounds reasonable. It's not perfect, but I understand where you all are coming from, and I think this makes sense.

Btw, completely random, I've set up a OpenCli group in the Spectre.Console Discord server: https://discord.com/invite/DxqCxpmA2K

Comment options

Created issue #86

Comment options

Implemented in #85

You must be logged in to vote
0 replies
Answer selected by patriksvensson
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /