Add option parsing

This commit is contained in:
Evan Fiordeliso 2023-11-11 20:49:38 -05:00
parent 9079086626
commit 34bd7544b1
8 changed files with 256 additions and 33 deletions

View File

@ -67,27 +67,34 @@ func (c *Command) Execute(args []string) {
args = args[1:] args = args[1:]
} }
if err := c.opts.Parse(args); err != nil { restArgs, err := c.opts.Parse(args)
if err != nil {
c.ShowHelp() c.ShowHelp()
return return
} }
if len(args) > 0 { restArgs, err = opts.Globals().Parse(restArgs)
sc, ok := c.subcommands.Get(args[0]) if err != nil {
c.ShowHelp()
return
}
if len(restArgs) > 0 {
sc, ok := c.subcommands.Get(restArgs[0])
if ok { if ok {
sc.Execute(args[1:]) sc.Execute(restArgs[1:])
return return
} }
}
// TODO: remove when done with option parsing helpOpt, ok := opts.Globals().GetBool("help")
if args[0] == "--help" { if ok && helpOpt.Value() {
c.ShowHelp() c.ShowHelp()
return return
} }
}
if c.CanRun() { if c.CanRun() {
c.Run(args) c.Run(restArgs)
return return
} }

View File

@ -9,7 +9,7 @@ type BoolOption struct {
value bool value bool
} }
var _ Option = (*StringOption)(nil) var _ Option = (*BoolOption)(nil)
func Bool(name, shortName string, defaultValue bool, description string) *BoolOption { func Bool(name, shortName string, defaultValue bool, description string) *BoolOption {
return &BoolOption{ return &BoolOption{
@ -39,6 +39,7 @@ func (o *BoolOption) ShortName() string {
// Value implements Option. // Value implements Option.
func (o *BoolOption) Parse(raw string) error { func (o *BoolOption) Parse(raw string) error {
if raw == "" { if raw == "" {
o.value = !o.value
return nil return nil
} }
@ -51,6 +52,11 @@ func (o *BoolOption) Parse(raw string) error {
return nil return nil
} }
// TakesArg implements Option.
func (*BoolOption) TakesArg() bool {
return false
}
func (o *BoolOption) Value() bool { func (o *BoolOption) Value() bool {
return o.value return o.value
} }

View File

@ -51,6 +51,11 @@ func (o *FloatOption) Parse(raw string) error {
return nil return nil
} }
// TakesArg implements Option.
func (*FloatOption) TakesArg() bool {
return true
}
func (o *FloatOption) Value() float64 { func (o *FloatOption) Value() float64 {
return o.value return o.value
} }

View File

@ -9,7 +9,7 @@ type IntOption struct {
value int value int
} }
var _ Option = (*StringOption)(nil) var _ Option = (*IntOption)(nil)
func Int(name, shortName string, defaultValue int, description string) *IntOption { func Int(name, shortName string, defaultValue int, description string) *IntOption {
return &IntOption{ return &IntOption{
@ -51,6 +51,11 @@ func (o *IntOption) Parse(raw string) error {
return nil return nil
} }
// TakesArg implements Option.
func (*IntOption) TakesArg() bool {
return true
}
func (o *IntOption) Value() int { func (o *IntOption) Value() int {
return o.value return o.value
} }

View File

@ -7,6 +7,7 @@ type Option interface {
ShortName() string ShortName() string
Description() string Description() string
Parse(raw string) error Parse(raw string) error
TakesArg() bool
} }
func Names(o Option) string { func Names(o Option) string {

143
opts/parser.go Normal file
View File

@ -0,0 +1,143 @@
package opts
import (
"errors"
"fmt"
"strings"
)
var (
ErrInvalidShortOption = errors.New("invalid short option")
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]
// Handle regular argument
if !strings.HasPrefix(arg, "-") {
restArgs = append(restArgs, arg)
continue
}
// Handle options terminator
if arg == "--" {
restArgs = append(restArgs, args[i+1:]...)
return
}
// Handle long option
if strings.HasPrefix(arg, "--") {
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++
}
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))
}
}
}
}
}
return
}

View File

@ -6,23 +6,6 @@ func NewSet() Set {
return Set{} return Set{}
} }
func (s *Set) Add(f Option) {
*s = append(*s, f)
}
func (s Set) Get(name string) (Option, bool) {
for _, f := range s {
if f.Name() == name {
return f, true
}
if f.ShortName() == name {
return f, true
}
}
return nil, false
}
func (s Set) MaxWidth() int { func (s Set) MaxWidth() int {
max := 0 max := 0
for _, f := range s { for _, f := range s {
@ -33,7 +16,75 @@ func (s Set) MaxWidth() int {
return max return max
} }
// TODO: Implement func (s *Set) Add(f Option) {
func (s Set) Parse(args []string) error { *s = append(*s, f)
return nil }
func (s Set) Get(name string) (Option, bool) {
for _, o := range s {
if o.Name() == name {
return o, true
}
if o.ShortName() == name {
return o, true
}
}
return nil, false
}
func (s Set) GetBool(name string) (*BoolOption, bool) {
o, ok := s.Get(name)
if !ok {
return nil, false
}
b, ok := o.(*BoolOption)
if !ok {
return nil, false
}
return b, true
}
func (s Set) GetString(name string) (*StringOption, bool) {
o, ok := s.Get(name)
if !ok {
return nil, false
}
b, ok := o.(*StringOption)
if !ok {
return nil, false
}
return b, true
}
func (s Set) GetInt(name string) (*IntOption, bool) {
o, ok := s.Get(name)
if !ok {
return nil, false
}
b, ok := o.(*IntOption)
if !ok {
return nil, false
}
return b, true
}
func (s Set) GetFloat(name string) (*FloatOption, bool) {
o, ok := s.Get(name)
if !ok {
return nil, false
}
b, ok := o.(*FloatOption)
if !ok {
return nil, false
}
return b, true
} }

View File

@ -43,6 +43,11 @@ func (o *StringOption) Parse(raw string) error {
return nil return nil
} }
// TakesArg implements Option.
func (*StringOption) TakesArg() bool {
return true
}
func (o *StringOption) Value() string { func (o *StringOption) Value() string {
return o.value return o.value
} }