Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00b84278d8 | |||
| 9e28ee6545 | |||
| 92267aec50 | |||
| 95fae40d5f | |||
| b5f8d9eaab | |||
| ac8d5fa3a9 | |||
| f55e9cea82 |
110
cli.go
110
cli.go
@ -14,10 +14,11 @@ type cliParser interface {
|
|||||||
getCliArgs(startIndex, endIndex int) (args []string)
|
getCliArgs(startIndex, endIndex int) (args []string)
|
||||||
PrintVersion(specs []string)
|
PrintVersion(specs []string)
|
||||||
PrintUsage()
|
PrintUsage()
|
||||||
|
FlagIsSet(flag int16) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type cliOptionParser interface {
|
type cliOptionParser interface {
|
||||||
parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error)
|
parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, optValue string, err error)
|
||||||
getTemplate() string
|
getTemplate() string
|
||||||
getDefaultValue() string
|
getDefaultValue() string
|
||||||
getBase() *cliOptionBase
|
getBase() *cliOptionBase
|
||||||
@ -25,6 +26,7 @@ type cliOptionParser interface {
|
|||||||
isSet() bool
|
isSet() bool
|
||||||
isHidden() bool
|
isHidden() bool
|
||||||
requiresValue() bool
|
requiresValue() bool
|
||||||
|
finalCheck() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptManager interface {
|
type OptManager interface {
|
||||||
@ -32,26 +34,39 @@ 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(currentValue any) (err error)
|
||||||
|
|
||||||
type OptReference interface {
|
type OptReference interface {
|
||||||
AddSpecialValue(cliValue string, specialFunc SpecialValueFunc)
|
AddSpecialValue(cliValue string, specialFunc SpecialValueFunc)
|
||||||
|
OnFinalCheck(checkFunc FinalCheckFunc)
|
||||||
SetHidden(hidden bool)
|
SetHidden(hidden bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ResetOnEqualSign = int16(1 << iota) // for some option types, like arrays, the equal signs reset to empty the accumulator of values
|
||||||
|
)
|
||||||
|
|
||||||
type CliParser struct {
|
type CliParser struct {
|
||||||
description string
|
description string
|
||||||
version string
|
version string
|
||||||
options []cliOptionParser
|
options []cliOptionParser
|
||||||
argSpecs []argSpec
|
argSpecs []argSpec
|
||||||
cliArgs []string
|
cliArgs []string
|
||||||
|
flags int16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CliParser) Init(argv []string, version string, description string) {
|
func (cli *CliParser) Init(argv []string, version string, description string, flags ...int16) {
|
||||||
cli.version = version
|
cli.version = version
|
||||||
cli.description = description
|
cli.description = description
|
||||||
cli.cliArgs = argv
|
cli.cliArgs = argv
|
||||||
|
for _, flag := range flags {
|
||||||
|
cli.flags |= flag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) FlagIsSet(flag int16) bool {
|
||||||
|
return (cli.flags & flag) == flag
|
||||||
|
}
|
||||||
func (cli *CliParser) GetOption(name string) (ref OptReference) {
|
func (cli *CliParser) GetOption(name string) (ref OptReference) {
|
||||||
var opt cliOptionParser
|
var opt cliOptionParser
|
||||||
if strings.HasPrefix(name, "-") {
|
if strings.HasPrefix(name, "-") {
|
||||||
@ -155,16 +170,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 args []string
|
|
||||||
var optionsAllowed bool = true
|
var optionsAllowed bool = true
|
||||||
|
|
||||||
cli.addHelpAndVersion()
|
|
||||||
|
|
||||||
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 {
|
||||||
@ -178,43 +189,82 @@ func (cli *CliParser) Parse() (err error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
args = append(args, arg)
|
commandArgs = append(commandArgs, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
return
|
||||||
var argSpec argSpec
|
}
|
||||||
var n, specIndex int
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
for specIndex, argSpec = range cli.argSpecs {
|
func (cli *CliParser) checkOptionValues() (err error) {
|
||||||
if n, err = argSpec.parse(cli, specIndex, args, i); err != nil {
|
for _, opt := range cli.options {
|
||||||
break
|
if err = opt.finalCheck(); err != nil {
|
||||||
}
|
break
|
||||||
i += n
|
|
||||||
if i >= len(args) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err == nil {
|
}
|
||||||
if i < len(args) {
|
return
|
||||||
err = fmt.Errorf("too many arguments: %d allowed", i)
|
}
|
||||||
} else {
|
|
||||||
specIndex++
|
func (cli *CliParser) parseCommandArgs(commandArgs []string) (err error) {
|
||||||
|
var n int
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:] {
|
for _, spec := range cli.argSpecs[specIndex:] {
|
||||||
if !spec.getBase().required {
|
if !spec.getBase().required {
|
||||||
specIndex++
|
specIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if specIndex < len(cli.argSpecs) {
|
}
|
||||||
err = errTooFewArguments(len(cli.argSpecs))
|
// return error if there are remaining arg-specs that require a value
|
||||||
}
|
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 {
|
||||||
|
|||||||
47
cli_test.go
47
cli_test.go
@ -85,16 +85,17 @@ where:
|
|||||||
<dest> Output destination file
|
<dest> Output destination file
|
||||||
<report> Optional report file
|
<report> Optional report file
|
||||||
<options>
|
<options>
|
||||||
-o, --print-ocr Print the OCR output to stderr
|
-V, --verbose Print verbose output (default: "0")
|
||||||
-s, --save-clip Save the image clips as PNG files (alias: save-clips)
|
-o, --print-ocr Print the OCR output to stderr
|
||||||
-t, --trace Enable trace mode for detailed logging
|
-s, --save-clip Save the image clips as PNG files (alias: save-clips)
|
||||||
-p, --page(s) <num>["," ...] Process only the specified pages (comma-separated list)
|
-t, --trace Enable trace mode for detailed logging
|
||||||
-c, --config <file> Alternate configuration file
|
-p, --page(s) <num>["," ...] Process only the specified pages (comma-separated list)
|
||||||
-l, --log(s) <string>["," ...] Logging options (comma-separated list)
|
-c, --config <file> Alternate configuration file
|
||||||
-V, --var(s) <key=value>["," ...] Define one or more comma separated variables for the actions context (multiple allowed)
|
-l, --log(s) <string>["," ...] Logging options (comma-separated list)
|
||||||
-n, --input-name <string> Input file name when source comes from stdin
|
--var(s) <key=value>["," ...] Define one or more comma separated variables for the actions context (multiple allowed)
|
||||||
-d, --work-dir <dir> Work directory
|
-n, --input-name <string> Input file name when source comes from stdin
|
||||||
--attempts <num> Attempts for retrying failed operations (default: "1")
|
-d, --work-dir <dir> Work directory
|
||||||
|
--attempts <num> Attempts for retrying failed operations (default: "1")
|
||||||
`
|
`
|
||||||
var cli CliParser
|
var cli CliParser
|
||||||
var gd GlobalData
|
var gd GlobalData
|
||||||
@ -109,7 +110,8 @@ where:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestParser(t *testing.T) {
|
func TestParser(t *testing.T) {
|
||||||
const expectedOutput = `Option: print-ocr, Type: bool, Value: true
|
const expectedOutput = `Option: verbose, Type: num, Value: 3
|
||||||
|
Option: print-ocr, Type: bool, Value: true
|
||||||
Option: save-clip, Type: bool, Value: false
|
Option: save-clip, Type: bool, Value: false
|
||||||
Option: trace, Type: bool, Value: true
|
Option: trace, Type: bool, Value: true
|
||||||
Option: page, Type: num-array, Value: [17 18]
|
Option: page, Type: num-array, Value: [17 18]
|
||||||
@ -135,7 +137,7 @@ Option: version, Type: n/a, Value: <nil>
|
|||||||
if err := cli.Parse(); err == nil {
|
if err := cli.Parse(); err == nil {
|
||||||
cli.TraceOptions(tracer)
|
cli.TraceOptions(tracer)
|
||||||
if sb.String() != expectedOutput {
|
if sb.String() != expectedOutput {
|
||||||
t.Errorf("Parsed options do not match expected.\nGot:\n%q\nExpected:\n%q", sb.String(), expectedOutput)
|
t.Errorf("Parsed options do not match expected list.\nGot:\n%q\nExpected:\n%q", sb.String(), expectedOutput)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@ -322,15 +324,16 @@ where:
|
|||||||
<dest> Output destination file
|
<dest> Output destination file
|
||||||
<report> Optional report file
|
<report> Optional report file
|
||||||
<options>
|
<options>
|
||||||
-o, --print-ocr Print the OCR output to stderr
|
-V, --verbose Print verbose output (default: "0")
|
||||||
-t, --trace Enable trace mode for detailed logging
|
-o, --print-ocr Print the OCR output to stderr
|
||||||
-p, --page(s) <num>["," ...] Process only the specified pages (comma-separated list)
|
-t, --trace Enable trace mode for detailed logging
|
||||||
-c, --config <file> Alternate configuration file
|
-p, --page(s) <num>["," ...] Process only the specified pages (comma-separated list)
|
||||||
-l, --log(s) <string>["," ...] Logging options (comma-separated list)
|
-c, --config <file> Alternate configuration file
|
||||||
-V, --var(s) <key=value>["," ...] Define one or more comma separated variables for the actions context (multiple allowed)
|
-l, --log(s) <string>["," ...] Logging options (comma-separated list)
|
||||||
-n, --input-name <string> Input file name when source comes from stdin
|
--var(s) <key=value>["," ...] Define one or more comma separated variables for the actions context (multiple allowed)
|
||||||
-d, --work-dir <dir> Work directory
|
-n, --input-name <string> Input file name when source comes from stdin
|
||||||
--attempts <num> Attempts for retrying failed operations (default: "1")
|
-d, --work-dir <dir> Work directory
|
||||||
|
--attempts <num> Attempts for retrying failed operations (default: "1")
|
||||||
`
|
`
|
||||||
var cli CliParser
|
var cli CliParser
|
||||||
var gd GlobalData
|
var gd GlobalData
|
||||||
@ -341,7 +344,7 @@ where:
|
|||||||
}
|
}
|
||||||
usage := cli.Usage()
|
usage := cli.Usage()
|
||||||
if usage != expectedUsage {
|
if usage != expectedUsage {
|
||||||
t.Errorf("Usage output does not match expected.\nGot:\n%s\nExpected:\n%s", usage, expectedUsage)
|
t.Errorf("Usage output does not match expected text.\nGot:\n%s\nExpected:\n%s", usage, expectedUsage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
opt-base.go
22
opt-base.go
@ -13,7 +13,9 @@ type cliOptionBase struct {
|
|||||||
description string
|
description string
|
||||||
isArray bool
|
isArray bool
|
||||||
hidden bool
|
hidden bool
|
||||||
|
alreadySeen bool
|
||||||
specialValues map[string]SpecialValueFunc
|
specialValues map[string]SpecialValueFunc
|
||||||
|
finalCheckFunc FinalCheckFunc
|
||||||
incompatibleWith []string
|
incompatibleWith []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +42,10 @@ func (opt *cliOptionBase) SetHidden(hidden bool) {
|
|||||||
opt.hidden = hidden
|
opt.hidden = hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) OnFinalCheck(checkFunc FinalCheckFunc) {
|
||||||
|
opt.finalCheckFunc = checkFunc
|
||||||
|
}
|
||||||
|
|
||||||
func (opt *cliOptionBase) AddSpecialValue(cliValue string, specialFunc SpecialValueFunc) {
|
func (opt *cliOptionBase) AddSpecialValue(cliValue string, specialFunc SpecialValueFunc) {
|
||||||
if opt.specialValues == nil {
|
if opt.specialValues == nil {
|
||||||
opt.specialValues = make(map[string]SpecialValueFunc)
|
opt.specialValues = make(map[string]SpecialValueFunc)
|
||||||
@ -66,8 +72,9 @@ func (opt *cliOptionBase) addIncompatibleOption(names ...string) {
|
|||||||
opt.incompatibleWith = append(opt.incompatibleWith, names...)
|
opt.incompatibleWith = append(opt.incompatibleWith, names...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionBase) parse(parser cliParser, valuePtr *string) (err error) {
|
func (opt *cliOptionBase) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, value string, err error) {
|
||||||
return fmt.Errorf("unhandled option %q", opt.name)
|
err = fmt.Errorf("unhandled option %q", opt.name)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionBase) getDefaultValue() string {
|
func (opt *cliOptionBase) getDefaultValue() string {
|
||||||
@ -145,3 +152,14 @@ func (opt *cliOptionBase) fetchOptionValue(parser cliParser, argIndex int, value
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) finalCheck() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) checkValue(value any) (err error) {
|
||||||
|
if opt.finalCheckFunc != nil {
|
||||||
|
err = opt.finalCheckFunc(value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
11
opt-bool.go
11
opt-bool.go
@ -47,11 +47,11 @@ func (opt *cliOptionBool) getTemplate() (templ string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionBool) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
func (opt *cliOptionBool) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, optValue string, err error) {
|
||||||
var boxedValue any
|
var boxedValue any
|
||||||
value := "true"
|
optValue = "true"
|
||||||
|
|
||||||
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
if boxedValue, err = opt.getSpecialValue(parser, optValue, opt.targetVar); err == nil {
|
||||||
if opt.targetVar != nil {
|
if opt.targetVar != nil {
|
||||||
if boxedValue != nil {
|
if boxedValue != nil {
|
||||||
if val, ok := boxedValue.(bool); ok {
|
if val, ok := boxedValue.(bool); ok {
|
||||||
@ -67,6 +67,11 @@ func (opt *cliOptionBool) parse(parser cliParser, argIndex int, valuePtr *string
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBool) finalCheck() (err error) {
|
||||||
|
currentValue, _ := opt.getTargetVar()
|
||||||
|
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 {
|
||||||
if cli.optionExists(name, short, aliases) {
|
if cli.optionExists(name, short, aliases) {
|
||||||
panic(errOptionAlreadyDefined(name))
|
panic(errOptionAlreadyDefined(name))
|
||||||
|
|||||||
@ -23,7 +23,7 @@ func (opt *cliOptionHelp) getTemplate() string {
|
|||||||
return opt.makeOptSimpleTemplate(false, false, "")
|
return opt.makeOptSimpleTemplate(false, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionHelp) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
func (opt *cliOptionHelp) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, value string, err error) {
|
||||||
parser.PrintUsage()
|
parser.PrintUsage()
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
return
|
return
|
||||||
|
|||||||
@ -63,8 +63,7 @@ func parseIntRange(value string) (min int, max int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionIntArray) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
func (opt *cliOptionIntArray) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, optValue string, err error) {
|
||||||
var optValue string
|
|
||||||
if optValue, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
if optValue, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
||||||
var boxedValue any
|
var boxedValue any
|
||||||
if boxedValue, err = opt.getSpecialValue(parser, optValue, opt.targetVar); err == nil {
|
if boxedValue, err = opt.getSpecialValue(parser, optValue, opt.targetVar); err == nil {
|
||||||
@ -76,6 +75,10 @@ func (opt *cliOptionIntArray) parse(parser cliParser, argIndex int, valuePtr *st
|
|||||||
err = errInvalidOptionValue(opt.name, boxedValue, "num-array")
|
err = errInvalidOptionValue(opt.name, boxedValue, "num-array")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if !opt.alreadySeen || (valuePtr != nil && parser.FlagIsSet(ResetOnEqualSign)) {
|
||||||
|
*opt.targetVar = []int{}
|
||||||
|
opt.alreadySeen = true
|
||||||
|
}
|
||||||
for value := range strings.SplitSeq(optValue, ",") {
|
for value := range strings.SplitSeq(optValue, ",") {
|
||||||
var minRange, maxRange int
|
var minRange, maxRange int
|
||||||
if minRange, maxRange, err = parseIntRange(value); err == nil {
|
if minRange, maxRange, err = parseIntRange(value); err == nil {
|
||||||
@ -94,6 +97,11 @@ func (opt *cliOptionIntArray) parse(parser cliParser, argIndex int, valuePtr *st
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionIntArray) finalCheck() (err error) {
|
||||||
|
currentValue, _ := opt.getTargetVar()
|
||||||
|
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 {
|
||||||
aliases = cli.checkAlreadyUsedNames(name, short, aliases)
|
aliases = cli.checkAlreadyUsedNames(name, short, aliases)
|
||||||
opt := &cliOptionIntArray{
|
opt := &cliOptionIntArray{
|
||||||
|
|||||||
13
opt-int.go
13
opt-int.go
@ -38,11 +38,10 @@ func (opt *cliOptionInt) getTemplate() string {
|
|||||||
return opt.makeOptTemplate(false, intTypeName)
|
return opt.makeOptTemplate(false, intTypeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionInt) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
func (opt *cliOptionInt) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, optValue string, err error) {
|
||||||
var value string
|
if optValue, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
||||||
if value, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
|
||||||
var boxedValue any
|
var boxedValue any
|
||||||
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
if boxedValue, err = opt.getSpecialValue(parser, optValue, opt.targetVar); err == nil {
|
||||||
if opt.targetVar != nil {
|
if opt.targetVar != nil {
|
||||||
if boxedValue != nil {
|
if boxedValue != nil {
|
||||||
if val, ok := boxedValue.(string); ok {
|
if val, ok := boxedValue.(string); ok {
|
||||||
@ -51,13 +50,17 @@ func (opt *cliOptionInt) parse(parser cliParser, argIndex int, valuePtr *string)
|
|||||||
err = errInvalidOptionValue(opt.name, boxedValue, "int")
|
err = errInvalidOptionValue(opt.name, boxedValue, "int")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
*opt.targetVar, err = strconv.Atoi(value)
|
*opt.targetVar, err = strconv.Atoi(optValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func (opt *cliOptionInt) finalCheck() (err error) {
|
||||||
|
currentValue, _ := opt.getTargetVar()
|
||||||
|
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 {
|
||||||
if cli.optionExists(name, short, aliases) {
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package cli
|
|||||||
|
|
||||||
func (cli *CliParser) SetOptionValue(name string, value string) (err error) {
|
func (cli *CliParser) SetOptionValue(name string, value string) (err error) {
|
||||||
if opt := cli.findOptionByArg(name); opt != nil {
|
if opt := cli.findOptionByArg(name); opt != nil {
|
||||||
_, err = opt.parse(cli, -1, &value)
|
_, _, err = opt.parse(cli, -1, &value)
|
||||||
} else {
|
} else {
|
||||||
err = errOptionNotFound(name)
|
err = errOptionNotFound(name)
|
||||||
}
|
}
|
||||||
|
|||||||
21
opt-multi.go
21
opt-multi.go
@ -1,6 +1,9 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import "strconv"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
multiTypeName = "num"
|
multiTypeName = "num"
|
||||||
@ -34,16 +37,26 @@ func (opt *cliOptionMulti) getDefaultValue() string {
|
|||||||
return strconv.Itoa(opt.defaultValue)
|
return strconv.Itoa(opt.defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionMulti) getTemplate() string {
|
func (opt *cliOptionMulti) getTemplate() (templ string) {
|
||||||
return opt.makeOptTemplate(false, multiTypeName)
|
if opt.shortAlias != "" {
|
||||||
|
templ = fmt.Sprintf(`-%s, --%s`, opt.shortAlias, opt.name)
|
||||||
|
} else {
|
||||||
|
templ = fmt.Sprintf(`--%s`, opt.name)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionMulti) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
func (opt *cliOptionMulti) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, value string, err error) {
|
||||||
if opt.targetVar != nil {
|
if opt.targetVar != nil {
|
||||||
*opt.targetVar++
|
*opt.targetVar++
|
||||||
}
|
}
|
||||||
|
value = "true"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func (opt *cliOptionMulti) finalCheck() (err error) {
|
||||||
|
currentValue, _ := opt.getTargetVar()
|
||||||
|
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 {
|
||||||
if cli.optionExists(name, short, aliases) {
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
|||||||
@ -43,8 +43,7 @@ func (opt *cliOptionStringArray) getTemplate() string {
|
|||||||
|
|
||||||
// parse retrieves the option value from the parser and updates the target variable.
|
// parse retrieves the option value from the parser and updates the target variable.
|
||||||
// It handles comma-separated values and special values if configured.
|
// It handles comma-separated values and special values if configured.
|
||||||
func (opt *cliOptionStringArray) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
func (opt *cliOptionStringArray) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, value string, err error) {
|
||||||
var value string
|
|
||||||
if value, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
if value, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
||||||
var boxedValue any
|
var boxedValue any
|
||||||
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
||||||
@ -55,6 +54,8 @@ func (opt *cliOptionStringArray) parse(parser cliParser, argIndex int, valuePtr
|
|||||||
} else {
|
} else {
|
||||||
err = errInvalidOptionValue(opt.name, boxedValue, "array of string")
|
err = errInvalidOptionValue(opt.name, boxedValue, "array of string")
|
||||||
}
|
}
|
||||||
|
} else if opt.alreadySeen {
|
||||||
|
*opt.targetVar = append(*opt.targetVar, strings.Split(value, ",")...)
|
||||||
} else {
|
} else {
|
||||||
*opt.targetVar = strings.Split(value, ",")
|
*opt.targetVar = strings.Split(value, ",")
|
||||||
}
|
}
|
||||||
@ -83,3 +84,8 @@ func (cli *CliParser) AddStringArrayOpt(name, short string, targetVar *[]string,
|
|||||||
cli.options = append(cli.options, opt)
|
cli.options = append(cli.options, opt)
|
||||||
return opt
|
return opt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringArray) finalCheck() (err error) {
|
||||||
|
currentValue, _ := opt.getTargetVar()
|
||||||
|
return opt.getBase().checkValue(currentValue)
|
||||||
|
}
|
||||||
|
|||||||
@ -57,8 +57,7 @@ func (opt *cliOptionStringMap) getTemplate() string {
|
|||||||
return opt.makeOptTemplate(true, "key=value")
|
return opt.makeOptTemplate(true, "key=value")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionStringMap) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
func (opt *cliOptionStringMap) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, value string, err error) {
|
||||||
var value string
|
|
||||||
if value, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
if value, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
||||||
var boxedValue any
|
var boxedValue any
|
||||||
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
||||||
@ -71,7 +70,7 @@ func (opt *cliOptionStringMap) parse(parser cliParser, argIndex int, valuePtr *s
|
|||||||
err = errInvalidOptionValue(opt.name, boxedValue, "map of string")
|
err = errInvalidOptionValue(opt.name, boxedValue, "map of string")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if dict == nil {
|
if dict == nil || !opt.alreadySeen {
|
||||||
dict = make(map[string]string)
|
dict = make(map[string]string)
|
||||||
}
|
}
|
||||||
for value := range strings.SplitSeq(value, ",") {
|
for value := range strings.SplitSeq(value, ",") {
|
||||||
@ -90,6 +89,11 @@ func (opt *cliOptionStringMap) parse(parser cliParser, argIndex int, valuePtr *s
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringMap) finalCheck() (err error) {
|
||||||
|
currentValue, _ := opt.getTargetVar()
|
||||||
|
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 {
|
||||||
aliases = cli.checkAlreadyUsedNames(name, short, aliases)
|
aliases = cli.checkAlreadyUsedNames(name, short, aliases)
|
||||||
opt := &cliOptionStringMap{
|
opt := &cliOptionStringMap{
|
||||||
|
|||||||
@ -36,8 +36,7 @@ func (opt *cliOptionString) getTemplate() string {
|
|||||||
return opt.makeOptTemplate(false, stringTypeName)
|
return opt.makeOptTemplate(false, stringTypeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionString) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
func (opt *cliOptionString) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, value string, err error) {
|
||||||
var value string
|
|
||||||
if value, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
if value, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil {
|
||||||
var boxedValue any
|
var boxedValue any
|
||||||
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
||||||
@ -56,6 +55,10 @@ func (opt *cliOptionString) parse(parser cliParser, argIndex int, valuePtr *stri
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func (opt *cliOptionString) finalCheck() (err error) {
|
||||||
|
currentValue, _ := opt.getTargetVar()
|
||||||
|
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 {
|
||||||
if cli.optionExists(name, short, aliases) {
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ func (opt *cliOptionVersion) getTemplate() string {
|
|||||||
return opt.makeOptSimpleTemplate(false, true, "section")
|
return opt.makeOptSimpleTemplate(false, true, "section")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *cliOptionVersion) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
func (opt *cliOptionVersion) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, value string, err error) {
|
||||||
var args []string
|
var args []string
|
||||||
if valuePtr != nil {
|
if valuePtr != nil {
|
||||||
args = []string{*valuePtr}
|
args = []string{*valuePtr}
|
||||||
|
|||||||
143
opt_test.go
Normal file
143
opt_test.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOneOptWithEqual(t *testing.T) {
|
||||||
|
var cli CliParser
|
||||||
|
var color string
|
||||||
|
// Always recover from panic to return error before adding options
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if err := r.(error); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Define options
|
||||||
|
cli.AddStringOpt("color", "c", &color, "white", "Set color")
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"TestOneOptWithEqual",
|
||||||
|
"--color=blue",
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.Init(args, "1.0.0", "TestOneOptWithEqual description")
|
||||||
|
if err := cli.Parse(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if color != "blue" {
|
||||||
|
t.Errorf("Expected color blue, got %q", color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addBoxOption(cli *CliParser, boxPtr *[]int) {
|
||||||
|
// Define options
|
||||||
|
optRef := cli.AddIntArrayOpt("box", "b", boxPtr, []int{1, 2, 3, 4}, "Box spec: Left,Width,Top,Height; provide 4 int values")
|
||||||
|
optRef.OnFinalCheck(func(currentValue any) (err error) {
|
||||||
|
if array, ok := currentValue.([]int); ok {
|
||||||
|
if len(array) != 4 {
|
||||||
|
err = fmt.Errorf("--box option requires exactly 4 items, %d provided", len(array))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("wrong datatype for --box option value")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIntArrayOptOk(t *testing.T, n int, args []string, flags ...int16) {
|
||||||
|
var box []int
|
||||||
|
var cli CliParser
|
||||||
|
t.Logf("Arg set n. %d", n)
|
||||||
|
addBoxOption(&cli, &box)
|
||||||
|
cli.Init(args, "1.0.0", "TestIntArrayOpt description", flags...)
|
||||||
|
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, flags ...int16) {
|
||||||
|
var box []int
|
||||||
|
var cli CliParser
|
||||||
|
t.Logf("Arg set n. %d", n)
|
||||||
|
addBoxOption(&cli, &box)
|
||||||
|
cli.Init(args, "1.0.0", "TestIntArrayOpt description", flags...)
|
||||||
|
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
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if err := r.(error); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
n := 0
|
||||||
|
|
||||||
|
n++
|
||||||
|
args = []string{
|
||||||
|
"TestIntArrayOpt",
|
||||||
|
"--box=100,200,150,450",
|
||||||
|
}
|
||||||
|
testIntArrayOptOk(t, n, args)
|
||||||
|
|
||||||
|
n++
|
||||||
|
args = []string{
|
||||||
|
"TestIntArrayOpt",
|
||||||
|
"--box=100,200,150",
|
||||||
|
}
|
||||||
|
testIntArrayOptKo(t, n, args, "--box option requires exactly 4 items, 3 provided")
|
||||||
|
|
||||||
|
n++
|
||||||
|
args = []string{
|
||||||
|
"TestIntArrayOpt",
|
||||||
|
"--box", "100,200,150,450",
|
||||||
|
}
|
||||||
|
testIntArrayOptOk(t, n, args)
|
||||||
|
|
||||||
|
n++
|
||||||
|
args = []string{
|
||||||
|
"TestIntArrayOpt",
|
||||||
|
"--box",
|
||||||
|
}
|
||||||
|
testIntArrayOptKo(t, n, args, `option "box" requires a value`)
|
||||||
|
|
||||||
|
n++
|
||||||
|
args = []string{
|
||||||
|
"TestIntArrayOpt",
|
||||||
|
"--box", "100,200,150",
|
||||||
|
"--box", "450",
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
|
||||||
|
n++
|
||||||
|
args = []string{
|
||||||
|
"TestIntArrayOpt",
|
||||||
|
"--box", "100,200,150",
|
||||||
|
"--box=100,200,150,450",
|
||||||
|
}
|
||||||
|
testIntArrayOptOk(t, n, args, ResetOnEqualSign)
|
||||||
|
}
|
||||||
@ -105,11 +105,11 @@ 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 {
|
||||||
if equalPresent && i == len(opts)-1 {
|
if equalPresent && i == len(opts)-1 {
|
||||||
_, 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, err = opt.parse(cli, index, nil)
|
skipNextArg, _, err = opt.parse(cli, index, nil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = errUnknownOption(dashes + optName)
|
err = errUnknownOption(dashes + optName)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user