1package arg
  2
  3import (
  4	"encoding"
  5	"fmt"
  6	"reflect"
  7	"unicode"
  8	"unicode/utf8"
  9
 10	scalar "github.com/alexflint/go-scalar"
 11)
 12
 13var 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
 19type cardinality int
 20
 21const (
 22	zero cardinality = iota
 23	one
 24	multiple
 25	unsupported
 26)
 27
 28func (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
 44func 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 can be parsed from a single string
 78func isBoolean(t reflect.Type) bool {
 79	switch {
 80	case t.Implements(textUnmarshalerType):
 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// isExported returns true if the struct field name is exported
 92func isExported(field string) bool {
 93	r, _ := utf8.DecodeRuneInString(field) // returns RuneError for empty string or invalid UTF8
 94	return unicode.IsLetter(r) && unicode.IsUpper(r)
 95}
 96
 97// isZero returns true if v contains the zero value for its type
 98func isZero(v reflect.Value) bool {
 99	t := v.Type()
100	if t.Kind() == reflect.Slice || t.Kind() == reflect.Map {
101		return v.IsNil()
102	}
103	if !t.Comparable() {
104		return false
105	}
106	return v.Interface() == reflect.Zero(t).Interface()
107}