From d84cefc365447ca002cff6d30e9c819904d2f04c Mon Sep 17 00:00:00 2001 From: Kenneth Shaw Date: Sun, 24 Nov 2024 11:05:29 +0700 Subject: [PATCH] Fixing NoArg --- cmd.go | 19 +++- cmd_test.go | 1 + conv.go | 4 +- ctx.go | 14 +-- opts.go | 22 ++-- ox.go | 10 +- type.go | 56 +++++----- var.go | 291 ++++++++++++++++++++++++++++++---------------------- 8 files changed, 242 insertions(+), 175 deletions(-) diff --git a/cmd.go b/cmd.go index 7e71236..d3e1dab 100644 --- a/cmd.go +++ b/cmd.go @@ -146,7 +146,7 @@ func (fs *FlagSet) apply(val any) error { switch v := val.(type) { case *Command: v.Flags = fs - case *RunContext: + case *Context: default: return fmt.Errorf("FlagSet: %w", ErrAppliedToInvalidType) } @@ -390,6 +390,13 @@ func NewFlag(name, usage string, opts ...Option) (*Flag, error) { return nil, err } } + if opts, ok := typeOpts[g.Type]; ok { + for _, o := range opts { + if err := o.apply(g); err != nil { + return nil, err + } + } + } if g.Name() == "" { return nil, ErrInvalidFlagName } @@ -480,15 +487,19 @@ func Populate(cmd *Command, all, overwrite bool, vars Vars) error { if _, ok := vars[name]; ok && overwrite { delete(vars, name) } - var value any + var value string switch { case g.Type == HookT, g.Def == nil && !all: continue case g.Def != nil: - value = g.Def + s, err := asString[string](g.Def, g.Type.Layout()) + if err != nil { + return err + } + value = s } if err := vars.Set(g, value, false); err != nil { - return fmt.Errorf("cannot set %s to %q: %w", name, value, err) + return fmt.Errorf("cannot populate %s with %q: %w", name, value, err) } } return nil diff --git a/cmd_test.go b/cmd_test.go index 7fba4a0..cf4215e 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -13,6 +13,7 @@ func TestParse(t *testing.T) { root := testCommand(t) for i, test := range parseTests() { t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Logf("invoked: %v", test.v) vars := make(Vars) cmd, args, err := Parse(root, test.v[1:], vars) switch { diff --git a/conv.go b/conv.go index 2f905db..e61e798 100644 --- a/conv.go +++ b/conv.go @@ -472,9 +472,9 @@ func asNew(typ Type) (any, func([]byte) error, error) { ok bool asText bool ) - if f, ok = text[typ]; ok { + if f, ok = typeTextNews[typ]; ok { asText = true - } else if f, ok = binary[typ]; ok { + } else if f, ok = typeBinaryNews[typ]; ok { asText = false } if !ok { diff --git a/ctx.go b/ctx.go index 3093d0f..0be135b 100644 --- a/ctx.go +++ b/ctx.go @@ -95,18 +95,18 @@ func VarsOK(ctx context.Context) (Vars, bool) { // AnyOK returns a variable, its set status, and if it was defined from the // context. -func AnyOK(ctx context.Context, name string) (Value, bool, bool) { +func AnyOK(ctx context.Context, name string) (Value, bool) { if vars, ok := VarsOK(ctx); ok { - if vs, ok := vars[name]; ok { - return vs.Var, vs.WasSet, true + if val, ok := vars[name]; ok { + return val, true } } - return nil, false, false + return nil, false } // GetOK returns a variable. func GetOK[T any](ctx context.Context, name string) (T, bool) { - if val, _, ok := AnyOK(ctx, name); ok { + if val, ok := AnyOK(ctx, name); ok { if v, err := As[T](val); err == nil { return v, true } @@ -123,7 +123,7 @@ func Get[T any](ctx context.Context, name string) T { // Slice returns the slice variable from the context. func Slice[T any](ctx context.Context, name string) []T { - if val, _, ok := AnyOK(ctx, name); ok { + if val, ok := AnyOK(ctx, name); ok { if v, err := SliceAs[T](val); err == nil { return v } @@ -133,7 +133,7 @@ func Slice[T any](ctx context.Context, name string) []T { // Map returns the map variable from the context. func Map[K cmp.Ordered, T any](ctx context.Context, name string) map[K]T { - if val, _, ok := AnyOK(ctx, name); ok { + if val, ok := AnyOK(ctx, name); ok { if m, err := MapAs[K, T](val); err == nil { return m } diff --git a/opts.go b/opts.go index 2743491..458f03d 100644 --- a/opts.go +++ b/opts.go @@ -9,11 +9,11 @@ import ( "unicode/utf8" ) -// RunArgs is a [Run] option to set the command-line arguments to use. -func RunArgs(args []string) RunOption { +// WithArgs is a [Context] option to set the command-line arguments to use. +func WithArgs(args []string) ContextOption { return option{ - name: "RunArgs", - ctx: func(opts *RunContext) error { + name: "WithArgs", + ctx: func(opts *Context) error { opts.Args = args return nil }, @@ -364,13 +364,13 @@ func Relative(dir string) Option { */ // Option is the interface for options that can be passed when creating a -// [RunContext], [Command], or [Flag]. +// [Context], [Command], or [Flag]. // -// The [Option] type is aliased as [RunOption], [CommandOption], +// The [Option] type is aliased as [ContextOption], [CommandOption], // [CommandFlagOption], and [FlagOption] and provided for ease-of-use, // readibility, and categorization within documentation. // -// A [RunOption] can be applied to a [RunContext] and passed to [Run]. +// A [ContextOption] can be applied to a [Context] and passed to [Run]. // // A [CommandOption] can be applied to a [Command] and passed to [NewCommand]. // @@ -386,8 +386,8 @@ type Option interface { apply(any) error } -// RunOption are [Option]'s that apply to a [RunContext]. -type RunOption = Option +// ContextOption are [Option]'s that apply to a [Context]. +type ContextOption = Option // CommandOption are [Option]'s that apply to a [Command]. type CommandOption = Option @@ -401,10 +401,10 @@ type FlagOption = Option // option wraps an option. type option struct { name string + ctx func(*Context) error cmd func(*Command) error set func(*FlagSet) error flag func(*Flag) error - ctx func(*RunContext) error } // apply satisfies the [Option] interface. @@ -419,7 +419,7 @@ func (opt option) apply(val any) error { if opt.flag != nil { err = opt.flag(v) } - case *RunContext: + case *Context: if opt.ctx != nil { err = opt.ctx(v) } diff --git a/ox.go b/ox.go index a6551ff..2a2c9ee 100644 --- a/ox.go +++ b/ox.go @@ -15,9 +15,9 @@ import ( ) // Run creates a [Command] for f using [os.Args] by default, unless arguments -// were specified using a [RunOption]. +// were specified using a [ContextOption]. func Run[T ExecFunc](f T, opts ...Option) { - ctx := &RunContext{ + ctx := &Context{ Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, @@ -47,8 +47,8 @@ func Run[T ExecFunc](f T, opts ...Option) { } } -// RunContext are run context options. -type RunContext struct { +// Context are run context options. +type Context struct { Args []string Stdin io.Writer Stdout io.Writer @@ -58,7 +58,7 @@ type RunContext struct { } // Handle handles an error. -func (ctx *RunContext) Handle(err error) { +func (ctx *Context) Handle(err error) { var w io.Writer = os.Stderr if ctx.Stderr != nil { w = ctx.Stderr diff --git a/type.go b/type.go index 4faf392..98b5b48 100644 --- a/type.go +++ b/type.go @@ -86,7 +86,7 @@ func (typ Type) String() string { // Layout returns the type's registered [time.Time.Format] [time.Layout]. func (typ Type) Layout() string { - return layouts[typ] + return typeLayouts[typ] } // New creates a new [Value] for the registered type. @@ -94,7 +94,7 @@ func (typ Type) New() (Value, error) { if typ == HookT { return nil, nil } - f, ok := types[typ] + f, ok := typeNews[typ] if !ok { return nil, fmt.Errorf("%w: type not registered", ErrCouldNotCreateValue) } @@ -105,23 +105,27 @@ func (typ Type) New() (Value, error) { return v, nil } -// types are registered type descriptions. -var types map[Type]func() (Value, error) +// typeNews are registered type creation funcs. +var typeNews map[Type]func() (Value, error) -// typeNames are type name lookups for registered types. -var typeNames map[string]Type +// typeOpts are registered type flag options. +var typeOpts map[Type][]Option -// layouts are [time.Time] parsing layouts. -var layouts map[Type]string +// typeLayouts are [time.Time] parsing typeLayouts. +var typeLayouts map[Type]string -// text holds new text funcs. -var text map[Type]func() (any, error) +// reflectTypes are reflect type name lookups for registered types. +var reflectTypes map[string]Type -// binary holds new binary funcs. -var binary map[Type]func() (any, error) +// typeTextNews are registered type creation funcs for text marshalable types. +var typeTextNews map[Type]func() (any, error) + +// typeBinaryNews are registered type creation funcs for binary marshalable +// types. +var typeBinaryNews map[Type]func() (any, error) func init() { - types = map[Type]func() (Value, error){ + typeNews = map[Type]func() (Value, error){ BytesT: NewVal[[]byte](), StringT: NewVal[string](), RunesT: NewVal[[]rune](), @@ -155,15 +159,19 @@ func init() { MapT: NewMap(), // HookT: NewTypeDesc(NewHook(), NoArg(true)), } - layouts = map[Type]string{ + typeLayouts = map[Type]string{ TimestampT: time.RFC3339Nano, DateTimeT: time.DateTime, DateT: time.DateOnly, TimeT: time.TimeOnly, } - typeNames = make(map[string]Type) + typeOpts = map[Type][]Option{ + BoolT: {NoArg(true)}, + CountT: {NoArg(true)}, + } + reflectTypes = make(map[string]Type) // text marshal types - text = make(map[Type]func() (any, error)) + typeTextNews = make(map[Type]func() (any, error)) RegisterTextType(func() (*big.Int, error) { return big.NewInt(0), nil }) @@ -183,7 +191,7 @@ func init() { return new(netip.Prefix), nil }) // binary marshal types - binary = make(map[Type]func() (any, error)) + typeBinaryNews = make(map[Type]func() (any, error)) RegisterBinaryType(func() (*url.URL, error) { return new(url.URL), nil }) @@ -192,35 +200,35 @@ func init() { // RegisterTypeName registers a type name. func RegisterTypeName(typ Type, names ...string) { for _, name := range names { - typeNames[name] = typ + reflectTypes[name] = typ } } // RegisterLayout registers a time layout for the type. func RegisterLayout(typ Type, layout string) { - layouts[typ] = layout + typeLayouts[typ] = layout } // RegisterTextType registers a new text type. func RegisterTextType[T TextMarshaler](f func() (T, error)) { - registerMarshaler[T](func() (any, error) { return f() }, text) + registerMarshaler[T](func() (any, error) { return f() }, typeTextNews) } // RegisterBinaryType registers a new binary type. func RegisterBinaryType[T BinaryMarshaler](f func() (T, error)) { - registerMarshaler[T](func() (any, error) { return f() }, binary) + registerMarshaler[T](func() (any, error) { return f() }, typeBinaryNews) } // registerMarshaler registers a type marshaler. func registerMarshaler[T any](f func() (any, error), descs map[Type]func() (any, error)) { typ := typeType[T]() - if _, ok := types[typ]; ok { + if _, ok := typeNews[typ]; ok { return } if _, ok := descs[typ]; ok { return } - types[typ], descs[typ] = NewVal[T](typ), f + typeNews[typ], descs[typ] = NewVal[T](typ), f } // TextMarshaler is the text marshal interface. @@ -300,7 +308,7 @@ func typeRef(val any) Type { return URLT } s := reflect.TypeOf(val).String() - if typ, ok := typeNames[s]; ok { + if typ, ok := reflectTypes[s]; ok { return typ } return Type(s) diff --git a/var.go b/var.go index f3461ed..7a71a44 100644 --- a/var.go +++ b/var.go @@ -20,32 +20,27 @@ type Value interface { WasSet() bool Set(string) error Get() (string, error) + String() string } // anyVal wraps a value. type anyVal[T any] struct { - typ Type - v T - noArg bool - set bool + typ Type + v T + set bool } // NewVal returns a [Value] func. func NewVal[T any](opts ...Option) func() (Value, error) { return func() (Value, error) { - typ := typeType[T]() val := &anyVal[T]{ - typ: typ, - noArg: typ == BoolT, + typ: typeType[T](), } for _, o := range opts { if err := o.apply(val); err != nil { return nil, err } } - if typ != val.typ && val.typ == CountT { - val.noArg = true - } return val, nil } } @@ -128,9 +123,15 @@ func (val *anyVal[T]) Get() (string, error) { return s, nil } +func (val *anyVal[T]) String() string { + s, _ := val.Get() + return s +} + // sliceVal is a slice value. type sliceVal struct { typ Type + set bool v []Value } @@ -145,8 +146,7 @@ func NewSlice(opts ...Option) func() (Value, error) { return nil, err } } - return nil, nil - // return val, nil + return val, nil } } @@ -159,10 +159,26 @@ func (val *sliceVal) Val() any { } func (val *sliceVal) Get() (string, error) { - return string(val.Type()), nil + return val.String(), nil +} + +func (val *sliceVal) SetSet(set bool) { + val.set = set +} + +func (val *sliceVal) WasSet() bool { + return val.set } func (val *sliceVal) Set(s string) error { + v, err := val.typ.New() + if err != nil { + return err + } + if err := v.Set(s); err != nil { + return err + } + val.v = append(val.v, v) return nil } @@ -184,160 +200,190 @@ func (val *sliceVal) Len() int { } // mapVal is a map value. -type mapVal[K cmp.Ordered] struct { - v map[K]Value +type mapVal struct { + key Type + typ Type + v mapValue + set bool } // NewMap creates a map value of type. func NewMap(opts ...Option) func() (Value, error) { return func() (Value, error) { - /* - val := &mapVal[string]{} - for _, o := range opts { - if err := o.apply(val); err != nil { - return nil, err - } + val := &mapVal{ + key: StringT, + typ: StringT, + } + for _, o := range opts { + if err := o.apply(val); err != nil { + return nil, err } - return val, nil - */ - return nil, nil + } + var err error + if val.v, err = makeMap(val.key, val.typ); err != nil { + return nil, err + } + return val, nil } } -func (val *mapVal[K]) Type() Type { - // return "map[" + val.key + "]" + val.typ - return "" +func (val *mapVal) Type() Type { + return "map[" + val.key + "]" + val.typ } -func (val *mapVal[K]) Val() any { +func (val *mapVal) Val() any { return val.v } -func (val *mapVal[K]) Get() (string, error) { - return "", nil +func (val *mapVal) SetSet(set bool) { + val.set = set } -func (val *mapVal[K]) Set(s string) error { +func (val *mapVal) WasSet() bool { + return val.set +} + +func (val *mapVal) Set(s string) error { + return val.v.Set(s) +} + +func (val *mapVal) String() string { + return val.v.String() +} + +func (val *mapVal) Get() (string, error) { + return val.v.String(), nil +} + +type mapValue interface { + Set(s string) error + String() string +} + +// makeMap makes a map for the key and type. +func makeMap(key, typ Type) (mapValue, error) { + switch key { + case StringT: + return newValueMap[string](typ), nil + case Int64T: + return newValueMap[int64](typ), nil + case Int32T: + return newValueMap[int32](typ), nil + case Int16T: + return newValueMap[int16](typ), nil + case Int8T: + return newValueMap[int8](typ), nil + case IntT: + return newValueMap[int](typ), nil + case Uint64T: + return newValueMap[uint64](typ), nil + case Uint32T: + return newValueMap[uint32](typ), nil + case Uint16T: + return newValueMap[uint16](typ), nil + case Uint8T: + return newValueMap[uint8](typ), nil + case UintT: + return newValueMap[uint](typ), nil + } + return nil, fmt.Errorf("%w: bad map key type %s", ErrInvalidType, key) +} + +// valueMap wraps a map. +type valueMap[K cmp.Ordered] struct { + typ Type + v map[K]Value +} + +// newValueMap creates a new value map. +func newValueMap[K cmp.Ordered](typ Type) mapValue { + return &valueMap[K]{ + typ: typ, + } +} + +func (val *valueMap[K]) Set(s string) error { if val.v == nil { val.v = make(map[K]Value) } - key, value, ok := strings.Cut(s, "=") - key, value = key, value - if !ok || key == "" { - return fmt.Errorf("%w: %s", ErrInvalidValue, "bad map key") + keystr, value, ok := strings.Cut(s, "=") + if !ok || keystr == "" { + return fmt.Errorf("%w: %s", ErrInvalidValue, "missing map key") } - /* - k, err := NewValue(val.key, "", "") - if err != nil { - return err - } - if err := k.Set(ctx, key); err != nil { - return err - } - v, err := NewValue(val.typ, "", "") - if err != nil { - return err - } - if err := v.Set(ctx, value); err != nil { + key, err := as[K](keystr, "") + if err != nil { + return fmt.Errorf("%w: bad map key", err) + } + k, ok := key.(K) + if !ok { + return fmt.Errorf("%w: string->%T", ErrInvalidConversion, k) + } + v, ok := val.v[k] + if !ok { + if v, err = val.typ.New(); err != nil { return err } - val.v[k] = v - */ + } + if err := v.Set(value); err != nil { + return err + } + val.v[k] = v return nil } -func (val *mapVal[K]) String() string { - /* - s := make([]string, len(val.v)) - for i, k := range slices.Sorted(maps.Keys(val.v)) { - value, _ := val.v[k].Get() - s[i] = toString(k) + ":" + value - s[i] = k + ":" + toString(val.v[k].Var) - i++ - } - return "[" + strings.Join(s, " ") + "]" - */ - return "" +func (val valueMap[K]) String() string { + s := make([]string, len(val.v)) + for i, k := range slices.Sorted(maps.Keys(val.v)) { + value, _ := val.v[k].Get() + s[i] = toString[string](k, "") + ":" + value + } + return "[" + strings.Join(s, " ") + "]" } // Vars is the type for storing variables in the context. -type Vars map[string]*VarSet +type Vars map[string]Value // String satisfies the [fmt.Stringer] interfaec. func (vars Vars) String() string { var v []string for _, k := range slices.Sorted(maps.Keys(vars)) { - if val, ok := vars[k].Var.(interface{ String() string }); ok { - v = append(v, k+":"+val.String()) - } else { - if s, err := vars[k].Var.Get(); err == nil { - v = append(v, k+":"+s) - } + if s, err := vars[k].Get(); err == nil { + v = append(v, k+":"+s) } } return "[" + strings.Join(v, " ") + "]" } // Set sets a variable in the vars. -func (vars Vars) Set(g *Flag, value any, wasSet bool) error { - /* - // fmt.Fprintf(os.Stdout, "SETTING: %q (%s/%s): %q\n", g.Name(), g.Type, g.Sub, value) - name := g.Name() - if name == "" { - return ErrInvalidFlagName - } - vs, err := vars[name].Set(g.Type, g.Sub, value, wasSet) - if err != nil { +func (vars Vars) Set(g *Flag, s string, set bool) error { + // fmt.Fprintf(os.Stdout, "SETTING: %q (%s/%s): %q\n", g.Name(), g.Type, g.Sub, s) + name := g.Name() + if name == "" { + return ErrInvalidFlagName + } + var err error + v, ok := vars[name] + if !ok { + if v, err = g.Type.New(); err != nil { return err } - for i, val := range g.Binds { - if err := val.Set(value); err != nil { - return fmt.Errorf("flag %s: bind %d (%T): cannot set %q: %w", g.Name(), i, val.Get(), value, err) - } - } - vars[name] = vs - */ - return nil -} - -// VarSet wraps a variable and its set state. -type VarSet struct { - Type Type - Var Value - WasSet bool -} - -// Set sets a var. -func (vs *VarSet) Set(typ, sub Type, value string, wasSet bool) (*VarSet, error) { - if vs == nil { - vs = &VarSet{ - Type: typ, - } } - if vs.Type != typ { - return nil, ErrTypeMismatch + if err := v.Set(s); err != nil { + return err } - if vs.Var == nil { - var err error - if vs.Var, err = typ.New(); err != nil { - return nil, fmt.Errorf("%w: %w", ErrCouldNotCreateValue, err) - } - if sub != "" { - if err := sub.apply(vs.Var); err != nil { - return nil, err - } + v.SetSet(set) + for i, val := range g.Binds { + if err := val.Set(s); err != nil { + return fmt.Errorf("flag %s: bind %d (%T): cannot set %q: %w", g.Name(), i, val.Get(), s, err) } } - if err := vs.Var.Set(value); err != nil { - return nil, err - } - vs.WasSet = wasSet - return vs, nil + vars[name] = v + return nil } // BoundValue is the bound value interface. type BoundValue interface { - Set(any) error + Set(string) error Get() any } @@ -355,7 +401,7 @@ func NewBind[T *E, E any](v T, b *bool) (BoundValue, error) { }, nil } -func (val *boundVal[T, E]) Set(v any) error { +func (val *boundVal[T, E]) Set(s string) error { typ := reflect.TypeOf(*val.v) switch typ.Kind() { case reflect.Slice: @@ -383,7 +429,8 @@ func (val *boundVal[T, E]) Set(v any) error { } */ } - return fmt.Errorf("%w: %s->%T", ErrInvalidConversion, typ, *val.v) + // return fmt.Errorf("%w: %s->%T", ErrInvalidConversion, typ, *v.v) + return ErrInvalidConversion } func (val *boundVal[T, E]) Get() any { @@ -408,7 +455,7 @@ func NewBindValue(value reflect.Value, b *bool) (BoundValue, error) { }, nil } -func (val *boundRefVal) Set(v any) error { +func (val *boundRefVal) Set(s string) error { typ := val.v.Elem().Type() switch typ.Kind() { case reflect.Slice: @@ -434,7 +481,7 @@ func (val *boundRefVal) Set(v any) error { } */ } - return fmt.Errorf("%w: cannot convert %T->%s", ErrInvalidConversion, v, typ) + return fmt.Errorf("%w: cannot convert %T->%s", ErrInvalidConversion, s, typ) } func (val *boundRefVal) sliceSet(s string) bool {