Bubble errors instead of panicking (#194)

This commit is contained in:
Simon Frei
2021-08-19 10:00:53 +02:00
committed by GitHub
parent f547618963
commit 5a9c9c7864
7 changed files with 90 additions and 85 deletions
+51 -28
View File
@@ -12,7 +12,6 @@ import (
type Plugins []interface{}
func build(k *Kong, ast interface{}) (app *Application, err error) {
defer catch(&err)
v := reflect.ValueOf(ast)
iv := reflect.Indirect(v)
if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct {
@@ -25,7 +24,10 @@ func build(k *Kong, ast interface{}) (app *Application, err error) {
for _, flag := range extraFlags {
seenFlags[flag.Name] = true
}
node := buildNode(k, iv, ApplicationNode, seenFlags)
node, err := buildNode(k, iv, ApplicationNode, seenFlags)
if err != nil {
return nil, err
}
if len(node.Positional) > 0 && len(node.Children) > 0 {
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
}
@@ -46,12 +48,15 @@ type flattenedField struct {
tag *Tag
}
func flattenedFields(v reflect.Value) (out []flattenedField) {
func flattenedFields(v reflect.Value) (out []flattenedField, err error) {
v = reflect.Indirect(v)
for i := 0; i < v.NumField(); i++ {
ft := v.Type().Field(i)
fv := v.Field(i)
tag := parseTag(v, ft)
tag, err := parseTag(v, ft)
if err != nil {
return nil, err
}
if tag.Ignored {
continue
}
@@ -67,11 +72,18 @@ func flattenedFields(v reflect.Value) (out []flattenedField) {
fv = fv.Elem()
} else if fv.Type() == reflect.TypeOf(Plugins{}) {
for i := 0; i < fv.Len(); i++ {
out = append(out, flattenedFields(fv.Index(i).Elem())...)
fields, ferr := flattenedFields(fv.Index(i).Elem())
if ferr != nil {
return nil, ferr
}
out = append(out, fields...)
}
continue
}
sub := flattenedFields(fv)
sub, err := flattenedFields(fv)
if err != nil {
return nil, err
}
for _, subf := range sub {
// Assign parent if it's not already set.
if subf.tag.Group == "" {
@@ -84,16 +96,20 @@ func flattenedFields(v reflect.Value) (out []flattenedField) {
}
out = append(out, sub...)
}
return out
return out, nil
}
func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node {
func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool) (*Node, error) {
node := &Node{
Type: typ,
Target: v,
Tag: newEmptyTag(),
}
for _, field := range flattenedFields(v) {
fields, err := flattenedFields(v)
if err != nil {
return nil, err
}
for _, field := range fields {
ft := field.field
fv := field.value
@@ -111,9 +127,12 @@ func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool
if tag.Arg {
typ = ArgumentNode
}
buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
err = buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
} else {
buildField(k, node, v, ft, fv, tag, name, seenFlags)
err = buildField(k, node, v, ft, fv, tag, name, seenFlags)
}
if err != nil {
return nil, err
}
}
@@ -129,18 +148,21 @@ func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool
last := true
for i, p := range node.Positional {
if !last && p.Required {
fail("argument %q can not be required after an optional", p.Name)
return nil, fmt.Errorf("argument %q can not be required after an optional", p.Name)
}
last = p.Required
p.Position = i
}
return node
return node, nil
}
func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
child := buildNode(k, fv, typ, seenFlags)
func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error {
child, err := buildNode(k, fv, typ, seenFlags)
if err != nil {
return err
}
child.Name = name
child.Tag = tag
child.Parent = node
@@ -157,10 +179,10 @@ func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.S
// a positional argument is provided to the child, and move it to the branching argument field.
if tag.Arg {
if len(child.Positional) == 0 {
failField(v, ft, "positional branch must have at least one child positional argument named %q", name)
return failField(v, ft, "positional branch must have at least one child positional argument named %q", name)
}
if child.Positional[0].Name != name {
failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name)
return failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name)
}
child.Argument = child.Positional[0]
@@ -170,24 +192,26 @@ func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.S
}
} else if tag.Default != "" {
if node.DefaultCmd != nil {
failField(v, ft, "can't have more than one default command under %s", node.Summary())
return failField(v, ft, "can't have more than one default command under %s", node.Summary())
}
if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) {
failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary())
return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary())
}
node.DefaultCmd = child
}
node.Children = append(node.Children, child)
if len(child.Positional) > 0 && len(child.Children) > 0 {
failField(v, ft, "can't mix positional arguments and branching arguments")
return failField(v, ft, "can't mix positional arguments and branching arguments")
}
return nil
}
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error {
mapper := k.registry.ForNamedValue(tag.Type, fv)
if mapper == nil {
failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type)
return failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type)
}
value := &Value{
@@ -210,16 +234,14 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
node.Positional = append(node.Positional, value)
} else {
if seenFlags["--"+value.Name] {
failField(v, ft, "duplicate flag --%s", value.Name)
} else {
seenFlags["--"+value.Name] = true
return failField(v, ft, "duplicate flag --%s", value.Name)
}
seenFlags["--"+value.Name] = true
if tag.Short != 0 {
if seenFlags["-"+string(tag.Short)] {
failField(v, ft, "duplicate short flag -%c", tag.Short)
} else {
seenFlags["-"+string(tag.Short)] = true
return failField(v, ft, "duplicate short flag -%c", tag.Short)
}
seenFlags["-"+string(tag.Short)] = true
}
flag := &Flag{
Value: value,
@@ -233,6 +255,7 @@ func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv
value.Flag = flag
node.Flags = append(node.Flags, flag)
}
return nil
}
func buildGroupForKey(k *Kong, key string) *Group {