gopkg.in/essentialkaos/ek.v3@v3.5.1/arg/arg.go (about) 1 // Package arg provides methods for working with command-line arguments 2 package arg 3 4 // ////////////////////////////////////////////////////////////////////////////////// // 5 // // 6 // Copyright (c) 2009-2016 Essential Kaos // 7 // Essential Kaos Open Source License <http://essentialkaos.com/ekol?en> // 8 // // 9 // ////////////////////////////////////////////////////////////////////////////////// // 10 11 import ( 12 "fmt" 13 "os" 14 "strconv" 15 "strings" 16 17 "pkg.re/essentialkaos/ek.v3/mathutil" 18 ) 19 20 // ////////////////////////////////////////////////////////////////////////////////// // 21 22 /* 23 STRING argument type is string 24 INT argument type is integer 25 BOOL argument type is boolean 26 FLOAT argument type is floating number 27 */ 28 const ( 29 STRING = 0 30 INT = 1 31 BOOL = 2 32 FLOAT = 3 33 ) 34 35 // Error codes 36 const ( 37 ERROR_UNSUPPORTED = 0 38 ERROR_NO_NAME = 1 39 ERROR_DUPLICATE_LONGNAME = 2 40 ERROR_DUPLICATE_SHORTNAME = 3 41 ERROR_ARG_IS_NIL = 4 42 ERROR_EMPTY_VALUE = 5 43 ERROR_REQUIRED_NOT_SET = 6 44 ERROR_WRONG_FORMAT = 7 45 ) 46 47 // ////////////////////////////////////////////////////////////////////////////////// // 48 49 // V basic argument struct 50 type V struct { 51 Type int // argument type 52 Max float64 // maximum integer argument value 53 Min float64 // minimum integer argument value 54 Alias string // list of aliases 55 Mergeble bool // argument supports arguments value merging 56 Required bool // argument is required 57 58 set bool // Non exported field 59 60 Value interface{} // default value 61 } 62 63 // Map is map with list of argumens 64 type Map map[string]*V 65 66 // Arguments arguments struct 67 type Arguments struct { 68 full Map 69 short map[string]string 70 initialized bool 71 hasRequired bool 72 } 73 74 // ArgumentError argument parsing error 75 type ArgumentError struct { 76 Arg string 77 Type int 78 } 79 80 // ////////////////////////////////////////////////////////////////////////////////// // 81 82 var global *Arguments 83 84 // ////////////////////////////////////////////////////////////////////////////////// // 85 86 // Add add new supported argument 87 func (args *Arguments) Add(name string, arg *V) error { 88 if !args.initialized { 89 initArgs(args) 90 } 91 92 longName, shortName := parseName(name) 93 94 switch { 95 case arg == nil: 96 return ArgumentError{"--" + longName, ERROR_ARG_IS_NIL} 97 case longName == "": 98 return ArgumentError{"", ERROR_NO_NAME} 99 case args.full[longName] != nil: 100 return ArgumentError{"--" + longName, ERROR_DUPLICATE_LONGNAME} 101 case shortName != "" && args.short[shortName] != "": 102 return ArgumentError{"-" + shortName, ERROR_DUPLICATE_SHORTNAME} 103 } 104 105 if arg.Required == true { 106 args.hasRequired = true 107 } 108 109 args.full[longName] = arg 110 111 if shortName != "" { 112 args.short[shortName] = longName 113 } 114 115 if arg.Alias != "" { 116 aliases := strings.Split(arg.Alias, " ") 117 118 for _, v := range aliases { 119 alLongName, alShortName := parseName(v) 120 121 args.full[alLongName] = arg 122 123 if alShortName != "" { 124 args.short[alShortName] = longName 125 } 126 } 127 } 128 129 return nil 130 } 131 132 // AddMap add supported arguments as map 133 func (args *Arguments) AddMap(argsMap Map) []error { 134 var errs []error 135 136 for name, arg := range argsMap { 137 err := args.Add(name, arg) 138 139 if err != nil { 140 errs = append(errs, err) 141 } 142 } 143 144 return errs 145 } 146 147 // GetS get argument value as string 148 func (args *Arguments) GetS(name string) string { 149 longName, _ := parseName(name) 150 arg, ok := args.full[longName] 151 152 switch { 153 case !ok: 154 return "" 155 case args.full[longName].Value == nil: 156 return "" 157 case arg.Type == INT: 158 return strconv.Itoa(arg.Value.(int)) 159 case arg.Type == FLOAT: 160 return strconv.FormatFloat(arg.Value.(float64), 'f', -1, 64) 161 case arg.Type == BOOL: 162 return strconv.FormatBool(arg.Value.(bool)) 163 default: 164 return arg.Value.(string) 165 } 166 } 167 168 // GetI get argument value as integer 169 func (args *Arguments) GetI(name string) int { 170 longName, _ := parseName(name) 171 arg, ok := args.full[longName] 172 173 switch { 174 case !ok: 175 return 0 176 177 case args.full[longName].Value == nil: 178 return 0 179 180 case arg.Type == STRING: 181 result, err := strconv.Atoi(arg.Value.(string)) 182 if err == nil { 183 return result 184 } 185 return 0 186 187 case arg.Type == FLOAT: 188 return int(arg.Value.(float64)) 189 190 case arg.Type == BOOL: 191 if arg.Value.(bool) { 192 return 1 193 } 194 return 0 195 196 default: 197 return arg.Value.(int) 198 } 199 } 200 201 // GetB get argument value as boolean 202 func (args *Arguments) GetB(name string) bool { 203 longName, _ := parseName(name) 204 arg, ok := args.full[longName] 205 206 switch { 207 case !ok: 208 return false 209 210 case args.full[longName].Value == nil: 211 return false 212 213 case arg.Type == STRING: 214 if arg.Value.(string) == "" { 215 return false 216 } 217 return true 218 219 case arg.Type == FLOAT: 220 if arg.Value.(float64) > 0 { 221 return true 222 } 223 return false 224 225 case arg.Type == INT: 226 if arg.Value.(int) > 0 { 227 return true 228 } 229 return false 230 231 default: 232 return arg.Value.(bool) 233 } 234 } 235 236 // GetF get argument value as floating number 237 func (args *Arguments) GetF(name string) float64 { 238 longName, _ := parseName(name) 239 arg, ok := args.full[longName] 240 241 switch { 242 case !ok: 243 return 0.0 244 245 case args.full[longName].Value == nil: 246 return 0.0 247 248 case arg.Type == STRING: 249 result, err := strconv.ParseFloat(arg.Value.(string), 64) 250 if err == nil { 251 return result 252 } 253 return 0.0 254 255 case arg.Type == INT: 256 return float64(arg.Value.(int)) 257 258 case arg.Type == BOOL: 259 if arg.Value.(bool) { 260 return 1.0 261 } 262 return 0.0 263 264 default: 265 return arg.Value.(float64) 266 } 267 } 268 269 // Has check that argument exists and set 270 func (args *Arguments) Has(name string) bool { 271 longName, _ := parseName(name) 272 arg, ok := args.full[longName] 273 274 if !ok { 275 return false 276 } 277 278 if !arg.set { 279 return false 280 } 281 282 return true 283 } 284 285 // Parse parse arguments 286 func (args *Arguments) Parse(rawArgs []string, argsMap ...Map) ([]string, []error) { 287 var errs []error 288 289 if len(argsMap) != 0 { 290 for _, amap := range argsMap { 291 errs = append(errs, args.AddMap(amap)...) 292 } 293 } 294 295 if len(errs) != 0 { 296 return []string{}, errs 297 } 298 299 return args.parseArgs(rawArgs) 300 } 301 302 // ////////////////////////////////////////////////////////////////////////////////// // 303 304 // NewArguments create new arguments struct 305 func NewArguments() *Arguments { 306 return &Arguments{ 307 full: make(Map), 308 short: make(map[string]string), 309 initialized: true, 310 } 311 } 312 313 // Add add new supported argument 314 func Add(name string, arg *V) error { 315 if global == nil || global.initialized == false { 316 global = NewArguments() 317 } 318 319 return global.Add(name, arg) 320 } 321 322 // AddMap add supported arguments as map 323 func AddMap(argsMap Map) []error { 324 if global == nil || global.initialized == false { 325 global = NewArguments() 326 } 327 328 return global.AddMap(argsMap) 329 } 330 331 // GetS get argument value as string 332 func GetS(name string) string { 333 if global == nil || global.initialized == false { 334 return "" 335 } 336 337 return global.GetS(name) 338 } 339 340 // GetI get argument value as integer 341 func GetI(name string) int { 342 if global == nil || global.initialized == false { 343 return 0 344 } 345 346 return global.GetI(name) 347 } 348 349 // GetB get argument value as boolean 350 func GetB(name string) bool { 351 if global == nil || global.initialized == false { 352 return false 353 } 354 355 return global.GetB(name) 356 } 357 358 // GetF get argument value as floating number 359 func GetF(name string) float64 { 360 if global == nil || global.initialized == false { 361 return 0.0 362 } 363 364 return global.GetF(name) 365 } 366 367 // Has check that argument exists and set 368 func Has(name string) bool { 369 if global == nil || global.initialized == false { 370 return false 371 } 372 373 return global.Has(name) 374 } 375 376 // Parse parse arguments 377 func Parse(argsMap ...Map) ([]string, []error) { 378 if global == nil || global.initialized == false { 379 global = NewArguments() 380 } 381 382 return global.Parse(os.Args[1:], argsMap...) 383 } 384 385 // ParseArgName parse combined name and return long and short arguments 386 func ParseArgName(arg string) (string, string) { 387 return parseName(arg) 388 } 389 390 // ////////////////////////////////////////////////////////////////////////////////// // 391 392 func (args *Arguments) parseArgs(rawArgs []string) ([]string, []error) { 393 if len(rawArgs) == 0 { 394 return nil, args.getErrorsForRequiredArgs() 395 } 396 397 var ( 398 argName string 399 argList []string 400 errorList []error 401 ) 402 403 for _, curArg := range rawArgs { 404 if argName == "" { 405 var ( 406 curArgName string 407 curArgValue string 408 err error 409 ) 410 411 var curArgLen = len(curArg) 412 413 switch { 414 case strings.TrimRight(curArg, "-") == "": 415 argList = append(argList, curArg) 416 continue 417 418 case curArgLen > 2 && curArg[0:2] == "--": 419 curArgName, curArgValue, err = args.parseLongArgument(curArg[2:curArgLen]) 420 421 case curArgLen > 1 && curArg[0:1] == "-": 422 curArgName, curArgValue, err = args.parseShortArgument(curArg[1:curArgLen]) 423 424 default: 425 argList = append(argList, curArg) 426 continue 427 } 428 429 if err != nil { 430 errorList = append(errorList, err) 431 continue 432 } 433 434 if curArgValue != "" { 435 errorList = appendError( 436 errorList, 437 updateArgument(args.full[curArgName], curArgName, curArgValue), 438 ) 439 } else { 440 if args.full[curArgName] != nil && args.full[curArgName].Type == BOOL { 441 errorList = appendError( 442 errorList, 443 updateArgument(args.full[curArgName], curArgName, ""), 444 ) 445 } else { 446 argName = curArgName 447 } 448 } 449 } else { 450 errorList = appendError( 451 errorList, 452 updateArgument(args.full[argName], argName, curArg), 453 ) 454 455 argName = "" 456 } 457 } 458 459 errorList = append(errorList, args.getErrorsForRequiredArgs()...) 460 461 if argName != "" { 462 errorList = append(errorList, ArgumentError{"--" + argName, ERROR_EMPTY_VALUE}) 463 } 464 465 return argList, errorList 466 } 467 468 func (args *Arguments) parseLongArgument(arg string) (string, string, error) { 469 if strings.Contains(arg, "=") { 470 argSlice := strings.Split(arg, "=") 471 472 if len(argSlice) <= 1 || argSlice[1] == "" { 473 return "", "", ArgumentError{"--" + argSlice[0], ERROR_WRONG_FORMAT} 474 } 475 476 return argSlice[0], strings.Join(argSlice[1:], "="), nil 477 } 478 479 if args.full[arg] != nil { 480 return arg, "", nil 481 } 482 483 return "", "", ArgumentError{"--" + arg, ERROR_UNSUPPORTED} 484 } 485 486 func (args *Arguments) parseShortArgument(arg string) (string, string, error) { 487 if strings.Contains(arg, "=") { 488 argSlice := strings.Split(arg, "=") 489 490 if len(argSlice) <= 1 || argSlice[1] == "" { 491 return "", "", ArgumentError{"-" + argSlice[0], ERROR_WRONG_FORMAT} 492 } 493 494 argName := argSlice[0] 495 496 if args.short[argName] == "" { 497 return "", "", ArgumentError{"-" + argName, ERROR_UNSUPPORTED} 498 } 499 500 return args.short[argName], strings.Join(argSlice[1:], "="), nil 501 } 502 503 if args.short[arg] == "" { 504 return "", "", ArgumentError{"-" + arg, ERROR_UNSUPPORTED} 505 } 506 507 return args.short[arg], "", nil 508 } 509 510 func (args *Arguments) getErrorsForRequiredArgs() []error { 511 if args.hasRequired == false { 512 return nil 513 } 514 515 var errorList []error 516 517 for n, v := range args.full { 518 if v.Required == true && v.Value == nil { 519 errorList = append(errorList, ArgumentError{n, ERROR_REQUIRED_NOT_SET}) 520 } 521 } 522 523 return errorList 524 } 525 526 // ////////////////////////////////////////////////////////////////////////////////// // 527 528 func initArgs(args *Arguments) { 529 args.full = make(Map) 530 args.short = make(map[string]string) 531 args.initialized = true 532 } 533 534 func parseName(name string) (string, string) { 535 na := strings.Split(name, ":") 536 537 if len(na) == 1 { 538 return na[0], "" 539 } 540 541 return na[1], na[0] 542 } 543 544 func updateArgument(arg *V, name string, value string) error { 545 switch arg.Type { 546 case STRING: 547 return updateStringArgument(arg, value) 548 549 case BOOL: 550 return updateBooleanArgument(arg) 551 552 case FLOAT: 553 return updateFloatArgument(name, arg, value) 554 555 case INT: 556 return updateIntArgument(name, arg, value) 557 } 558 559 return fmt.Errorf("Unsuported argument type %d", arg.Type) 560 } 561 562 func updateStringArgument(arg *V, value string) error { 563 if arg.set && arg.Mergeble { 564 arg.Value = arg.Value.(string) + " " + value 565 } else { 566 arg.Value = value 567 arg.set = true 568 } 569 570 return nil 571 } 572 573 func updateBooleanArgument(arg *V) error { 574 arg.Value = true 575 arg.set = true 576 577 return nil 578 } 579 580 func updateFloatArgument(name string, arg *V, value string) error { 581 floatValue, err := strconv.ParseFloat(value, 64) 582 583 if err != nil { 584 return ArgumentError{"--" + name, ERROR_WRONG_FORMAT} 585 } 586 587 var resultFloat float64 588 589 if arg.Min != arg.Max { 590 resultFloat = mathutil.BetweenF(floatValue, arg.Min, arg.Max) 591 } else { 592 resultFloat = floatValue 593 } 594 595 if arg.set && arg.Mergeble { 596 arg.Value = arg.Value.(float64) + resultFloat 597 } else { 598 arg.Value = resultFloat 599 arg.set = true 600 } 601 602 return nil 603 } 604 605 func updateIntArgument(name string, arg *V, value string) error { 606 intValue, err := strconv.Atoi(value) 607 608 if err != nil { 609 return ArgumentError{"--" + name, ERROR_WRONG_FORMAT} 610 } 611 612 var resultInt int 613 614 if arg.Min != arg.Max { 615 resultInt = mathutil.Between(intValue, int(arg.Min), int(arg.Max)) 616 } else { 617 resultInt = intValue 618 } 619 620 if arg.set && arg.Mergeble { 621 arg.Value = arg.Value.(int) + resultInt 622 } else { 623 arg.Value = resultInt 624 arg.set = true 625 } 626 627 return nil 628 } 629 630 func appendError(errList []error, err error) []error { 631 if err == nil { 632 return errList 633 } 634 635 return append(errList, err) 636 } 637 638 func (e ArgumentError) Error() string { 639 switch e.Type { 640 default: 641 return fmt.Sprintf("Argument %s is not supported", e.Arg) 642 case ERROR_EMPTY_VALUE: 643 return fmt.Sprintf("Non-boolean argument %s is empty", e.Arg) 644 case ERROR_REQUIRED_NOT_SET: 645 return fmt.Sprintf("Required argument %s is not set", e.Arg) 646 case ERROR_WRONG_FORMAT: 647 return fmt.Sprintf("Argument %s has wrong format", e.Arg) 648 case ERROR_ARG_IS_NIL: 649 return fmt.Sprintf("Struct for argument %s is nil", e.Arg) 650 case ERROR_DUPLICATE_LONGNAME, ERROR_DUPLICATE_SHORTNAME: 651 return fmt.Sprintf("Argument %s defined 2 or more times", e.Arg) 652 case ERROR_NO_NAME: 653 return "Some argument does not have a name" 654 } 655 } 656 657 // ////////////////////////////////////////////////////////////////////////////////// //