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  // ////////////////////////////////////////////////////////////////////////////////// //