diff --git a/flags/flags.go b/flags/flags.go index 9d271dd..637a96d 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -1,12 +1,20 @@ package flags import ( - "log" + "fmt" "reflect" "strings" ) -const INTERNAL_ERR_PREFIX = "um internal err: ParseArgs" +const PARSE_ERROR_PREFIX = "internal err: ParseArgs" + +type ParseError struct { + message string +} + +func (pe ParseError) Error() string { + return fmt.Sprintf("%s: %s", PARSE_ERROR_PREFIX, pe.message) +} // interface as func parameter type Flag interface { @@ -123,39 +131,36 @@ func missingValue(args []string, i int) bool { // 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 expandOpts(opts any) []Flag { +func expandOpts(opts any) ([]Flag, error) { flags := []Flag{} 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) + return nil, ParseError{fmt.Sprintf("needs a pointer to a struct: %#v", opts)} } if v.Kind() != reflect.Struct { - log.Fatalf("%s needs an underlying struct: %#v", INTERNAL_ERR_PREFIX, opts) + return nil, ParseError{fmt.Sprintf("needs an underlying struct: %#v", opts)} } for j := 0; j < v.NumField(); j++ { field := v.Field(j) if field.Kind() != reflect.Struct { - log.Fatalf("%s needs a struct: %s", INTERNAL_ERR_PREFIX, field.Kind()) + return nil, ParseError{fmt.Sprintf("needs a struct: %#v", field.Kind())} } if !field.CanAddr() { - log.Fatalf("%s needs an addressable type: %s", INTERNAL_ERR_PREFIX, field.Type()) + return nil, ParseError{fmt.Sprintf("needs an addressable type: %#v", field.Type())} } // NOTE: Addr() gets the underlying address: if f, ok := field.Addr().Interface().(Flag); ok { flags = append(flags, f) } else { - log.Fatalf("%s needs a []Flag interface: %s", INTERNAL_ERR_PREFIX, field.Type()) + return nil, ParseError{fmt.Sprintf("needs a []Flag interface: %#v", field.Type())} } } - return flags + return flags, nil } // internal for type safety testing -// -// 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++ { @@ -183,6 +188,9 @@ argloop: // assign values of args to opts struct using Flag interface methods func ParseArgs(help HelpError, args []string, opts any) error { - flags := expandOpts(opts) + flags, err := expandOpts(opts) + if err != nil { + return err + } return parseArgsInternal(help, args, opts, flags) } diff --git a/flags/help.go b/flags/help.go index 77858ef..6408f11 100644 --- a/flags/help.go +++ b/flags/help.go @@ -48,6 +48,9 @@ func (h HelpError) Help(opts any) error { if v.Kind() == reflect.Ptr { v = v.Elem() } + if v.Kind() != reflect.Struct { + return ParseError{fmt.Sprintf("needs an underlying struct: %#v", opts)} + } t := v.Type() buf := &bytes.Buffer{} @@ -55,28 +58,33 @@ func (h HelpError) Help(opts any) error { positional := "" for i := 0; i < v.NumField(); i++ { - field := t.Field(i) - value := v.Field(i) - switch value.Interface().(type) { + tField := t.Field(i) + vField := v.Field(i) + switch vField.Interface().(type) { case Arg: - positional += fmt.Sprintf(" [%s]", strings.ToLower(field.Name)) - fmt.Fprintf(w, "[%v]\tstring\t%v\n", strings.ToLower(field.Name), value.FieldByName("Help")) + positional += fmt.Sprintf(" [%s]", strings.ToLower(tField.Name)) + fmt.Fprintf(w, "[%v]\tstring\t%v\n", strings.ToLower(tField.Name), vField.FieldByName("Help")) case String, Bool: - if field.Name != "Help" { - positional += fmt.Sprintf(" [%s]", value.FieldByName("Long")) + if tField.Name != "Help" { + positional += fmt.Sprintf(" [%s]", vField.FieldByName("Long")) } fmt.Fprintf(w, "%v | %v\t%v\t%v\n", - value.FieldByName("Long"), - value.FieldByName("Short"), - value.FieldByName("Val").Type(), - value.FieldByName("Help"), + vField.FieldByName("Long"), + vField.FieldByName("Short"), + vField.FieldByName("Val").Type(), + vField.FieldByName("Help"), ) + default: + return ParseError{fmt.Sprintf("needs a []Flag interface: %#v", vField.Type())} + } } 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() + if err := w.Flush(); err != nil { + return err + } h.message = buf.String() return h }