diff --git a/command.go b/command.go index 746d5d3..6075a43 100644 --- a/command.go +++ b/command.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "os" "path/filepath" @@ -67,14 +68,10 @@ func (c *Command) Execute(args []string) { args = args[1:] } - restArgs, err := c.opts.Parse(args) - if err != nil { - c.ShowHelp() - return - } - - restArgs, err = opts.Globals().Parse(restArgs) + parser := opts.NewParser(args, c.opts) + restArgs, err := parser.Parse() if err != nil { + fmt.Printf("Option error: %s\n", err.Error()) c.ShowHelp() return } diff --git a/opts/bool.go b/opts/bool.go index 7bbf0c8..5f5fd34 100644 --- a/opts/bool.go +++ b/opts/bool.go @@ -1,6 +1,8 @@ package opts -import "strconv" +import ( + "strconv" +) type BoolOption struct { name string diff --git a/opts/parser.go b/opts/parser.go index e3f9260..6d61292 100644 --- a/opts/parser.go +++ b/opts/parser.go @@ -2,7 +2,6 @@ package opts import ( "errors" - "fmt" "strings" ) @@ -11,133 +10,136 @@ var ( ErrCannotChainOption = errors.New("cannot chain option as it takes an argument") ) -func (s Set) Parse(args []string) (restArgs []string, err error) { - for i := 0; i < len(args); i++ { - arg := args[i] +type Parser struct { + opts Set + args []string + curr int +} - // Handle regular argument - if !strings.HasPrefix(arg, "-") { +func NewParser(args []string, opts []Option) *Parser { + return &Parser{ + opts: append(opts, globalOpts...), + args: args, + curr: -1, + } +} + +func (p *Parser) Parse() (restArgs []string, err error) { + for p.hasNext() { + arg := p.next() + + if !strings.HasPrefix(arg, "-") { // Regular argument restArgs = append(restArgs, arg) - continue - } - - // Handle options terminator - if arg == "--" { - restArgs = append(restArgs, args[i+1:]...) + } else if arg == "--" { // Options terminator + restArgs = append(restArgs, p.restArgs()...) return - } - - // Handle long option - if strings.HasPrefix(arg, "--") { + } else if strings.HasPrefix(arg, "--") { // Long option longName := arg[2:] - value := "" - - equals := strings.Index(longName, "=") - if equals > 0 { - longName = longName[:equals] - value = longName[equals+1:] - } else if i < len(args)-1 && !strings.HasPrefix(args[i+1], "-") { - value = args[i+1] - i++ + if err = p.parseLongOption(longName); err != nil { + return } - - parsed := false - for _, opt := range s { - if opt.Name() != longName { - continue - } - - if err = opt.Parse(value); err != nil { - return - } - - parsed = true - break - } - - if !parsed { - restArgs = append(restArgs, arg) - } - - continue - } - - // Handle short option - shortNames := arg[1:] - if len(shortNames) == 0 { - err = ErrInvalidShortOption - } else if len(shortNames) == 1 { - parsed := false - value := "" - if i < len(args)-1 && !strings.HasPrefix(args[i+1], "-") { - value = args[i+1] - i++ - } - - for _, opt := range s { - if opt.ShortName() != shortNames { - continue - } - - if err = opt.Parse(value); err != nil { - return - } - - parsed = true - break - } - - if !parsed { - restArgs = append(restArgs, arg) - } - } else { - for j := 0; j < len(shortNames); j++ { - shortName := shortNames[j] - value := "" - takesValue := false - parsed := false - - for _, opt := range s { - if opt.ShortName() != string(shortName) { - continue - } - - if opt.TakesArg() { - if j > 0 { - err = ErrCannotChainOption - return - } - - takesValue = true - value = shortNames[j+1:] - value = strings.TrimPrefix(value, "=") - if len(value) == 0 && i < len(args)-1 && !strings.HasPrefix(args[i+1], "-") { - value = args[i+1] - i++ - } - j = len(shortNames) - } - - parsed = true - - if err = opt.Parse(value); err != nil { - return - } - - break - } - - if !parsed { - if takesValue { - restArgs = append(restArgs, arg) - break - } else { - restArgs = append(restArgs, fmt.Sprintf("-%c", shortName)) - } - } + } else { // Short option + shortNames := arg[1:] + if err = p.parseShortOption(shortNames); err != nil { + return } } } return } + +func (p *Parser) hasNext() bool { + return (p.curr + 1) < len(p.args) +} + +func (p *Parser) next() string { + p.curr++ + + if p.curr >= len(p.args) { + return "" + } + + return p.args[p.curr] +} + +func (p *Parser) value() string { + p.curr++ + + if p.curr >= len(p.args) { + return "" + } + + arg := p.args[p.curr] + return arg +} + +func (p *Parser) restArgs() []string { + rest := p.args[p.curr+1:] + p.curr = len(p.args) + + return rest +} + +func (p *Parser) parseLongOption(longName string) error { + value := "" + + equals := strings.Index(longName, "=") + if equals >= 0 { + longName = longName[:equals] + value = longName[equals+1:] + } else { + value = p.value() + } + + opt, ok := p.opts.GetByLongName(longName) + if ok { + if err := opt.Parse(value); err != nil { + return err + } + } + + return nil +} + +func (p *Parser) parseShortOption(shortNames string) error { + if len(shortNames) == 0 { + return ErrInvalidShortOption + } else if len(shortNames) == 1 { + value := p.value() + + opt, ok := p.opts.GetByShortName(shortNames) + if ok { + if err := opt.Parse(value); err != nil { + return err + } + } + } else { + for j := 0; j < len(shortNames); j++ { + shortName := shortNames[j] + value := "" + + opt, ok := p.opts.GetByShortName(string(shortName)) + if ok { + if opt.TakesArg() { + if j > 0 { + return ErrCannotChainOption + } + + value = shortNames[1:] + value = strings.TrimPrefix(value, "=") + if len(value) == 0 { + value = p.value() + } + j = len(shortNames) + } + + if err := opt.Parse(value); err != nil { + return err + } + } + } + } + + return nil +} diff --git a/opts/set.go b/opts/set.go index 00c1da0..c13dab6 100644 --- a/opts/set.go +++ b/opts/set.go @@ -33,6 +33,24 @@ func (s Set) Get(name string) (Option, bool) { return nil, false } +func (s Set) GetByLongName(longName string) (Option, bool) { + for _, o := range s { + if o.Name() == longName { + return o, true + } + } + return nil, false +} + +func (s Set) GetByShortName(shortName string) (Option, bool) { + for _, o := range s { + if o.ShortName() == shortName { + return o, true + } + } + return nil, false +} + func (s Set) GetBool(name string) (*BoolOption, bool) { o, ok := s.Get(name) if !ok {