flags : reflection-powered general ParseArgs : uglier than tidy switch statements per cmd, but much easier to add new flags
This commit is contained in:
parent
924024fa73
commit
6f9f5dbb23
5 changed files with 93 additions and 87 deletions
|
|
@ -11,6 +11,8 @@ import (
|
|||
"github.com/brtholomy/um/go/cmd"
|
||||
)
|
||||
|
||||
const INTERNAL_ERR_PREFIX = "um internal err: ParseArgs"
|
||||
|
||||
type Arg struct {
|
||||
Val string
|
||||
Help string
|
||||
|
|
@ -61,9 +63,9 @@ func missingValue(args []string, i int) bool {
|
|||
}
|
||||
|
||||
// if no value ahead in the args, print error and exit
|
||||
func validValueOrExit(args []string, i int) {
|
||||
func validValueOrExit(sub cmd.Subcommand, args []string, i int) {
|
||||
if missingValue(args, i) {
|
||||
log.Printf("um: %s needs a value assignment\n", args[i])
|
||||
log.Printf("um %s: %s needs a value assignment\n", sub, args[i])
|
||||
log.Fatal("try: um [cmd] --help")
|
||||
}
|
||||
}
|
||||
|
|
@ -71,8 +73,8 @@ func validValueOrExit(args []string, i int) {
|
|||
// 1. validate a Flag[string] by looking ahead in args, if missing, os.Exit(1)
|
||||
// 2. increment the i to skip that value and return
|
||||
// 3. fetch that value and return
|
||||
func ValidateIncrementFetchOrExit(args []string, i int) (int, string) {
|
||||
validValueOrExit(args, i)
|
||||
func ValidateIncrementFetchOrExit(sub cmd.Subcommand, args []string, i int) (int, string) {
|
||||
validValueOrExit(sub, args, i)
|
||||
return i + 1, args[i+1]
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +90,9 @@ func HelpInvalidArg(sub cmd.Subcommand, arg string) {
|
|||
// and os.Exit(0)
|
||||
func Help(sub cmd.Subcommand, summary string, opts any) {
|
||||
v := reflect.ValueOf(opts)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
t := v.Type()
|
||||
|
||||
// everything should go to either stderr or stdout:
|
||||
|
|
@ -118,3 +123,72 @@ func Help(sub cmd.Subcommand, summary string, opts any) {
|
|||
w.Flush()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// parse the commandline args
|
||||
//
|
||||
// reflect on the incoming opts struct filled out with flags.Arg, flags.Flag[string], flags.Flag[string]
|
||||
// and fill their "Val" field appropriately.
|
||||
//
|
||||
// WARN: incoming opts must be a pointer!
|
||||
// WORB: opts struct must list its Arg types first and in expected order
|
||||
// NOTE: uglier than the pretty switch statements I had per cmd, but much more convenient.
|
||||
func ParseArgs(sub cmd.Subcommand, summary string, args []string, opts any) {
|
||||
// immediately get the underlying value of the incoming pointer:
|
||||
v := reflect.ValueOf(opts)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
} else {
|
||||
log.Fatalf("%s needs a pointer to a struct: %#v", INTERNAL_ERR_PREFIX, opts)
|
||||
}
|
||||
if v.Kind() != reflect.Struct {
|
||||
log.Fatalf("%s needs an underlying struct: %#v", INTERNAL_ERR_PREFIX, opts)
|
||||
}
|
||||
|
||||
// for all args, iterate through all opts fields and reflect on type
|
||||
argloop:
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
|
||||
for j := 0; j < v.NumField(); j++ {
|
||||
field := v.Field(j)
|
||||
if field.Kind() != reflect.Struct {
|
||||
log.Fatalf("%s needs a struct of flag types: %#v is type: %s", INTERNAL_ERR_PREFIX, field, field.Kind())
|
||||
}
|
||||
val := field.FieldByName("Val")
|
||||
// NORT: FieldByName().String() won't panic if not present, as with Arg type, just prints "<invalid Value>"
|
||||
long := field.FieldByName("Long").String()
|
||||
short := field.FieldByName("Short").String()
|
||||
if !val.CanSet() {
|
||||
log.Fatalf("%s failed to set flag value: %#v", INTERNAL_ERR_PREFIX, field)
|
||||
}
|
||||
switch field.Interface().(type) {
|
||||
case Arg:
|
||||
// NOTE: we assume the opts struct lists its Arg types first and in order
|
||||
if j == i && !HasDashPrefix(arg) {
|
||||
val.SetString(arg)
|
||||
continue argloop
|
||||
}
|
||||
case Flag[bool]:
|
||||
if arg == long || arg == short {
|
||||
val.SetBool(true)
|
||||
if long == "--help" {
|
||||
Help(sub, summary, opts)
|
||||
}
|
||||
continue argloop
|
||||
}
|
||||
case Flag[string]:
|
||||
if arg == long || arg == short {
|
||||
fetch := ""
|
||||
i, fetch = ValidateIncrementFetchOrExit(sub, args, i)
|
||||
val.SetString(fetch)
|
||||
continue argloop
|
||||
}
|
||||
default:
|
||||
log.Fatalf("um internal err: unrecognized flag type in opts struct: %#v", field)
|
||||
}
|
||||
}
|
||||
// BLAK: if we didn't skip ahead, arg didn't match:
|
||||
HelpInvalidArg(sub, arg)
|
||||
Help(sub, summary, opts)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue