flags : handle String validation : works but getting messier all the time

This commit is contained in:
bth 2026-01-26 20:35:58 -08:00
commit a243d3c125

View file

@ -12,7 +12,7 @@ const INTERNAL_ERR_PREFIX = "um internal err: ParseArgs"
// interface as func parameter // interface as func parameter
type Flag interface { type Flag interface {
Set(string) SetValid(args []string, i int) (int, bool)
Match(arg string, i, j int) bool Match(arg string, i, j int) bool
IsSet() bool IsSet() bool
IsHelp() bool IsHelp() bool
@ -37,8 +37,9 @@ type Bool struct {
Help string Help string
} }
func (f *Arg) Set(val string) { func (f *Arg) SetValid(args []string, i int) (int, bool) {
f.Val = val f.Val = args[i]
return i, true
} }
func (f *Arg) Match(arg string, i, j int) bool { func (f *Arg) Match(arg string, i, j int) bool {
@ -53,12 +54,12 @@ func (f *Arg) IsHelp() bool {
return false return false
} }
func (f *String) Set(val string) { func (f *String) SetValid(args []string, i int) (int, bool) {
i, val, ok := validateIncrementFetch(args, i)
f.Val = val f.Val = val
return i, ok
} }
// TODO: this should look ahead
// i, fetch = ValidateIncrementFetchOrExit(sub, args, i)
func (f *String) Match(arg string, _, _ int) bool { func (f *String) Match(arg string, _, _ int) bool {
return arg == f.Long || arg == f.Short return arg == f.Long || arg == f.Short
} }
@ -71,8 +72,9 @@ func (f *String) IsHelp() bool {
return false return false
} }
func (f *Bool) Set(_ string) { func (f *Bool) SetValid(_ []string, i int) (int, bool) {
f.Val = true f.Val = true
return i, true
} }
func (f *Bool) Match(arg string, _, _ int) bool { func (f *Bool) Match(arg string, _, _ int) bool {
@ -96,29 +98,21 @@ func missingValue(args []string, i int) bool {
return i+1 == len(args) || hasDashPrefix(args[i+1]) return i+1 == len(args) || hasDashPrefix(args[i+1])
} }
// if no value ahead in the args, print error and exit // 1. validate a Flag[string] by looking ahead in args, if missing, return false
func validValueOrExit(sub cmd.Subcommand, args []string, i int) {
if missingValue(args, i) {
log.Printf("um %s: %s needs a value assignment\n", sub, args[i])
log.Fatal("try: um [cmd] --help")
}
}
// 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(sub cmd.Subcommand, args []string, i int) (int, string) { func validateIncrementFetch(args []string, i int) (int, string, bool) {
if missingValue(args, i) { if missingValue(args, i) {
log.Printf("um %s: %s needs a value assignment\n", sub, args[i]) return 0, "", false
log.Fatal("try: um [cmd] --help")
} }
return i + 1, args[i+1] return i + 1, args[i+1], true
} }
// expand incoming opts struct into a []Flag // expand incoming opts struct into a []Flag
// //
// WARN: incoming opts must be a pointer! // WARN: incoming opts must be a pointer!
// WORB: opts struct must list its Arg types first and in expected order // 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 expandOpts(opts any) []Flag { func expandOpts(opts any) []Flag {
flags := []Flag{} flags := []Flag{}
v := reflect.ValueOf(opts) v := reflect.ValueOf(opts)
@ -147,18 +141,22 @@ func expandOpts(opts any) []Flag {
} }
// assign values of args to opts struct using Flag interface methods // assign values of args to opts struct using Flag interface methods
//
// 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) { func ParseArgs(sub cmd.Subcommand, summary string, args []string, opts any) {
flags := expandOpts(opts) flags := expandOpts(opts)
argloop: argloop:
for i, arg := range args { for i := 0; i < len(args); i++ {
arg := args[i]
for j, f := range flags { for j, f := range flags {
if f.Match(arg, i, j) { if f.Match(arg, i, j) {
if f.IsHelp() { if f.IsHelp() {
Help(sub, summary, opts) Help(sub, summary, opts)
} }
f.Set(arg) ok := false
i, ok = f.SetValid(args, i)
if !ok {
log.Printf("um %s: %s needs a value assignment\n", sub, args[i])
log.Fatal("try: um [cmd] --help")
}
continue argloop continue argloop
} }
} }