diff --git a/help.go b/help.go index 8338ed3..809481e 100644 --- a/help.go +++ b/help.go @@ -18,7 +18,7 @@ usage: {{.Name}} var defaultHelpTemplate = template.Must(template.New("help").Parse(defaultHelp)) // Help returns a Hook that will display help and exit. -func Help(tmpl *template.Template, tmplctx map[string]interface{}) Hook { +func Help(tmpl *template.Template, tmplctx map[string]interface{}) HookFunction { return func(app *Kong, ctx *Context, trace *Trace) error { merged := map[string]interface{}{ "Application": app.Model, diff --git a/kong.go b/kong.go index d43623b..d166bb9 100644 --- a/kong.go +++ b/kong.go @@ -9,7 +9,7 @@ import ( "text/template" ) -type Hook func(app *Kong, ctx *Context, trace *Trace) error +type HookFunction func(app *Kong, ctx *Context, trace *Trace) error // Error reported by Kong. type Error struct{ msg string } @@ -37,10 +37,11 @@ type Kong struct { Stdout io.Writer Stderr io.Writer - help *template.Template - helpContext map[string]interface{} - helpFuncs template.FuncMap - hooks map[reflect.Value]Hook + help *template.Template + helpContext map[string]interface{} + helpFuncs template.FuncMap + hooks map[reflect.Value]HookFunction + noDefaultHelp bool } // New creates a new Kong parser into ast. @@ -52,7 +53,7 @@ func New(ast interface{}, options ...Option) (*Kong, error) { help: defaultHelpTemplate, helpContext: map[string]interface{}{}, helpFuncs: template.FuncMap{}, - hooks: map[reflect.Value]Hook{}, + hooks: map[reflect.Value]HookFunction{}, } model, err := build(ast) @@ -66,9 +67,28 @@ func New(ast interface{}, options ...Option) (*Kong, error) { option(k) } + if !k.noDefaultHelp { + k.integrateHelp() + } + return k, nil } +func (k *Kong) integrateHelp() { + helpValue := false + help := &Flag{ + Value: Value{ + Name: "help", + Help: "Show context-sensitive help.", + Flag: true, + Value: reflect.ValueOf(&helpValue).Elem(), + Decoder: kindDecoders[reflect.Bool], + }, + } + k.Model.Flags = append([]*Flag{help}, k.Model.Flags...) + Hook(&helpValue, Help(defaultHelpTemplate, nil))(k) +} + // Trace parses the command-line, validating and collecting matching grammar nodes. func (k *Kong) Trace(args []string) (*Context, error) { p := &Context{ @@ -89,12 +109,6 @@ func (k *Kong) Trace(args []string) (*Context, error) { return p, nil } -// Hook to execute when a command is encountered. -func (k *Kong) Hook(ptr interface{}, hook Hook) *Kong { - k.hooks[reflect.ValueOf(ptr)] = hook - return k -} - // Parse arguments into target. // // The returned "command" is a space separated path to the final selected command, if any. Commands appear as diff --git a/kong_test.go b/kong_test.go index cdfa1a9..29acfd6 100644 --- a/kong_test.go +++ b/kong_test.go @@ -7,11 +7,12 @@ import ( "github.com/stretchr/testify/require" ) -func mustNew(t *testing.T, cli interface{}) *Kong { +func mustNew(t *testing.T, cli interface{}, options ...Option) *Kong { t.Helper() - parser, err := New(cli, ExitFunction(func(int) { + options = append(options, ExitFunction(func(int) { t.Fatalf("unexpected exit()") })) + parser, err := New(cli, options...) require.NoError(t, err) return parser } @@ -381,19 +382,13 @@ func TestHooks(t *testing.T) { {"Flag", "one --three=three", values{true, "", "three"}}, {"ArgAndFlag", "one two --three=three", values{true, "two", "three"}}, } - p := mustNew(t, &cli). - Hook(&cli.One, func(app *Kong, ctx *Context, trace *Trace) error { - hooked.one = true - return nil - }). - Hook(&cli.One.Two, func(app *Kong, ctx *Context, trace *Trace) error { - hooked.two = trace.Value.String() - return nil - }). - Hook(&cli.One.Three, func(app *Kong, ctx *Context, trace *Trace) error { - hooked.three = trace.Value.String() - return nil - }) + setOne := func(app *Kong, ctx *Context, trace *Trace) error { hooked.one = true; return nil } + setTwo := func(app *Kong, ctx *Context, trace *Trace) error { hooked.two = trace.Value.String(); return nil } + setThree := func(app *Kong, ctx *Context, trace *Trace) error { hooked.three = trace.Value.String(); return nil } + p := mustNew(t, &cli, + Hook(&cli.One, setOne), + Hook(&cli.One.Two, setTwo), + Hook(&cli.One.Three, setThree)) for _, test := range tests { hooked = values{} diff --git a/options.go b/options.go index 17ef158..e573cea 100644 --- a/options.go +++ b/options.go @@ -2,6 +2,7 @@ package kong import ( "io" + "reflect" "text/template" ) @@ -12,6 +13,13 @@ func ExitFunction(exit func(int)) Option { return func(k *Kong) { k.Exit = exit } } +// NoDefaultHelp disables the default help flags. +func NoDefaultHelp() Option { + return func(k *Kong) { + k.noDefaultHelp = true + } +} + // Name overrides the application name. func Name(name string) Option { return func(k *Kong) { k.Model.Name = name } @@ -41,3 +49,14 @@ func Writers(stdout, stderr io.Writer) Option { k.Stderr = stderr } } + +// Hook to execute when a command, flag or positional argument is encountered. +func Hook(ptr interface{}, hook HookFunction) Option { + key := reflect.ValueOf(ptr) + if key.Kind() != reflect.Ptr { + panic("expected a pointer") + } + return func(k *Kong) { + k.hooks[key] = hook + } +}