reflect.go raw

   1  package arg
   2  
   3  import (
   4  	"encoding"
   5  	"fmt"
   6  	"reflect"
   7  	"unicode"
   8  	"unicode/utf8"
   9  
  10  	scalar "github.com/alexflint/go-scalar"
  11  )
  12  
  13  var textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem()
  14  
  15  // cardinality tracks how many tokens are expected for a given spec
  16  //   - zero is a boolean, which does to expect any value
  17  //   - one is an ordinary option that will be parsed from a single token
  18  //   - multiple is a slice or map that can accept zero or more tokens
  19  type cardinality int
  20  
  21  const (
  22  	zero cardinality = iota
  23  	one
  24  	multiple
  25  	unsupported
  26  )
  27  
  28  func (k cardinality) String() string {
  29  	switch k {
  30  	case zero:
  31  		return "zero"
  32  	case one:
  33  		return "one"
  34  	case multiple:
  35  		return "multiple"
  36  	case unsupported:
  37  		return "unsupported"
  38  	default:
  39  		return fmt.Sprintf("unknown(%d)", int(k))
  40  	}
  41  }
  42  
  43  // cardinalityOf returns true if the type can be parsed from a string
  44  func cardinalityOf(t reflect.Type) (cardinality, error) {
  45  	if scalar.CanParse(t) {
  46  		if isBoolean(t) {
  47  			return zero, nil
  48  		}
  49  		return one, nil
  50  	}
  51  
  52  	// look inside pointer types
  53  	if t.Kind() == reflect.Ptr {
  54  		t = t.Elem()
  55  	}
  56  
  57  	// look inside slice and map types
  58  	switch t.Kind() {
  59  	case reflect.Slice:
  60  		if !scalar.CanParse(t.Elem()) {
  61  			return unsupported, fmt.Errorf("cannot parse into %v because %v not supported", t, t.Elem())
  62  		}
  63  		return multiple, nil
  64  	case reflect.Map:
  65  		if !scalar.CanParse(t.Key()) {
  66  			return unsupported, fmt.Errorf("cannot parse into %v because key type %v not supported", t, t.Elem())
  67  		}
  68  		if !scalar.CanParse(t.Elem()) {
  69  			return unsupported, fmt.Errorf("cannot parse into %v because value type %v not supported", t, t.Elem())
  70  		}
  71  		return multiple, nil
  72  	default:
  73  		return unsupported, fmt.Errorf("cannot parse into %v", t)
  74  	}
  75  }
  76  
  77  // isBoolean returns true if the type is a boolean or a pointer to a boolean
  78  func isBoolean(t reflect.Type) bool {
  79  	switch {
  80  	case isTextUnmarshaler(t):
  81  		return false
  82  	case t.Kind() == reflect.Bool:
  83  		return true
  84  	case t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Bool:
  85  		return true
  86  	default:
  87  		return false
  88  	}
  89  }
  90  
  91  // isTextUnmarshaler returns true if the type or its pointer implements encoding.TextUnmarshaler
  92  func isTextUnmarshaler(t reflect.Type) bool {
  93  	return t.Implements(textUnmarshalerType) || reflect.PtrTo(t).Implements(textUnmarshalerType)
  94  }
  95  
  96  // isExported returns true if the struct field name is exported
  97  func isExported(field string) bool {
  98  	r, _ := utf8.DecodeRuneInString(field) // returns RuneError for empty string or invalid UTF8
  99  	return unicode.IsLetter(r) && unicode.IsUpper(r)
 100  }
 101  
 102  // isZero returns true if v contains the zero value for its type
 103  func isZero(v reflect.Value) bool {
 104  	t := v.Type()
 105  	if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice || t.Kind() == reflect.Map || t.Kind() == reflect.Chan || t.Kind() == reflect.Interface {
 106  		return v.IsNil()
 107  	}
 108  	if !t.Comparable() {
 109  		return false
 110  	}
 111  	return v.Interface() == reflect.Zero(t).Interface()
 112  }
 113