Restructure how command usage is stored and displayed

- No more `c.Short` property. Instead, the first line of `c.Long`
  property is considered a short command description.

- The `c.Usage` text can now contain multiple lines.

- The new `c.Synopsis()` method renders usage synopsis for humans:

    Usage: hub my-command --arg
           hub my-command --alternative-arg

- The new `c.HelpText()` method renders synopsis + full help text.
This commit is contained in:
Mislav Marohnić 2016-01-24 19:47:17 +11:00
Родитель 9c9ff2d341
Коммит 1918e011a6
4 изменённых файлов: 22 добавлений и 92 удалений

Просмотреть файл

@ -103,7 +103,7 @@ func browse(command *Command, args *Args) {
}
if project == nil {
err := fmt.Errorf(command.FormattedUsage())
err := fmt.Errorf(command.Synopsis())
utils.Check(err)
}

Просмотреть файл

@ -1,7 +1,6 @@
package commands
import (
"bytes"
"fmt"
"strings"
@ -23,7 +22,6 @@ type Command struct {
Key string
Usage string
Short string
Long string
GitExtension bool
@ -52,7 +50,10 @@ func (c *Command) Call(args *Args) (err error) {
func (c *Command) parseArguments(args *Args) (err error) {
c.Flag.SetInterspersed(true)
c.Flag.Init(c.Name(), flag.ContinueOnError)
c.Flag.Usage = c.PrintUsage
c.Flag.Usage = func() {
ui.Errorln("")
ui.Errorln(c.Synopsis())
}
if err = c.Flag.Parse(args.Params); err == nil {
for _, arg := range args.Params {
if arg == "--" {
@ -72,53 +73,35 @@ func (c *Command) Use(subCommand *Command) {
c.subCommands[subCommand.Name()] = subCommand
}
func (c *Command) PrintUsage() {
if c.Runnable() {
ui.Printf("usage: %s\n\n", c.FormattedUsage())
}
func (c *Command) Synopsis() string {
lines := []string{}
usagePrefix := "Usage:"
ui.Println(strings.Trim(c.Long, "\n"))
for _, line := range strings.Split(c.Usage, "\n") {
if line != "" {
usage := fmt.Sprintf("%s hub %s", usagePrefix, line)
usagePrefix = " "
lines = append(lines, usage)
}
}
return strings.Join(lines, "\n")
}
func (c *Command) FormattedUsage() string {
return fmt.Sprintf("git %s", c.Usage)
}
func (c *Command) subCommandsUsage() string {
buffer := bytes.NewBufferString("")
usage := "usage"
usage = printUsageBuffer(c, buffer, usage)
for _, s := range c.subCommands {
usage = printUsageBuffer(s, buffer, usage)
}
return buffer.String()
}
func printUsageBuffer(c *Command, b *bytes.Buffer, usage string) string {
if c.Runnable() {
b.WriteString(fmt.Sprintf("%s: %s\n", usage, c.FormattedUsage()))
usage = " or"
}
return usage
func (c *Command) HelpText() string {
return fmt.Sprintf("%s\n\n%s", c.Synopsis(), strings.Replace(c.Long, "'", "`", -1))
}
func (c *Command) Name() string {
if c.Key != "" {
return c.Key
}
return strings.Split(c.Usage, " ")[0]
return strings.Split(strings.TrimSpace(c.Usage), " ")[0]
}
func (c *Command) Runnable() bool {
return c.Run != nil
}
func (c *Command) List() bool {
return c.Short != ""
}
func (c *Command) lookupSubCommand(args *Args) (runCommand *Command, err error) {
if len(c.subCommands) > 0 && args.HasSubcommand() {
subCommandName := args.FirstParam()
@ -126,7 +109,7 @@ func (c *Command) lookupSubCommand(args *Args) (runCommand *Command, err error)
runCommand = subCommand
args.Params = args.Params[1:]
} else {
err = fmt.Errorf("error: Unknown subcommand: %s\n%s", subCommandName, c.subCommandsUsage())
err = fmt.Errorf("error: Unknown subcommand: %s", subCommandName)
}
} else {
runCommand = c

Просмотреть файл

@ -87,41 +87,6 @@ func TestFlagsAfterArguments(t *testing.T) {
assert.Equal(t, "bar", args.LastParam())
}
func TestCommandUsageSubCommands(t *testing.T) {
f1 := func(c *Command, args *Args) {}
f2 := func(c *Command, args *Args) {}
c := &Command{Usage: "foo", Run: f1}
s := &Command{Key: "bar", Usage: "foo bar", Run: f2}
c.Use(s)
usage := c.subCommandsUsage()
expected := `usage: git foo
or: git foo bar
`
assert.Equal(t, expected, usage)
}
func TestCommandUsageSubCommandsPrintOnlyRunnables(t *testing.T) {
f1 := func(c *Command, args *Args) {}
c := &Command{Usage: "foo"}
s := &Command{Key: "bar", Usage: "foo bar", Run: f1}
c.Use(s)
usage := c.subCommandsUsage()
expected := `usage: git foo bar
`
assert.Equal(t, expected, usage)
}
func TestCommandNameTakeUsage(t *testing.T) {
c := &Command{Usage: "foo -t -v --foo"}
assert.Equal(t, "foo", c.Name())
}
func TestCommandNameTakeKey(t *testing.T) {
c := &Command{Key: "bar", Usage: "foo -t -v --foo"}
assert.Equal(t, "bar", c.Name())
@ -162,22 +127,3 @@ func TestSubCommandCall(t *testing.T) {
c.Call(args)
assert.Equal(t, "baz", result)
}
func TestSubCommandsUsage(t *testing.T) {
// with subcommand
f1 := func(c *Command, args *Args) {}
f2 := func(c *Command, args *Args) {}
c := &Command{Usage: "foo", Run: f1}
s := &Command{Key: "bar", Usage: "foo bar", Run: f2}
c.Use(s)
usage := c.subCommandsUsage()
assert.Equal(t, "usage: git foo\n or: git foo bar\n", usage)
// no subcommand
cc := &Command{Usage: "foo", Run: f1}
usage = cc.subCommandsUsage()
assert.Equal(t, "usage: git foo\n", usage)
}

Просмотреть файл

@ -8,6 +8,7 @@ import (
"github.com/github/hub/cmd"
"github.com/github/hub/git"
"github.com/github/hub/ui"
"github.com/github/hub/utils"
)
@ -45,7 +46,7 @@ func runHelp(helpCmd *Command, args *Args) {
c := CmdRunner.Lookup(command)
if c != nil && !c.GitExtension {
c.PrintUsage()
ui.Println(c.HelpText())
os.Exit(0)
} else if c == nil {
if args.HasFlags("-a", "--all") {