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 }