diff --git a/cli_test.go b/cli_test.go index cbb5a32..fbc6bbf 100644 --- a/cli_test.go +++ b/cli_test.go @@ -6,76 +6,370 @@ import ( "testing" ) -type GlobaData struct { - config string - log []string - printOcr bool - saveClips bool - trace bool - page []int - sources []string - dest string - facoltativo string +const version = `$VER:ddt-ocr,1.0.1,2025-11-23,celestino.amoroso@gmail.com:$` + +type GlobalData struct { + config string + log []string + printOcr bool + saveClips bool + trace bool + page []int + cliVars map[string]string + inputName string + workDir string + attempts int + sources []string + dest string + report string } -func (gd *GlobaData) String() string { - var sb strings.Builder - fmt.Fprintf(&sb, "Options:\n") - fmt.Fprintf(&sb, " Config.....: %q\n", gd.config) - fmt.Fprintf(&sb, " Log........: %v\n", gd.log) - fmt.Fprintf(&sb, " Print-Ocr..: %v\n", gd.printOcr) - fmt.Fprintf(&sb, " Save-Clips.: %v\n", gd.saveClips) - fmt.Fprintf(&sb, " Page.......: %v\n", gd.page) - fmt.Fprintf(&sb, " Trace......: %v\n", gd.trace) - fmt.Fprintf(&sb, "Argumentes:\n") - fmt.Fprintf(&sb, " Sources....: %s\n", strings.Join(gd.sources, ", ")) - fmt.Fprintf(&sb, " Destination: %s\n", gd.dest) - fmt.Fprintf(&sb, " Facoltativo: %s\n", gd.facoltativo) - return sb.String() -} - -func TestUsage(t *testing.T) { +func TestVersion(t *testing.T) { var cli CliParser - var version = `$VER:mini-ddt-ocr,1.0.1,2025-11-23,celestino.amoroso@gmail.com:$` - var gd GlobaData - args := []string{ - "mini-ddt-ocr", - "--log", "all", - "--config", "devel-config.yaml", - // "--var", "deploy_env=devel", - "--print-ocr", - "--pages", "17,18", - "../../dev-stuff/ocis-upload.log", "ciccio", "bello", "pippo", - } - cli.Init(args, version) - if err := gd.addOptions(&cli); err != nil { + var gd GlobalData + + if err := initCli(&cli, &gd); err != nil { t.Error(err) return } - usage := cli.Usage() - fmt.Println(usage) - if err := cli.Parse(); err == nil { - fmt.Println(&gd) + if v, err := cli.GetVersionSection("all"); err == nil { + if v != version[5:len(version)-2] { + t.Errorf("Version string does not match expected.\nGot:\n%q\nExpected:\n%q", v, version) + } } else { t.Error(err) } } -func (gd *GlobaData) addOptions(cli *CliParser) (err error) { +func TestVersionNumber(t *testing.T) { + var cli CliParser + var gd GlobalData + + if err := initCli(&cli, &gd); err != nil { + t.Error(err) + return + } + secValues := []string{"ddt-ocr", "1.0.1", "2025-11-23", "celestino.amoroso@gmail.com"} + for i, sec := range []string{"program", "version", "date", "email"} { + if v, err := cli.GetVersionSection(sec); err == nil { + if v != secValues[i] { + t.Errorf("Version section %q does not match expected value.\nGot:\n%q\nExpected:\n%q", sec, v, version) + } + } else { + t.Error(err) + } + } +} + +func TestUsage(t *testing.T) { + const expectedUsage = `NAME ddt-ocr - cli-test +USAGE: ddt-ocr [] ... [] +where: + Source image files + Output destination file + Optional report file + + -o, --print-ocr Print the OCR output to stderr + -s, --save-clip Save the image clips as PNG files (alias: save-clips) + -t, --trace Enable trace mode for detailed logging + -p, --page(s) ["," ...] Process only the specified pages (comma-separated list) + -c, --config Alternate configuration file + -l, --log(s) ["," ...] Logging options (comma-separated list) + -V, --var(s) ["," ...] Define one or more comma separated variables for the actions context (multiple allowed) + -n, --input-name Input file name when source comes from stdin + -d, --work-dir Work directory + --attempts Attempts for retrying failed operations (default: "1") +` + var cli CliParser + var gd GlobalData + + if err := initCli(&cli, &gd); err != nil { + t.Error(err) + return + } + usage := cli.Usage() + if usage != expectedUsage { + t.Errorf("Usage output does not match expected.\nGot:\n%s\nExpected:\n%s", usage, expectedUsage) + } +} +func TestParser(t *testing.T) { + const expectedOutput = `Option: print-ocr, Type: bool, Value: true +Option: save-clip, Type: bool, Value: false +Option: trace, Type: bool, Value: true +Option: page, Type: num-array, Value: [17 18] +Option: config, Type: string, Value: devel-config.yaml +Option: log, Type: string-array, Value: [all] +Option: var, Type: map-string, Value: map[deploy_env:devel] +Option: input-name, Type: string, Value: my-scan.pdf +Option: work-dir, Type: string, Value: +Option: attempts, Type: num, Value: 1 +Option: help, Type: n/a, Value: +Option: version, Type: n/a, Value: +` + + var cli CliParser + var gd GlobalData + var sb strings.Builder + + if err := initCli(&cli, &gd); err != nil { + t.Error(err) + return + } + tracer := NewSimpleOptionTracer(&sb) + if err := cli.Parse(); err == nil { + cli.TraceOptions(tracer) + if sb.String() != expectedOutput { + t.Errorf("Parsed options do not match expected.\nGot:\n%q\nExpected:\n%q", sb.String(), expectedOutput) + } + } else { + t.Error(err) + } +} + +func initCli(cli *CliParser, gd *GlobalData) (err error) { + args := []string{ + "ddt-ocr", + "--log", "all", + "-t", + "--config", "devel-config.yaml", + "--var", "deploy_env=devel", + "--print-ocr", + "--pages", "17,18", + "--input-name=my-scan.pdf", + "scan1.pdf", "scan2.pdf", "result.txt", "report.txt", + } + cli.Init(args, version, "cli-test") + err = gd.addOptions(cli) + return +} + +func TestOptErrorUnknownOption(t *testing.T) { + const unknownOption = "--logging" + var expectedErr = errUnknownOption(unknownOption) + var cli CliParser + var gd GlobalData + + if err := initCliUnknownOption(&cli, &gd, unknownOption); err == nil { + if err = cli.Parse(); err != nil { + if err.Error() != expectedErr.Error() { + t.Errorf("Invalid error message.\nGot:\n%v\nExpected:\n%v", err, expectedErr) + } + } else { + t.Errorf("Expected error for unknown option %q, but got none", unknownOption) + } + } else { + t.Error(err) + return + } +} + +func initCliUnknownOption(cli *CliParser, gd *GlobalData, option string) (err error) { + args := []string{ + "ddt-ocr", + option, + "scan1.pdf", "scan2.pdf", "result.txt", "report.txt", + } + cli.Init(args, version, "cli-test") + err = gd.addOptions(cli) + return +} + +func TestOptErrorMissingOptionValue(t *testing.T) { + const missingValueOption = "--page" + var expectedErr = fmt.Errorf(`invalid value scan1.pdf (string) for option "page" (num-array)`) + var cli CliParser + var gd GlobalData + + if err := initCliMissingOptionValue(&cli, &gd, missingValueOption); err == nil { + if err = cli.Parse(); err != nil { + if err.Error() != expectedErr.Error() { + t.Errorf("Invalid error message.\nGot:\n%v\nExpected:\n%v", err, expectedErr) + } + } else { + t.Errorf("Expected error for unknown option %q, but got none", missingValueOption) + } + } else { + t.Error(err) + return + } +} + +func initCliMissingOptionValue(cli *CliParser, gd *GlobalData, option string) (err error) { + args := []string{ + "ddt-ocr", + option, + "scan1.pdf", "scan2.pdf", "result.txt", "report.txt", + } + cli.Init(args, version, "cli-test") + err = gd.addOptions(cli) + return +} + +func TestOptErrorInvalidOptionValue(t *testing.T) { + const missingInvalidValueOption = "--page" + var expectedErr = errInvalidOptionValue("page", "some", "num-array") + var cli CliParser + var gd GlobalData + + if err := initCliInvalidOptionValue(&cli, &gd, missingInvalidValueOption); err == nil { + if err = cli.Parse(); err != nil { + if err.Error() != expectedErr.Error() { + t.Errorf("Invalid error message.\nGot:\n%v\nExpected:\n%v", err, expectedErr) + } + } else { + t.Errorf("Expected error for unknown option %q, but got none", missingInvalidValueOption) + } + } else { + t.Error(err) + return + } +} + +func initCliInvalidOptionValue(cli *CliParser, gd *GlobalData, option string) (err error) { + args := []string{ + "ddt-ocr", + option, "some", + "scan1.pdf", "scan2.pdf", "result.txt", "report.txt", + } + cli.Init(args, version, "cli-test") + err = gd.addOptions(cli) + return +} + +func TestArgErrorMissingRequired(t *testing.T) { + const missingRequiredArg = "--page" + var expectedErr = fmt.Errorf(`missing required arg `) + var cli CliParser + var gd GlobalData + + if err := initCliMissingRequiredArg(&cli, &gd); err == nil { + if err = cli.Parse(); err != nil { + if err.Error() != expectedErr.Error() { + t.Errorf("Invalid error message.\nGot:\n%v\nExpected:\n%v", err, expectedErr) + } + } else { + t.Errorf("Expected error for unknown option %q, but got none", missingRequiredArg) + } + } else { + t.Error(err) + return + } +} + +func initCliMissingRequiredArg(cli *CliParser, gd *GlobalData) (err error) { + args := []string{ + "ddt-ocr", + } + cli.Init(args, version, "cli-test") + err = gd.addOptions(cli) + return +} + +func TestArgErrorReapet(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.Error() != expectedErr.Error() { + t.Errorf("Invalid error message.\nGot:\n%v\nExpected:\n%v", err, expectedErr) + } + } else { + t.Errorf("Expected error, but got none") + } +} + +func initCliReapetArg(cli *CliParser, gd *GlobalData) (err error) { + args := []string{ + "ddt-ocr", + "scan1.pdf", "scan2.pdf", "result.txt", "report.txt", + } + cli.Init(args, version, "cli-test") + if err = gd.addOptions(cli); err == nil { + defer func() { + if r := recover(); r != nil { + err = r.(error) + } + }() + cli.AddStringArrayArg("other", true, nil, "other args") + cli.AddStringArrayArg("other2", true, nil, "other args 2") + } + return +} + +func TestOptHidden(t *testing.T) { + const expectedUsage = `NAME ddt-ocr - cli-test +USAGE: ddt-ocr [] ... [] +where: + Source image files + Output destination file + Optional report file + + -o, --print-ocr Print the OCR output to stderr + -t, --trace Enable trace mode for detailed logging + -p, --page(s) ["," ...] Process only the specified pages (comma-separated list) + -c, --config Alternate configuration file + -l, --log(s) ["," ...] Logging options (comma-separated list) + -V, --var(s) ["," ...] Define one or more comma separated variables for the actions context (multiple allowed) + -n, --input-name Input file name when source comes from stdin + -d, --work-dir Work directory + --attempts Attempts for retrying failed operations (default: "1") +` + var cli CliParser + var gd GlobalData + + if err := initCliHiddenOpt(&cli, &gd); err != nil { + t.Error(err) + return + } + usage := cli.Usage() + if usage != expectedUsage { + t.Errorf("Usage output does not match expected.\nGot:\n%s\nExpected:\n%s", usage, expectedUsage) + } +} + +func initCliHiddenOpt(cli *CliParser, gd *GlobalData) (err error) { + if err = initCli(cli, gd); err == nil { + if ref := cli.GetOption("save-clip"); ref != nil { + ref.SetHidden(true) + } else { + err = fmt.Errorf("option save-clip not found") + } + } + return +} + +func (gd *GlobalData) addOptions(cli *CliParser) (err error) { + // Always recover from panic to return error before adding options defer func() { if r := recover(); r != nil { err = r.(error) } }() - cli.AddBoolOpt("print-ocr", "o", &gd.printOcr, "Stampa l'output del programma OCR") - cli.AddBoolOpt("save-clip", "s", &gd.saveClips, "Registra le immagini delle aree ritagliata", "save-clips") - cli.AddBoolOpt("trace", "t", &gd.trace, "Attiva la modalità di tracciamento delle operazioni") - cli.AddIntArrayOpt("page", "p", &gd.page, gd.page, "Elabora la pagina specificata") - cli.AddStringOpt("config", "c", &gd.config, gd.config, "Specifica un percorso alternativo per il file di configurazione") - cli.AddStringArrayOpt("log", "l", &gd.log, gd.log, "Maschera di livelli di log") - cli.AddStringArrayArg("sorgenti", true, &gd.sources, "file da elaborare") - cli.AddStringArg("dest", true, &gd.dest, "file di outout") - cli.AddStringArg("facoltativo", false, &gd.facoltativo, "file facoltativo") + // Define options + 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.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 { + ref.AddSpecialValue("many", func(manager OptManager, cliValue string, currentValue any) (any, error) { + return 1000, nil + }) + ref.AddSpecialValue("few", func(manager OptManager, cliValue string, currentValue any) (any, error) { + manager.SetOptionValue("page", "1") + return nil, nil + }) + } + + // Define arguments + cli.AddStringArrayArg("image-sources", true, &gd.sources, "Source image files") + cli.AddStringArg("dest", true, &gd.dest, "Output destination file") + cli.AddStringArg("report", false, &gd.report, "Optional report file") return }