Merge pull request #1106 from cli/h-e-l-p
Style all commands like the root help command
This commit is contained in:
commit
fb7fdc3045
5 changed files with 173 additions and 113 deletions
119
command/help.go
Normal file
119
command/help.go
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func rootHelpFunc(command *cobra.Command, args []string) {
|
||||
// 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 != RootCmd {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
coreCommands := []string{}
|
||||
additionalCommands := []string{}
|
||||
for _, c := range command.Commands() {
|
||||
if c.Short == "" {
|
||||
continue
|
||||
}
|
||||
if c.Hidden {
|
||||
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{}
|
||||
if command.Long != "" {
|
||||
helpEntries = append(helpEntries, helpEntry{"", command.Long})
|
||||
} else if command.Short != "" {
|
||||
helpEntries = append(helpEntries, helpEntry{"", command.Short})
|
||||
}
|
||||
helpEntries = append(helpEntries, helpEntry{"USAGE", command.UseLine()})
|
||||
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:arguments"]; ok {
|
||||
helpEntries = append(helpEntries, helpEntry{"ARGUMENTS", command.Annotations["help:arguments"]})
|
||||
}
|
||||
if command.Example != "" {
|
||||
helpEntries = append(helpEntries, helpEntry{"EXAMPLES", command.Example})
|
||||
}
|
||||
helpEntries = append(helpEntries, helpEntry{"LEARN MORE", `
|
||||
Use "gh <command> <subcommand> --help" for more information about a command.
|
||||
Read the manual at http://cli.github.com/manual`})
|
||||
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(strings.Trim(e.Body, "\n\r"), "\n") {
|
||||
l = strings.Trim(l, " \n\r")
|
||||
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)
|
||||
}
|
||||
|
|
@ -49,13 +49,17 @@ func init() {
|
|||
}
|
||||
|
||||
var issueCmd = &cobra.Command{
|
||||
Use: "issue",
|
||||
Use: "issue <command>",
|
||||
Short: "Create and view issues",
|
||||
Long: `Work with GitHub issues.
|
||||
|
||||
An issue can be supplied as argument in any of the following formats:
|
||||
Long: `Work with GitHub issues`,
|
||||
Example: `$ gh issue list
|
||||
$ gh issue create --fill
|
||||
$ gh issue view --web`,
|
||||
Annotations: map[string]string{
|
||||
"IsCore": "true",
|
||||
"help:arguments": `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".`,
|
||||
- by URL, e.g. "https://github.com/OWNER/REPO/issues/123".`},
|
||||
}
|
||||
var issueCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
|
|
|
|||
|
|
@ -46,19 +46,27 @@ func init() {
|
|||
}
|
||||
|
||||
var prCmd = &cobra.Command{
|
||||
Use: "pr",
|
||||
Use: "pr <command>",
|
||||
Short: "Create, view, and checkout pull requests",
|
||||
Long: `Work with GitHub pull requests.
|
||||
|
||||
A pull request can be supplied as argument in any of the following formats:
|
||||
Long: `Work with GitHub pull requests`,
|
||||
Example: `$ gh pr checkout 353
|
||||
$ gh pr checkout bug-fix-branch
|
||||
$ gh pr create --fill
|
||||
$ gh pr view --web`,
|
||||
Annotations: map[string]string{
|
||||
"IsCore": "true",
|
||||
"help:arguments": `A pull request can be supplied as argument in any of the following formats:
|
||||
- by number, e.g. "123";
|
||||
- by URL, e.g. "https://github.com/OWNER/REPO/pull/123"; or
|
||||
- by the name of its head branch, e.g. "patch-1" or "OWNER:patch-1".`,
|
||||
- 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,
|
||||
Example: `$ gh pr list --limit all
|
||||
$ gh pr list --state closed
|
||||
$ gh pr list --label “priority 1” “bug”`,
|
||||
RunE: prList,
|
||||
}
|
||||
var prStatusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
|
|
|
|||
|
|
@ -44,13 +44,19 @@ func init() {
|
|||
}
|
||||
|
||||
var repoCmd = &cobra.Command{
|
||||
Use: "repo",
|
||||
Use: "repo <command>",
|
||||
Short: "Create, clone, fork, and view repositories",
|
||||
Long: `Work with GitHub repositories.
|
||||
|
||||
Long: `Work with GitHub repositories`,
|
||||
Example: `$ gh repo create
|
||||
$ gh repo clone cli/cli
|
||||
$ gh repo view --web
|
||||
`,
|
||||
Annotations: map[string]string{
|
||||
"IsCore": "true",
|
||||
"help:arguments": `
|
||||
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"`,
|
||||
- by URL, e.g. "https://github.com/OWNER/REPO"`},
|
||||
}
|
||||
|
||||
var repoCloneCmd = &cobra.Command{
|
||||
|
|
@ -69,9 +75,18 @@ To pass 'git clone' flags, separate them with '--'.`,
|
|||
var repoCreateCmd = &cobra.Command{
|
||||
Use: "create [<name>]",
|
||||
Short: "Create a new repository",
|
||||
Long: `Create a new GitHub repository.
|
||||
Long: `Create a new GitHub repository`,
|
||||
Example: utils.Bold("$ gh repo create") + `
|
||||
Will create a repository on your account using the name of your current directory
|
||||
|
||||
Use the "ORG/NAME" syntax to create a repository within your organization.`,
|
||||
` + utils.Bold("$ gh repo create my-project") + `
|
||||
Will create a repository on your account using the name 'my-project'
|
||||
|
||||
` + utils.Bold("$ gh repo create cli/my-project") + `
|
||||
Will create a repository in the organization 'cli' using the name 'my-project'`,
|
||||
Annotations: map[string]string{"help:arguments": `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"`},
|
||||
RunE: repoCreate,
|
||||
}
|
||||
|
||||
|
|
|
|||
106
command/root.go
106
command/root.go
|
|
@ -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,9 +57,11 @@ func init() {
|
|||
// TODO:
|
||||
// RootCmd.PersistentFlags().BoolP("verbose", "V", false, "enable verbose output")
|
||||
|
||||
cobraDefaultHelpFunc = RootCmd.HelpFunc()
|
||||
RootCmd.SetHelpFunc(rootHelpFunc)
|
||||
|
||||
// This will silence the usage func on error
|
||||
RootCmd.SetUsageFunc(func(_ *cobra.Command) error { return nil })
|
||||
|
||||
RootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
|
||||
if err == pflag.ErrHelp {
|
||||
return err
|
||||
|
|
@ -95,6 +96,13 @@ var RootCmd = &cobra.Command{
|
|||
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
Example: `$ gh issue create
|
||||
$ gh repo clone
|
||||
$ gh pr checkout 321`,
|
||||
Annotations: map[string]string{
|
||||
"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 +328,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.Hidden {
|
||||
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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue