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"
|
"github.com/brtholomy/um/go/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const INTERNAL_ERR_PREFIX = "um internal err: ParseArgs"
|
||||||
|
|
||||||
type Arg struct {
|
type Arg struct {
|
||||||
Val string
|
Val string
|
||||||
Help 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
|
// 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) {
|
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")
|
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)
|
// 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
|
// 2. increment the i to skip that value and return
|
||||||
// 3. fetch that value and return
|
// 3. fetch that value and return
|
||||||
func ValidateIncrementFetchOrExit(args []string, i int) (int, string) {
|
func ValidateIncrementFetchOrExit(sub cmd.Subcommand, args []string, i int) (int, string) {
|
||||||
validValueOrExit(args, i)
|
validValueOrExit(sub, args, i)
|
||||||
return i + 1, args[i+1]
|
return i + 1, args[i+1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,6 +90,9 @@ func HelpInvalidArg(sub cmd.Subcommand, arg string) {
|
||||||
// and os.Exit(0)
|
// and os.Exit(0)
|
||||||
func Help(sub cmd.Subcommand, summary string, opts any) {
|
func Help(sub cmd.Subcommand, summary string, opts any) {
|
||||||
v := reflect.ValueOf(opts)
|
v := reflect.ValueOf(opts)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
|
|
||||||
// everything should go to either stderr or stdout:
|
// everything should go to either stderr or stdout:
|
||||||
|
|
@ -118,3 +123,72 @@ func Help(sub cmd.Subcommand, summary string, opts any) {
|
||||||
w.Flush()
|
w.Flush()
|
||||||
os.Exit(0)
|
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
|
// get the lexical last file from GLOB
|
||||||
func GlobLast(glob string) (string, error) {
|
func GlobLast(glob string) (string, error) {
|
||||||
// NOTE: filepath.Glob is more reliable than a manual ls call:
|
// 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) {
|
func Last(args []string) {
|
||||||
_ = parseArgs(args)
|
opts := initOpts()
|
||||||
|
flags.ParseArgs(CMD, SUMMARY, args, &opts)
|
||||||
|
|
||||||
s, err := GlobLast(GLOB)
|
s, err := GlobLast(GLOB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("um %s: %v", cmd.Last, err)
|
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
|
// takes the complete last file string
|
||||||
// returns the number as string
|
// returns the number as string
|
||||||
func numFromLast(l string) (string, error) {
|
func numFromLast(l string) (string, error) {
|
||||||
|
|
@ -116,7 +98,9 @@ func emacsNext(f string, tags string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Next(args []string) {
|
func Next(args []string) {
|
||||||
opts := parseArgs(args)
|
opts := initOpts()
|
||||||
|
flags.ParseArgs(CMD, SUMMARY, args, &opts)
|
||||||
|
|
||||||
l, err := last.GlobLast(last.GLOB)
|
l, err := last.GlobLast(last.GLOB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("um %s: %v", CMD, err)
|
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:
|
// reads files from stdin if present, otherwise from the filename:
|
||||||
func fileListSplitMaybeStdin(f string) []string {
|
func fileListSplitMaybeStdin(f string) []string {
|
||||||
filelist, err := pipe.GetStdin()
|
filelist, err := pipe.GetStdin()
|
||||||
|
|
@ -112,7 +87,13 @@ func write(file string, content string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Sort(args []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)
|
sslice := fileListSplitMaybeStdin(opts.Source.Val)
|
||||||
kslice := fileListSplit(opts.Key.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) {
|
func Tag(args []string) {
|
||||||
opts := parseArgs(args)
|
opts := initOpts()
|
||||||
|
flags.ParseArgs(CMD, SUMMARY, args, &opts)
|
||||||
|
|
||||||
queries := parseQuery(opts.Query.Val)
|
queries := parseQuery(opts.Query.Val)
|
||||||
entries := entriesGlobOrStdin(last.GLOB)
|
entries := entriesGlobOrStdin(last.GLOB)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue