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.
344 lines
9.2 KiB
344 lines
9.2 KiB
2 years ago
|
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 interface{} // value and default value
|
||
|
description string // description for this parameter
|
||
|
parsed bool // true if it was successfully parsed from commandline
|
||
|
opts ConfigParameterOptions
|
||
|
}
|
||
|
|
||
|
var configMap map[string]ConfigParameter = make(map[string]ConfigParameter, 16)
|
||
|
var configAliasMap map[string]string = make(map[string]string, 4)
|
||
|
|
||
|
var ConfigParsingFinished bool = false
|
||
|
|
||
|
// registration
|
||
|
|
||
|
func genericRegister(name string, def interface{}, 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)
|
||
|
|
||
|
p := ConfigParameter{}
|
||
|
p.name = name
|
||
|
p.value = def
|
||
|
p.def = def
|
||
|
p.description = description
|
||
|
p.opts = opts
|
||
|
|
||
|
configMap[name] = p
|
||
|
}
|
||
|
|
||
|
func CfgRegister(name string, def interface{}, description string) {
|
||
|
genericRegister(name, def, description, ConfigParameterOptions{})
|
||
|
}
|
||
|
|
||
|
func CfgRegisterEx(name string, def interface{}, description string, options ConfigParameterOptions) {
|
||
|
genericRegister(name, def, description, options)
|
||
|
}
|
||
|
|
||
|
func CfgRegisterCallback(name string, def interface{}, description string, callback func()) {
|
||
|
genericRegister(name, def, description, ConfigParameterOptions{callback: callback})
|
||
|
}
|
||
|
|
||
|
func CfgRegisterCommand(name string, description string, callback func()) {
|
||
|
genericRegister(name, false, description, ConfigParameterOptions{command: true, callback: callback})
|
||
|
}
|
||
|
|
||
|
func CfgRegisterSwitch(name string, description string) {
|
||
|
genericRegister(name, false, description, ConfigParameterOptions{sw: true})
|
||
|
}
|
||
|
|
||
|
func CfgRegisterHidden(name string, def interface{}) {
|
||
|
genericRegister(name, def, "", ConfigParameterOptions{hidden: true})
|
||
|
}
|
||
|
|
||
|
func CfgRegisterAlias(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 CfgGet(name string) interface{} {
|
||
|
name = strings.ToLower(name)
|
||
|
parm, ok := configMap[name]
|
||
|
failIf(!ok, "unknown config parameter: \"%v\"", name)
|
||
|
|
||
|
if parm.opts.sw {
|
||
|
return parm.parsed // switches always return true if they were parsed
|
||
|
} else if parm.parsed || parm.opts.hidden {
|
||
|
return parm.value // parsed and hidden parms return their current value
|
||
|
} else {
|
||
|
return parm.def // otherwise, use default value
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func CfgGetInt(name string) int {
|
||
|
return CfgGet(name).(int)
|
||
|
}
|
||
|
|
||
|
func CfgGetFloat(name string) float64 {
|
||
|
return CfgGet(name).(float64)
|
||
|
}
|
||
|
|
||
|
func CfgGetIntSlice(name string) []int {
|
||
|
return CfgGet(name).([]int)
|
||
|
}
|
||
|
|
||
|
func CfgGetBool(name string) bool {
|
||
|
return CfgGet(name).(bool)
|
||
|
}
|
||
|
|
||
|
func CfgGetSwitch(name string) bool {
|
||
|
return CfgGet(name).(bool)
|
||
|
}
|
||
|
|
||
|
func CfgGetString(name string) string {
|
||
|
return CfgGet(name).(string)
|
||
|
}
|
||
|
|
||
|
func CfgGetStringSlice(name string) []string {
|
||
|
return CfgGet(name).([]string)
|
||
|
}
|
||
|
|
||
|
func CfgGetDurationMS(name string) time.Duration {
|
||
|
tm := CfgGet(name).(int)
|
||
|
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 CfgSet(name string, value interface{}) {
|
||
|
name = strings.ToLower(name)
|
||
|
parm, ok := configMap[name]
|
||
|
failIf(!ok, "unknown config parameter: \"%v\"", name)
|
||
|
failIf(!parm.opts.hidden, "tried to set \"%v\", but it is not a hidden parameter", name)
|
||
|
|
||
|
parm.value = value
|
||
|
if parm.opts.callback != nil {
|
||
|
parm.opts.callback()
|
||
|
}
|
||
|
|
||
|
configMap[name] = parm
|
||
|
}
|
||
|
|
||
|
// parsing
|
||
|
|
||
|
// getCmdlineParm returns a trimmed commandline parameter with specified index.
|
||
|
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)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// finalizeParm marks a ConfigParameter as parsed, adds it to a global config map
|
||
|
// and calls its callback, if one is present.
|
||
|
func (parm *ConfigParameter) finalizeParm() {
|
||
|
parm.parsed = true
|
||
|
configMap[parm.name] = *parm
|
||
|
|
||
|
if parm.opts.callback != nil {
|
||
|
parm.opts.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.opts.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.opts.command {
|
||
|
if parm.opts.sw {
|
||
|
parm.writeParmValue("true")
|
||
|
} else {
|
||
|
i++
|
||
|
parm.writeParmValue(getCmdlineParm(i))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
finalizeParm(&parm)
|
||
|
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.opts.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.opts.command || parm.opts.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() {
|
||
|
CfgRegisterCommand("help", "show program usage", showHelp)
|
||
|
CfgRegisterAlias("?", "help")
|
||
|
CfgRegisterAlias("h", "help")
|
||
|
}
|