2023-11-11 20:49:38 -05:00
|
|
|
package opts
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrInvalidShortOption = errors.New("invalid short option")
|
|
|
|
ErrCannotChainOption = errors.New("cannot chain option as it takes an argument")
|
|
|
|
)
|
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
type Parser struct {
|
|
|
|
opts Set
|
|
|
|
args []string
|
|
|
|
curr int
|
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
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()
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -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
|
2023-11-11 21:18:18 -05:00
|
|
|
} 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 21:18:18 -05:00
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
return
|
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
func (p *Parser) hasNext() bool {
|
|
|
|
return (p.curr + 1) < len(p.args)
|
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
func (p *Parser) next() string {
|
|
|
|
p.curr++
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
if p.curr >= len(p.args) {
|
|
|
|
return ""
|
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
return p.args[p.curr]
|
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
func (p *Parser) value() string {
|
|
|
|
p.curr++
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
if p.curr >= len(p.args) {
|
|
|
|
return ""
|
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
arg := p.args[p.curr]
|
|
|
|
return arg
|
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -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
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
return rest
|
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
func (p *Parser) parseLongOption(longName string) error {
|
|
|
|
value := ""
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -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
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
opt, ok := p.opts.GetByLongName(longName)
|
|
|
|
if ok {
|
|
|
|
if err := opt.Parse(value); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-11-11 20:49:38 -05:00
|
|
|
|
2023-11-11 21:18:18 -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
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
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
|
2023-11-11 20:49:38 -05:00
|
|
|
}
|
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
value = shortNames[1:]
|
|
|
|
value = strings.TrimPrefix(value, "=")
|
|
|
|
if len(value) == 0 {
|
|
|
|
value = p.value()
|
|
|
|
}
|
|
|
|
j = len(shortNames)
|
2023-11-11 20:49:38 -05:00
|
|
|
}
|
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
if err := opt.Parse(value); err != nil {
|
|
|
|
return err
|
2023-11-11 20:49:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-11 21:18:18 -05:00
|
|
|
return nil
|
2023-11-11 20:49:38 -05:00
|
|
|
}
|