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  }