From 0bb304449c6524b2339555081eb2e4e709d9bb53 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Fri, 25 May 2018 00:05:04 -0400 Subject: [PATCH] Add Trace() function. --- build.go | 13 ++++--------- context.go | 4 +++- help.go | 2 +- kong.go | 18 ++++++++++++++++++ kong_test.go | 27 ++++++++++++++++++++++++--- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/build.go b/build.go index 36ffb4e..2eabbf9 100644 --- a/build.go +++ b/build.go @@ -33,7 +33,7 @@ func build(ast interface{}) (app *Application, err error) { Decoder: kindDecoders[reflect.Bool], }}, } - node := buildNode(iv, map[string]bool{"help": true}, true) + node := buildNode(iv, map[string]bool{"help": true}) if len(node.Positional) > 0 && len(node.Children) > 0 { return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast) } @@ -47,7 +47,7 @@ func dashedString(s string) string { return strings.Join(camelCase(s), "-") } -func buildNode(v reflect.Value, seenFlags map[string]bool, cmd bool) *Node { +func buildNode(v reflect.Value, seenFlags map[string]bool) *Node { node := &Node{} for i := 0; i < v.NumField(); i++ { ft := v.Type().Field(i) @@ -63,12 +63,8 @@ func buildNode(v reflect.Value, seenFlags map[string]bool, cmd bool) *Node { tag := parseTag(fv, ft.Tag.Get("kong")) - if !cmd { - cmd = tag.Cmd - } - // Nested structs are either commands or args. - if ft.Type.Kind() == reflect.Struct && (cmd || tag.Arg) { + if ft.Type.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) { buildChild(node, v, ft, fv, tag, name, seenFlags) } else { buildField(node, v, ft, fv, tag, name, seenFlags) @@ -94,7 +90,7 @@ func buildNode(v reflect.Value, seenFlags map[string]bool, cmd bool) *Node { } func buildChild(node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) { - child := buildNode(fv, seenFlags, false) + child := buildNode(fv, seenFlags) child.Help = tag.Help // A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that @@ -168,4 +164,3 @@ func buildField(node *Node, v reflect.Value, ft reflect.StructField, fv reflect. }) } } - diff --git a/context.go b/context.go index babbbaf..4047672 100644 --- a/context.go +++ b/context.go @@ -20,6 +20,7 @@ type ParseTrace struct { type ParseContext struct { Trace []*ParseTrace // A trace through parsed nodes. + Error error // Error that occurred during trace, if any. command []string // Full command path. flags []*Flag // Accumulated available flags. @@ -40,7 +41,8 @@ func Trace(args []string, app *Application) (*ParseContext, error) { if err != nil { return nil, err } - return p, p.trace(&p.app.Node) + p.Error = p.trace(&p.app.Node) + return p, nil } // FlagValue returns the set value of a flag, if it was encountered and exists. diff --git a/help.go b/help.go index b9e9625..9df8215 100644 --- a/help.go +++ b/help.go @@ -9,7 +9,7 @@ const defaultHelp = `{{- with .Application -}} usage: {{.Name}} {{.Help}} -{{range .Flags}} +{{range .Context.Flags}} --{{.Name}} {{end}} diff --git a/kong.go b/kong.go index 3df6c09..6498f69 100644 --- a/kong.go +++ b/kong.go @@ -69,12 +69,30 @@ func (k *Kong) Parse(args []string) (command string, err error) { if err != nil { return "", err } + if ctx.Error != nil { + return "", ctx.Error + } if value := ctx.FlagValue(k.Model.HelpFlag); value.IsValid() && value.Bool() { return "", nil } return ctx.Apply() } +// Trace through the command tree. +// +// The returned context will include a trace of all parsed objects encountered; flags, arguments, commands. +func (k *Kong) Trace(args []string) (ctx *ParseContext, err error) { + defer func() { + msg := recover() + if test, ok := msg.(Error); ok { + err = test + } else if msg != nil { + panic(msg) + } + }() + return Trace(args, k.Model) +} + func (k *Kong) Errorf(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, k.Model.Name+": "+format, args...) } diff --git a/kong_test.go b/kong_test.go index 77638c3..55f3947 100644 --- a/kong_test.go +++ b/kong_test.go @@ -321,12 +321,20 @@ func TestHelp(t *testing.T) { require.NotEqual(t, "hello", cli.Flag) } +func TestCommandMissingTagIsInvalid(t *testing.T) { + var cli struct { + One struct{} + } + _, err := New(&cli) + require.Error(t, err) +} + func TestDuplicateFlag(t *testing.T) { var cli struct { Flag bool Cmd struct { Flag bool - } + } `kong:"cmd"` } _, err := New(&cli) require.Error(t, err) @@ -336,11 +344,24 @@ func TestDuplicateFlagOnPeerCommandIsOkay(t *testing.T) { var cli struct { Cmd1 struct { Flag bool - } + } `kong:"cmd"` Cmd2 struct { Flag bool - } + } `kong:"cmd"` } _, err := New(&cli) require.NoError(t, err) } + +func TestTraceErrorPartiallySucceeds(t *testing.T) { + var cli struct { + One struct { + Two struct { + } `kong:"cmd"` + } `kong:"cmd"` + } + p := mustNew(t, &cli) + trace, err := p.Trace([]string{"one", "bad"}) + require.NoError(t, err) + require.Error(t, trace.Error) +}