arg/reflect.go

108 lines
2.5 KiB
Go
Raw Normal View History

2019-05-02 11:28:17 -05:00
package arg
import (
"encoding"
2021-04-19 14:49:49 -05:00
"fmt"
2019-05-02 11:28:17 -05:00
"reflect"
2021-01-31 20:29:22 -06:00
"unicode"
"unicode/utf8"
2019-05-02 11:28:17 -05:00
scalar "github.com/alexflint/go-scalar"
)
var textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem()
// cardinality tracks how many tokens are expected for a given spec
// - zero is a boolean, which does to expect any value
// - one is an ordinary option that will be parsed from a single token
// - multiple is a slice or map that can accept zero or more tokens
type cardinality int
2019-05-02 11:28:17 -05:00
2021-04-19 14:49:49 -05:00
const (
zero cardinality = iota
one
multiple
2021-04-19 14:49:49 -05:00
unsupported
)
func (k cardinality) String() string {
2021-04-19 14:49:49 -05:00
switch k {
case zero:
return "zero"
case one:
return "one"
case multiple:
return "multiple"
2021-04-19 14:49:49 -05:00
case unsupported:
return "unsupported"
default:
return fmt.Sprintf("unknown(%d)", int(k))
2019-05-02 11:28:17 -05:00
}
2021-04-19 14:49:49 -05:00
}
2019-05-02 11:28:17 -05:00
// cardinalityOf returns true if the type can be parsed from a string
func cardinalityOf(t reflect.Type) (cardinality, error) {
2021-04-19 14:49:49 -05:00
if scalar.CanParse(t) {
if isBoolean(t) {
return zero, nil
2021-04-19 14:49:49 -05:00
}
2021-04-20 14:14:14 -05:00
return one, nil
2019-05-02 11:28:17 -05:00
}
2021-04-19 14:49:49 -05:00
// look inside pointer types
2019-05-02 11:28:17 -05:00
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
2021-04-19 14:49:49 -05:00
// look inside slice and map types
switch t.Kind() {
case reflect.Slice:
if !scalar.CanParse(t.Elem()) {
2021-04-19 16:50:05 -05:00
return unsupported, fmt.Errorf("cannot parse into %v because %v not supported", t, t.Elem())
2021-04-19 14:49:49 -05:00
}
return multiple, nil
2021-04-19 14:49:49 -05:00
case reflect.Map:
if !scalar.CanParse(t.Key()) {
2021-04-19 16:50:05 -05:00
return unsupported, fmt.Errorf("cannot parse into %v because key type %v not supported", t, t.Elem())
2021-04-19 14:49:49 -05:00
}
if !scalar.CanParse(t.Elem()) {
2021-04-19 16:50:05 -05:00
return unsupported, fmt.Errorf("cannot parse into %v because value type %v not supported", t, t.Elem())
2021-04-19 14:49:49 -05:00
}
return multiple, nil
2021-04-19 14:49:49 -05:00
default:
return unsupported, fmt.Errorf("cannot parse into %v", t)
2019-05-02 11:28:17 -05:00
}
}
// isBoolean returns true if the type can be parsed from a single string
func isBoolean(t reflect.Type) bool {
switch {
case t.Implements(textUnmarshalerType):
return false
case t.Kind() == reflect.Bool:
return true
case t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Bool:
return true
default:
return false
}
}
2021-01-31 20:29:22 -06:00
// isExported returns true if the struct field name is exported
func isExported(field string) bool {
r, _ := utf8.DecodeRuneInString(field) // returns RuneError for empty string or invalid UTF8
return unicode.IsLetter(r) && unicode.IsUpper(r)
}
2021-04-19 23:03:43 -05:00
// isZero returns true if v contains the zero value for its type
func isZero(v reflect.Value) bool {
t := v.Type()
if t.Kind() == reflect.Slice || t.Kind() == reflect.Map {
return v.IsNil()
}
if !t.Comparable() {
return false
}
return v.Interface() == reflect.Zero(t).Interface()
}