Handle maps

This commit is contained in:
Thomas Pelletier
2021-02-18 22:39:33 -05:00
parent 46573551f1
commit 629a2475a9
2 changed files with 116 additions and 60 deletions
+1
View File
@@ -2,6 +2,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+115 -60
View File
@@ -15,6 +15,34 @@ type fieldGetter func(s reflect.Value) reflect.Value
// collection of fieldGetters for a given struct type // collection of fieldGetters for a given struct type
type structFieldGetters map[string]fieldGetter type structFieldGetters map[string]fieldGetter
type target interface {
get() reflect.Value
set(value reflect.Value)
}
type valueTarget reflect.Value
func (v valueTarget) get() reflect.Value {
return reflect.Value(v)
}
func (v valueTarget) set(value reflect.Value) {
reflect.Value(v).Set(value)
}
type mapTarget struct {
index reflect.Value
m reflect.Value
}
func (v mapTarget) get() reflect.Value {
return v.m.MapIndex(v.index)
}
func (v mapTarget) set(value reflect.Value) {
v.m.SetMapIndex(v.index, value)
}
// Builder wraps a value and provides method to modify its structure. // Builder wraps a value and provides method to modify its structure.
// It is a stateful object that keeps a cursor of what part of the object is // It is a stateful object that keeps a cursor of what part of the object is
// being modified. // being modified.
@@ -23,7 +51,7 @@ type Builder struct {
root reflect.Value root reflect.Value
// Root is always a pointer to a non-nil value. // Root is always a pointer to a non-nil value.
// Cursor is the top of the stack. // Cursor is the top of the stack.
stack []reflect.Value stack []target
// Struct field tag to use to retrieve name. // Struct field tag to use to retrieve name.
nameTag string nameTag string
// Cache of functions to access specific fields. // Cache of functions to access specific fields.
@@ -117,17 +145,17 @@ func NewBuilder(tag string, v interface{}) (Builder, error) {
return Builder{ return Builder{
root: rv.Elem(), root: rv.Elem(),
stack: []reflect.Value{rv.Elem()}, stack: []target{valueTarget(rv.Elem())},
nameTag: tag, nameTag: tag,
}, nil }, nil
} }
func (b *Builder) top() reflect.Value { func (b *Builder) top() target {
return b.stack[len(b.stack)-1] return b.stack[len(b.stack)-1]
} }
func (b *Builder) push(v reflect.Value) { func (b *Builder) duplicate() {
b.stack = append(b.stack, v) b.stack = append(b.stack, b.stack[len(b.stack)-1])
// TODO: remove me. just here to make sure the method is included in the // TODO: remove me. just here to make sure the method is included in the
// binary for debug // binary for debug
b.Dump() b.Dump()
@@ -149,14 +177,14 @@ func (b *Builder) Dump() string {
if i > 0 { if i > 0 {
str.WriteString(" | ") str.WriteString(" | ")
} }
fmt.Fprintf(&str, "%s (%s)", x.Type(), x) fmt.Fprintf(&str, "%s", x)
} }
str.WriteByte(']') str.WriteByte(']')
return str.String() return str.String()
} }
func (b *Builder) replace(v reflect.Value) { func (b *Builder) replace(v target) {
b.stack[len(b.stack)-1] = v b.stack[len(b.stack)-1] = v
} }
@@ -165,27 +193,47 @@ func (b *Builder) replace(v reflect.Value) {
// Errors if the current value is not a struct, or the field does not exist. // Errors if the current value is not a struct, or the field does not exist.
func (b *Builder) DigField(s string) error { func (b *Builder) DigField(s string) error {
t := b.top() t := b.top()
v := t.get()
for t.Kind() == reflect.Interface || t.Kind() == reflect.Ptr { for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
t = t.Elem() if v.IsNil() {
thing := reflect.New(v.Type().Elem())
v.Set(thing)
}
v = v.Elem()
} }
err := checkKind(t.Type(), reflect.Struct) if v.Kind() == reflect.Map {
if err != nil { // if map is nil, allocate it
return err if v.IsNil() {
} v.Set(reflect.MakeMap(v.Type()))
}
g, err := b.fieldGetter(t.Type(), s) // TODO: handle error when map is not indexed by strings
if err != nil { key := reflect.ValueOf(s)
return FieldNotFoundError{FieldName: s, Struct: t}
}
f := g(t) b.replace(mapTarget{
if !f.IsValid() { index: key,
return FieldNotFoundError{FieldName: s, Struct: t} m: v,
} })
} else {
err := checkKind(v.Type(), reflect.Struct)
if err != nil {
return err
}
b.replace(f) g, err := b.fieldGetter(v.Type(), s)
if err != nil {
return FieldNotFoundError{FieldName: s, Struct: v}
}
f := g(v)
if !f.IsValid() {
return FieldNotFoundError{FieldName: s, Struct: v}
}
b.replace(valueTarget(f))
}
return nil return nil
} }
@@ -194,13 +242,13 @@ func (b *Builder) DigField(s string) error {
// It can be restored using Back(). // It can be restored using Back().
// Save points are stored as a stack. // Save points are stored as a stack.
func (b *Builder) Save() { func (b *Builder) Save() {
b.push(b.top()) b.duplicate()
} }
// Reset brings the cursor back to the root object. // Reset brings the cursor back to the root object.
func (b *Builder) Reset() { func (b *Builder) Reset() {
b.stack = b.stack[:1] b.stack = b.stack[:1]
b.stack[0] = b.root b.stack[0] = valueTarget(b.root)
} }
// Load is the opposite of Save. It discards the current cursor and loads the // Load is the opposite of Save. It discards the current cursor and loads the
@@ -215,15 +263,15 @@ func (b *Builder) Load() {
// Cursor returns the value pointed at by the cursor. // Cursor returns the value pointed at by the cursor.
func (b *Builder) Cursor() reflect.Value { func (b *Builder) Cursor() reflect.Value {
return b.top() return b.top().get()
} }
func (b *Builder) IsSlice() bool { func (b *Builder) IsSlice() bool {
return b.top().Kind() == reflect.Slice return b.top().get().Kind() == reflect.Slice
} }
func (b *Builder) IsSliceOrPtr() bool { func (b *Builder) IsSliceOrPtr() bool {
return b.top().Kind() == reflect.Slice || (b.top().Kind() == reflect.Ptr && b.top().Type().Elem().Kind() == reflect.Slice) return b.top().get().Kind() == reflect.Slice || (b.top().get().Kind() == reflect.Ptr && b.top().get().Type().Elem().Kind() == reflect.Slice)
} }
// Last moves the cursor to the last value of the current value. // Last moves the cursor to the last value of the current value.
@@ -235,7 +283,7 @@ func (b *Builder) Last() {
length := b.Cursor().Len() length := b.Cursor().Len()
if length > 0 { if length > 0 {
x := b.Cursor().Index(length - 1) x := b.Cursor().Index(length - 1)
b.replace(x) b.replace(valueTarget(x)) // TODO: create a "sliceTarget" ?
} }
} }
} }
@@ -244,12 +292,13 @@ func (b *Builder) Last() {
// Otherwise creates a new element in that slice and moves to it. // Otherwise creates a new element in that slice and moves to it.
func (b *Builder) SliceLastOrCreate() error { func (b *Builder) SliceLastOrCreate() error {
t := b.top() t := b.top()
err := checkKind(t.Type(), reflect.Slice) v := t.get()
err := checkKind(v.Type(), reflect.Slice)
if err != nil { if err != nil {
return err return err
} }
if t.Len() == 0 { if v.Len() == 0 {
return b.SliceNewElem() return b.SliceNewElem()
} }
b.Last() b.Last()
@@ -261,14 +310,15 @@ func (b *Builder) SliceLastOrCreate() error {
// object. // object.
func (b *Builder) SliceNewElem() error { func (b *Builder) SliceNewElem() error {
t := b.top() t := b.top()
err := checkKind(t.Type(), reflect.Slice) v := t.get()
err := checkKind(v.Type(), reflect.Slice)
if err != nil { if err != nil {
return err return err
} }
elem := reflect.New(t.Type().Elem()) elem := reflect.New(v.Type().Elem())
newSlice := reflect.Append(t, elem.Elem()) newSlice := reflect.Append(v, elem.Elem())
t.Set(newSlice) v.Set(newSlice)
b.replace(t.Index(t.Len() - 1)) b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget"?
return nil return nil
} }
@@ -278,37 +328,38 @@ func assertPtr(v reflect.Value) {
} }
} }
func (b *Builder) SliceAppend(v reflect.Value) error { func (b *Builder) SliceAppend(value reflect.Value) error {
assertPtr(v) assertPtr(value)
t := b.top() t := b.top()
v := t.get()
// pointer to a slice // pointer to a slice
if t.Kind() == reflect.Ptr { if v.Kind() == reflect.Ptr {
// if the pointer is nil we need to allocate the slice // if the pointer is nil we need to allocate the slice
if t.IsNil() { if v.IsNil() {
x := reflect.New(t.Type().Elem()) x := reflect.New(v.Type().Elem())
t.Set(x) v.Set(x)
} }
// target the slice itself // target the slice itself
t = t.Elem() v = v.Elem()
} }
err := checkKind(t.Type(), reflect.Slice) err := checkKind(v.Type(), reflect.Slice)
if err != nil { if err != nil {
return err return err
} }
if t.Type().Elem().Kind() == reflect.Ptr { if v.Type().Elem().Kind() == reflect.Ptr {
// if it is a slice of pointers, we can just append // if it is a slice of pointers, we can just append
} else { } else {
// otherwise we need to reference the value // otherwise we need to reference the value
v = v.Elem() value = value.Elem()
} }
newSlice := reflect.Append(t, v) newSlice := reflect.Append(v, value)
t.Set(newSlice) v.Set(newSlice)
b.replace(t.Index(t.Len() - 1)) b.replace(valueTarget(v.Index(v.Len() - 1))) // TODO: "sliceTarget" ?
return nil return nil
} }
@@ -316,61 +367,65 @@ func (b *Builder) SliceAppend(v reflect.Value) error {
// Errors if a string cannot be assigned to the current value. // Errors if a string cannot be assigned to the current value.
func (b *Builder) SetString(s string) error { func (b *Builder) SetString(s string) error {
t := b.top() t := b.top()
v := t.get()
if t.Kind() == reflect.Ptr { if v.Kind() == reflect.Ptr {
t.Set(reflect.ValueOf(&s)) v.Set(reflect.ValueOf(&s))
} else { } else {
err := checkKind(t.Type(), reflect.String) err := checkKind(v.Type(), reflect.String)
if err != nil { if err != nil {
return err return err
} }
t.SetString(s) v.SetString(s)
} }
return nil return nil
} }
// Set the value at the cursor to the given boolean. // Set the value at the cursor to the given boolean.
// Errors if a boolean cannot be assigned to the current value. // Errors if a boolean cannot be assigned to the current value.
func (b *Builder) SetBool(v bool) error { func (b *Builder) SetBool(value bool) error {
t := b.top() t := b.top()
v := t.get()
err := checkKind(t.Type(), reflect.Bool) err := checkKind(v.Type(), reflect.Bool)
if err != nil { if err != nil {
return err return err
} }
t.SetBool(v) v.SetBool(value)
return nil return nil
} }
func (b *Builder) SetFloat(n float64) error { func (b *Builder) SetFloat(n float64) error {
t := b.top() t := b.top()
v := t.get()
err := checkKindFloat(t.Type()) err := checkKindFloat(v.Type())
if err != nil { if err != nil {
return err return err
} }
t.SetFloat(n) v.SetFloat(n)
return nil return nil
} }
func (b *Builder) SetInt(n int64) error { func (b *Builder) SetInt(n int64) error {
t := b.top() t := b.top()
v := t.get()
err := checkKindInt(t.Type()) err := checkKindInt(v.Type())
if err != nil { if err != nil {
return err return err
} }
t.SetInt(n) v.SetInt(n)
return nil return nil
} }
func (b *Builder) Set(v reflect.Value) error { func (b *Builder) Set(v reflect.Value) error {
t := b.top() t := b.top()
t.Set(v) t.set(v)
return nil return nil
} }