Refactor the option parsing code to use methods on a struct

This commit is contained in:
Evan Fiordeliso 2023-11-11 21:18:18 -05:00
parent 34bd7544b1
commit f4c5adc4c7
4 changed files with 147 additions and 128 deletions

View File

@ -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
}

View File

@ -1,6 +1,8 @@
package opts
import "strconv"
import (
"strconv"
)
type BoolOption struct {
name string

View File

@ -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
}

View File

@ -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 {