283 lines
5.7 KiB
Go
283 lines
5.7 KiB
Go
|
package cli
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
type upMatcher interface {
|
||
|
match(args []string, c *parseContext) (bool, []string)
|
||
|
}
|
||
|
|
||
|
type upShortcut bool
|
||
|
|
||
|
func (u upShortcut) match(args []string, c *parseContext) (bool, []string) {
|
||
|
return true, args
|
||
|
}
|
||
|
|
||
|
func (u upShortcut) String() string {
|
||
|
return "*"
|
||
|
}
|
||
|
|
||
|
type upOptsEnd bool
|
||
|
|
||
|
func (u upOptsEnd) match(args []string, c *parseContext) (bool, []string) {
|
||
|
c.rejectOptions = true
|
||
|
return true, args
|
||
|
}
|
||
|
|
||
|
func (u upOptsEnd) String() string {
|
||
|
return "--"
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
shortcut = upShortcut(true)
|
||
|
optsEnd = upOptsEnd(true)
|
||
|
)
|
||
|
|
||
|
func (arg *arg) match(args []string, c *parseContext) (bool, []string) {
|
||
|
if len(args) == 0 {
|
||
|
return false, args
|
||
|
}
|
||
|
if !c.rejectOptions && strings.HasPrefix(args[0], "-") && args[0] != "-" {
|
||
|
return false, args
|
||
|
}
|
||
|
c.args[arg] = append(c.args[arg], args[0])
|
||
|
return true, args[1:]
|
||
|
}
|
||
|
|
||
|
type optMatcher struct {
|
||
|
theOne *opt
|
||
|
optionsIdx map[string]*opt
|
||
|
}
|
||
|
|
||
|
func (o *optMatcher) match(args []string, c *parseContext) (bool, []string) {
|
||
|
if len(args) == 0 || c.rejectOptions {
|
||
|
return o.theOne.valueSetFromEnv, args
|
||
|
}
|
||
|
|
||
|
idx := 0
|
||
|
for idx < len(args) {
|
||
|
arg := args[idx]
|
||
|
switch {
|
||
|
case arg == "-":
|
||
|
idx++
|
||
|
case arg == "--":
|
||
|
return o.theOne.valueSetFromEnv, nil
|
||
|
case strings.HasPrefix(arg, "--"):
|
||
|
matched, consumed, nargs := o.matchLongOpt(args, idx, c)
|
||
|
|
||
|
if matched {
|
||
|
return true, nargs
|
||
|
}
|
||
|
if consumed == 0 {
|
||
|
return o.theOne.valueSetFromEnv, args
|
||
|
}
|
||
|
idx += consumed
|
||
|
|
||
|
case strings.HasPrefix(arg, "-"):
|
||
|
matched, consumed, nargs := o.matchShortOpt(args, idx, c)
|
||
|
if matched {
|
||
|
return true, nargs
|
||
|
}
|
||
|
if consumed == 0 {
|
||
|
return o.theOne.valueSetFromEnv, args
|
||
|
}
|
||
|
idx += consumed
|
||
|
|
||
|
default:
|
||
|
return o.theOne.valueSetFromEnv, args
|
||
|
}
|
||
|
}
|
||
|
return o.theOne.valueSetFromEnv, args
|
||
|
}
|
||
|
|
||
|
func (o *optMatcher) matchLongOpt(args []string, idx int, c *parseContext) (bool, int, []string) {
|
||
|
arg := args[idx]
|
||
|
kv := strings.Split(arg, "=")
|
||
|
name := kv[0]
|
||
|
opt, found := o.optionsIdx[name]
|
||
|
if !found {
|
||
|
return false, 0, args
|
||
|
}
|
||
|
|
||
|
switch {
|
||
|
case len(kv) == 2:
|
||
|
if opt != o.theOne {
|
||
|
return false, 1, args
|
||
|
}
|
||
|
value := kv[1]
|
||
|
c.opts[o.theOne] = append(c.opts[o.theOne], value)
|
||
|
return true, 1, removeStringAt(idx, args)
|
||
|
case opt.isBool():
|
||
|
if opt != o.theOne {
|
||
|
return false, 1, args
|
||
|
}
|
||
|
c.opts[o.theOne] = append(c.opts[o.theOne], "true")
|
||
|
return true, 1, removeStringAt(idx, args)
|
||
|
default:
|
||
|
if len(args[idx:]) < 2 {
|
||
|
return false, 0, args
|
||
|
}
|
||
|
if opt != o.theOne {
|
||
|
return false, 2, args
|
||
|
}
|
||
|
value := args[idx+1]
|
||
|
if strings.HasPrefix(value, "-") {
|
||
|
return false, 0, args
|
||
|
}
|
||
|
c.opts[o.theOne] = append(c.opts[o.theOne], value)
|
||
|
return true, 2, removeStringsBetween(idx, idx+1, args)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (o *optMatcher) matchShortOpt(args []string, idx int, c *parseContext) (bool, int, []string) {
|
||
|
arg := args[idx]
|
||
|
if len(arg) < 2 {
|
||
|
return false, 0, args
|
||
|
}
|
||
|
|
||
|
if strings.HasPrefix(arg[2:], "=") {
|
||
|
name := arg[0:2]
|
||
|
opt, _ := o.optionsIdx[name]
|
||
|
if opt == o.theOne {
|
||
|
value := arg[3:]
|
||
|
if value == "" {
|
||
|
return false, 0, args
|
||
|
}
|
||
|
c.opts[o.theOne] = append(c.opts[o.theOne], value)
|
||
|
return true, 1, removeStringAt(idx, args)
|
||
|
}
|
||
|
|
||
|
return false, 1, args
|
||
|
}
|
||
|
|
||
|
rem := arg[1:]
|
||
|
|
||
|
remIdx := 0
|
||
|
for len(rem[remIdx:]) > 0 {
|
||
|
name := "-" + rem[remIdx:remIdx+1]
|
||
|
|
||
|
opt, found := o.optionsIdx[name]
|
||
|
if !found {
|
||
|
return false, 0, args
|
||
|
}
|
||
|
|
||
|
if opt.isBool() {
|
||
|
if opt != o.theOne {
|
||
|
remIdx++
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
c.opts[o.theOne] = append(c.opts[o.theOne], "true")
|
||
|
newRem := rem[:remIdx] + rem[remIdx+1:]
|
||
|
if newRem == "" {
|
||
|
return true, 1, removeStringAt(idx, args)
|
||
|
}
|
||
|
return true, 0, replaceStringAt(idx, "-"+newRem, args)
|
||
|
}
|
||
|
|
||
|
value := rem[remIdx+1:]
|
||
|
if value == "" {
|
||
|
if len(args[idx+1:]) == 0 {
|
||
|
return false, 0, args
|
||
|
}
|
||
|
if opt != o.theOne {
|
||
|
return false, 2, args
|
||
|
}
|
||
|
|
||
|
value = args[idx+1]
|
||
|
if strings.HasPrefix(value, "-") {
|
||
|
return false, 0, args
|
||
|
}
|
||
|
c.opts[o.theOne] = append(c.opts[o.theOne], value)
|
||
|
|
||
|
newRem := rem[:remIdx]
|
||
|
if newRem == "" {
|
||
|
return true, 2, removeStringsBetween(idx, idx+1, args)
|
||
|
}
|
||
|
|
||
|
nargs := replaceStringAt(idx, "-"+newRem, args)
|
||
|
|
||
|
return true, 1, removeStringAt(idx+1, nargs)
|
||
|
}
|
||
|
|
||
|
if opt != o.theOne {
|
||
|
return false, 1, args
|
||
|
}
|
||
|
c.opts[o.theOne] = append(c.opts[o.theOne], value)
|
||
|
newRem := rem[:remIdx]
|
||
|
if newRem == "" {
|
||
|
return true, 1, removeStringAt(idx, args)
|
||
|
}
|
||
|
return true, 0, replaceStringAt(idx, "-"+newRem, args)
|
||
|
|
||
|
}
|
||
|
|
||
|
return false, 1, args
|
||
|
}
|
||
|
|
||
|
type optsMatcher struct {
|
||
|
options []*opt
|
||
|
optionsIndex map[string]*opt
|
||
|
}
|
||
|
|
||
|
func (om optsMatcher) try(args []string, c *parseContext) (bool, []string) {
|
||
|
if len(args) == 0 || c.rejectOptions {
|
||
|
return false, args
|
||
|
}
|
||
|
for _, o := range om.options {
|
||
|
if _, exclude := c.excludedOpts[o]; exclude {
|
||
|
continue
|
||
|
}
|
||
|
if ok, nargs := (&optMatcher{theOne: o, optionsIdx: om.optionsIndex}).match(args, c); ok {
|
||
|
if o.valueSetFromEnv {
|
||
|
c.excludedOpts[o] = struct{}{}
|
||
|
}
|
||
|
return true, nargs
|
||
|
}
|
||
|
}
|
||
|
return false, args
|
||
|
}
|
||
|
|
||
|
func (om optsMatcher) match(args []string, c *parseContext) (bool, []string) {
|
||
|
ok, nargs := om.try(args, c)
|
||
|
if !ok {
|
||
|
return false, args
|
||
|
}
|
||
|
|
||
|
for {
|
||
|
ok, nnargs := om.try(nargs, c)
|
||
|
if !ok {
|
||
|
return true, nargs
|
||
|
}
|
||
|
nargs = nnargs
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (om optsMatcher) String() string {
|
||
|
return fmt.Sprintf("Opts(%v)", om.options)
|
||
|
}
|
||
|
|
||
|
func removeStringAt(idx int, arr []string) []string {
|
||
|
res := make([]string, len(arr)-1)
|
||
|
copy(res, arr[:idx])
|
||
|
copy(res[idx:], arr[idx+1:])
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
func removeStringsBetween(from, to int, arr []string) []string {
|
||
|
res := make([]string, len(arr)-(to-from+1))
|
||
|
copy(res, arr[:from])
|
||
|
copy(res[from:], arr[to+1:])
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
func replaceStringAt(idx int, with string, arr []string) []string {
|
||
|
res := make([]string, len(arr))
|
||
|
copy(res, arr[:idx])
|
||
|
res[idx] = with
|
||
|
copy(res[idx+1:], arr[idx+1:])
|
||
|
return res
|
||
|
}
|