Compare commits

..

No commits in common. "main" and "v1.2.0" have entirely different histories.
main ... v1.2.0

15 changed files with 81 additions and 337 deletions

86
cli.go
View File

@ -14,11 +14,10 @@ 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, optValue string, err error) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error)
getTemplate() string getTemplate() string
getDefaultValue() string getDefaultValue() string
getBase() *cliOptionBase getBase() *cliOptionBase
@ -26,7 +25,6 @@ type cliOptionParser interface {
isSet() bool isSet() bool
isHidden() bool isHidden() bool
requiresValue() bool requiresValue() bool
finalCheck() (err error)
} }
type OptManager interface { type OptManager interface {
@ -34,39 +32,26 @@ 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, flags ...int16) { func (cli *CliParser) Init(argv []string, version string, description string) {
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, "-") {
@ -170,12 +155,16 @@ func (cli *CliParser) addHelpAndVersion() {
} }
func (cli *CliParser) parseOptions() (commandArgs []string, err error) { func (cli *CliParser) Parse() (err error) {
// var commandArgs []string var arg 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 {
@ -189,79 +178,40 @@ func (cli *CliParser) parseOptions() (commandArgs []string, err error) {
break break
} }
} else { } else {
commandArgs = append(commandArgs, arg) args = append(args, arg)
} }
} }
} }
return if err == nil {
} var argSpec argSpec
var n, specIndex int
i = 0
func (cli *CliParser) checkOptionValues() (err error) { for specIndex, argSpec = range cli.argSpecs {
for _, opt := range cli.options { if n, err = argSpec.parse(cli, specIndex, args, i); err != nil {
if err = opt.finalCheck(); err != nil {
break
}
}
return
}
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 break
} }
i += n i += n
if i >= len(commandArgs) { if i >= len(args) {
break break
} }
} }
// check if there are remaining arg-specs that require a value
if err == nil { if err == nil {
if i < len(commandArgs) { if i < len(args) {
err = fmt.Errorf("too many arguments: %d allowed", i) err = fmt.Errorf("too many arguments: %d allowed", i)
} else { } else {
specIndex++ 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++
} }
} }
}
// return error if there are remaining arg-specs that require a value
if specIndex < len(cli.argSpecs) { if specIndex < len(cli.argSpecs) {
err = errTooFewArguments(len(cli.argSpecs)) err = errTooFewArguments(len(cli.argSpecs))
} }
} }
} }
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 return
} }

View File

@ -85,14 +85,13 @@ where:
<dest> Output destination file <dest> Output destination file
<report> Optional report file <report> Optional report file
<options> <options>
-V, --verbose Print verbose output (default: "0")
-o, --print-ocr Print the OCR output to stderr -o, --print-ocr Print the OCR output to stderr
-s, --save-clip Save the image clips as PNG files (alias: save-clips) -s, --save-clip Save the image clips as PNG files (alias: save-clips)
-t, --trace Enable trace mode for detailed logging -t, --trace Enable trace mode for detailed logging
-p, --page(s) <num>["," ...] Process only the specified pages (comma-separated list) -p, --page(s) <num>["," ...] Process only the specified pages (comma-separated list)
-c, --config <file> Alternate configuration file -c, --config <file> Alternate configuration file
-l, --log(s) <string>["," ...] Logging options (comma-separated list) -l, --log(s) <string>["," ...] Logging options (comma-separated list)
--var(s) <key=value>["," ...] Define one or more comma separated variables for the actions context (multiple allowed) -V, --var(s) <key=value>["," ...] Define one or more comma separated variables for the actions context (multiple allowed)
-n, --input-name <string> Input file name when source comes from stdin -n, --input-name <string> Input file name when source comes from stdin
-d, --work-dir <dir> Work directory -d, --work-dir <dir> Work directory
--attempts <num> Attempts for retrying failed operations (default: "1") --attempts <num> Attempts for retrying failed operations (default: "1")
@ -110,8 +109,7 @@ where:
} }
} }
func TestParser(t *testing.T) { func TestParser(t *testing.T) {
const expectedOutput = `Option: verbose, Type: num, Value: 3 const expectedOutput = `Option: print-ocr, Type: bool, Value: true
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]
@ -137,7 +135,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 list.\nGot:\n%q\nExpected:\n%q", sb.String(), expectedOutput) t.Errorf("Parsed options do not match expected.\nGot:\n%q\nExpected:\n%q", sb.String(), expectedOutput)
} }
} else { } else {
t.Error(err) t.Error(err)
@ -324,13 +322,12 @@ where:
<dest> Output destination file <dest> Output destination file
<report> Optional report file <report> Optional report file
<options> <options>
-V, --verbose Print verbose output (default: "0")
-o, --print-ocr Print the OCR output to stderr -o, --print-ocr Print the OCR output to stderr
-t, --trace Enable trace mode for detailed logging -t, --trace Enable trace mode for detailed logging
-p, --page(s) <num>["," ...] Process only the specified pages (comma-separated list) -p, --page(s) <num>["," ...] Process only the specified pages (comma-separated list)
-c, --config <file> Alternate configuration file -c, --config <file> Alternate configuration file
-l, --log(s) <string>["," ...] Logging options (comma-separated list) -l, --log(s) <string>["," ...] Logging options (comma-separated list)
--var(s) <key=value>["," ...] Define one or more comma separated variables for the actions context (multiple allowed) -V, --var(s) <key=value>["," ...] Define one or more comma separated variables for the actions context (multiple allowed)
-n, --input-name <string> Input file name when source comes from stdin -n, --input-name <string> Input file name when source comes from stdin
-d, --work-dir <dir> Work directory -d, --work-dir <dir> Work directory
--attempts <num> Attempts for retrying failed operations (default: "1") --attempts <num> Attempts for retrying failed operations (default: "1")
@ -344,7 +341,7 @@ where:
} }
usage := cli.Usage() usage := cli.Usage()
if usage != expectedUsage { if usage != expectedUsage {
t.Errorf("Usage output does not match expected text.\nGot:\n%s\nExpected:\n%s", usage, expectedUsage) t.Errorf("Usage output does not match expected.\nGot:\n%s\nExpected:\n%s", usage, expectedUsage)
} }
} }

View File

@ -13,9 +13,7 @@ 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
} }
@ -42,10 +40,6 @@ 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)
@ -72,9 +66,8 @@ func (opt *cliOptionBase) addIncompatibleOption(names ...string) {
opt.incompatibleWith = append(opt.incompatibleWith, names...) opt.incompatibleWith = append(opt.incompatibleWith, names...)
} }
func (opt *cliOptionBase) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, value string, err error) { func (opt *cliOptionBase) parse(parser cliParser, valuePtr *string) (err error) {
err = fmt.Errorf("unhandled option %q", opt.name) return fmt.Errorf("unhandled option %q", opt.name)
return
} }
func (opt *cliOptionBase) getDefaultValue() string { func (opt *cliOptionBase) getDefaultValue() string {
@ -152,14 +145,3 @@ 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
}

View File

@ -47,11 +47,11 @@ func (opt *cliOptionBool) getTemplate() (templ string) {
return return
} }
func (opt *cliOptionBool) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, optValue string, err error) { func (opt *cliOptionBool) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
var boxedValue any var boxedValue any
optValue = "true" value := "true"
if boxedValue, err = opt.getSpecialValue(parser, optValue, opt.targetVar); err == nil { if boxedValue, err = opt.getSpecialValue(parser, value, 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,11 +67,6 @@ 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))

View File

@ -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, value string, err error) { func (opt *cliOptionHelp) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
parser.PrintUsage() parser.PrintUsage()
err = io.EOF err = io.EOF
return return

View File

@ -63,7 +63,8 @@ func parseIntRange(value string) (min int, max int, err error) {
return return
} }
func (opt *cliOptionIntArray) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, optValue string, err error) { func (opt *cliOptionIntArray) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, 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 {
@ -75,10 +76,6 @@ 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 {
@ -97,11 +94,6 @@ 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{

View File

@ -38,10 +38,11 @@ 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, optValue string, err error) { func (opt *cliOptionInt) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
if optValue, skipNextArg, err = opt.fetchOptionValue(parser, argIndex, valuePtr); err == nil { var value string
if value, 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, value, 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 {
@ -50,17 +51,13 @@ 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(optValue) *opt.targetVar, err = strconv.Atoi(value)
} }
} }
} }
} }
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) {

View File

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

View File

@ -1,9 +1,6 @@
package cli package cli
import ( import "strconv"
"fmt"
"strconv"
)
const ( const (
multiTypeName = "num" multiTypeName = "num"
@ -37,26 +34,16 @@ func (opt *cliOptionMulti) getDefaultValue() string {
return strconv.Itoa(opt.defaultValue) return strconv.Itoa(opt.defaultValue)
} }
func (opt *cliOptionMulti) getTemplate() (templ string) { func (opt *cliOptionMulti) getTemplate() string {
if opt.shortAlias != "" { return opt.makeOptTemplate(false, multiTypeName)
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, value string, err error) { func (opt *cliOptionMulti) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, 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) {

View File

@ -43,7 +43,8 @@ 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, value string, err error) { func (opt *cliOptionStringArray) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, 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 {
@ -54,8 +55,6 @@ 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, ",")
} }
@ -84,8 +83,3 @@ 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)
}

View File

@ -57,7 +57,8 @@ 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, value string, err error) { func (opt *cliOptionStringMap) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, 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 {
@ -70,7 +71,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 || !opt.alreadySeen { if dict == nil {
dict = make(map[string]string) dict = make(map[string]string)
} }
for value := range strings.SplitSeq(value, ",") { for value := range strings.SplitSeq(value, ",") {
@ -89,11 +90,6 @@ 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{

View File

@ -36,7 +36,8 @@ 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, value string, err error) { func (opt *cliOptionString) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, 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,10 +56,6 @@ 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) {

View File

@ -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, value string, err error) { func (opt *cliOptionVersion) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
var args []string var args []string
if valuePtr != nil { if valuePtr != nil {
args = []string{*valuePtr} args = []string{*valuePtr}

View File

@ -1,143 +0,0 @@
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)
}

View File

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