Better help command

This commit is contained in:
Corey Johnson 2020-06-04 12:51:26 -07:00
parent ee2b38df68
commit 4d182c4314
3 changed files with 133 additions and 99 deletions

117
command/help.go Normal file
View file

@ -0,0 +1,117 @@
package command
import (
"fmt"
"strings"
"github.com/cli/cli/utils"
"github.com/spf13/cobra"
)
func rootDisplayCommandTypoHelp(command *cobra.Command, args []string) {
if command != RootCmd {
// Display helpful error message in case subcommand name was mistyped.
// This matches Cobra's behavior for root command, which Cobra
// confusingly doesn't apply to nested commands.
if command.Parent() == RootCmd && len(args) >= 2 {
if command.SuggestionsMinimumDistance <= 0 {
command.SuggestionsMinimumDistance = 2
}
candidates := command.SuggestionsFor(args[1])
errOut := command.OutOrStderr()
fmt.Fprintf(errOut, "unknown command %q for %q\n", args[1], "gh "+args[0])
if len(candidates) > 0 {
fmt.Fprint(errOut, "\nDid you mean this?\n")
for _, c := range candidates {
fmt.Fprintf(errOut, "\t%s\n", c)
}
fmt.Fprint(errOut, "\n")
}
oldOut := command.OutOrStdout()
command.SetOut(errOut)
defer command.SetOut(oldOut)
}
}
}
func rootHelpFunc(command *cobra.Command, args []string) {
rootDisplayCommandTypoHelp(command, args)
coreCommands := []string{}
additionalCommands := []string{}
for _, c := range command.Commands() {
if c.Short == "" {
continue
}
s := rpad(c.Name()+":", c.NamePadding()) + c.Short
if _, ok := c.Annotations["IsCore"]; ok {
coreCommands = append(coreCommands, 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
Body string
}
helpEntries := []helpEntry{
{"", command.Long},
{"USAGE", command.Use},
}
if len(coreCommands) > 0 {
helpEntries = append(helpEntries, helpEntry{"CORE COMMANDS", strings.Join(coreCommands, "\n")})
}
if len(additionalCommands) > 0 {
helpEntries = append(helpEntries, helpEntry{"ADDITIONAL COMMANDS", strings.Join(additionalCommands, "\n")})
}
if command.HasLocalFlags() {
helpEntries = append(helpEntries, helpEntry{"FLAGS", strings.TrimRight(command.LocalFlags().FlagUsages(), "\n")})
}
if _, ok := command.Annotations["help:examples"]; ok {
helpEntries = append(helpEntries, helpEntry{"EXAMPLES", command.Annotations["help:examples"]})
}
if _, ok := command.Annotations["help:learnmore"]; ok {
helpEntries = append(helpEntries, helpEntry{"LEARN MORE", command.Annotations["help:learnmore"]})
}
if _, ok := command.Annotations["help:feedback"]; ok {
helpEntries = append(helpEntries, helpEntry{"FEEDBACK", command.Annotations["help:feedback"]})
}
out := colorableOut(command)
for _, e := range helpEntries {
if e.Title != "" {
// If there is a title, add indentation to each line in the body
fmt.Fprintln(out, utils.Bold(e.Title))
for _, l := range strings.Split(e.Body, "\n") {
l = strings.Trim(l, " \n\r")
if l == "" {
continue
}
fmt.Fprintln(out, " "+l)
}
} else {
// If there is no title print the body as is
fmt.Fprintln(out, e.Body)
}
fmt.Fprintln(out)
}
}
// rpad adds padding to the right of a string.
func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds ", padding)
return fmt.Sprintf(template, s)
}

View file

@ -56,9 +56,10 @@ A pull request can be supplied as argument in any of the following formats:
- by the name of its head branch, e.g. "patch-1" or "OWNER:patch-1".`,
}
var prListCmd = &cobra.Command{
Use: "list",
Short: "List and filter pull requests in this repository",
RunE: prList,
Use: "list",
Short: "List and filter pull requests in this repository",
RunE: prList,
Annotations: map[string]string{"IsCore": "true"},
}
var prStatusCmd = &cobra.Command{
Use: "status",

View file

@ -34,7 +34,6 @@ var Version = "DEV"
var BuildDate = "" // YYYY-MM-DD
var versionOutput = ""
var cobraDefaultHelpFunc func(*cobra.Command, []string)
func init() {
if Version == "DEV" {
@ -58,7 +57,6 @@ func init() {
// TODO:
// RootCmd.PersistentFlags().BoolP("verbose", "V", false, "enable verbose output")
cobraDefaultHelpFunc = RootCmd.HelpFunc()
RootCmd.SetHelpFunc(rootHelpFunc)
RootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
@ -95,6 +93,18 @@ var RootCmd = &cobra.Command{
SilenceErrors: true,
SilenceUsage: true,
Annotations: map[string]string{
"help:examples": `
$ gh issue create
$ gh repo clone
$ gh pr checkout 321`,
"help:learnmore": `
Use "gh <command> <subcommand> --help" for more information about a command.
Read the manual at <http://cli.github.com/manual>`,
"help:feedback": `
Fill out our feedback form <https://forms.gle/umxd3h31c7aMQFKG7>
Open an issue using gh issue create -R cli/cli`},
}
var versionCmd = &cobra.Command{
@ -320,100 +330,6 @@ func determineBaseRepo(apiClient *api.Client, cmd *cobra.Command, ctx context.Co
return baseRepo, nil
}
func rootHelpFunc(command *cobra.Command, args []string) {
if command != RootCmd {
// Display helpful error message in case subcommand name was mistyped.
// This matches Cobra's behavior for root command, which Cobra
// confusingly doesn't apply to nested commands.
if command.Parent() == RootCmd && len(args) >= 2 {
if command.SuggestionsMinimumDistance <= 0 {
command.SuggestionsMinimumDistance = 2
}
candidates := command.SuggestionsFor(args[1])
errOut := command.OutOrStderr()
fmt.Fprintf(errOut, "unknown command %q for %q\n", args[1], "gh "+args[0])
if len(candidates) > 0 {
fmt.Fprint(errOut, "\nDid you mean this?\n")
for _, c := range candidates {
fmt.Fprintf(errOut, "\t%s\n", c)
}
fmt.Fprint(errOut, "\n")
}
oldOut := command.OutOrStdout()
command.SetOut(errOut)
defer command.SetOut(oldOut)
}
cobraDefaultHelpFunc(command, args)
return
}
type helpEntry struct {
Title string
Body string
}
coreCommandNames := []string{"issue", "pr", "repo"}
var coreCommands []string
var additionalCommands []string
for _, c := range command.Commands() {
if c.Short == "" {
continue
}
s := " " + rpad(c.Name()+":", c.NamePadding()) + c.Short
if includes(coreCommandNames, c.Name()) {
coreCommands = append(coreCommands, s)
} else if c != creditsCmd {
additionalCommands = append(additionalCommands, s)
}
}
helpEntries := []helpEntry{
{
"",
command.Long},
{"USAGE", command.Use},
{"CORE COMMANDS", strings.Join(coreCommands, "\n")},
{"ADDITIONAL COMMANDS", strings.Join(additionalCommands, "\n")},
{"FLAGS", strings.TrimRight(command.LocalFlags().FlagUsages(), "\n")},
{"EXAMPLES", `
$ gh issue create
$ gh repo clone
$ gh pr checkout 321`},
{"LEARN MORE", `
Use "gh <command> <subcommand> --help" for more information about a command.
Read the manual at <http://cli.github.com/manual>`},
{"FEEDBACK", `
Fill out our feedback form <https://forms.gle/umxd3h31c7aMQFKG7>
Open an issue using gh issue create -R cli/cli`},
}
out := colorableOut(command)
for _, e := range helpEntries {
if e.Title != "" {
fmt.Fprintln(out, utils.Bold(e.Title))
}
fmt.Fprintln(out, strings.TrimLeft(e.Body, "\n")+"\n")
}
}
// rpad adds padding to the right of a string.
func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds ", padding)
return fmt.Sprintf(template, s)
}
func includes(a []string, s string) bool {
for _, x := range a {
if x == s {
return true
}
}
return false
}
func formatRemoteURL(cmd *cobra.Command, fullRepoName string) string {
ctx := contextForCommand(cmd)