github.com/elves/elvish@v0.15.0/pkg/eval/vals/struct_map.go (about) 1 package vals 2 3 import ( 4 "reflect" 5 "sync" 6 7 "github.com/elves/elvish/pkg/strutil" 8 ) 9 10 // StructMap may be implemented by a struct to mark the struct as a "struct 11 // map", which causes Elvish to treat it like a read-only map. Each exported, 12 // named field and getter method (a method taking no argument and returning one 13 // value) becomes a field of the map, with the name mapped to dash-case. 14 // 15 // The following operations are derived for structmaps: Kind, Repr, Hash, Len, 16 // Index, HasKey and IterateKeys. 17 // 18 // Example: 19 // 20 // type someStruct struct { 21 // FooBar int 22 // lorem string 23 // } 24 // 25 // func (someStruct) IsStructMap() { } 26 // 27 // func (s SomeStruct) Ipsum() string { return s.lorem } 28 // 29 // func (s SomeStruct) OtherMethod(int) { } 30 // 31 // An instance of someStruct behaves like a read-only map with 3 fields: 32 // foo-bar, lorem and ipsum. 33 type StructMap interface{ IsStructMap() } 34 35 // PseudoStructMap may be implemented by a type to derive the Repr, Index, 36 // HasKey and IterateKeys operations from the struct map returned by the Fields 37 // method. 38 type PseudoStructMap interface{ Fields() StructMap } 39 40 // Keeps cached information about a structMap. 41 type structMapInfo struct { 42 filledFields int 43 plainFields int 44 // Dash-case names for all fields. The first plainFields elements 45 // corresponds to all the plain fields, while the rest corresponds to getter 46 // fields. May contain empty strings if the corresponding field is not 47 // reflected onto the structMap (i.e. unexported fields, unexported methods 48 // and non-getter methods). 49 fieldNames []string 50 } 51 52 var structMapInfos sync.Map 53 54 // Gets the structMapInfo associated with a type, caching the result. 55 func getStructMapInfo(t reflect.Type) structMapInfo { 56 if info, ok := structMapInfos.Load(t); ok { 57 return info.(structMapInfo) 58 } 59 info := makeStructMapInfo(t) 60 structMapInfos.Store(t, info) 61 return info 62 } 63 64 func makeStructMapInfo(t reflect.Type) structMapInfo { 65 n := t.NumField() 66 m := t.NumMethod() 67 fieldNames := make([]string, n+m) 68 filledFields := 0 69 70 for i := 0; i < n; i++ { 71 field := t.Field(i) 72 if field.PkgPath == "" && !field.Anonymous { 73 fieldNames[i] = strutil.CamelToDashed(field.Name) 74 filledFields++ 75 } 76 } 77 78 for i := 0; i < m; i++ { 79 method := t.Method(i) 80 if method.PkgPath == "" && method.Type.NumIn() == 1 && method.Type.NumOut() == 1 { 81 fieldNames[i+n] = strutil.CamelToDashed(method.Name) 82 filledFields++ 83 } 84 } 85 86 return structMapInfo{filledFields, n, fieldNames} 87 } 88 89 type structMapIterator struct { 90 info structMapInfo 91 i int 92 } 93 94 func iterateStructMap(t reflect.Type) *structMapIterator { 95 return &structMapIterator{getStructMapInfo(t), -1} 96 } 97 98 func (it *structMapIterator) Next() bool { 99 fields := it.info.fieldNames 100 if it.i >= len(fields) { 101 return false 102 } 103 104 it.i++ 105 for it.i < len(fields) && fields[it.i] == "" { 106 it.i++ 107 } 108 return it.i < len(fields) 109 } 110 111 func (it *structMapIterator) Get(v reflect.Value) (string, interface{}) { 112 name := it.info.fieldNames[it.i] 113 if it.i < it.info.plainFields { 114 return name, v.Field(it.i).Interface() 115 } 116 method := v.Method(it.i - it.info.plainFields) 117 return name, method.Call(nil)[0].Interface() 118 }