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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
last/last.go
18
last/last.go
|
|
@ -31,20 +31,6 @@ func initOpts() options {
|
|||
}
|
||||
}
|
||||
|
||||
func parseArgs(args []string) options {
|
||||
opts := initOpts()
|
||||
for _, arg := range args {
|
||||
switch {
|
||||
case arg == opts.Help.Long || arg == opts.Help.Short:
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
default:
|
||||
flags.HelpInvalidArg(CMD, arg)
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// get the lexical last file from GLOB
|
||||
func GlobLast(glob string) (string, error) {
|
||||
// NOTE: filepath.Glob is more reliable than a manual ls call:
|
||||
|
|
@ -59,7 +45,9 @@ func GlobLast(glob string) (string, error) {
|
|||
}
|
||||
|
||||
func Last(args []string) {
|
||||
_ = parseArgs(args)
|
||||
opts := initOpts()
|
||||
flags.ParseArgs(CMD, SUMMARY, args, &opts)
|
||||
|
||||
s, err := GlobLast(GLOB)
|
||||
if err != nil {
|
||||
log.Fatalf("um %s: %v", cmd.Last, err)
|
||||
|
|
|
|||
22
next/next.go
22
next/next.go
|
|
@ -36,24 +36,6 @@ func initOpts() options {
|
|||
}
|
||||
}
|
||||
|
||||
func parseArgs(args []string) options {
|
||||
opts := initOpts()
|
||||
for i, arg := range args {
|
||||
switch {
|
||||
case i == 0 && !flags.HasDashPrefix(arg):
|
||||
opts.Descriptor.Val = arg
|
||||
case i == 1 && !flags.HasDashPrefix(arg):
|
||||
opts.Tags.Val = arg
|
||||
case arg == opts.Help.Long || arg == opts.Help.Short:
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
default:
|
||||
flags.HelpInvalidArg(CMD, arg)
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// takes the complete last file string
|
||||
// returns the number as string
|
||||
func numFromLast(l string) (string, error) {
|
||||
|
|
@ -116,7 +98,9 @@ func emacsNext(f string, tags string) error {
|
|||
}
|
||||
|
||||
func Next(args []string) {
|
||||
opts := parseArgs(args)
|
||||
opts := initOpts()
|
||||
flags.ParseArgs(CMD, SUMMARY, args, &opts)
|
||||
|
||||
l, err := last.GlobLast(last.GLOB)
|
||||
if err != nil {
|
||||
log.Fatalf("um %s: %v", CMD, err)
|
||||
|
|
|
|||
33
sort/sort.go
33
sort/sort.go
|
|
@ -33,31 +33,6 @@ func initOpts() options {
|
|||
}
|
||||
}
|
||||
|
||||
func parseArgs(args []string) options {
|
||||
opts := initOpts()
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
switch {
|
||||
case arg == opts.Source.Long || arg == opts.Source.Short:
|
||||
i, opts.Source.Val = flags.ValidateIncrementFetchOrExit(args, i)
|
||||
case arg == opts.Key.Long || arg == opts.Key.Short:
|
||||
i, opts.Key.Val = flags.ValidateIncrementFetchOrExit(args, i)
|
||||
case arg == opts.Write.Long || arg == opts.Write.Short:
|
||||
opts.Write.Val = true
|
||||
case arg == opts.Help.Long || arg == opts.Help.Short:
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
default:
|
||||
flags.HelpInvalidArg(CMD, arg)
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
}
|
||||
}
|
||||
if !opts.Key.IsSet() {
|
||||
flags.HelpRequired(CMD, opts.Key.Long)
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// reads files from stdin if present, otherwise from the filename:
|
||||
func fileListSplitMaybeStdin(f string) []string {
|
||||
filelist, err := pipe.GetStdin()
|
||||
|
|
@ -112,7 +87,13 @@ func write(file string, content string) {
|
|||
}
|
||||
|
||||
func Sort(args []string) {
|
||||
opts := parseArgs(args)
|
||||
opts := initOpts()
|
||||
flags.ParseArgs(CMD, SUMMARY, args, &opts)
|
||||
// BORK: by hand for now:
|
||||
if !opts.Key.IsSet() {
|
||||
flags.HelpRequired(CMD, opts.Key.Long)
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
}
|
||||
|
||||
sslice := fileListSplitMaybeStdin(opts.Source.Val)
|
||||
kslice := fileListSplit(opts.Key.Val)
|
||||
|
|
|
|||
27
tag/tag.go
27
tag/tag.go
|
|
@ -31,31 +31,10 @@ func initOpts() options {
|
|||
}
|
||||
}
|
||||
|
||||
func parseArgs(args []string) options {
|
||||
opts := initOpts()
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
switch {
|
||||
case i == 0 && !flags.HasDashPrefix(arg):
|
||||
opts.Query.Val = arg
|
||||
case arg == opts.Invert.Long || arg == opts.Invert.Short:
|
||||
opts.Invert.Val = true
|
||||
case arg == opts.Verbose.Long || arg == opts.Verbose.Short:
|
||||
opts.Verbose.Val = true
|
||||
case arg == opts.Date.Long || arg == opts.Date.Short:
|
||||
i, opts.Date.Val = flags.ValidateIncrementFetchOrExit(args, i)
|
||||
case arg == opts.Help.Long || arg == opts.Help.Short:
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
default:
|
||||
flags.HelpInvalidArg(CMD, arg)
|
||||
flags.Help(CMD, SUMMARY, opts)
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func Tag(args []string) {
|
||||
opts := parseArgs(args)
|
||||
opts := initOpts()
|
||||
flags.ParseArgs(CMD, SUMMARY, args, &opts)
|
||||
|
||||
queries := parseQuery(opts.Query.Val)
|
||||
entries := entriesGlobOrStdin(last.GLOB)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue