From 8fe659a1715e6314b3bf56adabce41939c6cae32 Mon Sep 17 00:00:00 2001 From: bth Date: Thu, 29 Jan 2026 11:09:55 -0800 Subject: [PATCH] help : HelpError holds cmd.Subcommand and summary : NewHelpError constructor --- cat/cat.go | 3 ++- flags/flags.go | 17 +++++++++-------- flags/flags_test.go | 10 ++++++---- flags/help.go | 40 +++++++++++++++++++++++----------------- last/last.go | 3 ++- mv/mv.go | 3 ++- next/next.go | 3 ++- sort/sort.go | 5 +++-- tag/tag.go | 3 ++- 9 files changed, 51 insertions(+), 36 deletions(-) diff --git a/cat/cat.go b/cat/cat.go index 43cef16..4b8286b 100644 --- a/cat/cat.go +++ b/cat/cat.go @@ -83,7 +83,8 @@ func cat(files []string, opts options) (string, error) { func Cat(args []string) { opts := initOpts() - if err := flags.ParseArgs(CMD, SUMMARY, args, &opts); err != nil { + help := flags.NewHelpError(CMD, SUMMARY) + if err := flags.ParseArgs(help, args, &opts); err != nil { var herr flags.HelpError if errors.As(err, &herr) { fmt.Println(herr) diff --git a/flags/flags.go b/flags/flags.go index 3acbe97..9d271dd 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -4,8 +4,6 @@ import ( "log" "reflect" "strings" - - "github.com/brtholomy/um/go/cmd" ) const INTERNAL_ERR_PREFIX = "um internal err: ParseArgs" @@ -155,7 +153,10 @@ func expandOpts(opts any) []Flag { } // internal for type safety testing -func parseArgsInternal(sub cmd.Subcommand, summary string, args []string, opts any, flags []Flag) error { +// +// NOTE: we aren't currently returning any other kind of error, so keeping HelpError as error type +// may be unnecessary, although it allows another err eventually. +func parseArgsInternal(help HelpError, args []string, opts any, flags []Flag) error { argloop: for i := 0; i < len(args); i++ { arg := args[i] @@ -164,10 +165,10 @@ argloop: if f.IsHelp() { // so Help.Val is true to avoid confusion: f.Set(arg) - return Help(sub, summary, opts) + return help.Help(opts) } if !f.Valid(args, i) { - return HelpMissingAssignment(sub, arg) + return help.HelpMissingAssignment(arg) } i = f.MaybeIncrement(i) f.Set(args[i]) @@ -175,13 +176,13 @@ argloop: } } // NOTE: if we didn't skip ahead, arg didn't match: - return HelpInvalidArg(sub, arg) + return help.HelpInvalidArg(arg) } return nil } // assign values of args to opts struct using Flag interface methods -func ParseArgs(sub cmd.Subcommand, summary string, args []string, opts any) error { +func ParseArgs(help HelpError, args []string, opts any) error { flags := expandOpts(opts) - return parseArgsInternal(sub, summary, args, opts, flags) + return parseArgsInternal(help, args, opts, flags) } diff --git a/flags/flags_test.go b/flags/flags_test.go index 2193244..3e7a5b3 100644 --- a/flags/flags_test.go +++ b/flags/flags_test.go @@ -11,6 +11,8 @@ const ( SUMMARY = "SUMMARY" ) +var helpErr = NewHelpError(CMD, SUMMARY) + type options struct { Descriptor Arg Tags Arg @@ -38,7 +40,7 @@ func TestParseArgsInternal(t *testing.T) { &Bool{"--help", "-h", false, "show help"}, } args := []string{"--source", "foo"} - parseArgsInternal(CMD, SUMMARY, args, initOpts(), flags) + parseArgsInternal(helpErr, args, initOpts(), flags) assert.False(t, flags[0].IsSet()) assert.False(t, flags[0].IsHelp()) assert.True(t, flags[2].IsSet()) @@ -49,7 +51,7 @@ func TestParseArgsInternal(t *testing.T) { func TestParseArgsHelp(t *testing.T) { args := []string{"--help"} opts := initOpts() - err := ParseArgs(CMD, SUMMARY, args, &opts) + err := ParseArgs(helpErr, args, &opts) assert.ErrorContains(t, err, "um TEST [descriptor] [tags] [--source] [--write]\n\nSUMMARY") // we set the Val true although we don't expect to use it: assert.True(t, opts.Help.Val) @@ -58,7 +60,7 @@ func TestParseArgsHelp(t *testing.T) { func TestParseArgsString(t *testing.T) { args := []string{"--source", "foo"} opts := initOpts() - err := ParseArgs(CMD, SUMMARY, args, &opts) + err := ParseArgs(helpErr, args, &opts) assert.NoError(t, err) assert.Equal(t, opts.Source.Val, "foo") } @@ -82,7 +84,7 @@ func TestParseArgs(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - err := ParseArgs(CMD, SUMMARY, tc.args, &opts) + err := ParseArgs(helpErr, tc.args, &opts) assert.NoError(t, err) if tc.boolVal != nil { assert.True(t, *tc.boolVal) diff --git a/flags/help.go b/flags/help.go index 6b6e2b7..77858ef 100644 --- a/flags/help.go +++ b/flags/help.go @@ -11,6 +11,8 @@ import ( ) type HelpError struct { + sub cmd.Subcommand + summary string message string } @@ -18,27 +20,30 @@ func (h HelpError) Error() string { return h.message } -func HelpRequired(sub cmd.Subcommand, long string) error { - return HelpError{ - fmt.Sprintf("um %s: %s is required", sub, long), - } +// TODO: is this odd? Constructing an error at the callsite and passing it down and back up? +// but how else should I fill out its fields to be reused later? +func NewHelpError(sub cmd.Subcommand, summary string) HelpError { + return HelpError{sub, summary, ""} } -func HelpInvalidArg(sub cmd.Subcommand, arg string) error { - return HelpError{ - fmt.Sprintf("um %s: invalid argument: %s", sub, arg), - } +func (h HelpError) HelpRequired(long string) error { + h.message = fmt.Sprintf("um %s: %s is required", h.sub, long) + return h } -func HelpMissingAssignment(sub cmd.Subcommand, arg string) error { - return HelpError{ - fmt.Sprintf("um %s: %s needs a value assignment", sub, arg), - } +func (h HelpError) HelpInvalidArg(arg string) error { + h.message = fmt.Sprintf("um %s: invalid argument: %s", h.sub, arg) + return h + } -// print out help string by reflecting over fields of provided opts struct -// and os.Exit(0) -func Help(sub cmd.Subcommand, summary string, opts any) error { +func (h HelpError) HelpMissingAssignment(arg string) error { + h.message = fmt.Sprintf("um %s: %s needs a value assignment", h.sub, arg) + return h +} + +// assemble --help string by reflecting over fields of provided opts struct +func (h HelpError) Help(opts any) error { v := reflect.ValueOf(opts) if v.Kind() == reflect.Ptr { v = v.Elem() @@ -68,9 +73,10 @@ func Help(sub cmd.Subcommand, summary string, opts any) error { ) } } - if _, err := buf.WriteString(fmt.Sprintf("um %s%s\n\n%s\n\n", sub, positional, summary)); err != nil { + if _, err := buf.WriteString(fmt.Sprintf("um %s%s\n\n%s\n\n", h.sub, positional, h.summary)); err != nil { return err } w.Flush() - return HelpError{buf.String()} + h.message = buf.String() + return h } diff --git a/last/last.go b/last/last.go index 34fdca1..3485184 100644 --- a/last/last.go +++ b/last/last.go @@ -46,7 +46,8 @@ func GlobLast(glob string) (string, error) { func Last(args []string) { opts := initOpts() - if err := flags.ParseArgs(CMD, SUMMARY, args, &opts); err != nil { + help := flags.NewHelpError(CMD, SUMMARY) + if err := flags.ParseArgs(help, args, &opts); err != nil { var herr flags.HelpError if errors.As(err, &herr) { fmt.Println(herr) diff --git a/mv/mv.go b/mv/mv.go index f2a6529..e91ecbf 100644 --- a/mv/mv.go +++ b/mv/mv.go @@ -90,7 +90,8 @@ func mv(opts options) error { func Mv(args []string) { opts := initOpts() - if err := flags.ParseArgs(CMD, SUMMARY, args, &opts); err != nil { + help := flags.NewHelpError(CMD, SUMMARY) + if err := flags.ParseArgs(help, args, &opts); err != nil { var herr flags.HelpError if errors.As(err, &herr) { fmt.Println(herr) diff --git a/next/next.go b/next/next.go index 74540cd..910ee3b 100644 --- a/next/next.go +++ b/next/next.go @@ -100,7 +100,8 @@ func emacsNext(f string, tags string) error { func Next(args []string) { opts := initOpts() - if err := flags.ParseArgs(CMD, SUMMARY, args, &opts); err != nil { + help := flags.NewHelpError(CMD, SUMMARY) + if err := flags.ParseArgs(help, args, &opts); err != nil { var herr flags.HelpError if errors.As(err, &herr) { fmt.Println(herr) diff --git a/sort/sort.go b/sort/sort.go index 71655f3..8b91c82 100644 --- a/sort/sort.go +++ b/sort/sort.go @@ -70,7 +70,8 @@ func write(file string, content string) { func Sort(args []string) { opts := initOpts() - if err := flags.ParseArgs(CMD, SUMMARY, args, &opts); err != nil { + help := flags.NewHelpError(CMD, SUMMARY) + if err := flags.ParseArgs(help, args, &opts); err != nil { var herr flags.HelpError if errors.As(err, &herr) { fmt.Println(herr) @@ -80,7 +81,7 @@ func Sort(args []string) { } // BORK: by hand for now: if !opts.Key.IsSet() { - fmt.Println(flags.HelpRequired(CMD, opts.Key.Long)) + fmt.Println(help.HelpRequired(opts.Key.Long)) return } diff --git a/tag/tag.go b/tag/tag.go index 8c621c8..10814da 100644 --- a/tag/tag.go +++ b/tag/tag.go @@ -36,7 +36,8 @@ func initOpts() options { func Tag(args []string) { opts := initOpts() - if err := flags.ParseArgs(CMD, SUMMARY, args, &opts); err != nil { + help := flags.NewHelpError(CMD, SUMMARY) + if err := flags.ParseArgs(help, args, &opts); err != nil { var herr flags.HelpError if errors.As(err, &herr) { fmt.Println(herr)