final checks on opt values moved after option parsing is complete
This commit is contained in:
parent
92267aec50
commit
9e28ee6545
108
cli.go
108
cli.go
@ -25,7 +25,7 @@ type cliOptionParser interface {
|
|||||||
isSet() bool
|
isSet() bool
|
||||||
isHidden() bool
|
isHidden() bool
|
||||||
requiresValue() bool
|
requiresValue() bool
|
||||||
finalCheck(cliValue string) (err error)
|
finalCheck() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptManager interface {
|
type OptManager interface {
|
||||||
@ -33,7 +33,7 @@ type OptManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SpecialValueFunc func(manager OptManager, cliValue string, currentValue any) (value any, err error)
|
type SpecialValueFunc func(manager OptManager, cliValue string, currentValue any) (value any, err error)
|
||||||
type FinalCheckFunc func(cliValue string, currentValue any) (err error)
|
type FinalCheckFunc func(currentValue any) (err error)
|
||||||
|
|
||||||
type OptReference interface {
|
type OptReference interface {
|
||||||
AddSpecialValue(cliValue string, specialFunc SpecialValueFunc)
|
AddSpecialValue(cliValue string, specialFunc SpecialValueFunc)
|
||||||
@ -158,17 +158,12 @@ func (cli *CliParser) addHelpAndVersion() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CliParser) Parse() (err error) {
|
func (cli *CliParser) parseOptions() (commandArgs []string, err error) {
|
||||||
var arg string
|
// var commandArgs []string
|
||||||
var i int
|
|
||||||
var commandArgs []string
|
|
||||||
var optionsAllowed bool = true
|
var optionsAllowed bool = true
|
||||||
|
|
||||||
cli.addHelpAndVersion()
|
|
||||||
|
|
||||||
// first parse options and collect argument in the args array
|
|
||||||
skipNext := false
|
skipNext := false
|
||||||
for i, arg = range cli.cliArgs[1:] {
|
for i, arg := range cli.cliArgs[1:] {
|
||||||
if skipNext {
|
if skipNext {
|
||||||
skipNext = false
|
skipNext = false
|
||||||
} else {
|
} else {
|
||||||
@ -186,49 +181,78 @@ func (cli *CliParser) Parse() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// then parse collected arguments
|
func (cli *CliParser) checkOptionValues() (err error) {
|
||||||
if err == nil {
|
for _, opt := range cli.options {
|
||||||
var n int
|
if err = opt.finalCheck(); err != nil {
|
||||||
i = 0
|
break
|
||||||
|
|
||||||
// acquire arguments as required by the argSpecs
|
|
||||||
specIndex := -1
|
|
||||||
for index, argSpec := range cli.argSpecs {
|
|
||||||
specIndex = index
|
|
||||||
if n, err = argSpec.parse(cli, specIndex, commandArgs, i); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i += n
|
|
||||||
if i >= len(commandArgs) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// check if there are remaining arg-specs that require a value
|
func (cli *CliParser) parseCommandArgs(commandArgs []string) (err error) {
|
||||||
if err == nil {
|
var n int
|
||||||
if i < len(commandArgs) {
|
|
||||||
err = fmt.Errorf("too many arguments: %d allowed", i)
|
i := 0
|
||||||
} else {
|
// acquire arguments as required by the argSpecs
|
||||||
specIndex++
|
specIndex := -1
|
||||||
if specIndex < len(cli.argSpecs) {
|
for index, argSpec := range cli.argSpecs {
|
||||||
// skip all non required args
|
specIndex = index
|
||||||
for _, spec := range cli.argSpecs[specIndex:] {
|
if n, err = argSpec.parse(cli, specIndex, commandArgs, i); err != nil {
|
||||||
if !spec.getBase().required {
|
break
|
||||||
specIndex++
|
}
|
||||||
}
|
i += n
|
||||||
|
if i >= len(commandArgs) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there are remaining arg-specs that require a value
|
||||||
|
if err == nil {
|
||||||
|
if i < len(commandArgs) {
|
||||||
|
err = fmt.Errorf("too many arguments: %d allowed", i)
|
||||||
|
} else {
|
||||||
|
specIndex++
|
||||||
|
if specIndex < len(cli.argSpecs) {
|
||||||
|
// skip all non required args
|
||||||
|
for _, spec := range cli.argSpecs[specIndex:] {
|
||||||
|
if !spec.getBase().required {
|
||||||
|
specIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// return error if there are remaining arg-specs that require a value
|
}
|
||||||
if specIndex < len(cli.argSpecs) {
|
// return error if there are remaining arg-specs that require a value
|
||||||
err = errTooFewArguments(len(cli.argSpecs))
|
if specIndex < len(cli.argSpecs) {
|
||||||
}
|
err = errTooFewArguments(len(cli.argSpecs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) Parse() (err error) {
|
||||||
|
var commandArgs []string
|
||||||
|
|
||||||
|
cli.addHelpAndVersion()
|
||||||
|
|
||||||
|
// first parse options and collect command arguments in the args array
|
||||||
|
if commandArgs, err = cli.parseOptions(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// do final checks
|
||||||
|
if err = cli.checkOptionValues(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// then parse collected arguments
|
||||||
|
err = cli.parseCommandArgs(commandArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (cli *CliParser) checkCompatibility(arg string) (err error) {
|
func (cli *CliParser) checkCompatibility(arg string) (err error) {
|
||||||
var opti cliOptionParser
|
var opti cliOptionParser
|
||||||
if opti = cli.findOptionByArg(arg); opti != nil {
|
if opti = cli.findOptionByArg(arg); opti != nil {
|
||||||
|
|||||||
@ -153,13 +153,13 @@ func (opt *cliOptionBase) fetchOptionValue(parser cliParser, argIndex int, value
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionBase) finalCheck(cliValue string) (err error) {
|
func (opt *cliOptionBase) finalCheck() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionBase) checkValue(cliValue string, value any) (err error) {
|
func (opt *cliOptionBase) checkValue(value any) (err error) {
|
||||||
if opt.finalCheckFunc != nil {
|
if opt.finalCheckFunc != nil {
|
||||||
err = opt.finalCheckFunc(cliValue, value)
|
err = opt.finalCheckFunc(value)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,9 +67,9 @@ func (opt *cliOptionBool) parse(parser cliParser, argIndex int, valuePtr *string
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionBool) finalCheck(cliValue string) (err error) {
|
func (opt *cliOptionBool) finalCheck() (err error) {
|
||||||
currentValue, _ := opt.getTargetVar()
|
currentValue, _ := opt.getTargetVar()
|
||||||
return opt.getBase().checkValue(cliValue, currentValue)
|
return opt.getBase().checkValue(currentValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CliParser) AddBoolOpt(name, short string, targetVar *bool, description string, aliases ...string) OptReference {
|
func (cli *CliParser) AddBoolOpt(name, short string, targetVar *bool, description string, aliases ...string) OptReference {
|
||||||
|
|||||||
@ -97,9 +97,9 @@ func (opt *cliOptionIntArray) parse(parser cliParser, argIndex int, valuePtr *st
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionIntArray) finalCheck(cliValue string) (err error) {
|
func (opt *cliOptionIntArray) finalCheck() (err error) {
|
||||||
currentValue, _ := opt.getTargetVar()
|
currentValue, _ := opt.getTargetVar()
|
||||||
return opt.getBase().checkValue(cliValue, currentValue)
|
return opt.getBase().checkValue(currentValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CliParser) AddIntArrayOpt(name, short string, targetVar *[]int, defaultValue []int, description string, aliases ...string) OptReference {
|
func (cli *CliParser) AddIntArrayOpt(name, short string, targetVar *[]int, defaultValue []int, description string, aliases ...string) OptReference {
|
||||||
|
|||||||
@ -57,9 +57,9 @@ func (opt *cliOptionInt) parse(parser cliParser, argIndex int, valuePtr *string)
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (opt *cliOptionInt) finalCheck(cliValue string) (err error) {
|
func (opt *cliOptionInt) finalCheck() (err error) {
|
||||||
currentValue, _ := opt.getTargetVar()
|
currentValue, _ := opt.getTargetVar()
|
||||||
return opt.getBase().checkValue(cliValue, currentValue)
|
return opt.getBase().checkValue(currentValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CliParser) AddIntOpt(name, short string, targetVar *int, defaultValue int, description string, aliases ...string) OptReference {
|
func (cli *CliParser) AddIntOpt(name, short string, targetVar *int, defaultValue int, description string, aliases ...string) OptReference {
|
||||||
|
|||||||
@ -53,9 +53,9 @@ func (opt *cliOptionMulti) parse(parser cliParser, argIndex int, valuePtr *strin
|
|||||||
value = "true"
|
value = "true"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (opt *cliOptionMulti) finalCheck(cliValue string) (err error) {
|
func (opt *cliOptionMulti) finalCheck() (err error) {
|
||||||
currentValue, _ := opt.getTargetVar()
|
currentValue, _ := opt.getTargetVar()
|
||||||
return opt.getBase().checkValue(cliValue, currentValue)
|
return opt.getBase().checkValue(currentValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CliParser) AddMultiOpt(name, short string, targetVar *int, defaultValue int, description string, aliases ...string) OptReference {
|
func (cli *CliParser) AddMultiOpt(name, short string, targetVar *int, defaultValue int, description string, aliases ...string) OptReference {
|
||||||
|
|||||||
@ -85,7 +85,7 @@ func (cli *CliParser) AddStringArrayOpt(name, short string, targetVar *[]string,
|
|||||||
return opt
|
return opt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionStringArray) finalCheck(cliValue string) (err error) {
|
func (opt *cliOptionStringArray) finalCheck() (err error) {
|
||||||
currentValue, _ := opt.getTargetVar()
|
currentValue, _ := opt.getTargetVar()
|
||||||
return opt.getBase().checkValue(cliValue, currentValue)
|
return opt.getBase().checkValue(currentValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,9 +89,9 @@ func (opt *cliOptionStringMap) parse(parser cliParser, argIndex int, valuePtr *s
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionStringMap) finalCheck(cliValue string) (err error) {
|
func (opt *cliOptionStringMap) finalCheck() (err error) {
|
||||||
currentValue, _ := opt.getTargetVar()
|
currentValue, _ := opt.getTargetVar()
|
||||||
return opt.getBase().checkValue(cliValue, currentValue)
|
return opt.getBase().checkValue(currentValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CliParser) AddStringMapOpt(name, short string, targetVar *map[string]string, defaultValue map[string]string, description string, aliases ...string) OptReference {
|
func (cli *CliParser) AddStringMapOpt(name, short string, targetVar *map[string]string, defaultValue map[string]string, description string, aliases ...string) OptReference {
|
||||||
|
|||||||
@ -55,9 +55,9 @@ func (opt *cliOptionString) parse(parser cliParser, argIndex int, valuePtr *stri
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (opt *cliOptionString) finalCheck(cliValue string) (err error) {
|
func (opt *cliOptionString) finalCheck() (err error) {
|
||||||
currentValue, _ := opt.getTargetVar()
|
currentValue, _ := opt.getTargetVar()
|
||||||
return opt.getBase().checkValue(cliValue, currentValue)
|
return opt.getBase().checkValue(currentValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CliParser) AddStringOpt(name, short string, targetVar *string, defaultValue string, description string, aliases ...string) OptReference {
|
func (cli *CliParser) AddStringOpt(name, short string, targetVar *string, defaultValue string, description string, aliases ...string) OptReference {
|
||||||
|
|||||||
107
opt_test.go
107
opt_test.go
@ -37,7 +37,7 @@ func TestOneOptWithEqual(t *testing.T) {
|
|||||||
func addBoxOption(cli *CliParser, boxPtr *[]int) {
|
func addBoxOption(cli *CliParser, boxPtr *[]int) {
|
||||||
// Define options
|
// Define options
|
||||||
optRef := cli.AddIntArrayOpt("box", "b", boxPtr, []int{1, 2, 3, 4}, "Box spec: Left,Width,Top,Height; provide 4 int values")
|
optRef := cli.AddIntArrayOpt("box", "b", boxPtr, []int{1, 2, 3, 4}, "Box spec: Left,Width,Top,Height; provide 4 int values")
|
||||||
optRef.OnFinalCheck(func(cliValue string, currentValue any) (err error) {
|
optRef.OnFinalCheck(func(currentValue any) (err error) {
|
||||||
if array, ok := currentValue.([]int); ok {
|
if array, ok := currentValue.([]int); ok {
|
||||||
if len(array) != 4 {
|
if len(array) != 4 {
|
||||||
err = fmt.Errorf("--box option requires exactly 4 items, %d provided", len(array))
|
err = fmt.Errorf("--box option requires exactly 4 items, %d provided", len(array))
|
||||||
@ -49,8 +49,34 @@ func addBoxOption(cli *CliParser, boxPtr *[]int) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntArrayOpt(t *testing.T) {
|
func testIntArrayOptOk(t *testing.T, n int, args []string) {
|
||||||
var box []int
|
var box []int
|
||||||
|
var cli CliParser
|
||||||
|
t.Logf("Arg set n. %d", n)
|
||||||
|
addBoxOption(&cli, &box)
|
||||||
|
cli.Init(args, "1.0.0", "TestIntArrayOpt description")
|
||||||
|
if err := cli.Parse(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if len(box) != 4 {
|
||||||
|
t.Errorf(`Expected 4 items, got %d`, len(box))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIntArrayOptKo(t *testing.T, n int, args []string, msg string) {
|
||||||
|
var box []int
|
||||||
|
var cli CliParser
|
||||||
|
t.Logf("Arg set n. %d", n)
|
||||||
|
addBoxOption(&cli, &box)
|
||||||
|
cli.Init(args, "1.0.0", "TestIntArrayOpt description")
|
||||||
|
if err := cli.Parse(); err == nil {
|
||||||
|
t.Errorf("Expected error, got nil")
|
||||||
|
} else if err.Error() != msg {
|
||||||
|
t.Errorf(`Invalid error: %q; expected: %q`, err.Error(), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntArrayOpt(t *testing.T) {
|
||||||
|
var args []string
|
||||||
// Always recover from panic to return error before adding options
|
// Always recover from panic to return error before adding options
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -61,68 +87,49 @@ func TestIntArrayOpt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
args_1 := []string{
|
n := 0
|
||||||
|
|
||||||
|
n++
|
||||||
|
args = []string{
|
||||||
"TestIntArrayOpt",
|
"TestIntArrayOpt",
|
||||||
"--box=100,200,150,450",
|
"--box=100,200,150,450",
|
||||||
}
|
}
|
||||||
|
testIntArrayOptOk(t, n, args)
|
||||||
|
|
||||||
if true {
|
n++
|
||||||
var cli CliParser
|
args = []string{
|
||||||
t.Logf("Arg set n. 1")
|
|
||||||
addBoxOption(&cli, &box)
|
|
||||||
cli.Init(args_1, "1.0.0", "TestIntArrayOpt description")
|
|
||||||
if err := cli.Parse(); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else if len(box) != 4 {
|
|
||||||
t.Errorf(`Expected 4 items, got %d`, len(box))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args_2 := []string{
|
|
||||||
"TestIntArrayOpt",
|
"TestIntArrayOpt",
|
||||||
"--box=100,200,150",
|
"--box=100,200,150",
|
||||||
}
|
}
|
||||||
if true {
|
testIntArrayOptKo(t, n, args, "--box option requires exactly 4 items, 3 provided")
|
||||||
var cli CliParser
|
|
||||||
t.Logf("Arg set n. 2")
|
|
||||||
addBoxOption(&cli, &box)
|
|
||||||
cli.Init(args_2, "1.0.0", "TestIntArrayOpt description")
|
|
||||||
if err := cli.Parse(); err == nil {
|
|
||||||
t.Errorf("Expected error, got nil")
|
|
||||||
} else if err.Error() != "--box option requires exactly 4 items, 3 provided" {
|
|
||||||
t.Errorf(`Invalid error: %q; expected: "--box option requires exactly 4 items, 3 provided"`, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args_3 := []string{
|
n++
|
||||||
|
args = []string{
|
||||||
"TestIntArrayOpt",
|
"TestIntArrayOpt",
|
||||||
"--box", "100,200,150,450",
|
"--box", "100,200,150,450",
|
||||||
}
|
}
|
||||||
if true {
|
testIntArrayOptOk(t, n, args)
|
||||||
var cli CliParser
|
|
||||||
t.Logf("Arg set n. 3")
|
|
||||||
addBoxOption(&cli, &box)
|
|
||||||
cli.Init(args_3, "1.0.0", "TestIntArrayOpt description")
|
|
||||||
if err := cli.Parse(); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else if len(box) != 4 {
|
|
||||||
t.Errorf(`Expected 4 items, got %d`, len(box))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args_4 := []string{
|
n++
|
||||||
|
args = []string{
|
||||||
"TestIntArrayOpt",
|
"TestIntArrayOpt",
|
||||||
"--box",
|
"--box",
|
||||||
}
|
}
|
||||||
if true {
|
testIntArrayOptKo(t, n, args, `option "box" requires a value`)
|
||||||
var cli CliParser
|
|
||||||
t.Logf("Arg set n. 4")
|
n++
|
||||||
addBoxOption(&cli, &box)
|
args = []string{
|
||||||
cli.Init(args_4, "1.0.0", "TestIntArrayOpt description")
|
"TestIntArrayOpt",
|
||||||
if err := cli.Parse(); err == nil {
|
"--box", "100,200,150",
|
||||||
t.Errorf("Expected error, got nil")
|
"--box", "450",
|
||||||
} else if err.Error() != `option "box" requires a value` {
|
|
||||||
t.Errorf(`Invalid error - Expected: 'option "box" requires a value'; got '%s'`, err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
testIntArrayOptOk(t, n, args)
|
||||||
|
|
||||||
|
n++
|
||||||
|
args = []string{
|
||||||
|
"TestIntArrayOpt",
|
||||||
|
"--box", "100,200,150",
|
||||||
|
"--box", "450,12",
|
||||||
|
}
|
||||||
|
testIntArrayOptKo(t, n, args, "--box option requires exactly 4 items, 5 provided")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,16 +104,12 @@ func (cli *CliParser) parseArg(arg string, index int) (skipNextArg bool, err err
|
|||||||
}
|
}
|
||||||
for i, optName := range opts {
|
for i, optName := range opts {
|
||||||
if opt := cli.findOptionByArg(dashes + optName); opt != nil {
|
if opt := cli.findOptionByArg(dashes + optName); opt != nil {
|
||||||
var optValue string
|
|
||||||
if equalPresent && i == len(opts)-1 {
|
if equalPresent && i == len(opts)-1 {
|
||||||
_, optValue, err = opt.parse(cli, index, &value)
|
_, _, err = opt.parse(cli, index, &value)
|
||||||
} else if i < len(opts)-1 && opt.requiresValue() {
|
} else if i < len(opts)-1 && opt.requiresValue() {
|
||||||
err = errMissingOptionValue(dashes + optName)
|
err = errMissingOptionValue(dashes + optName)
|
||||||
} else {
|
} else {
|
||||||
skipNextArg, optValue, err = opt.parse(cli, index, nil)
|
skipNextArg, _, err = opt.parse(cli, index, nil)
|
||||||
}
|
|
||||||
if err == nil && i == len(opts)-1 {
|
|
||||||
err = opt.finalCheck(optValue)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = errUnknownOption(dashes + optName)
|
err = errUnknownOption(dashes + optName)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user