cmd/opts/parser.go

170 lines
3.1 KiB
Go
Raw Normal View History

2023-11-11 20:49:38 -05:00
package opts
import (
"errors"
2023-11-11 21:29:56 -05:00
"fmt"
2023-11-11 20:49:38 -05:00
"strings"
)
var (
2023-11-11 21:29:56 -05:00
ErrUnknownOption = errors.New("unknown option")
2023-11-11 20:49:38 -05:00
ErrInvalidShortOption = errors.New("invalid short option")
ErrCannotChainOption = errors.New("cannot chain option as it takes an argument")
)
type Parser struct {
opts Set
args []string
curr int
2023-11-11 21:29:56 -05:00
ignoreUnknown bool
}
2023-11-11 20:49:38 -05:00
2023-11-11 21:29:56 -05:00
func NewParser(args []string, opts []Option, ignoreUnknown bool) *Parser {
return &Parser{
opts: append(opts, globalOpts...),
args: args,
curr: -1,
2023-11-11 21:29:56 -05:00
ignoreUnknown: ignoreUnknown,
}
}
func (p *Parser) Parse() (restArgs []string, err error) {
for p.hasNext() {
arg := p.next()
2023-11-11 20:49:38 -05:00
if !strings.HasPrefix(arg, "-") { // Regular argument
restArgs = append(restArgs, arg)
} else if arg == "--" { // Options terminator
restArgs = append(restArgs, p.restArgs()...)
2023-11-11 20:49:38 -05:00
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
}
2023-11-11 20:49:38 -05:00
}
}
2023-11-11 20:49:38 -05:00
return
}
2023-11-11 20:49:38 -05:00
func (p *Parser) hasNext() bool {
return (p.curr + 1) < len(p.args)
}
2023-11-11 20:49:38 -05:00
func (p *Parser) next() string {
p.curr++
2023-11-11 20:49:38 -05:00
if p.curr >= len(p.args) {
return ""
}
2023-11-11 20:49:38 -05:00
return p.args[p.curr]
}
2023-11-11 20:49:38 -05:00
func (p *Parser) value() string {
p.curr++
2023-11-11 20:49:38 -05:00
if p.curr >= len(p.args) {
return ""
}
2023-11-11 20:49:38 -05:00
arg := p.args[p.curr]
return arg
}
2023-11-11 20:49:38 -05:00
func (p *Parser) restArgs() []string {
rest := p.args[p.curr+1:]
p.curr = len(p.args)
2023-11-11 20:49:38 -05:00
return rest
}
2023-11-11 20:49:38 -05:00
func (p *Parser) parseLongOption(longName string) error {
value := ""
2023-11-11 20:49:38 -05:00
equals := strings.Index(longName, "=")
if equals >= 0 {
longName = longName[:equals]
value = longName[equals+1:]
} else {
value = p.value()
}
2023-11-11 20:49:38 -05:00
opt, ok := p.opts.GetByLongName(longName)
2023-11-11 21:29:56 -05:00
if !ok {
if !p.ignoreUnknown {
return fmt.Errorf("%w: %s", ErrUnknownOption, "--"+longName)
}
2023-11-11 21:29:56 -05:00
return nil // Ignore unknown option. Continue parsing.
}
if err := opt.Parse(value); err != nil {
return err
}
return nil
}
2023-11-11 20:49:38 -05:00
func (p *Parser) parseShortOption(shortNames string) error {
if len(shortNames) == 0 {
return ErrInvalidShortOption
} else if len(shortNames) == 1 {
value := p.value()
2023-11-11 20:49:38 -05:00
opt, ok := p.opts.GetByShortName(shortNames)
2023-11-11 21:29:56 -05:00
if !ok {
if !p.ignoreUnknown {
return fmt.Errorf("%w: %s", ErrUnknownOption, "-"+shortNames)
}
2023-11-11 21:29:56 -05:00
return nil // Ignore unknown option. Continue parsing.
}
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))
2023-11-11 21:29:56 -05:00
if !ok {
if !p.ignoreUnknown {
return fmt.Errorf("%w: %s", ErrUnknownOption, "-"+string(shortName))
2023-11-11 20:49:38 -05:00
}
2023-11-11 21:29:56 -05:00
continue // Ignore unknown option. Continue parsing.
}
if opt.TakesArg() {
if j > 0 {
return ErrCannotChainOption
2023-11-11 20:49:38 -05:00
}
2023-11-11 21:29:56 -05:00
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
2023-11-11 20:49:38 -05:00
}
}
}
return nil
2023-11-11 20:49:38 -05:00
}