From 1300b2a3bdffaa7cb735c04e68f38c4b930d5906 Mon Sep 17 00:00:00 2001 From: Julia Ogris Date: Sat, 13 Feb 2021 18:37:59 +1100 Subject: [PATCH] options: Add kong.ShortUsageOnError() option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add kong.ShortUsageOnError() option similar to kong.UsageOnError(). Add tests for UsageOnError and ShortUsageOnError. Remove the HelpOption summary reset at the beginning of DefaultHelpPrinter: func DefaultHelpPrinter(options HelpOptions, ctx *Context) error { - if ctx.Empty() { - options.Summary = false - } ⚠️ I'm not really sure what the implications of this are, tests still seem to pass, but maybe this has unintended side effects. --- help.go | 15 ++++++++++ help_test.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++ kong.go | 27 ++++++++++++++---- options.go | 23 +++++++++++++++- 4 files changed, 137 insertions(+), 6 deletions(-) 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/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()) +} 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 }) }