Add Trace() function.

This commit is contained in:
Alec Thomas
2018-05-25 00:05:04 -04:00
parent 93e21885b1
commit 0bb304449c
5 changed files with 50 additions and 14 deletions
+4 -9
View File
@@ -33,7 +33,7 @@ func build(ast interface{}) (app *Application, err error) {
Decoder: kindDecoders[reflect.Bool], 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 { if len(node.Positional) > 0 && len(node.Children) > 0 {
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast) 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), "-") 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{} node := &Node{}
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(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")) tag := parseTag(fv, ft.Tag.Get("kong"))
if !cmd {
cmd = tag.Cmd
}
// Nested structs are either commands or args. // 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) buildChild(node, v, ft, fv, tag, name, seenFlags)
} else { } else {
buildField(node, v, ft, fv, tag, name, seenFlags) 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) { 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 child.Help = tag.Help
// A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that // 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.
}) })
} }
} }
+3 -1
View File
@@ -20,6 +20,7 @@ type ParseTrace struct {
type ParseContext struct { type ParseContext struct {
Trace []*ParseTrace // A trace through parsed nodes. Trace []*ParseTrace // A trace through parsed nodes.
Error error // Error that occurred during trace, if any.
command []string // Full command path. command []string // Full command path.
flags []*Flag // Accumulated available flags. flags []*Flag // Accumulated available flags.
@@ -40,7 +41,8 @@ func Trace(args []string, app *Application) (*ParseContext, error) {
if err != nil { if err != nil {
return nil, err 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. // FlagValue returns the set value of a flag, if it was encountered and exists.
+1 -1
View File
@@ -9,7 +9,7 @@ const defaultHelp = `{{- with .Application -}}
usage: {{.Name}} usage: {{.Name}}
{{.Help}} {{.Help}}
{{range .Flags}} {{range .Context.Flags}}
--{{.Name}} --{{.Name}}
{{end}} {{end}}
+18
View File
@@ -69,12 +69,30 @@ func (k *Kong) Parse(args []string) (command string, err error) {
if err != nil { if err != nil {
return "", err return "", err
} }
if ctx.Error != nil {
return "", ctx.Error
}
if value := ctx.FlagValue(k.Model.HelpFlag); value.IsValid() && value.Bool() { if value := ctx.FlagValue(k.Model.HelpFlag); value.IsValid() && value.Bool() {
return "", nil return "", nil
} }
return ctx.Apply() 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{}) { func (k *Kong) Errorf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, k.Model.Name+": "+format, args...) fmt.Fprintf(os.Stderr, k.Model.Name+": "+format, args...)
} }
+24 -3
View File
@@ -321,12 +321,20 @@ func TestHelp(t *testing.T) {
require.NotEqual(t, "hello", cli.Flag) 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) { func TestDuplicateFlag(t *testing.T) {
var cli struct { var cli struct {
Flag bool Flag bool
Cmd struct { Cmd struct {
Flag bool Flag bool
} } `kong:"cmd"`
} }
_, err := New(&cli) _, err := New(&cli)
require.Error(t, err) require.Error(t, err)
@@ -336,11 +344,24 @@ func TestDuplicateFlagOnPeerCommandIsOkay(t *testing.T) {
var cli struct { var cli struct {
Cmd1 struct { Cmd1 struct {
Flag bool Flag bool
} } `kong:"cmd"`
Cmd2 struct { Cmd2 struct {
Flag bool Flag bool
} } `kong:"cmd"`
} }
_, err := New(&cli) _, err := New(&cli)
require.NoError(t, err) 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)
}