From 4111864916f4a088bd1cce5e94479c3e520dd550 Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Wed, 4 Feb 2026 05:22:43 +0100 Subject: [PATCH] New option type 'multi'. Its value is the number of occurences of an option --- cli_test.go | 26 +++++++++++++++++---- opt-multi.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 opt-multi.go diff --git a/cli_test.go b/cli_test.go index fbc6bbf..2c32ad0 100644 --- a/cli_test.go +++ b/cli_test.go @@ -22,6 +22,22 @@ type GlobalData struct { sources []string dest string report string + verbose int +} + +func TestVerbose(t *testing.T) { + var cli CliParser + var gd GlobalData + + if err := initCli(&cli, &gd); err != nil { + t.Error(err) + return + } + if err := cli.Parse(); err != nil { + t.Error(err) + } else if gd.verbose != 3 { + t.Errorf("Expected verbose level 3, got %d", gd.verbose) + } } func TestVersion(t *testing.T) { @@ -131,6 +147,7 @@ func initCli(cli *CliParser, gd *GlobalData) (err error) { "ddt-ocr", "--log", "all", "-t", + "-VVV", "--config", "devel-config.yaml", "--var", "deploy_env=devel", "--print-ocr", @@ -265,12 +282,12 @@ func initCliMissingRequiredArg(cli *CliParser, gd *GlobalData) (err error) { return } -func TestArgErrorReapet(t *testing.T) { +func TestArgErrorRepeat(t *testing.T) { var expectedErr = fmt.Errorf(`repeat property already set for arg `) var cli CliParser var gd GlobalData - if err := initCliReapetArg(&cli, &gd); err != nil { + if err := initCliRepeatArg(&cli, &gd); err != nil { if err.Error() != expectedErr.Error() { t.Errorf("Invalid error message.\nGot:\n%v\nExpected:\n%v", err, expectedErr) } @@ -279,7 +296,7 @@ func TestArgErrorReapet(t *testing.T) { } } -func initCliReapetArg(cli *CliParser, gd *GlobalData) (err error) { +func initCliRepeatArg(cli *CliParser, gd *GlobalData) (err error) { args := []string{ "ddt-ocr", "scan1.pdf", "scan2.pdf", "result.txt", "report.txt", @@ -348,13 +365,14 @@ func (gd *GlobalData) addOptions(cli *CliParser) (err error) { }() // Define options + cli.AddMultiOpt("verbose", "V", &gd.verbose, 0, "Print verbose output") cli.AddBoolOpt("print-ocr", "o", &gd.printOcr, "Print the OCR output to stderr") cli.AddBoolOpt("save-clip", "s", &gd.saveClips, "Save the image clips as PNG files", "save-clips") cli.AddBoolOpt("trace", "t", &gd.trace, "Enable trace mode for detailed logging") cli.AddIntArrayOpt("page", "p", &gd.page, gd.page, "Process only the specified pages (comma-separated list)") cli.AddFileOpt("config", "c", &gd.config, gd.config, "Alternate configuration file") cli.AddStringArrayOpt("log", "l", &gd.log, gd.log, "Logging options (comma-separated list)") - cli.AddStringMapOpt("var", "V", &gd.cliVars, nil, "Define one or more comma separated variables for the actions context (multiple allowed)") + cli.AddStringMapOpt("var", "", &gd.cliVars, nil, "Define one or more comma separated variables for the actions context (multiple allowed)") cli.AddStringOpt("input-name", "n", &gd.inputName, "", "Input file name when source comes from stdin") cli.AddDirOpt("work-dir", "d", &gd.workDir, "", "Work directory") if ref := cli.AddIntOpt("attempts", "", &gd.attempts, 1, "Attempts for retrying failed operations"); ref != nil { diff --git a/opt-multi.go b/opt-multi.go new file mode 100644 index 0000000..f4a999e --- /dev/null +++ b/opt-multi.go @@ -0,0 +1,64 @@ +package cli + +import "strconv" + +const ( + multiTypeName = "num" +) + +type cliOptionMulti struct { + cliOptionBase + defaultValue int + targetVar *int +} + +func (opt *cliOptionMulti) init() { + if opt.targetVar != nil { + *opt.targetVar = opt.defaultValue + } +} + +func (opt *cliOptionMulti) getTargetVar() (any, string) { + var value int + if opt.targetVar != nil { + value = *opt.targetVar + } + return value, multiTypeName +} + +func (opt *cliOptionMulti) requiresValue() bool { + return false +} + +func (opt *cliOptionMulti) getDefaultValue() string { + return strconv.Itoa(opt.defaultValue) +} + +func (opt *cliOptionMulti) getTemplate() string { + return opt.makeOptTemplate(false, multiTypeName) +} + +func (opt *cliOptionMulti) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) { + if opt.targetVar != nil { + *opt.targetVar++ + } + return +} + +func (cli *CliParser) AddMultiOpt(name, short string, targetVar *int, defaultValue int, description string, aliases ...string) OptReference { + if cli.optionExists(name, short, aliases) { + panic(errOptionAlreadyDefined(name)) + } + opt := &cliOptionMulti{ + cliOptionBase: cliOptionBase{ + name: name, + shortAlias: short, + aliases: aliases, + description: description, + }, + targetVar: targetVar, + defaultValue: defaultValue, + } + cli.options = append(cli.options, opt) + return opt +}