diff --git a/flags/flags.go b/flags/flags.go index a0c26aa..6d79a64 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -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 "" + 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) + } +} diff --git a/last/last.go b/last/last.go index c4c910b..731c9b3 100644 --- a/last/last.go +++ b/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) diff --git a/next/next.go b/next/next.go index d414cd5..ccab0dc 100644 --- a/next/next.go +++ b/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) diff --git a/sort/sort.go b/sort/sort.go index d2a9bdb..8af6718 100644 --- a/sort/sort.go +++ b/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) diff --git a/tag/tag.go b/tag/tag.go index ad95d52..9f83a01 100644 --- a/tag/tag.go +++ b/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)