new /v2 subdirectory
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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()
|
||||
FlagIsSet(flag int16) bool
|
||||
}
|
||||
|
||||
type cliOptionParser interface {
|
||||
parse(parser cliParser, argIndex int, valuePtr *string) (skipNextArg bool, optValue string, err error)
|
||||
getTemplate() string
|
||||
getDefaultValue() string
|
||||
getBase() *cliOptionBase
|
||||
init()
|
||||
isSet() bool
|
||||
isHidden() bool
|
||||
requiresValue() bool
|
||||
finalCheck() (err error)
|
||||
}
|
||||
|
||||
type OptManager interface {
|
||||
SetOptionValue(name string, value string) (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 {
|
||||
AddSpecialValue(cliValue string, specialFunc SpecialValueFunc)
|
||||
OnFinalCheck(checkFunc FinalCheckFunc)
|
||||
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 {
|
||||
description string
|
||||
version string
|
||||
options []cliOptionParser
|
||||
argSpecs []argSpec
|
||||
cliArgs []string
|
||||
flags int16
|
||||
}
|
||||
|
||||
func (cli *CliParser) Init(version string, description string, flags ...int16) {
|
||||
cli.version = version
|
||||
cli.description = description
|
||||
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) {
|
||||
var opt cliOptionParser
|
||||
if strings.HasPrefix(name, "-") {
|
||||
opt = cli.findOptionByArg(name)
|
||||
} else {
|
||||
opt = cli.findOptionByName(name)
|
||||
}
|
||||
if opt != nil {
|
||||
ref = opt.(OptReference)
|
||||
}
|
||||
return ref
|
||||
|
||||
}
|
||||
|
||||
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) parseOptions() (commandArgs []string, err error) {
|
||||
// var commandArgs []string
|
||||
var optionsAllowed bool = true
|
||||
|
||||
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 {
|
||||
commandArgs = append(commandArgs, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *CliParser) checkOptionValues() (err error) {
|
||||
for _, opt := range cli.options {
|
||||
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
|
||||
}
|
||||
i += n
|
||||
if i >= len(commandArgs) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// check if there are remaining arg-specs that require a value
|
||||
if err == nil {
|
||||
if i < len(commandArgs) {
|
||||
err = fmt.Errorf("too many arguments: %d allowed", i)
|
||||
} else {
|
||||
specIndex++
|
||||
if specIndex < len(cli.argSpecs) {
|
||||
// skip all non required args
|
||||
for _, spec := range cli.argSpecs[specIndex:] {
|
||||
if !spec.getBase().required {
|
||||
specIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
// return error if there are remaining arg-specs that require a value
|
||||
if specIndex < len(cli.argSpecs) {
|
||||
err = errTooFewArguments(len(cli.argSpecs))
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *CliParser) Parse(argv []string) (err error) {
|
||||
var commandArgs []string
|
||||
|
||||
cli.cliArgs = argv
|
||||
|
||||
cli.addHelpAndVersion()
|
||||
|
||||
// first parse options and collect command arguments in the args array
|
||||
if commandArgs, err = cli.parseOptions(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// do final checks
|
||||
if err = cli.checkOptionValues(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// then parse collected arguments
|
||||
err = cli.parseCommandArgs(commandArgs)
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *CliParser) checkCompatibility(arg string) (err error) {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user