github.com/michaellihs/golab@v0.1.0-beta3.0.20180726222757-f5cdabc76dfd/cmd/mapper/flag_mapper.go (about)

     1  // Copyright © 2017 Michael Lihs
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package mapper
    22  
    23  import (
    24  	"errors"
    25  	"reflect"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  
    30  	"encoding/json"
    31  
    32  	"github.com/spf13/cobra"
    33  	"github.com/xanzy/go-gitlab"
    34  )
    35  
    36  type FlagMapper struct {
    37  	cmd   *cobra.Command
    38  	flags interface{}
    39  	opts  interface{}
    40  }
    41  
    42  func New(cmd *cobra.Command) FlagMapper {
    43  	return FlagMapper{cmd: cmd}
    44  }
    45  
    46  func InitializedMapper(cmd *cobra.Command, flags interface{}, opts interface{}) FlagMapper {
    47  	mapper := FlagMapper{
    48  		cmd:   cmd,
    49  		flags: flags,
    50  		opts:  opts,
    51  	}
    52  	mapper.SetFlags(flags)
    53  	return mapper
    54  }
    55  
    56  func (m FlagMapper) SetFlags(flags interface{}) {
    57  	if flags != nil {
    58  		v := reflect.ValueOf(flags).Elem()
    59  		for i := 0; i < v.NumField(); i++ {
    60  			tag := v.Type().Field(i).Tag
    61  			f := v.Field(i)
    62  			flagName := tag.Get("flag_name")
    63  			shortHand := tag.Get("short")
    64  
    65  			switch f.Type().String() {
    66  			case "*int":
    67  				m.cmd.PersistentFlags().IntP(flagName, shortHand, 0, flagUsage(tag))
    68  			case "*string":
    69  				m.cmd.PersistentFlags().StringP(flagName, shortHand, "", flagUsage(tag))
    70  			case "*bool":
    71  				m.cmd.PersistentFlags().BoolP(flagName, shortHand, false, flagUsage(tag))
    72  			case "*[]string":
    73  				m.cmd.PersistentFlags().StringArrayP(flagName, shortHand, nil, flagUsage(tag))
    74  			case "[]int":
    75  				m.cmd.PersistentFlags().StringArrayP(flagName, shortHand, nil, flagUsage(tag))
    76  			default:
    77  				panic("Unknown type " + f.Type().String())
    78  			}
    79  		}
    80  	}
    81  }
    82  
    83  func flagUsage(tag reflect.StructTag) string {
    84  	description := tag.Get("description")
    85  	required := tag.Get("required")
    86  	usage := ""
    87  	if required == "yes" {
    88  		usage = "(required) "
    89  	} else {
    90  		usage = "(optional) "
    91  	}
    92  	return usage + description
    93  }
    94  
    95  func (m FlagMapper) AutoMap() (interface{}, interface{}, error) {
    96  	err := m.Map(m.flags, m.opts)
    97  	return m.flags, m.opts, err
    98  }
    99  
   100  func (m FlagMapper) MappedOpts() interface{} {
   101  	return m.opts
   102  }
   103  
   104  func (m FlagMapper) MappedFlags() interface{} {
   105  	return m.flags
   106  }
   107  
   108  func (m FlagMapper) Map(flags interface{}, opts interface{}) error {
   109  	if flags == nil {
   110  		return nil
   111  	}
   112  	var optsReflected reflect.Value
   113  	flagsReflected := reflect.ValueOf(flags).Elem()
   114  	if opts != nil {
   115  		optsReflected = reflect.ValueOf(opts).Elem()
   116  	}
   117  
   118  	for i := 0; i < flagsReflected.NumField(); i++ {
   119  		flag := flagsReflected.Field(i)
   120  		tag := flagsReflected.Type().Field(i).Tag
   121  
   122  		flagName := tag.Get("flag_name")
   123  		flagChanged := m.cmd.PersistentFlags().Changed(flagName) // flagChanged --> value for flag has been set on command line
   124  
   125  		// see https://stackoverflow.com/questions/6395076/using-reflect-how-do-you-set-the-value-of-a-struct-field
   126  		// see https://stackoverflow.com/questions/40060131/reflect-assign-a-pointer-struct-value
   127  		if flagChanged {
   128  			fieldName := flagsReflected.Type().Field(i).Name
   129  			if opts != nil {
   130  				opt := optsReflected.FieldByName(fieldName)
   131  				mapOpt(opt, tag, m, flagName, flag, fieldName)
   132  			}
   133  			mapFlag(flag, m, flagName)
   134  		} else {
   135  			if required := tag.Get("required"); required == "yes" {
   136  				return errors.New("required flag --" + flagName + " was empty")
   137  			}
   138  		}
   139  	}
   140  	return nil
   141  }
   142  
   143  func mapFlag(value reflect.Value, mapper FlagMapper, tagName string) {
   144  	mapValue(value, mapper, tagName, value)
   145  }
   146  
   147  func mapOpt(opt reflect.Value, tag reflect.StructTag, mapper FlagMapper, flagName string, value reflect.Value, fieldName string) {
   148  	if opt.IsValid() {
   149  		// A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields.
   150  		if opt.CanSet() {
   151  			if transform := tag.Get("transform"); transform != "" {
   152  				value, err := mapper.cmd.PersistentFlags().GetString(flagName)
   153  				if err != nil {
   154  					panic(err.Error())
   155  				}
   156  				transformAndSet(transform, opt, value)
   157  			} else {
   158  				mapValue(value, mapper, flagName, opt)
   159  			}
   160  		} else {
   161  			panic(fieldName + " can not be set")
   162  		}
   163  	} else {
   164  		// for the moment, we want to ignore flags, that are not available in opts
   165  		// panic(fieldName + " is not valid")
   166  	}
   167  }
   168  
   169  func mapValue(value reflect.Value, mapper FlagMapper, flagName string, opt reflect.Value) {
   170  	switch value.Type().String() {
   171  	case "*int":
   172  		mapInt(mapper, flagName, opt)
   173  	case "*string":
   174  		mapString(mapper, flagName, opt)
   175  	case "*bool":
   176  		mapBool(mapper, flagName, opt)
   177  	case "*[]string":
   178  		mapStringArray(mapper, flagName, opt)
   179  	case "[]int":
   180  		mapIntArray(mapper, flagName, opt)
   181  	default:
   182  		panic("Unknown type " + value.Type().String())
   183  	}
   184  }
   185  
   186  func mapInt(m FlagMapper, flagName string, opt reflect.Value) {
   187  	value, err := m.cmd.PersistentFlags().GetInt(flagName)
   188  	if err != nil {
   189  		panic(err.Error())
   190  	}
   191  	if typesMatch(opt, &value) {
   192  		opt.Set(reflect.ValueOf(&value))
   193  	}
   194  }
   195  
   196  func mapString(m FlagMapper, flagName string, opt reflect.Value) {
   197  	value, err := m.cmd.PersistentFlags().GetString(flagName)
   198  	if err != nil {
   199  		panic(err.Error())
   200  	}
   201  	if typesMatch(opt, &value) {
   202  		opt.Set(reflect.ValueOf(&value))
   203  	}
   204  }
   205  
   206  func mapStringArray(m FlagMapper, flagName string, opt reflect.Value) {
   207  	value, err := m.cmd.PersistentFlags().GetStringArray(flagName)
   208  	if err != nil {
   209  		panic(err.Error())
   210  	}
   211  	if typesMatch(opt, &value) {
   212  		opt.Set(reflect.ValueOf(&value))
   213  	}
   214  }
   215  
   216  func mapIntArray(m FlagMapper, flagName string, opt reflect.Value) {
   217  	value, err := m.cmd.PersistentFlags().GetStringArray(flagName)
   218  	if err != nil {
   219  		panic(err.Error())
   220  	}
   221  	// TODO cobra does not parse "1,2,3,4" into an array
   222  	sarr := strings.Split(value[0], ",")
   223  	arr := stringArray2IntArray(sarr)
   224  	if typesMatch(opt, arr) {
   225  		opt.Set(reflect.ValueOf(arr))
   226  	}
   227  }
   228  
   229  func stringArray2IntArray(s []string) []int {
   230  	var result = []int{}
   231  	for _, i := range s {
   232  		j, err := strconv.Atoi(i)
   233  		if err != nil {
   234  			panic(err)
   235  		}
   236  		result = append(result, j)
   237  	}
   238  	return result
   239  }
   240  
   241  func mapBool(m FlagMapper, flagName string, opt reflect.Value) {
   242  	value, err := m.cmd.PersistentFlags().GetBool(flagName)
   243  	if err != nil {
   244  		panic(err.Error())
   245  	}
   246  	if typesMatch(opt, &value) {
   247  		opt.Set(reflect.ValueOf(&value))
   248  	}
   249  }
   250  
   251  func transformAndSet(transform string, opt reflect.Value, value string) {
   252  	fieldType := opt.Type()
   253  
   254  	transformedValue, err := call(funcs, transform, value)
   255  	if err != nil {
   256  		panic(err.Error())
   257  	}
   258  
   259  	opt.Set(transformedValue[0].Convert(fieldType))
   260  }
   261  
   262  func str2Visibility(s string) *gitlab.VisibilityValue {
   263  	if s == "private" {
   264  		return gitlab.Visibility(gitlab.PrivateVisibility)
   265  	}
   266  	if s == "internal" {
   267  		return gitlab.Visibility(gitlab.InternalVisibility)
   268  	}
   269  	if s == "public" {
   270  		return gitlab.Visibility(gitlab.PublicVisibility)
   271  	}
   272  	return nil
   273  }
   274  
   275  func string2IsoTime(s string) *gitlab.ISOTime {
   276  	isotime, err := time.Parse("2006-01-02", s)
   277  	if err != nil {
   278  		panic(err.Error())
   279  	}
   280  	t := gitlab.ISOTime(isotime)
   281  	return &t
   282  }
   283  
   284  func str2AccessLevel(s string) *gitlab.AccessLevelValue {
   285  	if s == "10" {
   286  		return gitlab.AccessLevel(gitlab.GuestPermissions)
   287  	}
   288  	if s == "20" {
   289  		return gitlab.AccessLevel(gitlab.ReporterPermissions)
   290  	}
   291  	if s == "30" {
   292  		return gitlab.AccessLevel(gitlab.DeveloperPermissions)
   293  	}
   294  	if s == "40" {
   295  		return gitlab.AccessLevel(gitlab.MasterPermissions)
   296  	}
   297  	if s == "50" {
   298  		return gitlab.AccessLevel(gitlab.OwnerPermission)
   299  	}
   300  	panic("Unknown access level: " + s)
   301  }
   302  
   303  func string2TimeVal(s string) time.Time {
   304  	t, err := time.Parse("2006-01-02", s)
   305  	if err != nil {
   306  		panic(err.Error())
   307  	}
   308  	return t
   309  }
   310  
   311  func string2Time(s string) *time.Time {
   312  	t := string2TimeVal(s)
   313  	return &t
   314  }
   315  
   316  func string2Labels(s string) gitlab.Labels {
   317  	stringSlice := strings.Split(s, ",")
   318  	return stringSlice
   319  }
   320  
   321  func json2CommitActions(s string) []*gitlab.CommitAction {
   322  	var v []*gitlab.CommitAction
   323  	json.Unmarshal([]byte(s), &v)
   324  	return v
   325  }
   326  
   327  var funcs = map[string]interface{}{
   328  	"string2Labels":      string2Labels,
   329  	"string2visibility":  str2Visibility,
   330  	"string2IsoTime":     string2IsoTime,
   331  	"string2TimeVal":     string2TimeVal,
   332  	"string2Time":        string2Time,
   333  	"str2AccessLevel":    str2AccessLevel,
   334  	"json2CommitActions": json2CommitActions,
   335  }
   336  
   337  func call(m map[string]interface{}, name string, params ...interface{}) (result []reflect.Value, err error) {
   338  	f := reflect.ValueOf(m[name])
   339  	if len(params) != f.Type().NumIn() {
   340  		err = errors.New("the number of params is not adapted")
   341  		return
   342  	}
   343  	in := make([]reflect.Value, len(params))
   344  	for k, param := range params {
   345  		in[k] = reflect.ValueOf(param)
   346  	}
   347  	result = f.Call(in)
   348  	return
   349  }
   350  
   351  func typesMatch(target reflect.Value, source interface{}) bool {
   352  	targetType := target.Type().String()
   353  	sourceType := reflect.ValueOf(source).Type().String()
   354  
   355  	return targetType == sourceType
   356  }