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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user