package opt import ( "errors" "fmt" "strings" ) var ( ErrUnknownOption = errors.New("unknown option") ErrCannotChainOption = errors.New("cannot chain option") ErrOptionRequiresValue = errors.New("option requires value") ) type Parser struct { opts Set args []string curr int ignoreUnknown bool } func NewParser(args []string, opts Set, ignoreUnknown bool) *Parser { return &Parser{ opts: append(opts, globalOpts...), args: args, curr: -1, ignoreUnknown: ignoreUnknown, } } func (p *Parser) Parse() (restArgs []string, err error) { for p.hasNext() { arg := p.next() if !strings.HasPrefix(arg, "-") || arg == "-" { // Regular argument restArgs = append(restArgs, arg) } else if arg == "--" { // Options terminator restArgs = append(restArgs, p.restArgs()...) return } else if strings.HasPrefix(arg, "--") { // Long option longName := arg[2:] if err = p.parseLongOption(longName); err != nil { return } } 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) peek() string { if p.curr+1 >= len(p.args) { return "" } return p.args[p.curr+1] } func (p *Parser) value() (string, bool) { if !p.hasNext() || (p.peek() != "-" && strings.HasPrefix(p.peek(), "-")) { return "", false } return p.next(), true } 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 { value = longName[equals+1:] longName = longName[:equals] } opt, ok := p.opts.GetByLongName(longName) if !ok { if !p.ignoreUnknown { return fmt.Errorf("%w: %s", ErrUnknownOption, "--"+longName) } return nil // Ignore unknown option. Continue parsing. } if value == "" && opt.RequiresVal() { var ok bool value, ok = p.value() if !ok { return fmt.Errorf("%w: %s", ErrOptionRequiresValue, "--"+longName) } } if err := opt.Parse(value); err != nil { return err } return nil } func (p *Parser) parseShortOption(shortNames string) error { if len(shortNames) == 1 { value, valOk := p.value() opt, ok := p.opts.GetByShortName(shortNames) if !ok { if !p.ignoreUnknown { return fmt.Errorf("%w: %s", ErrUnknownOption, "-"+shortNames) } return nil // Ignore unknown option. Continue parsing. } if !valOk && opt.RequiresVal() { return fmt.Errorf("%w: %s", ErrOptionRequiresValue, "-"+shortNames) } 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 !p.ignoreUnknown { return fmt.Errorf("%w: %s", ErrUnknownOption, "-"+string(shortName)) } continue // Ignore unknown option. Continue parsing. } if opt.RequiresVal() { if j > 0 { return fmt.Errorf("%w: %s", ErrCannotChainOption, "-"+string(shortName)) } value = shortNames[1:] value = strings.TrimPrefix(value, "=") j = len(shortNames) } if err := opt.Parse(value); err != nil { return err } } } return nil }