Option types: bool int, int-array, string, string-array, string-map, file, dir
217 lines
4.9 KiB
Go
217 lines
4.9 KiB
Go
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
|
|
}
|