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 }