Migrate to the new Cobra command grouping feature

This commit is contained in:
Mislav Marohnić 2022-11-09 16:42:13 +01:00
parent 8c0feacb71
commit b12ea845ef
No known key found for this signature in database
13 changed files with 103 additions and 130 deletions

View file

@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/cli/cli/v2/pkg/cmd/root"
"github.com/cpuguy83/go-md2man/v2/md2man"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -175,11 +176,8 @@ func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
buf := new(bytes.Buffer)
manPreamble(buf, header, cmd, dashCommandName)
for _, g := range subcommandGroups(cmd) {
if len(g.Commands) == 0 {
continue
}
fmt.Fprintf(buf, "# %s\n", strings.ToUpper(g.Name))
for _, g := range root.GroupedCommands(cmd) {
fmt.Fprintf(buf, "# %s\n", strings.ToUpper(g.Title))
for _, subcmd := range g.Commands {
fmt.Fprintf(buf, "`%s`\n: %s\n\n", manLink(subcmd), subcmd.Short)
}

View file

@ -8,6 +8,7 @@ import (
"path/filepath"
"strings"
"github.com/cli/cli/v2/pkg/cmd/root"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@ -99,11 +100,8 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
fmt.Fprintf(w, "%s\n\n", cmd.Long)
}
for _, g := range subcommandGroups(cmd) {
if len(g.Commands) == 0 {
continue
}
fmt.Fprintf(w, "### %s\n\n", g.Name)
for _, g := range root.GroupedCommands(cmd) {
fmt.Fprintf(w, "### %s\n\n", g.Title)
for _, subcmd := range g.Commands {
fmt.Fprintf(w, "* [%s](%s)\n", subcmd.CommandPath(), linkHandler(cmdManualPath(subcmd)))
}
@ -130,56 +128,6 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
return nil
}
type commandGroup struct {
Name string
Commands []*cobra.Command
}
// subcommandGroups lists child commands of a Cobra command split into groups.
// TODO: have rootHelpFunc use this instead of repeating the same logic.
func subcommandGroups(c *cobra.Command) []commandGroup {
var rest []*cobra.Command
var core []*cobra.Command
var actions []*cobra.Command
for _, subcmd := range c.Commands() {
if !subcmd.IsAvailableCommand() {
continue
}
if _, ok := subcmd.Annotations["IsCore"]; ok {
core = append(core, subcmd)
} else if _, ok := subcmd.Annotations["IsActions"]; ok {
actions = append(actions, subcmd)
} else {
rest = append(rest, subcmd)
}
}
if len(core) > 0 {
return []commandGroup{
{
Name: "Core commands",
Commands: core,
},
{
Name: "Actions commands",
Commands: actions,
},
{
Name: "Additional commands",
Commands: rest,
},
}
}
return []commandGroup{
{
Name: "Commands",
Commands: rest,
},
}
}
// GenMarkdownTree will generate a markdown page for this command and all
// descendants in the directory given. The header may be nil.
// This function may not work correctly if your command names have `-` in them.

View file

@ -14,11 +14,9 @@ import (
func NewCmdAuth(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "auth <command>",
Short: "Authenticate gh and git with GitHub",
Annotations: map[string]string{
"IsCore": "true",
},
Use: "auth <command>",
Short: "Authenticate gh and git with GitHub",
GroupID: "core",
}
cmdutil.DisableAuthCheck(cmd)

View file

@ -76,7 +76,6 @@ func NewCmdBrowse(f *cmdutil.Factory, runF func(*BrowseOptions) error) *cobra.Co
#=> Open main.go in the main branch
`),
Annotations: map[string]string{
"IsCore": "true",
"help:arguments": heredoc.Doc(`
A browser location can be specified using arguments in the following format:
- by number for issue or pull request, e.g. "123"; or
@ -86,6 +85,7 @@ func NewCmdBrowse(f *cmdutil.Factory, runF func(*BrowseOptions) error) *cobra.Co
To configure a web browser other than the default, use the BROWSER environment variable.
`),
},
GroupID: "core",
RunE: func(cmd *cobra.Command, args []string) error {
opts.BaseRepo = f.BaseRepo

View file

@ -18,13 +18,13 @@ func NewCmdGist(f *cmdutil.Factory) *cobra.Command {
Short: "Manage gists",
Long: `Work with GitHub gists.`,
Annotations: map[string]string{
"IsCore": "true",
"help:arguments": heredoc.Doc(`
A gist can be supplied as argument in either of the following formats:
- by ID, e.g. 5b0e0062eb8e9654adad7bb1d81cc75f
- by URL, e.g. "https://gist.github.com/OWNER/5b0e0062eb8e9654adad7bb1d81cc75f"
`),
},
GroupID: "core",
}
cmd.AddCommand(gistCloneCmd.NewCmdClone(f, nil))

View file

@ -30,13 +30,13 @@ func NewCmdIssue(f *cmdutil.Factory) *cobra.Command {
$ gh issue view 123 --web
`),
Annotations: map[string]string{
"IsCore": "true",
"help:arguments": heredoc.Doc(`
An issue can be supplied as argument in any of the following formats:
- by number, e.g. "123"; or
- by URL, e.g. "https://github.com/OWNER/REPO/issues/123".
`),
},
GroupID: "core",
}
cmdutil.EnableRepoOverride(cmd, f)

View file

@ -31,7 +31,6 @@ func NewCmdPR(f *cmdutil.Factory) *cobra.Command {
$ gh pr view --web
`),
Annotations: map[string]string{
"IsCore": "true",
"help:arguments": heredoc.Doc(`
A pull request can be supplied as argument in any of the following formats:
- by number, e.g. "123";
@ -39,6 +38,7 @@ func NewCmdPR(f *cmdutil.Factory) *cobra.Command {
- by the name of its head branch, e.g. "patch-1" or "OWNER:patch-1".
`),
},
GroupID: "core",
}
cmdutil.EnableRepoOverride(cmd, f)

View file

@ -15,11 +15,9 @@ import (
func NewCmdRelease(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "release <command>",
Short: "Manage releases",
Annotations: map[string]string{
"IsCore": "true",
},
Use: "release <command>",
Short: "Manage releases",
GroupID: "core",
}
cmdutil.EnableRepoOverride(cmd, f)

View file

@ -30,13 +30,13 @@ func NewCmdRepo(f *cmdutil.Factory) *cobra.Command {
$ gh repo view --web
`),
Annotations: map[string]string{
"IsCore": "true",
"help:arguments": heredoc.Doc(`
A repository can be supplied as an argument in any of the following formats:
- "OWNER/REPO"
- by URL, e.g. "https://github.com/OWNER/REPO"
`),
},
GroupID: "core",
}
cmd.AddCommand(repoViewCmd.NewCmdView(f, nil))

View file

@ -102,32 +102,6 @@ func rootHelpFunc(f *cmdutil.Factory, command *cobra.Command, args []string) {
}
namePadding := 12
coreCommands := []string{}
actionsCommands := []string{}
additionalCommands := []string{}
for _, c := range command.Commands() {
if c.Short == "" {
continue
}
if c.Hidden {
continue
}
s := rpad(c.Name()+":", namePadding) + c.Short
if _, ok := c.Annotations["IsCore"]; ok {
coreCommands = append(coreCommands, s)
} else if _, ok := c.Annotations["IsActions"]; ok {
actionsCommands = append(actionsCommands, s)
} else {
additionalCommands = append(additionalCommands, s)
}
}
// If there are no core commands, assume everything is a core command
if len(coreCommands) == 0 {
coreCommands = additionalCommands
additionalCommands = []string{}
}
type helpEntry struct {
Title string
@ -148,14 +122,16 @@ func rootHelpFunc(f *cmdutil.Factory, command *cobra.Command, args []string) {
helpEntries = append(helpEntries, helpEntry{"", longText})
}
helpEntries = append(helpEntries, helpEntry{"USAGE", command.UseLine()})
if len(coreCommands) > 0 {
helpEntries = append(helpEntries, helpEntry{"CORE COMMANDS", strings.Join(coreCommands, "\n")})
}
if len(actionsCommands) > 0 {
helpEntries = append(helpEntries, helpEntry{"ACTIONS COMMANDS", strings.Join(actionsCommands, "\n")})
}
if len(additionalCommands) > 0 {
helpEntries = append(helpEntries, helpEntry{"ADDITIONAL COMMANDS", strings.Join(additionalCommands, "\n")})
for _, g := range GroupedCommands(command) {
var names []string
for _, c := range g.Commands {
names = append(names, rpad(c.Name()+":", namePadding)+c.Short)
}
helpEntries = append(helpEntries, helpEntry{
Title: strings.ToUpper(g.Title),
Body: strings.Join(names, "\n"),
})
}
if isRootCmd(command) {
@ -225,6 +201,49 @@ func findCommand(cmd *cobra.Command, name string) *cobra.Command {
return nil
}
type CommandGroup struct {
Title string
Commands []*cobra.Command
}
func GroupedCommands(cmd *cobra.Command) []CommandGroup {
var res []CommandGroup
for _, g := range cmd.Groups() {
var cmds []*cobra.Command
for _, c := range cmd.Commands() {
if c.GroupID == g.ID && c.IsAvailableCommand() {
cmds = append(cmds, c)
}
}
if len(cmds) > 0 {
res = append(res, CommandGroup{
Title: g.Title,
Commands: cmds,
})
}
}
var cmds []*cobra.Command
for _, c := range cmd.Commands() {
if c.GroupID == "" && c.IsAvailableCommand() {
cmds = append(cmds, c)
}
}
if len(cmds) > 0 {
defaultGroupTitle := "Additional commands"
if len(cmd.Groups()) == 0 {
defaultGroupTitle = "Available commands"
}
res = append(res, CommandGroup{
Title: defaultGroupTitle,
Commands: cmds,
})
}
return res
}
// rpad adds padding to the right of a string.
func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds ", padding)

View file

@ -43,8 +43,6 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
Short: "GitHub CLI",
Long: `Work seamlessly with GitHub from the command line.`,
SilenceErrors: true,
SilenceUsage: true,
Example: heredoc.Doc(`
$ gh issue create
$ gh repo clone cli/cli
@ -61,15 +59,33 @@ func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
// cmd.SetOut(f.IOStreams.Out) // can't use due to https://github.com/spf13/cobra/issues/1708
// cmd.SetErr(f.IOStreams.ErrOut) // just let it default to os.Stderr instead
cmd.Flags().Bool("version", false, "Show gh version")
cmd.PersistentFlags().Bool("help", false, "Show help for command")
cmd.SetHelpFunc(func(c *cobra.Command, args []string) {
rootHelpFunc(f, c, args)
// override Cobra's default behaviors unless an opt-out has been set
if os.Getenv("GH_COBRA") == "" {
cmd.SilenceErrors = true
cmd.SilenceUsage = true
// this --version flag is checked in rootHelpFunc
cmd.Flags().Bool("version", false, "Show gh version")
cmd.SetHelpFunc(func(c *cobra.Command, args []string) {
rootHelpFunc(f, c, args)
})
cmd.SetUsageFunc(func(c *cobra.Command) error {
return rootUsageFunc(f.IOStreams.ErrOut, c)
})
cmd.SetFlagErrorFunc(rootFlagErrorFunc)
}
cmd.AddGroup(&cobra.Group{
ID: "core",
Title: "Core commands",
})
cmd.SetUsageFunc(func(c *cobra.Command) error {
return rootUsageFunc(f.IOStreams.ErrOut, c)
cmd.AddGroup(&cobra.Group{
ID: "actions",
Title: "GitHub Actions commands",
})
cmd.SetFlagErrorFunc(rootFlagErrorFunc)
// Child commands
cmd.AddCommand(versionCmd.NewCmdVersion(f, version, buildDate))
@ -158,7 +174,7 @@ func newCodespaceCmd(f *cmdutil.Factory) *cobra.Command {
cmd := codespaceCmd.NewRootCmd(app)
cmd.Use = "codespace"
cmd.Aliases = []string{"cs"}
cmd.Annotations = map[string]string{"IsCore": "true"}
cmd.GroupID = "core"
return cmd
}

View file

@ -13,12 +13,10 @@ import (
func NewCmdRun(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "run <command>",
Short: "View details about workflow runs",
Long: "List, view, and watch recent workflow runs from GitHub Actions.",
Annotations: map[string]string{
"IsActions": "true",
},
Use: "run <command>",
Short: "View details about workflow runs",
Long: "List, view, and watch recent workflow runs from GitHub Actions.",
GroupID: "actions",
}
cmdutil.EnableRepoOverride(cmd, f)

View file

@ -12,12 +12,10 @@ import (
func NewCmdWorkflow(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "workflow <command>",
Short: "View details about GitHub Actions workflows",
Long: "List, view, and run workflows in GitHub Actions.",
Annotations: map[string]string{
"IsActions": "true",
},
Use: "workflow <command>",
Short: "View details about GitHub Actions workflows",
Long: "List, view, and run workflows in GitHub Actions.",
GroupID: "actions",
}
cmdutil.EnableRepoOverride(cmd, f)