diff --git a/go.mod b/go.mod index 0f6c07d..b671051 100644 --- a/go.mod +++ b/go.mod @@ -2,20 +2,22 @@ module asciigoat.org/core go 1.19 -require github.com/mgechev/revive v1.3.3 +require ( + github.com/fatih/structtag v1.2.0 + github.com/mgechev/revive v1.3.3 + github.com/pkg/errors v0.9.1 +) require ( github.com/BurntSushi/toml v1.3.2 // indirect github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab // indirect github.com/fatih/color v1.15.0 // indirect - github.com/fatih/structtag v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pkg/errors v0.9.1 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/tools v0.12.0 // indirect ) diff --git a/reflection/reflect_help.go b/reflection/reflect_help.go new file mode 100644 index 0000000..e61da22 --- /dev/null +++ b/reflection/reflect_help.go @@ -0,0 +1,16 @@ +package reflection + +import ( + "fmt" + "reflect" +) + +func structFieldName(sf reflect.StructField) string { + if sf.Anonymous { + idx := sf.Index[len(sf.Index)-1] + + return fmt.Sprintf("%s-%v", "anonymous", idx) + } + + return sf.Name +} diff --git a/reflection/reflection.go b/reflection/reflection.go index 990b3b3..2f1a83e 100644 --- a/reflection/reflection.go +++ b/reflection/reflection.go @@ -9,6 +9,8 @@ import ( // of a value type Reflection struct { v reflect.Value + + types map[reflect.Type]TypeInfo } // Value returns the object it reflects @@ -26,7 +28,8 @@ func New(v any) (*Reflection, error) { } r := &Reflection{ - v: rv, + v: rv, + types: make(map[reflect.Type]TypeInfo), } if err := r.scan(); err != nil { diff --git a/reflection/scan.go b/reflection/scan.go index 60d84b6..8d2bf3e 100644 --- a/reflection/scan.go +++ b/reflection/scan.go @@ -1,5 +1,37 @@ package reflection -func (*Reflection) scan() error { +import ( + "reflect" +) + +func (r *Reflection) scanType(rt reflect.Type) error { + switch rt.Kind() { + case reflect.Pointer, reflect.Slice: + rt = rt.Elem() + } + + if _, ok := r.types[rt]; ok { + // cached + return nil + } + + ti, err := NewTypeInfo(rt) + if err != nil { + return &UnmarshalTypeError{Type: rt, Err: err} + } + + r.types[rt] = ti + + for i := range ti.fields { + err := r.scanType(ti.fields[i].sf.Type) + if err != nil { + return err + } + } + return nil } + +func (r *Reflection) scan() error { + return r.scanType(r.v.Type()) +} diff --git a/reflection/typeinfo.go b/reflection/typeinfo.go new file mode 100644 index 0000000..500461e --- /dev/null +++ b/reflection/typeinfo.go @@ -0,0 +1,108 @@ +package reflection + +import ( + "log" + "reflect" + + "github.com/fatih/structtag" + "github.com/pkg/errors" +) + +// TypeInfo ... +type TypeInfo struct { + rt reflect.Type + + fields []TypeInfoField +} + +// Type ... +func (ti *TypeInfo) Type() reflect.Type { + return ti.rt +} + +// Kind ... +func (ti *TypeInfo) Kind() reflect.Kind { + return ti.rt.Kind() +} + +// TypeInfoField ... +type TypeInfoField struct { + sf reflect.StructField + tags *structtag.Tags + + Name string +} + +// Type ... +func (tif *TypeInfoField) Type() reflect.Type { + return tif.sf.Type +} + +// Kind ... +func (tif *TypeInfoField) Kind() reflect.Kind { + return tif.sf.Type.Kind() +} + +// Tag ... +func (tif *TypeInfoField) Tag(key string) (*structtag.Tag, bool) { + if tif.tags != nil { + tag, _ := tif.tags.Get(key) + if tag != nil { + return tag, true + } + } + return nil, false +} + +// NewTypeInfo ... +func NewTypeInfo(rt reflect.Type) (TypeInfo, error) { + log.Printf("%s.%s: %s (%s)", "reflection", "NewTypeInfo", rt, rt.Kind()) + + ti := TypeInfo{ + rt: rt, + } + + err := ti.init() + return ti, err +} + +func (ti *TypeInfo) init() error { + switch ti.Kind() { + case reflect.Struct: + return ti.initStruct() + default: + return nil + } +} + +func (ti *TypeInfo) initStruct() error { + // load fields + n := ti.rt.NumField() + fields := make([]TypeInfoField, 0, n) + for i := 0; i < n; i++ { + sf := ti.rt.Field(i) + tags, err := structtag.Parse(string(sf.Tag)) + + if err != nil { + // tag parse error + err = &UnmarshalTypeError{ + Type: ti.rt, + Err: errors.Wrap(err, structFieldName(sf)), + } + return err + } + + if sf.IsExported() { + log.Printf("%s.%s: [%v]: %q %s (%s)", + "reflection", "NewTypeInfo", + i, structFieldName(sf), sf.Type, sf.Type.Kind()) + + fields = append(fields, TypeInfoField{ + sf: sf, + tags: tags, + }) + } + } + ti.fields = fields + return nil +}