diff --git a/argparse.go b/argparse.go index 1ecac29..d41345c 100644 --- a/argparse.go +++ b/argparse.go @@ -80,6 +80,9 @@ type Parser struct { // Options are specific options for every argument. They can be provided if necessary. // Possible fields are: // +// Options.Positional - tells Parser that the argument is positional (implies Required) +// Positional arguments do not require the flag name to precede them and must come in a specific order. +// // Options.Required - tells Parser that this argument is required to be provided. // useful when specific Command requires some data provided. // @@ -95,10 +98,11 @@ type Parser struct { // in case if this argument was not supplied on command line. File default value is a string which it will be open with // provided options. In case if provided value type does not match expected, the error will be returned on run-time. type Options struct { - Required bool - Validate func(args []string) error - Help string - Default interface{} + Positional bool + Required bool + Validate func(args []string) error + Help string + Default interface{} } // NewParser creates new Parser object that will allow to add arguments for parsing diff --git a/argument.go b/argument.go index 3bd7a74..1f25d32 100644 --- a/argument.go +++ b/argument.go @@ -29,6 +29,14 @@ type Arg interface { GetSname() string GetLname() string GetResult() interface{} + GetPositional() bool +} + +func (o arg) GetPositional() bool { + if o.opts != nil { + return o.opts.Positional + } + return false } func (o arg) GetOpts() *Options { @@ -68,7 +76,7 @@ func (o *arg) checkLongName(argument string) int { return 0 } -// checkShortName if argumet present. +// checkShortName if argument present. // checkShortName - returns the argumet's short name number of occurrences and error. // For shorthand argument - 0 if there is no occurrences, or count of occurrences. // Shorthand argument with parametr, mast be the only or last in the argument string. @@ -101,7 +109,7 @@ func (o *arg) checkShortName(argument string) (int, error) { return 0, nil } -// check if argumet present. +// check if argument present. // check - returns the argumet's number of occurrences and error. // For long name return value is 0 or 1. // For shorthand argument - 0 if there is no occurrences, or count of occurrences. @@ -115,6 +123,10 @@ func (o *arg) check(argument string) (int, error) { return o.checkShortName(argument) } +func (o *arg) reducePositional(position int, args *[]string) { + (*args)[position] = "" +} + func (o *arg) reduceLongName(position int, args *[]string) { argument := (*args)[position] // Check for long name only if not empty @@ -166,8 +178,12 @@ func (o *arg) reduceShortName(position int, args *[]string) { // clear out already used argument from args at position func (o *arg) reduce(position int, args *[]string) { - o.reduceLongName(position, args) - o.reduceShortName(position, args) + if o.GetPositional() { + o.reducePositional(position, args) + } else { + o.reduceLongName(position, args) + o.reduceShortName(position, args) + } } func (o *arg) parseInt(args []string, argCount int) error { @@ -382,6 +398,10 @@ func (o *arg) parseSomeType(args []string, argCount int) error { return err } +func (o *arg) parsePositional(arg string) error { + return o.parse([]string{arg}, 1) +} + func (o *arg) parse(args []string, argCount int) error { // If unique do not allow more than one time if o.unique && (o.parsed || argCount > 1) { diff --git a/command.go b/command.go index 7bccf3b..4347582 100644 --- a/command.go +++ b/command.go @@ -51,7 +51,13 @@ func (o *Command) addArg(a *arg) error { current = current.parent } a.parent = o + + if a.GetPositional() { + a.opts.Required = true + a.size = 1 // We could allow other sizes in the future + } o.args = append(o.args, a) + return nil } @@ -73,22 +79,49 @@ func (o *Command) parseSubCommands(args *[]string) error { return nil } +func (o *Command) splitArgOnEqual(arg) { + +} + //parseArguments - Parses arguments -func (o *Command) parseArguments(args *[]string) error { +func (o *Command) parseArguments(inputArgs *[]string) error { // Iterate over the args - for i := 0; i < len(o.args); i++ { - oarg := o.args[i] - for j := 0; j < len(*args); j++ { - arg := (*args)[j] + for _, oarg := range o.args { + for j := 0; j < len(*inputArgs); j++ { + arg := (*inputArgs)[j] if arg == "" { continue } + if oarg.GetPositional() { + // Skip any flags + // This has the subtle effect of requiring flags + // to use `=` for their value pairing if any + // positionals are defined AND are not satisfied yet. + // If they don't use `=` then the positional parse + // will unknowingly consume the arg on next iteration. + // + // It would be possible to potentially avoid this + // requirement IF we choose to check whether the + // flag in question has a default. If not then we + // know either: + // it must be for that flag OR + // the user made an error + // However this is highly ambiguous so best avoided. + if strings.HasPrefix(arg, "-") { + continue + } + if err := oarg.parsePositional(arg); err != nil { + return err + } + oarg.reduce(j, inputArgs) + break // Positionals can only occur once + } if strings.Contains(arg, "=") { splitInd := strings.LastIndex(arg, "=") equalArg := []string{arg[:splitInd], arg[splitInd+1:]} if cnt, err := oarg.check(equalArg[0]); err != nil { return err - } else if cnt > 0 { + } else if cnt > 0 { // No args implies we supply default if equalArg[1] == "" { return fmt.Errorf("not enough arguments for %s", oarg.name()) } @@ -99,21 +132,21 @@ func (o *Command) parseArguments(args *[]string) error { if err != nil { return err } - oarg.reduce(j, args) + oarg.reduce(j, inputArgs) continue } } if cnt, err := oarg.check(arg); err != nil { return err } else if cnt > 0 { - if len(*args) < j+oarg.size { + if len(*inputArgs) < j+oarg.size { return fmt.Errorf("not enough arguments for %s", oarg.name()) } - err := oarg.parse((*args)[j+1:j+oarg.size], cnt) + err := oarg.parse((*inputArgs)[j+1:j+oarg.size], cnt) if err != nil { return err } - oarg.reduce(j, args) + oarg.reduce(j, inputArgs) continue } } @@ -121,10 +154,8 @@ func (o *Command) parseArguments(args *[]string) error { // Check if arg is required and not provided if oarg.opts != nil && oarg.opts.Required && !oarg.parsed { return fmt.Errorf("[%s] is required", oarg.name()) - } - - // Check for argument default value and if provided try to type cast and assign - if oarg.opts != nil && oarg.opts.Default != nil && !oarg.parsed { + } else if oarg.opts != nil && oarg.opts.Default != nil && !oarg.parsed { + // Check for argument default value and if provided try to type cast and assign err := oarg.setDefault() if err != nil { return err