struct_field.go (138 lines of code) (raw):
package clihelpers
import (
"reflect"
"regexp"
"strings"
"github.com/urfave/cli"
)
type StructFieldValue struct {
field reflect.StructField
value reflect.Value
}
func (f StructFieldValue) IsBoolFlag() bool {
if f.value.Kind() == reflect.Bool {
return true
} else if f.value.Kind() == reflect.Ptr && f.value.Elem().Kind() == reflect.Bool {
return true
} else {
return false
}
}
func (s StructFieldValue) Set(val string) error {
return convert(val, s.value, s.field.Tag)
}
func (s StructFieldValue) String() string {
if !s.value.IsValid() {
return ""
}
if s.value.Kind() == reflect.Ptr && s.value.IsNil() {
return ""
}
retval, _ := convertToString(s.value, s.field.Tag)
return retval
}
type StructFieldFlag struct {
cli.GenericFlag
}
func (f StructFieldFlag) String() string {
if sf, ok := f.Value.(StructFieldValue); ok {
if sf.IsBoolFlag() {
flag := &cli.BoolFlag{
Name: f.Name,
Usage: f.Usage,
EnvVar: f.EnvVar,
}
return flag.String()
} else {
flag := &cli.StringFlag{
Name: f.Name,
Value: sf.String(),
Usage: f.Usage,
EnvVar: f.EnvVar,
}
return flag.String()
}
} else {
return f.GenericFlag.String()
}
}
var reName = regexp.MustCompile(`{(\w+)}`)
func getStructFieldFlag(field reflect.StructField, fieldValue reflect.Value, ns []string) []cli.Flag {
var names []string
if name := field.Tag.Get("short"); name != "" {
names = append(names, strings.Join(append(ns, name), "-"))
}
if name := field.Tag.Get("long"); name != "" {
names = append(names, strings.Join(append(ns, name), "-"))
}
if len(names) == 0 {
return []cli.Flag{}
}
envName := field.Tag.Get("env")
// If the env tag begins with "@", any namespace tags take affect, with an opininated format:
// - All characters are uppercased
// - All hyphens are converted to underscores
if strings.HasPrefix(envName, "@") {
envName = strings.Join(append(ns, envName[1:]), "_")
envName = strings.ToUpper(envName)
envName = strings.ReplaceAll(envName, "-", "_")
}
flag := cli.GenericFlag{
Name: strings.Join(names, ", "),
Value: StructFieldValue{
field: field,
value: fieldValue,
},
Usage: reName.ReplaceAllString(field.Tag.Get("description"), "`$1`"),
EnvVar: envName,
}
return []cli.Flag{StructFieldFlag{GenericFlag: flag}}
}
func getFlagsForStructField(field reflect.StructField, fieldValue reflect.Value, ns []string) []cli.Flag {
if !fieldValue.IsValid() {
return []cli.Flag{}
}
switch field.Type.Kind() {
case reflect.Struct:
if newNs := field.Tag.Get("namespace"); newNs != "" {
return getFlagsForValue(fieldValue, append(ns, newNs))
} else if field.Anonymous {
return getFlagsForValue(fieldValue, ns)
}
break
case reflect.Ptr:
if field.Type.Elem().Kind() == reflect.Struct {
if newNs := field.Tag.Get("namespace"); newNs != "" {
return getFlagsForValue(fieldValue, append(ns, newNs))
}
} else {
return getStructFieldFlag(field, fieldValue, ns)
}
break
case reflect.Chan:
case reflect.Func:
case reflect.Interface:
case reflect.UnsafePointer:
break
default:
return getStructFieldFlag(field, fieldValue, ns)
}
return []cli.Flag{}
}
func getFlagsForValue(value reflect.Value, ns []string) []cli.Flag {
var flags []cli.Flag
if value.Type().Kind() == reflect.Ptr && value.Type().Elem().Kind() == reflect.Struct {
if value.IsNil() {
value.Set(reflect.New(value.Type().Elem()))
}
value = reflect.Indirect(value)
} else if value.Type().Kind() != reflect.Struct {
return []cli.Flag{}
}
valueType := value.Type()
for i := 0; i < valueType.NumField(); i++ {
newFlags := getFlagsForStructField(valueType.Field(i), value.Field(i), ns)
flags = append(flags, newFlags...)
}
return flags
}
func GetFlagsFromStruct(data interface{}, ns ...string) []cli.Flag {
return getFlagsForValue(reflect.ValueOf(data), ns)
}