first commit status: option and argument parsing, short aliases grouping, special values, hidden options
Option types: bool int, int-array, string, string-array, string-map, file, dir
This commit is contained in:
parent
b9a4efc956
commit
38e36839f8
29
LICENSE
Normal file
29
LICENSE
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Celestino Amoroso, nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
40
arg-base.go
Normal file
40
arg-base.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type argSpec interface {
|
||||||
|
getBase() *argBase
|
||||||
|
parse(cli *CliParser, specIndex int, args []string, argIndex int) (consumedArgs int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type argBase struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
required bool
|
||||||
|
repeat bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- Argument Template functions ----------------
|
||||||
|
func (cli *CliParser) getArgTemplate(spec argSpec) (templ string) {
|
||||||
|
arg := spec.getBase()
|
||||||
|
templ = "<" + arg.name + ">"
|
||||||
|
if arg.repeat {
|
||||||
|
templ += " ..."
|
||||||
|
}
|
||||||
|
if !arg.required {
|
||||||
|
templ = "[" + templ + "]"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) getArgsTemplate() (argsTemplate string) {
|
||||||
|
templates := make([]string, len(cli.argSpecs))
|
||||||
|
for i, argSpec := range cli.argSpecs {
|
||||||
|
templates[i] = cli.getArgTemplate(argSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
argsTemplate = strings.Join(templates, " ")
|
||||||
|
return
|
||||||
|
}
|
||||||
49
arg-string-array.go
Normal file
49
arg-string-array.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
// -------------- argStringArray ----------------
|
||||||
|
type argStringArray struct {
|
||||||
|
base argBase
|
||||||
|
targetVar *[]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arg *argStringArray) getBase() *argBase {
|
||||||
|
return &arg.base
|
||||||
|
}
|
||||||
|
|
||||||
|
func (spec *argStringArray) parse(cli *CliParser, specIndex int, args []string, argIndex int) (consumedArgs int, err error) {
|
||||||
|
consumedArgs = 1
|
||||||
|
if spec.targetVar != nil {
|
||||||
|
if argIndex < len(args) {
|
||||||
|
remainingSpecs := len(cli.argSpecs) - specIndex - 1
|
||||||
|
availableArgs := len(args) - remainingSpecs
|
||||||
|
if availableArgs > 0 {
|
||||||
|
*spec.targetVar = args[argIndex : argIndex+availableArgs]
|
||||||
|
consumedArgs = availableArgs
|
||||||
|
} else {
|
||||||
|
err = errTooFewArguments(len(cli.argSpecs))
|
||||||
|
}
|
||||||
|
} else if spec.base.required {
|
||||||
|
err = errMissingRequiredArg(spec.base.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddStringArrayArg(name string, required bool, targetVar *[]string, description string) {
|
||||||
|
// todo: check if arg already exists
|
||||||
|
if len(cli.argSpecs) > 0 {
|
||||||
|
lastArg := cli.argSpecs[len(cli.argSpecs)-1].getBase()
|
||||||
|
if lastArg.repeat {
|
||||||
|
panic(errRepeatArgAlreadyDefined(lastArg.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cli.argSpecs = append(cli.argSpecs, &argStringArray{
|
||||||
|
base: argBase{
|
||||||
|
name: name,
|
||||||
|
description: description,
|
||||||
|
required: required,
|
||||||
|
repeat: true,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
})
|
||||||
|
}
|
||||||
36
arg-string.go
Normal file
36
arg-string.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
// -------------- argString ----------------
|
||||||
|
type argString struct {
|
||||||
|
base argBase
|
||||||
|
targetVar *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arg *argString) getBase() *argBase {
|
||||||
|
return &arg.base
|
||||||
|
}
|
||||||
|
|
||||||
|
func (spec *argString) parse(cli *CliParser, specIndex int, args []string, argIndex int) (consumedArgs int, err error) {
|
||||||
|
consumedArgs = 1
|
||||||
|
if spec.targetVar != nil {
|
||||||
|
if argIndex < len(args) {
|
||||||
|
*spec.targetVar = args[argIndex]
|
||||||
|
} else if spec.base.required {
|
||||||
|
err = errMissingRequiredArg(spec.base.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddStringArg(name string, required bool, targetVar *string, description string) {
|
||||||
|
// todo: check if arg already exists
|
||||||
|
cli.argSpecs = append(cli.argSpecs, &argString{
|
||||||
|
base: argBase{
|
||||||
|
name: name,
|
||||||
|
description: description,
|
||||||
|
required: required,
|
||||||
|
repeat: false,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
})
|
||||||
|
}
|
||||||
86
cli-usage.go
Normal file
86
cli-usage.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Usage()
|
||||||
|
func (cli *CliParser) Usage() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
program, _ := cli.GetVersionSection("program")
|
||||||
|
publicCount := cli.publicOptionCount()
|
||||||
|
if publicCount > 0 {
|
||||||
|
fmt.Fprintf(&sb, "USAGE: %s [<options>] %s\n", program, cli.getArgsTemplate())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(&sb, "USAGE: %s %s\n", program, cli.getArgsTemplate())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cli.argSpecs) > 0 || len(cli.options) > 0 {
|
||||||
|
fmt.Fprintf(&sb, "where:\n")
|
||||||
|
if len(cli.argSpecs) > 0 {
|
||||||
|
for _, argSpec := range cli.argSpecs {
|
||||||
|
arg := argSpec.getBase()
|
||||||
|
fmt.Fprintf(&sb, " <%s> %s\n", arg.name, arg.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if publicCount > 0 {
|
||||||
|
fmt.Fprintf(&sb, " <options>\n")
|
||||||
|
templates, maxWidth := cli.makeOptionTemplateList()
|
||||||
|
for i, opti := range cli.options {
|
||||||
|
if opti.isHidden() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opt := opti.getBase()
|
||||||
|
aliases := opt.aliases
|
||||||
|
if len(aliases) > 0 && opt.isArray {
|
||||||
|
// Skip the last alias because it is the implicit plural alias
|
||||||
|
aliases = aliases[0 : len(aliases)-1]
|
||||||
|
}
|
||||||
|
if len(aliases) > 0 {
|
||||||
|
fmt.Fprintf(&sb, " %-*s %s (alias: %s)", maxWidth, templates[i], opt.description, strings.Join(aliases, ", "))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(&sb, " %-*s %s", maxWidth, templates[i], opt.description)
|
||||||
|
}
|
||||||
|
if value := opti.getDefaultValue(); value != "" {
|
||||||
|
fmt.Fprintf(&sb, " (default: %q)", value)
|
||||||
|
}
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) publicOptionCount() (count int) {
|
||||||
|
for _, opti := range cli.options {
|
||||||
|
opt := opti.getBase()
|
||||||
|
if !opt.hidden {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) makeOptionTemplateList() (templates []string, maxWidth int) {
|
||||||
|
maxWidth = 0
|
||||||
|
templates = make([]string, len(cli.options))
|
||||||
|
for i, opti := range cli.options {
|
||||||
|
if opti.isHidden() {
|
||||||
|
templates[i] = ""
|
||||||
|
} else {
|
||||||
|
templates[i] = opti.getTemplate()
|
||||||
|
if len(templates[i]) > maxWidth {
|
||||||
|
maxWidth = len(templates[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return templates, maxWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintUsage()
|
||||||
|
func (cli *CliParser) PrintUsage() {
|
||||||
|
os.Stdout.WriteString(cli.Usage())
|
||||||
|
}
|
||||||
59
cli-version.go
Normal file
59
cli-version.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// const (
|
||||||
|
// VER_PROGRAM = iota
|
||||||
|
// VER_VERSION
|
||||||
|
// VER_DATE
|
||||||
|
// VER_EMAIL
|
||||||
|
// )
|
||||||
|
|
||||||
|
// GetVersionSection()
|
||||||
|
func (cli *CliParser) GetVersionSection(sectionName string) (secValue string, err error) {
|
||||||
|
var sectionId int
|
||||||
|
if sectionName == "" || sectionName == "all" || sectionName == "full" {
|
||||||
|
secValue = cli.version[5 : len(cli.version)-2]
|
||||||
|
} else {
|
||||||
|
sections := strings.Split(cli.version[5:len(cli.version)-2], ",")
|
||||||
|
switch sectionName {
|
||||||
|
case "program":
|
||||||
|
sectionId = 0
|
||||||
|
case "version", "number":
|
||||||
|
sectionId = 1
|
||||||
|
case "date":
|
||||||
|
sectionId = 2
|
||||||
|
case "email":
|
||||||
|
sectionId = 3
|
||||||
|
case "full":
|
||||||
|
sectionId = -1
|
||||||
|
default:
|
||||||
|
sectionId = 1
|
||||||
|
}
|
||||||
|
if sectionId >= 0 && sectionId < len(sections) {
|
||||||
|
secValue = sections[sectionId]
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown version section %q", sectionName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintVersion()
|
||||||
|
func (cli *CliParser) PrintVersion(specs []string) {
|
||||||
|
if len(specs) == 0 {
|
||||||
|
if specValue, err := cli.GetVersionSection("number"); err == nil {
|
||||||
|
os.Stdout.WriteString(specValue + "\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, spec := range specs {
|
||||||
|
if specValue, err := cli.GetVersionSection(spec); err == nil {
|
||||||
|
os.Stdout.WriteString(specValue + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
216
cli.go
Normal file
216
cli.go
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CliOptionTracer interface {
|
||||||
|
TraceCliOption(name string, valueType string, value any)
|
||||||
|
}
|
||||||
|
|
||||||
|
type cliParser interface {
|
||||||
|
getOptionValue(argIndex int) (string, bool)
|
||||||
|
getCliArgs(startIndex, endIndex int) (args []string)
|
||||||
|
PrintVersion(specs []string)
|
||||||
|
PrintUsage()
|
||||||
|
}
|
||||||
|
|
||||||
|
type cliOptionParser interface {
|
||||||
|
parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error)
|
||||||
|
getTemplate() string
|
||||||
|
getDefaultValue() string
|
||||||
|
getBase() *cliOptionBase
|
||||||
|
init()
|
||||||
|
isSet() bool
|
||||||
|
isHidden() bool
|
||||||
|
requiresValue() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptManager interface {
|
||||||
|
SetOptionValue(name string, value string) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpecialValueFunc func(manager OptManager, cliValue string, currentValue any) (value any, err error)
|
||||||
|
|
||||||
|
type OptReference interface {
|
||||||
|
AddSpecialValue(cliValue string, specialFunc SpecialValueFunc)
|
||||||
|
SetHidden(hidden bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CliParser struct {
|
||||||
|
version string
|
||||||
|
options []cliOptionParser
|
||||||
|
argSpecs []argSpec
|
||||||
|
cliArgs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) Init(argv []string, version string) {
|
||||||
|
cli.version = version
|
||||||
|
cli.cliArgs = argv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) SetIncompatibleOption(optName string, incompatibleOptNames ...string) error {
|
||||||
|
var opti cliOptionParser
|
||||||
|
if opti = cli.findOptionByName(optName); opti == nil {
|
||||||
|
return errOptionNotFound(optName)
|
||||||
|
}
|
||||||
|
for _, incompatibleName := range incompatibleOptNames {
|
||||||
|
var incompatibleOpti cliOptionParser
|
||||||
|
if incompatibleOpti = cli.findOptionByName(incompatibleName); incompatibleOpti == nil {
|
||||||
|
return errOptionNotFound(incompatibleName)
|
||||||
|
}
|
||||||
|
incompatibleOpti.getBase().addIncompatibleOption(optName)
|
||||||
|
}
|
||||||
|
opti.getBase().addIncompatibleOption(incompatibleOptNames...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) TraceOptions(tracer CliOptionTracer) {
|
||||||
|
for _, opti := range cli.options {
|
||||||
|
opt := opti.getBase()
|
||||||
|
var valueType string
|
||||||
|
var value any
|
||||||
|
if optiWithTargetVar, ok := opti.(interface {
|
||||||
|
getTargetVar() (any, string)
|
||||||
|
}); ok {
|
||||||
|
value, valueType = optiWithTargetVar.getTargetVar()
|
||||||
|
} else {
|
||||||
|
value = nil
|
||||||
|
valueType = "n/a"
|
||||||
|
}
|
||||||
|
tracer.TraceCliOption(opt.name, valueType, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddVersion() {
|
||||||
|
name := "version"
|
||||||
|
short := "v"
|
||||||
|
aliases := []string(nil)
|
||||||
|
description := `Print program info; allowed sections: "all", "program", "number" or "version", "date", "email"`
|
||||||
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
panic(errOptionAlreadyDefined(name))
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, &cliOptionVersion{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddHelp() {
|
||||||
|
name := "help"
|
||||||
|
short := "h"
|
||||||
|
aliases := []string(nil)
|
||||||
|
description := "Print this help text"
|
||||||
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
panic(errOptionAlreadyDefined(name))
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, &cliOptionHelp{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) addHelpAndVersion() {
|
||||||
|
var hasHelp, hasVersion bool
|
||||||
|
for _, opti := range cli.options {
|
||||||
|
opti.init()
|
||||||
|
opt := opti.getBase()
|
||||||
|
if opt.Is("help") {
|
||||||
|
hasHelp = true
|
||||||
|
} else if opt.Is("version") {
|
||||||
|
hasVersion = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasHelp {
|
||||||
|
cli.AddHelp()
|
||||||
|
}
|
||||||
|
if !hasVersion {
|
||||||
|
cli.AddVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) Parse() (err error) {
|
||||||
|
var arg string
|
||||||
|
var i int
|
||||||
|
var args []string
|
||||||
|
var optionsAllowed bool = true
|
||||||
|
|
||||||
|
cli.addHelpAndVersion()
|
||||||
|
|
||||||
|
skipNext := false
|
||||||
|
for i, arg = range cli.cliArgs[1:] {
|
||||||
|
if skipNext {
|
||||||
|
skipNext = false
|
||||||
|
} else {
|
||||||
|
if optionsAllowed && arg[0] == '-' {
|
||||||
|
if arg == "--" {
|
||||||
|
optionsAllowed = false
|
||||||
|
} else if skipNext, err = cli.parseArg(arg, i); err == nil {
|
||||||
|
err = cli.checkCompatibility(arg)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args = append(args, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
var argSpec argSpec
|
||||||
|
var n, specIndex int
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
for specIndex, argSpec = range cli.argSpecs {
|
||||||
|
if n, err = argSpec.parse(cli, specIndex, args, i); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i += n
|
||||||
|
if i >= len(args) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if i < len(args) {
|
||||||
|
err = fmt.Errorf("too many arguments: %d allowed", i)
|
||||||
|
} else {
|
||||||
|
specIndex++
|
||||||
|
for _, spec := range cli.argSpecs[specIndex:] {
|
||||||
|
if !spec.getBase().required {
|
||||||
|
specIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if specIndex < len(cli.argSpecs) {
|
||||||
|
err = errTooFewArguments(len(cli.argSpecs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) checkCompatibility(arg string) (err error) {
|
||||||
|
var opti cliOptionParser
|
||||||
|
if opti = cli.findOptionByArg(arg); opti != nil {
|
||||||
|
opt := opti.getBase()
|
||||||
|
for _, incompatibleName := range opt.incompatibleWith {
|
||||||
|
var incompatibleOpti cliOptionParser
|
||||||
|
if incompatibleOpti = cli.findOptionByName(incompatibleName); incompatibleOpti != nil {
|
||||||
|
if incompatibleOpti.isSet() {
|
||||||
|
err = errIncompatibleOptions(opt.name, incompatibleOpti.getBase().name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
81
cli_test.go
Normal file
81
cli_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GlobaData struct {
|
||||||
|
config string
|
||||||
|
log []string
|
||||||
|
printOcr bool
|
||||||
|
saveClips bool
|
||||||
|
trace bool
|
||||||
|
page []int
|
||||||
|
sources []string
|
||||||
|
dest string
|
||||||
|
facoltativo 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) {
|
||||||
|
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 {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
usage := cli.Usage()
|
||||||
|
fmt.Println(usage)
|
||||||
|
if err := cli.Parse(); err == nil {
|
||||||
|
fmt.Println(&gd)
|
||||||
|
} else {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gd *GlobaData) addOptions(cli *CliParser) (err error) {
|
||||||
|
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")
|
||||||
|
return
|
||||||
|
}
|
||||||
39
common.go
Normal file
39
common.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func errMissingOptionValue(opt string) error {
|
||||||
|
return fmt.Errorf("option %q requires a value", opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errOptionAlreadyDefined(opt string) error {
|
||||||
|
return fmt.Errorf("option name %q already used", opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errRepeatArgAlreadyDefined(argName string) error {
|
||||||
|
return fmt.Errorf("repeat property already set for arg <%s>", argName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errMissingRequiredArg(argName string) error {
|
||||||
|
return fmt.Errorf("missing required arg <%s>", argName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errTooFewArguments(numRequired int) error {
|
||||||
|
return fmt.Errorf("too few arguments: ad least %d required", numRequired)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errOptionNotFound(name string) error {
|
||||||
|
return fmt.Errorf("option %q not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalidOptionValue(name string, value any, optType string) error {
|
||||||
|
return fmt.Errorf("invalid value %v (%T) for option %q (%s)", value, value, name, optType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errIncompatibleOptions(currentOptName string, incompatibleOptName string) error {
|
||||||
|
return fmt.Errorf("option %q cannot specified together with option %q", currentOptName, incompatibleOptName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errUnknownOption(name string) error {
|
||||||
|
return fmt.Errorf("unknown option %q", name)
|
||||||
|
}
|
||||||
147
opt-base.go
Normal file
147
opt-base.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliOptionBase struct {
|
||||||
|
name string
|
||||||
|
shortAlias string
|
||||||
|
aliases []string
|
||||||
|
description string
|
||||||
|
isArray bool
|
||||||
|
hidden bool
|
||||||
|
specialValues map[string]SpecialValueFunc
|
||||||
|
incompatibleWith []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) getBase() *cliOptionBase {
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) getTargetVar() (any, string) {
|
||||||
|
return nil, "n/a"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) Is(name string) (result bool) {
|
||||||
|
if result = opt.name == name; !result && len(opt.aliases) > 0 {
|
||||||
|
result = slices.Contains(opt.aliases, name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) isHidden() bool {
|
||||||
|
return opt.hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) SetHidden(hidden bool) {
|
||||||
|
opt.hidden = hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) AddSpecialValue(cliValue string, specialFunc SpecialValueFunc) {
|
||||||
|
if opt.specialValues == nil {
|
||||||
|
opt.specialValues = make(map[string]SpecialValueFunc)
|
||||||
|
}
|
||||||
|
opt.specialValues[cliValue] = specialFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) getSpecialFunc(cliValue string) (specialFunc SpecialValueFunc, exists bool) {
|
||||||
|
if len(opt.specialValues) > 0 {
|
||||||
|
specialFunc, exists = opt.specialValues[cliValue]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) isSet() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) requiresValue() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) addIncompatibleOption(names ...string) {
|
||||||
|
opt.incompatibleWith = append(opt.incompatibleWith, names...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) parse(parser cliParser, valuePtr *string) (err error) {
|
||||||
|
return fmt.Errorf("unhandled option %q", opt.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) getDefaultValue() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluralSuffix(word string) (suffix string) {
|
||||||
|
if len(word) > 0 {
|
||||||
|
if strings.HasSuffix(word, "x") || strings.HasSuffix(word, "s") || strings.HasSuffix(word, "ch") {
|
||||||
|
suffix = "es"
|
||||||
|
} else {
|
||||||
|
suffix = "s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePlural(word string) (plural string) {
|
||||||
|
return word + pluralSuffix(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) makeOptTemplate(isArray bool, valueType string) (templ string) {
|
||||||
|
var suffix, multiValue string
|
||||||
|
if isArray {
|
||||||
|
suffix = "(" + pluralSuffix(opt.name) + ")"
|
||||||
|
multiValue = `["," ...]`
|
||||||
|
}
|
||||||
|
if opt.shortAlias != "" {
|
||||||
|
templ = fmt.Sprintf(`-%s, --%s%s <%s>%s`, opt.shortAlias, opt.name, suffix, valueType, multiValue)
|
||||||
|
} else {
|
||||||
|
templ = fmt.Sprintf(`--%s%s <%s>%s`, opt.name, suffix, valueType, multiValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) makeOptSimpleTemplate(valueRequired, multiValue bool, valueType string) (templ string) {
|
||||||
|
var valueSpec, dots string
|
||||||
|
|
||||||
|
if multiValue {
|
||||||
|
dots = " ..."
|
||||||
|
}
|
||||||
|
if len(valueType) > 0 {
|
||||||
|
valueSpec = " <" + valueType + ">" + dots
|
||||||
|
if !valueRequired {
|
||||||
|
valueSpec = " [" + valueSpec[1:] + "]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.shortAlias != "" {
|
||||||
|
templ = fmt.Sprintf(`-%s, --%s%s`, opt.shortAlias, opt.name, valueSpec)
|
||||||
|
} else {
|
||||||
|
templ = fmt.Sprintf(`--%s%s`, opt.name, valueSpec)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) getSpecialValue(parser cliParser, value string, targetVar any) (boxedValue any, err error) {
|
||||||
|
if specialFunc, exists := opt.getSpecialFunc(value); exists {
|
||||||
|
manager := parser.(OptManager)
|
||||||
|
boxedValue, err = specialFunc(manager, value, targetVar)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBase) fetchOptionValue(parser cliParser, argIndex int, valuePtr *string) (value string, skipNextArg bool, err error) {
|
||||||
|
if valuePtr != nil {
|
||||||
|
value = *valuePtr
|
||||||
|
} else {
|
||||||
|
if source, optionPresent := parser.getOptionValue(argIndex); optionPresent {
|
||||||
|
skipNextArg = true
|
||||||
|
value = source
|
||||||
|
} else {
|
||||||
|
err = errMissingOptionValue(opt.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
85
opt-bool.go
Normal file
85
opt-bool.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
boolTypeName = "bool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliOptionBool struct {
|
||||||
|
cliOptionBase
|
||||||
|
targetVar *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBool) init() {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
*opt.targetVar = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBool) getTargetVar() (any, string) {
|
||||||
|
var value bool
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
value = *opt.targetVar
|
||||||
|
}
|
||||||
|
return value, boolTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBool) isSet() bool {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
return *(opt.targetVar)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBool) requiresValue() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBool) getTemplate() (templ string) {
|
||||||
|
if opt.shortAlias != "" {
|
||||||
|
templ = fmt.Sprintf(`-%s, --%s`, opt.shortAlias, opt.name)
|
||||||
|
} else {
|
||||||
|
templ = fmt.Sprintf(`--%s`, opt.name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionBool) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
||||||
|
var boxedValue any
|
||||||
|
value := "true"
|
||||||
|
|
||||||
|
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
if boxedValue != nil {
|
||||||
|
if val, ok := boxedValue.(bool); ok {
|
||||||
|
*(opt.targetVar) = val
|
||||||
|
} else {
|
||||||
|
err = errInvalidOptionValue(opt.name, boxedValue, "bool")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*(opt.targetVar) = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddBoolOpt(name, short string, targetVar *bool, description string, aliases ...string) OptReference {
|
||||||
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
panic(errOptionAlreadyDefined(name))
|
||||||
|
}
|
||||||
|
opt := &cliOptionBool{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, opt)
|
||||||
|
return opt
|
||||||
|
}
|
||||||
30
opt-help.go
Normal file
30
opt-help.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliOptionHelp struct {
|
||||||
|
cliOptionBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionHelp) init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionHelp) requiresValue() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionHelp) getDefaultValue() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionHelp) getTemplate() string {
|
||||||
|
return opt.makeOptSimpleTemplate(false, false, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionHelp) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
||||||
|
parser.PrintUsage()
|
||||||
|
err = io.EOF
|
||||||
|
return
|
||||||
|
}
|
||||||
111
opt-int-array.go
Normal file
111
opt-int-array.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
intArrayTypeName = "num-array"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliOptionIntArray struct {
|
||||||
|
cliOptionBase
|
||||||
|
defaultValue []int
|
||||||
|
targetVar *[]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionIntArray) init() {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
*opt.targetVar = opt.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionIntArray) getTargetVar() (any, string) {
|
||||||
|
var value []int
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
value = *opt.targetVar
|
||||||
|
}
|
||||||
|
return value, intArrayTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionIntArray) requiresValue() bool {
|
||||||
|
return opt.targetVar != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionIntArray) getDefaultValue() string {
|
||||||
|
def := make([]string, len(opt.defaultValue))
|
||||||
|
for i, v := range opt.defaultValue {
|
||||||
|
def[i] = strconv.Itoa(v)
|
||||||
|
}
|
||||||
|
return strings.Join(def, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionIntArray) getTemplate() string {
|
||||||
|
return opt.makeOptTemplate(true, "num")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIntRange(value string) (min int, max int, err error) {
|
||||||
|
var dashPresent bool
|
||||||
|
var minStr, maxStr string
|
||||||
|
minStr, maxStr, dashPresent = strings.Cut(value, "-")
|
||||||
|
if dashPresent {
|
||||||
|
if min, err = strconv.Atoi(minStr); err == nil {
|
||||||
|
if max, err = strconv.Atoi(maxStr); err == nil {
|
||||||
|
if min > max {
|
||||||
|
err = errInvalidOptionValue("", value, "invalid range")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if min, err = strconv.Atoi(value); err == nil {
|
||||||
|
max = min
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
var boxedValue any
|
||||||
|
if boxedValue, err = opt.getSpecialValue(parser, optValue, opt.targetVar); err == nil {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
if boxedValue != nil {
|
||||||
|
if val, ok := boxedValue.([]int); ok {
|
||||||
|
*opt.targetVar = val
|
||||||
|
} else {
|
||||||
|
err = errInvalidOptionValue(opt.name, boxedValue, "array of int")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for value := range strings.SplitSeq(optValue, ",") {
|
||||||
|
var minRange, maxRange int
|
||||||
|
if minRange, maxRange, err = parseIntRange(value); err == nil {
|
||||||
|
for i := minRange; i <= maxRange; i++ {
|
||||||
|
*opt.targetVar = append(*opt.targetVar, i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddIntArrayOpt(name, short string, targetVar *[]int, defaultValue []int, description string, aliases ...string) OptReference {
|
||||||
|
aliases = cli.checkAlreadyUsedNames(name, short, aliases)
|
||||||
|
opt := &cliOptionIntArray{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
isArray: true,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
defaultValue: defaultValue,
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, opt)
|
||||||
|
return opt
|
||||||
|
}
|
||||||
78
opt-int.go
Normal file
78
opt-int.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
const (
|
||||||
|
intTypeName = "num"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliOptionInt struct {
|
||||||
|
cliOptionBase
|
||||||
|
defaultValue int
|
||||||
|
targetVar *int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionInt) init() {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
*opt.targetVar = opt.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionInt) getTargetVar() (any, string) {
|
||||||
|
var value int
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
value = *opt.targetVar
|
||||||
|
}
|
||||||
|
return value, intTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionInt) requiresValue() bool {
|
||||||
|
return opt.targetVar != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionInt) getDefaultValue() string {
|
||||||
|
return strconv.Itoa(opt.defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionInt) getTemplate() string {
|
||||||
|
return opt.makeOptTemplate(false, intTypeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionInt) 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 {
|
||||||
|
var boxedValue any
|
||||||
|
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
if boxedValue != nil {
|
||||||
|
if val, ok := boxedValue.(string); ok {
|
||||||
|
*opt.targetVar, err = strconv.Atoi(val)
|
||||||
|
} else {
|
||||||
|
err = errInvalidOptionValue(opt.name, boxedValue, "int")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*opt.targetVar, err = strconv.Atoi(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddIntOpt(name, short string, targetVar *int, defaultValue int, description string, aliases ...string) OptReference {
|
||||||
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
panic(errOptionAlreadyDefined(name))
|
||||||
|
}
|
||||||
|
opt := &cliOptionInt{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
defaultValue: defaultValue,
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, opt)
|
||||||
|
return opt
|
||||||
|
}
|
||||||
10
opt-manager.go
Normal file
10
opt-manager.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
func (cli *CliParser) SetOptionValue(name string, value string) (err error) {
|
||||||
|
if opt := cli.findOptionByArg(name); opt != nil {
|
||||||
|
_, err = opt.parse(cli, -1, &value)
|
||||||
|
} else {
|
||||||
|
err = errOptionNotFound(name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
85
opt-string-array.go
Normal file
85
opt-string-array.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
stringArrayTypeName = "string-array"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliOptionStringArray struct {
|
||||||
|
cliOptionBase
|
||||||
|
defaultValue []string
|
||||||
|
targetVar *[]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringArray) init() {
|
||||||
|
opt.isArray = true
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
*opt.targetVar = opt.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringArray) getTargetVar() (any, string) {
|
||||||
|
var value []string
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
value = *opt.targetVar
|
||||||
|
}
|
||||||
|
return value, stringArrayTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringArray) requiresValue() bool {
|
||||||
|
return opt.targetVar != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringArray) getDefaultValue() string {
|
||||||
|
return strings.Join(opt.defaultValue, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringArray) getTemplate() string {
|
||||||
|
return opt.makeOptTemplate(true, "string")
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse retrieves the option value from the parser and updates the target variable.
|
||||||
|
// It handles comma-separated values and special values if configured.
|
||||||
|
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 {
|
||||||
|
var boxedValue any
|
||||||
|
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
if boxedValue != nil {
|
||||||
|
if val, ok := boxedValue.([]string); ok {
|
||||||
|
*opt.targetVar = val
|
||||||
|
} else {
|
||||||
|
err = errInvalidOptionValue(opt.name, boxedValue, "array of string")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*opt.targetVar = strings.Split(value, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStringArrayOpt adds a new string array option to the CLI parser.
|
||||||
|
// It takes the name, short alias, target variable, default value, description, and optional aliases.
|
||||||
|
// It returns a reference to the created option.
|
||||||
|
func (cli *CliParser) AddStringArrayOpt(name, short string, targetVar *[]string, defaultValue []string, description string, aliases ...string) OptReference {
|
||||||
|
aliases = cli.checkAlreadyUsedNames(name, short, aliases)
|
||||||
|
opt := &cliOptionStringArray{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
isArray: true,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
defaultValue: defaultValue,
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, opt)
|
||||||
|
return opt
|
||||||
|
}
|
||||||
108
opt-string-map.go
Normal file
108
opt-string-map.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
stringMapTypeName = "map-string"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliOptionStringMap struct {
|
||||||
|
cliOptionBase
|
||||||
|
defaultValue map[string]string
|
||||||
|
targetVar *map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringMap) init() {
|
||||||
|
opt.isArray = true
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
*opt.targetVar = opt.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringMap) getTargetVar() (any, string) {
|
||||||
|
var value map[string]string
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
value = *opt.targetVar
|
||||||
|
}
|
||||||
|
return value, stringMapTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringMap) getBase() *cliOptionBase {
|
||||||
|
return &opt.cliOptionBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapJoin[T any](m map[string]T, kvSep, itemSep string) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
for key, value := range m {
|
||||||
|
if sb.Len() > 0 {
|
||||||
|
sb.WriteString(itemSep)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&sb, "%q%s%v", key, kvSep, value)
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringMap) requiresValue() bool {
|
||||||
|
return opt.targetVar != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringMap) getDefaultValue() string {
|
||||||
|
return MapJoin(opt.defaultValue, ",", "=")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionStringMap) getTemplate() string {
|
||||||
|
return opt.makeOptTemplate(true, "key=value")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
var boxedValue any
|
||||||
|
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
dict := *opt.targetVar
|
||||||
|
if boxedValue != nil {
|
||||||
|
if val, ok := boxedValue.(map[string]string); ok {
|
||||||
|
dict = val
|
||||||
|
} else {
|
||||||
|
err = errInvalidOptionValue(opt.name, boxedValue, "map of string")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dict == nil {
|
||||||
|
dict = make(map[string]string)
|
||||||
|
}
|
||||||
|
for value := range strings.SplitSeq(value, ",") {
|
||||||
|
if k, v, sepExists := strings.Cut(value, "="); sepExists {
|
||||||
|
dict[k] = v
|
||||||
|
} else {
|
||||||
|
dict[k] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*opt.targetVar = dict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
opt := &cliOptionStringMap{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
isArray: true,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
defaultValue: defaultValue,
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, opt)
|
||||||
|
return opt
|
||||||
|
}
|
||||||
136
opt-string.go
Normal file
136
opt-string.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
const (
|
||||||
|
stringTypeName = "string"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliOptionString struct {
|
||||||
|
cliOptionBase
|
||||||
|
defaultValue string
|
||||||
|
targetVar *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionString) init() {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
*opt.targetVar = opt.defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionString) getTargetVar() (any, string) {
|
||||||
|
var value string
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
value = *opt.targetVar
|
||||||
|
}
|
||||||
|
return value, stringTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionString) requiresValue() bool {
|
||||||
|
return opt.targetVar != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionString) getDefaultValue() string {
|
||||||
|
return opt.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionString) getTemplate() string {
|
||||||
|
return opt.makeOptTemplate(false, stringTypeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
var boxedValue any
|
||||||
|
if boxedValue, err = opt.getSpecialValue(parser, value, opt.targetVar); err == nil {
|
||||||
|
if opt.targetVar != nil {
|
||||||
|
if boxedValue != nil {
|
||||||
|
if val, ok := boxedValue.(string); ok {
|
||||||
|
*(opt.targetVar) = val
|
||||||
|
} else {
|
||||||
|
err = errInvalidOptionValue(opt.name, boxedValue, "string")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*(opt.targetVar) = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddStringOpt(name, short string, targetVar *string, defaultValue string, description string, aliases ...string) OptReference {
|
||||||
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
panic(errOptionAlreadyDefined(name))
|
||||||
|
}
|
||||||
|
opt := &cliOptionString{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
defaultValue: defaultValue,
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, opt)
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------- cliOptionDir ----------------------
|
||||||
|
|
||||||
|
type cliOptionDir struct {
|
||||||
|
cliOptionString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionDir) getTemplate() string {
|
||||||
|
return opt.cliOptionBase.makeOptTemplate(false, "dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddDirOpt(name, short string, targetVar *string, defaultValue string, description string, aliases ...string) OptReference {
|
||||||
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
panic(errOptionAlreadyDefined(name))
|
||||||
|
}
|
||||||
|
opt := &cliOptionDir{
|
||||||
|
cliOptionString: cliOptionString{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
defaultValue: defaultValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, opt)
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------- cliOptionFile ----------------------
|
||||||
|
|
||||||
|
type cliOptionFile struct {
|
||||||
|
cliOptionString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionFile) getTemplate() string {
|
||||||
|
return opt.cliOptionBase.makeOptTemplate(false, "file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) AddFileOpt(name, short string, targetVar *string, defaultValue string, description string, aliases ...string) OptReference {
|
||||||
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
panic(errOptionAlreadyDefined(name))
|
||||||
|
}
|
||||||
|
opt := &cliOptionFile{
|
||||||
|
cliOptionString: cliOptionString{
|
||||||
|
cliOptionBase: cliOptionBase{
|
||||||
|
name: name,
|
||||||
|
shortAlias: short,
|
||||||
|
aliases: aliases,
|
||||||
|
description: description,
|
||||||
|
},
|
||||||
|
targetVar: targetVar,
|
||||||
|
defaultValue: defaultValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cli.options = append(cli.options, opt)
|
||||||
|
return opt
|
||||||
|
}
|
||||||
36
opt-version.go
Normal file
36
opt-version.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliOptionVersion struct {
|
||||||
|
cliOptionBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionVersion) init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionVersion) requiresValue() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionVersion) getDefaultValue() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionVersion) getTemplate() string {
|
||||||
|
return opt.makeOptSimpleTemplate(false, true, "section")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *cliOptionVersion) parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, err error) {
|
||||||
|
var args []string
|
||||||
|
if valuePtr != nil {
|
||||||
|
args = []string{*valuePtr}
|
||||||
|
} else {
|
||||||
|
args = parser.getCliArgs(argIndex+2, -1)
|
||||||
|
}
|
||||||
|
parser.PrintVersion(args)
|
||||||
|
err = io.EOF
|
||||||
|
return
|
||||||
|
}
|
||||||
122
parser.go
Normal file
122
parser.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Private functions implementation
|
||||||
|
|
||||||
|
func (cli *CliParser) optionExists(name, short string, aliases []string) (exists bool) {
|
||||||
|
for _, opti := range cli.options {
|
||||||
|
opt := opti.getBase()
|
||||||
|
if len(opt.shortAlias) > 0 && opt.shortAlias == short {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
} else if opt.Is(name) {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
if slices.ContainsFunc(aliases, opt.Is) {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) checkAlreadyUsedNames(name, short string, aliases []string) []string {
|
||||||
|
if plural := makePlural(name); !slices.Contains(aliases, plural) {
|
||||||
|
aliases = append(aliases, plural)
|
||||||
|
}
|
||||||
|
if cli.optionExists(name, short, aliases) {
|
||||||
|
panic(errOptionAlreadyDefined(name))
|
||||||
|
}
|
||||||
|
return aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) getOptionValue(argIndex int) (value string, present bool) {
|
||||||
|
if argIndex < len(cli.cliArgs)-2 {
|
||||||
|
value = cli.cliArgs[1+argIndex+1]
|
||||||
|
present = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) getCliArgs(startIndex, endIndex int) (args []string) {
|
||||||
|
if endIndex < 0 || endIndex > len(cli.cliArgs) {
|
||||||
|
endIndex = len(cli.cliArgs)
|
||||||
|
}
|
||||||
|
if startIndex < 0 {
|
||||||
|
startIndex = 0
|
||||||
|
}
|
||||||
|
if startIndex > endIndex {
|
||||||
|
startIndex = endIndex
|
||||||
|
}
|
||||||
|
args = cli.cliArgs[startIndex:endIndex]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) findOptionByArg(arg string) (matchingOpt cliOptionParser) {
|
||||||
|
if strings.HasPrefix(arg, "--") {
|
||||||
|
for _, opti := range cli.options {
|
||||||
|
opt := opti.getBase()
|
||||||
|
if opt.Is(arg[2:]) {
|
||||||
|
matchingOpt = opti
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(arg, "-") {
|
||||||
|
for _, opti := range cli.options {
|
||||||
|
opt := opti.getBase()
|
||||||
|
if opt.shortAlias == arg[1:] {
|
||||||
|
matchingOpt = opti
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) findOptionByName(optName string) (matchingOpt cliOptionParser) {
|
||||||
|
for _, opti := range cli.options {
|
||||||
|
opt := opti.getBase()
|
||||||
|
if opt.Is(optName) {
|
||||||
|
matchingOpt = opti
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *CliParser) parseArg(arg string, index int) (skipNextArg bool, err error) {
|
||||||
|
var opts []string
|
||||||
|
var name, value, dashes string
|
||||||
|
var equalPresent bool
|
||||||
|
name, value, equalPresent = strings.Cut(arg, "=")
|
||||||
|
if strings.HasPrefix(name, "--") {
|
||||||
|
opts = []string{name[2:]}
|
||||||
|
dashes = "--"
|
||||||
|
} else {
|
||||||
|
opts = strings.Split(name[1:], "")
|
||||||
|
dashes = "-"
|
||||||
|
}
|
||||||
|
for i, optName := range opts {
|
||||||
|
if opt := cli.findOptionByArg(dashes + optName); opt != nil {
|
||||||
|
if equalPresent && i == len(opts)-1 {
|
||||||
|
_, err = opt.parse(cli, index, &value)
|
||||||
|
} else if i < len(opts)-1 && opt.requiresValue() {
|
||||||
|
err = errMissingOptionValue(dashes + optName)
|
||||||
|
} else {
|
||||||
|
skipNextArg, err = opt.parse(cli, index, nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errUnknownOption(dashes + optName)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
18
simple-opt-tracer.go
Normal file
18
simple-opt-tracer.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleOptionTracer struct {
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSimpleOptionTracer(w io.Writer) *SimpleOptionTracer {
|
||||||
|
return &SimpleOptionTracer{w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *SimpleOptionTracer) TraceCliOption(name string, valueType string, value any) {
|
||||||
|
fmt.Fprintf(tr.w, "Option: %s, Type: %s, Value: %v\n", name, valueType, value)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user