Repository: alecthomas/kong
Pull request #143 (2021-02-13 07:44:29)

options: Add kong.ShortUsageOnError() option
Add `kong.ShortUsageOnError()` option similar to `kong.UsageOnError()`.
For a custom short usage message use `kong.ShortHelp(kong.HelpPrinter)`
Add tests for UsageOnError and ShortUsageOnError.



juliaogris: @alecthomas - I've given it another pass; I've added the usageOnError enum as suggested and I've moved away from (ab)using `HelpOptions.Summary` as an indicator for `Short|FullUsage` message and instead introduced a `ShortHelpOption` and `DefaultShortHelpPrinter`. It is still a little awkward because the `ShortHelp` option doesn't really make a lot of sense without `ShortUsageOnError` - but maybe this is closer to acceptable?
juliaogris: I've added changes according to last comments and squashed the fixup-commits into a single commit.

Patch:
diff --git a/help.go b/help.go
index f08f1a1..ab66ab0 100644
--- a/help.go
+++ b/help.go
@@ -87,6 +87,21 @@ func DefaultHelpValueFormatter(value *Value) string {
 	}
 }
 
+// DefaultShortHelpPrinter is the default HelpPrinter for short help on error.
+func DefaultShortHelpPrinter(options HelpOptions, ctx *Context) error {
+	w := newHelpWriter(ctx, options)
+	cmd := ctx.Selected()
+	app := ctx.Model
+	if cmd == nil {
+		w.Printf("Usage: %s%s", app.Name, app.Summary())
+		w.Printf(`Run "%s --help" for more information.`, app.Name)
+	} else {
+		w.Printf("Usage: %s %s", app.Name, cmd.Summary())
+		w.Printf(`Run "%s --help" for more information.`, cmd.FullPath())
+	}
+	return w.Write(ctx.Stdout)
+}
+
 // DefaultHelpPrinter is the default HelpPrinter.
 func DefaultHelpPrinter(options HelpOptions, ctx *Context) error {
 	if ctx.Empty() {
diff --git a/kong.go b/kong.go
index 9b67db9..b59d61c 100644
--- a/kong.go
+++ b/kong.go
@@ -31,6 +31,13 @@ func Must(ast interface{}, options ...Option) *Kong {
 	return k
 }
 
+type usageOnError int
+
+const (
+	shortUsage usageOnError = iota + 1
+	fullUsage
+)
+
 // Kong is the main parser type.
 type Kong struct {
 	// Grammar model.
@@ -48,8 +55,9 @@ type Kong struct {
 	registry  *Registry
 
 	noDefaultHelp bool
-	usageOnError  bool
+	usageOnError  usageOnError
 	help          HelpPrinter
+	shortHelp     HelpPrinter
 	helpFormatter HelpValueFormatter
 	helpOptions   HelpOptions
 	helpFlag      *Flag
@@ -86,6 +94,10 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
 		k.help = DefaultHelpPrinter
 	}
 
+	if k.shortHelp == nil {
+		k.shortHelp = DefaultShortHelpPrinter
+	}
+
 	model, err := build(k, grammar)
 	if err != nil {
 		return k, err
@@ -331,10 +343,15 @@ func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
 		msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error()
 	}
 	// Maybe display usage information.
-	if err, ok := err.(*ParseError); ok && k.usageOnError {
-		options := k.helpOptions
-		_ = k.help(options, err.Context)
-		fmt.Fprintln(k.Stdout)
+	if err, ok := err.(*ParseError); ok {
+		switch k.usageOnError {
+		case fullUsage:
+			_ = k.help(k.helpOptions, err.Context)
+			fmt.Fprintln(k.Stdout)
+		case shortUsage:
+			_ = k.shortHelp(k.helpOptions, err.Context)
+			fmt.Fprintln(k.Stdout)
+		}
 	}
 	k.Fatalf("%s", msg)
 }
diff --git a/options.go b/options.go
index bdb57f6..ef83970 100644
--- a/options.go
+++ b/options.go
@@ -192,6 +192,17 @@ func Help(help HelpPrinter) Option {
 	})
 }
 
+// ShortHelp configures the short usage message.
+//
+// It should be used together with kong.ShortUsageOnError() to display a
+// custom short usage message on errors.
+func ShortHelp(shortHelp HelpPrinter) Option {
+	return OptionFunc(func(k *Kong) error {
+		k.shortHelp = shortHelp
+		return nil
+	})
+}
+
 // HelpFormatter configures how the help text is formatted.
 func HelpFormatter(helpFormatter HelpValueFormatter) Option {
 	return OptionFunc(func(k *Kong) error {
@@ -251,7 +262,17 @@ func ExplicitGroups(groups []Group) Option {
 // UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error.
 func UsageOnError() Option {
 	return OptionFunc(func(k *Kong) error {
-		k.usageOnError = true
+		k.usageOnError = fullUsage
+		return nil
+	})
+}
+
+// ShortUsageOnError configures Kong to display context-sensitive short
+// usage if FatalIfErrorf is called with an error. The default short
+// usage message can be overridden with kong.ShortHelp(...).
+func ShortUsageOnError() Option {
+	return OptionFunc(func(k *Kong) error {
+		k.usageOnError = shortUsage
 		return nil
 	})
 }


Test patch:
diff --git a/help_test.go b/help_test.go
index 0f0f2cb..6bf00b3 100644
--- a/help_test.go
+++ b/help_test.go
@@ -2,6 +2,7 @@ package kong_test
 
 import (
 	"bytes"
+	"fmt"
 	"strings"
 	"testing"
 
@@ -512,3 +513,80 @@ Group 2
 		require.Equal(t, expected, w.String())
 	})
 }
+
+func TestUsageOnError(t *testing.T) {
+	var cli struct {
+		Flag string `help:"A required flag." required`
+	}
+	w := &strings.Builder{}
+	p := mustNew(t, &cli,
+		kong.Writers(w, w),
+		kong.Description("Some description."),
+		kong.Exit(func(int) {}),
+		kong.UsageOnError(),
+	)
+	_, err := p.Parse([]string{})
+	p.FatalIfErrorf(err)
+
+	expected := `Usage: test --flag=STRING
+
+Some description.
+
+Flags:
+  -h, --help           Show context-sensitive help.
+      --flag=STRING    A required flag.
+
+test: error: missing flags: --flag=STRING
+`
+	require.Equal(t, expected, w.String())
+}
+
+func TestShortUsageOnError(t *testing.T) {
+	var cli struct {
+		Flag string `help:"A required flag." required`
+	}
+	w := &strings.Builder{}
+	p := mustNew(t, &cli,
+		kong.Writers(w, w),
+		kong.Description("Some description."),
+		kong.Exit(func(int) {}),
+		kong.ShortUsageOnError(),
+	)
+	_, err := p.Parse([]string{})
+	require.Error(t, err)
+	p.FatalIfErrorf(err)
+
+	expected := `Usage: test --flag=STRING
+Run "test --help" for more information.
+
+test: error: missing flags: --flag=STRING
+`
+	require.Equal(t, expected, w.String())
+}
+
+func TestCustomShortUsageOnError(t *testing.T) {
+	var cli struct {
+		Flag string `help:"A required flag." required`
+	}
+	w := &strings.Builder{}
+	shortHelp := func(_ kong.HelpOptions, ctx *kong.Context) error {
+		fmt.Fprintln(ctx.Stdout, "🤷 wish I could help")
+		return nil
+	}
+	p := mustNew(t, &cli,
+		kong.Writers(w, w),
+		kong.Description("Some description."),
+		kong.Exit(func(int) {}),
+		kong.ShortHelp(shortHelp),
+		kong.ShortUsageOnError(),
+	)
+	_, err := p.Parse([]string{})
+	require.Error(t, err)
+	p.FatalIfErrorf(err)
+
+	expected := `🤷 wish I could help
+
+test: error: missing flags: --flag=STRING
+`
+	require.Equal(t, expected, w.String())
+}