summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Flint <[email protected]>2020-01-24 14:30:29 -0800
committerAlex Flint <[email protected]>2020-01-24 14:30:29 -0800
commit711618869d25f9f1851bb18d4792c3f46ef9821b (patch)
tree0248ea715bc56f9884b1b7d0ec9cbcecfc75ca2b
parent7e2466d70798086a87f82713cfba49dae0af494f (diff)
fix issue with duplicate fields in embedded structs
-rw-r--r--go.mod2
-rw-r--r--parse.go36
-rw-r--r--parse_test.go17
3 files changed, 43 insertions, 12 deletions
diff --git a/go.mod b/go.mod
index c4c4879..14c6119 100644
--- a/go.mod
+++ b/go.mod
@@ -4,3 +4,5 @@ require (
github.com/alexflint/go-scalar v1.0.0
github.com/stretchr/testify v1.2.2
)
+
+go 1.13
diff --git a/parse.go b/parse.go
index db9c443..3fc82bd 100644
--- a/parse.go
+++ b/parse.go
@@ -19,23 +19,25 @@ var osExit = os.Exit
// path represents a sequence of steps to find the output location for an
// argument or subcommand in the final destination struct
type path struct {
- root int // index of the destination struct
- fields []string // sequence of struct field names to traverse
+ root int // index of the destination struct
+ fields []reflect.StructField // sequence of struct fields to traverse
}
// String gets a string representation of the given path
func (p path) String() string {
- if len(p.fields) == 0 {
- return "args"
+ s := "args"
+ for _, f := range p.fields {
+ s += "." + f.Name
}
- return "args." + strings.Join(p.fields, ".")
+ return s
}
// Child gets a new path representing a child of this path.
-func (p path) Child(child string) path {
+func (p path) Child(f reflect.StructField) path {
// copy the entire slice of fields to avoid possible slice overwrite
- subfields := make([]string, len(p.fields)+1)
- copy(subfields, append(p.fields, child))
+ subfields := make([]reflect.StructField, len(p.fields)+1)
+ copy(subfields, p.fields)
+ subfields[len(subfields)-1] = f
return path{
root: p.root,
fields: subfields,
@@ -151,11 +153,20 @@ type Described interface {
// walkFields calls a function for each field of a struct, recursively expanding struct fields.
func walkFields(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool) {
+ walkFieldsImpl(t, visit, nil)
+}
+
+func walkFieldsImpl(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool, path []int) {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
+ field.Index = append(path, i)
expand := visit(field, t)
if expand && field.Type.Kind() == reflect.Struct {
- walkFields(field.Type, visit)
+ var subpath []int
+ if field.Anonymous {
+ subpath = append(path, i)
+ }
+ walkFieldsImpl(field.Type, visit, subpath)
}
}
}
@@ -257,7 +268,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
}
// duplicate the entire path to avoid slice overwrites
- subdest := dest.Child(field.Name)
+ subdest := dest.Child(field)
spec := spec{
dest: subdest,
long: strings.ToLower(field.Name),
@@ -666,14 +677,15 @@ func (p *Parser) val(dest path) reflect.Value {
v = v.Elem()
}
- v = v.FieldByName(field)
- if !v.IsValid() {
+ next := v.FieldByIndex(field.Index)
+ if !next.IsValid() {
// it is appropriate to panic here because this can only happen due to
// an internal bug in this library (since we construct the path ourselves
// by reflecting on the same struct)
panic(fmt.Errorf("error resolving path %v: %v has no field named %v",
dest.fields, v.Type(), field))
}
+ v = next
}
return v
}
diff --git a/parse_test.go b/parse_test.go
index f75d1a7..ff521ae 100644
--- a/parse_test.go
+++ b/parse_test.go
@@ -910,6 +910,23 @@ func TestEmbeddedPtrIgnored(t *testing.T) {
assert.Equal(t, 321, args.Y)
}
+func TestEmbeddedWithDuplicateField(t *testing.T) {
+ // see https://github.com/alexflint/go-arg/issues/100
+ type T struct {
+ A string
+ }
+ type U struct {
+ A string
+ }
+ var args struct {
+ T
+ U
+ }
+
+ err := parse("", &args)
+ require.NoError(t, err)
+}
+
func TestEmptyArgs(t *testing.T) {
origArgs := os.Args