mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-04 10:57:50 +08:00
6992c0b693
Also make file:pipe output a struct map rather than a pseudomap.
139 lines
3.6 KiB
Go
139 lines
3.6 KiB
Go
package vals
|
|
|
|
import (
|
|
"reflect"
|
|
"sync"
|
|
|
|
"src.elv.sh/pkg/strutil"
|
|
)
|
|
|
|
// StructMap may be implemented by a struct to make it accessible to Elvish code
|
|
// as a map. Each exported, named field and getter method (a method taking no
|
|
// argument and returning one value) becomes a field of the map, with the name
|
|
// mapped to dash-case.
|
|
//
|
|
// Struct maps are indistinguishable from normal maps for Elvish code. The
|
|
// operations Kind, Repr, Hash, Equal, Len, Index, HasKey and IterateKeys handle
|
|
// struct maps consistently with maps; the Assoc and Dissoc operations convert
|
|
// struct maps to maps.
|
|
//
|
|
// Example:
|
|
//
|
|
// type someStruct struct {
|
|
// // Provides the "foo-bar" field
|
|
// FooBar int
|
|
// lorem string
|
|
// }
|
|
//
|
|
// // Marks someStruct as a struct map
|
|
// func (someStruct) IsStructMap() { }
|
|
//
|
|
// // Provides the "ipsum" field
|
|
// func (s SomeStruct) Ipsum() string { return s.lorem }
|
|
//
|
|
// // Not a getter method; doesn't provide any field
|
|
// func (s SomeStruct) OtherMethod(int) { }
|
|
type StructMap interface{ IsStructMap() }
|
|
|
|
func promoteToMap(v StructMap) Map {
|
|
m := EmptyMap
|
|
for it := iterateStructMap(v); it.HasElem(); it.Next() {
|
|
m = m.Assoc(it.Elem())
|
|
}
|
|
return m
|
|
}
|
|
|
|
// PseudoMap may be implemented by a type to support map-like introspection. The
|
|
// Repr, Index, HasKey and IterateKeys operations handle pseudo maps.
|
|
type PseudoMap interface{ Fields() StructMap }
|
|
|
|
// Keeps cached information about a structMap.
|
|
type structMapInfo struct {
|
|
filledFields int
|
|
plainFields int
|
|
// Dash-case names for all fields. The first plainFields elements
|
|
// corresponds to all the plain fields, while the rest corresponds to getter
|
|
// fields. May contain empty strings if the corresponding field is not
|
|
// reflected onto the structMap (i.e. unexported fields, unexported methods
|
|
// and non-getter methods).
|
|
fieldNames []string
|
|
}
|
|
|
|
var structMapInfos sync.Map
|
|
|
|
// Gets the structMapInfo associated with a type, caching the result.
|
|
func getStructMapInfo(t reflect.Type) structMapInfo {
|
|
if info, ok := structMapInfos.Load(t); ok {
|
|
return info.(structMapInfo)
|
|
}
|
|
info := makeStructMapInfo(t)
|
|
structMapInfos.Store(t, info)
|
|
return info
|
|
}
|
|
|
|
func makeStructMapInfo(t reflect.Type) structMapInfo {
|
|
n := t.NumField()
|
|
m := t.NumMethod()
|
|
fieldNames := make([]string, n+m)
|
|
filledFields := 0
|
|
|
|
for i := 0; i < n; i++ {
|
|
field := t.Field(i)
|
|
if field.PkgPath == "" && !field.Anonymous {
|
|
fieldNames[i] = strutil.CamelToDashed(field.Name)
|
|
filledFields++
|
|
}
|
|
}
|
|
|
|
for i := 0; i < m; i++ {
|
|
method := t.Method(i)
|
|
if method.PkgPath == "" && method.Type.NumIn() == 1 && method.Type.NumOut() == 1 {
|
|
fieldNames[i+n] = strutil.CamelToDashed(method.Name)
|
|
filledFields++
|
|
}
|
|
}
|
|
|
|
return structMapInfo{filledFields, n, fieldNames}
|
|
}
|
|
|
|
type structMapIterator struct {
|
|
m reflect.Value
|
|
info structMapInfo
|
|
index int
|
|
}
|
|
|
|
func iterateStructMap(m StructMap) *structMapIterator {
|
|
it := &structMapIterator{reflect.ValueOf(m), getStructMapInfo(reflect.TypeOf(m)), 0}
|
|
it.fixIndex()
|
|
return it
|
|
}
|
|
|
|
func (it *structMapIterator) fixIndex() {
|
|
fieldNames := it.info.fieldNames
|
|
for it.index < len(fieldNames) && fieldNames[it.index] == "" {
|
|
it.index++
|
|
}
|
|
}
|
|
|
|
func (it *structMapIterator) Elem() (any, any) {
|
|
return it.elem()
|
|
}
|
|
|
|
func (it *structMapIterator) elem() (string, any) {
|
|
name := it.info.fieldNames[it.index]
|
|
if it.index < it.info.plainFields {
|
|
return name, it.m.Field(it.index).Interface()
|
|
}
|
|
method := it.m.Method(it.index - it.info.plainFields)
|
|
return name, method.Call(nil)[0].Interface()
|
|
}
|
|
|
|
func (it *structMapIterator) HasElem() bool {
|
|
return it.index < len(it.info.fieldNames)
|
|
}
|
|
|
|
func (it *structMapIterator) Next() {
|
|
it.index++
|
|
it.fixIndex()
|
|
}
|