You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
9.7 KiB
354 lines
9.7 KiB
package main
|
|
|
|
import (
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// configParameterOptions represents additional options for a configParameter.
|
|
type configParameterOptions struct {
|
|
sw, hidden, command bool
|
|
callback func()
|
|
}
|
|
|
|
// configParameter represents a single configuration parameter.
|
|
type configParameter struct {
|
|
name string // duplicated in configMap, but also saved here for convenience
|
|
value, def any // value and default value
|
|
description string // description for this parameter
|
|
parsed bool // true if it was successfully parsed from commandline
|
|
configParameterOptions
|
|
}
|
|
|
|
type configParameterTypeUnion = interface {
|
|
bool | int | uint | float64 | string | []int | []uint | []float64 | []string | []bool
|
|
}
|
|
|
|
var configMap = map[string]configParameter{}
|
|
var configAliasMap = map[string]string{}
|
|
|
|
var configParsingFinished = false
|
|
|
|
// ---
|
|
// registration
|
|
|
|
func registerConfigParameter[T configParameterTypeUnion](name string, def T, description string, opts configParameterOptions) {
|
|
name = strings.ToLower(name)
|
|
|
|
_, ok := configMap[name]
|
|
failIf(ok, "cannot register config parameter (already exists): \"%v\"", name)
|
|
|
|
_, ok = configAliasMap[name]
|
|
failIf(ok, "cannot register config parameter (already exists as an alias): \"%v\"", name)
|
|
|
|
failIf(opts.command && opts.callback == nil, "\"%v\" is defined as a command but callback is missing", name)
|
|
|
|
configMap[name] = configParameter{name: name, value: def, def: def, description: description,
|
|
configParameterOptions: opts}
|
|
}
|
|
|
|
func registerParam[T configParameterTypeUnion](name string, def T, description string) {
|
|
registerConfigParameter(name, def, description, configParameterOptions{})
|
|
}
|
|
|
|
func registerParamEx[T configParameterTypeUnion](name string, def T, description string, options configParameterOptions) {
|
|
registerConfigParameter(name, def, description, options)
|
|
}
|
|
|
|
func registerParamHidden[T configParameterTypeUnion](name string, def T) {
|
|
registerConfigParameter(name, def, "", configParameterOptions{hidden: true})
|
|
}
|
|
|
|
func registerParamWithCallback[T configParameterTypeUnion](name string, def T, description string, callback func()) {
|
|
registerConfigParameter(name, def, description, configParameterOptions{callback: callback})
|
|
}
|
|
|
|
func registerCommand(name string, description string, callback func()) {
|
|
registerConfigParameter(name, false, description, configParameterOptions{command: true, callback: callback})
|
|
}
|
|
|
|
func registerSwitch(name string, description string) {
|
|
registerConfigParameter(name, false, description, configParameterOptions{sw: true})
|
|
}
|
|
|
|
func registerAlias(alias, target string) {
|
|
alias, target = strings.ToLower(alias), strings.ToLower(target)
|
|
|
|
_, ok := configAliasMap[alias]
|
|
failIf(ok, "cannot register alias (already exists): \"%v\"", alias)
|
|
|
|
_, ok = configMap[alias]
|
|
failIf(ok, "cannot register alias (already exists as a config parameter): \"%v\"", alias)
|
|
|
|
_, ok = configMap[target]
|
|
failIf(!ok, "cannot register alias \"%v\": target \"%v\" does not exist", alias, target)
|
|
|
|
configAliasMap[alias] = target
|
|
}
|
|
|
|
// ---
|
|
// acquisition
|
|
|
|
func getParamGeneric(name string) any {
|
|
name = strings.ToLower(name)
|
|
|
|
parm, ok := configMap[name]
|
|
failIf(!ok, "unknown config parameter: \"%v\"", name)
|
|
failIf(parm.command, "config parameter \"%v\" is a command", name)
|
|
|
|
if parm.sw {
|
|
return parm.parsed // switches always return true if they were parsed
|
|
} else if parm.parsed || parm.hidden {
|
|
return parm.value // parsed and hidden parms return their current value
|
|
} else {
|
|
return parm.def // otherwise, use default value
|
|
}
|
|
}
|
|
|
|
func getParam[T configParameterTypeUnion](name string) T {
|
|
return getParamGeneric(name).(T)
|
|
}
|
|
|
|
func getParamInt(name string) int {
|
|
return getParam[int](name)
|
|
}
|
|
|
|
func getParamFloat(name string) float64 {
|
|
return getParam[float64](name)
|
|
}
|
|
|
|
func getParamIntSlice(name string) []int {
|
|
return getParam[[]int](name)
|
|
}
|
|
|
|
func getParamBool(name string) bool {
|
|
return getParam[bool](name)
|
|
}
|
|
|
|
func getParamSwitch(name string) bool {
|
|
return getParamBool(name)
|
|
}
|
|
|
|
func getParamString(name string) string {
|
|
return getParam[string](name)
|
|
}
|
|
|
|
func getParamStringSlice(name string) []string {
|
|
return getParam[[]string](name)
|
|
}
|
|
|
|
func getParamDurationMS(name string) time.Duration {
|
|
tm := getParam[int](name)
|
|
failIf(tm < -1, "\"%v\" can only be set to -1 or a positive value", name)
|
|
if tm == -1 {
|
|
tm = 0
|
|
}
|
|
|
|
return time.Duration(tm) * time.Millisecond
|
|
}
|
|
|
|
// ---
|
|
// setting
|
|
|
|
func setParam(name string, value any) {
|
|
name = strings.ToLower(name)
|
|
|
|
parm, ok := configMap[name]
|
|
failIf(!ok, "unknown config parameter: \"%v\"", name)
|
|
failIf(parm.hidden, "config parameter \"%v\" is hidden and cannot be set", name)
|
|
failIf(parm.command, "config parameter \"%v\" is a command and cannot be set", name)
|
|
failIf(parm.sw && !value.(bool), "config parameter \"%v\" is a switch and only accepts boolean arguments", name)
|
|
|
|
parm.value = value
|
|
if parm.callback != nil {
|
|
parm.callback()
|
|
}
|
|
|
|
configMap[name] = parm
|
|
}
|
|
|
|
// ---
|
|
// parsing
|
|
|
|
// getCmdlineParm retrieves a commandline parameter with index i.
|
|
func getCmdlineParm(i int) string {
|
|
return strings.TrimSpace(os.Args[i])
|
|
}
|
|
|
|
// isSlice checks if a configParameter value is a slice.
|
|
func (parm *configParameter) isSlice() bool {
|
|
switch parm.value.(type) {
|
|
case []int, []uint, []string:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// writeParmValue saves raw commandline value into a configParameter.
|
|
func (parm *configParameter) writeParmValue(value string) {
|
|
var err error
|
|
|
|
switch parm.value.(type) {
|
|
case bool:
|
|
parm.value, err = strconv.ParseBool(value)
|
|
failIf(err != nil, "config parameter \"%v\" must be a boolean", parm.name)
|
|
case int:
|
|
v, err := strconv.ParseInt(value, 10, 0)
|
|
failIf(err != nil, "config parameter \"%v\" must be an integer", parm.name)
|
|
parm.value = int(v)
|
|
case uint:
|
|
v, err := strconv.ParseUint(value, 10, 0)
|
|
failIf(err != nil, "config parameter \"%v\" must be an unsigned integer", parm.name)
|
|
parm.value = uint(v)
|
|
case string:
|
|
parm.value = value
|
|
case []bool:
|
|
b, err := strconv.ParseBool(value)
|
|
failIf(err != nil, "config parameter \"%v\" must be a boolean", parm.name)
|
|
parm.value = append(parm.value.([]bool), b)
|
|
case []int:
|
|
i, err := strconv.ParseInt(value, 10, 0)
|
|
failIf(err != nil, "config parameter \"%v\" must be an integer", parm.name)
|
|
parm.value = append(parm.value.([]int), int(i))
|
|
case []uint:
|
|
u, err := strconv.ParseUint(value, 10, 0)
|
|
failIf(err != nil, "config parameter \"%v\" must be an unsigned integer", parm.name)
|
|
parm.value = append(parm.value.([]uint), uint(u))
|
|
case []string:
|
|
parm.value = append(parm.value.([]string), value)
|
|
default:
|
|
fail("unknown config parameter \"%v\" type: %T", parm.name, parm.value)
|
|
}
|
|
}
|
|
|
|
// finalize marks a configParameter as parsed, adds it to a global config map
|
|
// and calls its callback, if one is present.
|
|
func (parm *configParameter) finalize() {
|
|
parm.parsed = true
|
|
configMap[parm.name] = *parm
|
|
|
|
if parm.callback != nil {
|
|
parm.callback()
|
|
}
|
|
|
|
log("cfg", 2, "parse: %T \"%v\" -> def %v, now %v", parm.value, parm.name, parm.def, parm.value)
|
|
}
|
|
|
|
func parseAppConfig() {
|
|
log("cfg", 1, "parsing config")
|
|
|
|
totalFinalized := 0
|
|
|
|
for i := 1; i < len(os.Args); i++ {
|
|
arg := getCmdlineParm(i)
|
|
if len(arg) == 0 {
|
|
continue
|
|
}
|
|
|
|
failIf(arg[0] != '-', "\"%v\" is not a commandline parameter", arg)
|
|
arg = strings.TrimPrefix(arg, "-")
|
|
arg = strings.TrimPrefix(arg, "-")
|
|
|
|
failIf(len(arg) == 0, "\"%v\" is not a commandline parameter", getCmdlineParm(i))
|
|
|
|
parm, ok := configMap[strings.ToLower(arg)]
|
|
if !ok {
|
|
alias, ok := configAliasMap[strings.ToLower(arg)]
|
|
failIf(!ok, "unknown commandline parameter: \"%v\"", arg)
|
|
|
|
parm, ok = configMap[alias]
|
|
failIf(!ok, "alias \"%v\" references unknown commandline parameter", arg)
|
|
|
|
log("cfg", 3, "\"%v\" is aliased to \"%v\"", alias, parm.name)
|
|
}
|
|
|
|
failIf(parm.hidden, "\"%v\" is not a commandline parameter", getCmdlineParm(i))
|
|
failIf(parm.parsed && !parm.isSlice(), "multiple occurrences of commandline parameter \"%v\" are not allowed", parm.name)
|
|
|
|
if !parm.command {
|
|
if parm.sw {
|
|
parm.writeParmValue("true")
|
|
} else {
|
|
i++
|
|
parm.writeParmValue(getCmdlineParm(i))
|
|
}
|
|
}
|
|
|
|
parm.finalize()
|
|
totalFinalized++
|
|
}
|
|
|
|
log("cfg", 1, "parsed %v commandline parameters", totalFinalized)
|
|
configParsingFinished = true
|
|
}
|
|
|
|
func showHelp() {
|
|
log("", 0, "options:")
|
|
|
|
parms := make([]string, 0, len(configMap))
|
|
for key := range configMap {
|
|
parms = append(parms, key)
|
|
}
|
|
sort.Strings(parms)
|
|
|
|
for _, parmName := range parms {
|
|
parm := configMap[parmName]
|
|
|
|
if parm.hidden {
|
|
continue
|
|
}
|
|
|
|
header := "-" + parm.name
|
|
if len(parm.name) > 1 {
|
|
header = "-" + header
|
|
}
|
|
|
|
aliases := []string{}
|
|
for alias, target := range configAliasMap {
|
|
if target == parm.name {
|
|
if len(alias) == 1 {
|
|
aliases = append(aliases, "-"+alias)
|
|
} else {
|
|
aliases = append(aliases, "--"+alias)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(aliases) > 0 {
|
|
sort.Strings(aliases)
|
|
header = header + " (aliases: " + strings.Join(aliases, ", ") + ")"
|
|
}
|
|
|
|
header = header + ":"
|
|
description := " (description missing)"
|
|
if parm.description != "" {
|
|
description = " " + parm.description
|
|
}
|
|
|
|
if parm.command || parm.sw {
|
|
log("", 0, "%s\n%s", header, description)
|
|
} else {
|
|
log("", 0, "%s\n%s\n default: %v", header, description, parm.value)
|
|
}
|
|
}
|
|
|
|
log("", 0, "")
|
|
log("", 0, "examples:")
|
|
log("", 0, " single target:")
|
|
log("", 0, " ./mtbf --ip 127.0.0.1 --port 8291 --login admin --password 12345678 --out-file good.txt")
|
|
log("", 0, " multiple targets with multiple passwords:")
|
|
log("", 0, " ./mtbf --ip-list ips.txt --port 8291 --login admin --password-list passwords.txt --out-file good.txt")
|
|
|
|
os.Exit(0)
|
|
}
|
|
|
|
func init() {
|
|
registerCommand("help", "show program usage", showHelp)
|
|
registerAlias("?", "help")
|
|
registerAlias("h", "help")
|
|
}
|
|
|