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") }