Skip to content
278 changes: 246 additions & 32 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,25 @@
return ret
}

// Argument captures a positional argument that can
// be parsed
type Argument interface {
// which this argument can be accessed using the given name
HasName(string) bool

// Parse the given args and return unparsed args and/or error
Parse([]string) ([]string, error)

// The usage template for this argument to use in help
Usage() string

// The Value of this Arg
Get() any
}

// AnyArguments to differentiate between no arguments(nil) vs aleast one
var AnyArguments = []Argument{
&StringArg{
&StringArgs{
Max: -1,
},
}
Expand All @@ -77,14 +88,78 @@
Name string `json:"name"` // the name of this argument
Value T `json:"value"` // the default value of this argument
Destination *T `json:"-"` // the destination point for this argument
Values *[]T `json:"-"` // all the values of this argument, only if multiple are supported
UsageText string `json:"usageText"` // the usage text to show
Config C `json:"config"` // config for this argument similar to Flag Config

value *T
}

func (a *ArgumentBase[T, C, VC]) HasName(s string) bool {
return s == a.Name
}

func (a *ArgumentBase[T, C, VC]) Usage() string {
if a.UsageText != "" {
return a.UsageText
}

usageFormat := "%[1]s"
return fmt.Sprintf(usageFormat, a.Name)
}

func (a *ArgumentBase[T, C, VC]) Parse(s []string) ([]string, error) {
tracef("calling arg%[1] parse with args %[2]", a.Name, s)

var vc VC
var t T
value := vc.Create(a.Value, &t, a.Config)
a.value = &t

tracef("attempting arg%[1] parse", &a.Name)
if len(s) > 0 {
if err := value.Set(s[0]); err != nil {
return s, err
}
*a.value = value.Get().(T)
tracef("set arg%[1] one value", a.Name, *a.value)
}

if a.Destination != nil {
tracef("setting destination")
*a.Destination = *a.value
}

if len(s) > 0 {
return s[1:], nil
}
return s, nil
}

func (a *ArgumentBase[T, C, VC]) Get() any {
if a.value != nil {
return *a.value
}
return a.Value

Check warning on line 142 in args.go

View check run for this annotation

Codecov / codecov/patch

args.go#L142

Added line #L142 was not covered by tests
}

// ArgumentsBase is a base type for slice arguments
type ArgumentsBase[T any, C any, VC ValueCreator[T, C]] struct {
Name string `json:"name"` // the name of this argument
Value T `json:"value"` // the default value of this argument
Destination *[]T `json:"-"` // the destination point for this argument
UsageText string `json:"usageText"` // the usage text to show
Min int `json:"minTimes"` // the min num of occurrences of this argument
Max int `json:"maxTimes"` // the max num of occurrences of this argument, set to -1 for unlimited
Config C `json:"config"` // config for this argument similar to Flag Config

values []T
}

func (a *ArgumentBase[T, C, VC]) Usage() string {
func (a *ArgumentsBase[T, C, VC]) HasName(s string) bool {
return s == a.Name
}

func (a *ArgumentsBase[T, C, VC]) Usage() string {
if a.UsageText != "" {
return a.UsageText
}
Expand All @@ -102,7 +177,7 @@
return fmt.Sprintf(usageFormat, a.Name)
}

func (a *ArgumentBase[T, C, VC]) Parse(s []string) ([]string, error) {
func (a *ArgumentsBase[T, C, VC]) Parse(s []string) ([]string, error) {
tracef("calling arg%[1] parse with args %[2]", &a.Name, s)
if a.Max == 0 {
fmt.Printf("WARNING args %s has max 0, not parsing argument\n", a.Name)
Expand All @@ -117,13 +192,15 @@
var vc VC
var t T
value := vc.Create(a.Value, &t, a.Config)
values := []T{}
a.values = []T{}

tracef("attempting arg%[1] parse", &a.Name)
for _, arg := range s {
if err := value.Set(arg); err != nil {
return s, err
}
values = append(values, value.Get().(T))
tracef("set arg%[1] one value", &a.Name, value.Get().(T))
a.values = append(a.values, value.Get().(T))
count++
if count >= a.Max && a.Max > -1 {
break
Expand All @@ -133,36 +210,173 @@
return s, fmt.Errorf("sufficient count of arg %s not provided, given %d expected %d", a.Name, count, a.Min)
}

if a.Values == nil {
a.Values = &values
} else if count > 0 {
*a.Values = values
}

if a.Max == 1 && a.Destination != nil {
if len(values) > 0 {
*a.Destination = values[0]
} else {
*a.Destination = t
}
if a.Destination != nil {
tracef("appending destination")
*a.Destination = a.values // append(*a.Destination, a.values...)
}

return s[count:], nil
}

func (a *ArgumentsBase[T, C, VC]) Get() any {
if a.values != nil {
return a.values
}
return []T{}
}

type (
FloatArg = ArgumentBase[float64, NoConfig, floatValue]
IntArg = ArgumentBase[int, IntegerConfig, intValue[int]]
Int8Arg = ArgumentBase[int8, IntegerConfig, intValue[int8]]
Int16Arg = ArgumentBase[int16, IntegerConfig, intValue[int16]]
Int32Arg = ArgumentBase[int32, IntegerConfig, intValue[int32]]
Int64Arg = ArgumentBase[int64, IntegerConfig, intValue[int64]]
StringArg = ArgumentBase[string, StringConfig, stringValue]
StringMapArg = ArgumentBase[map[string]string, StringConfig, StringMap]
TimestampArg = ArgumentBase[time.Time, TimestampConfig, timestampValue]
UintArg = ArgumentBase[uint, IntegerConfig, uintValue[uint]]
Uint8Arg = ArgumentBase[uint8, IntegerConfig, uintValue[uint8]]
Uint16Arg = ArgumentBase[uint16, IntegerConfig, uintValue[uint16]]
Uint32Arg = ArgumentBase[uint32, IntegerConfig, uintValue[uint32]]
Uint64Arg = ArgumentBase[uint64, IntegerConfig, uintValue[uint64]]
FloatArg = ArgumentBase[float64, NoConfig, floatValue]
IntArg = ArgumentBase[int, IntegerConfig, intValue[int]]
Int8Arg = ArgumentBase[int8, IntegerConfig, intValue[int8]]
Int16Arg = ArgumentBase[int16, IntegerConfig, intValue[int16]]
Int32Arg = ArgumentBase[int32, IntegerConfig, intValue[int32]]
Int64Arg = ArgumentBase[int64, IntegerConfig, intValue[int64]]
StringArg = ArgumentBase[string, StringConfig, stringValue]
StringMapArgs = ArgumentBase[map[string]string, StringConfig, StringMap]
TimestampArg = ArgumentBase[time.Time, TimestampConfig, timestampValue]
UintArg = ArgumentBase[uint, IntegerConfig, uintValue[uint]]
Uint8Arg = ArgumentBase[uint8, IntegerConfig, uintValue[uint8]]
Uint16Arg = ArgumentBase[uint16, IntegerConfig, uintValue[uint16]]
Uint32Arg = ArgumentBase[uint32, IntegerConfig, uintValue[uint32]]
Uint64Arg = ArgumentBase[uint64, IntegerConfig, uintValue[uint64]]

FloatArgs = ArgumentsBase[float64, NoConfig, floatValue]
IntArgs = ArgumentsBase[int, IntegerConfig, intValue[int]]
Int8Args = ArgumentsBase[int8, IntegerConfig, intValue[int8]]
Int16Args = ArgumentsBase[int16, IntegerConfig, intValue[int16]]
Int32Args = ArgumentsBase[int32, IntegerConfig, intValue[int32]]
Int64Args = ArgumentsBase[int64, IntegerConfig, intValue[int64]]
StringArgs = ArgumentsBase[string, StringConfig, stringValue]
TimestampArgs = ArgumentsBase[time.Time, TimestampConfig, timestampValue]
UintArgs = ArgumentsBase[uint, IntegerConfig, uintValue[uint]]
Uint8Args = ArgumentsBase[uint8, IntegerConfig, uintValue[uint8]]
Uint16Args = ArgumentsBase[uint16, IntegerConfig, uintValue[uint16]]
Uint32Args = ArgumentsBase[uint32, IntegerConfig, uintValue[uint32]]
Uint64Args = ArgumentsBase[uint64, IntegerConfig, uintValue[uint64]]
)

func (c *Command) getArgValue(name string) any {
tracef("command %s looking for args %s", c.Name, name)
for _, arg := range c.Arguments {
if arg.HasName(name) {
tracef("command %s found args %s", c.Name, name)
return arg.Get()
}
}
tracef("command %s did not find args %s", c.Name, name)
return nil
}

func arg[T any](name string, c *Command) T {
val := c.getArgValue(name)
if a, ok := val.(T); ok {
return a
}
var zero T
return zero
}

func (c *Command) StringArg(name string) string {
return arg[string](name, c)
}

func (c *Command) StringArgs(name string) []string {
return arg[[]string](name, c)
}

func (c *Command) FloatArg(name string) float64 {
return arg[float64](name, c)
}

func (c *Command) FloatArgs(name string) []float64 {
return arg[[]float64](name, c)
}

func (c *Command) IntArg(name string) int {
return arg[int](name, c)
}

func (c *Command) IntArgs(name string) []int {
return arg[[]int](name, c)
}

func (c *Command) Int8Arg(name string) int8 {
return arg[int8](name, c)
}

func (c *Command) Int8Args(name string) []int8 {
return arg[[]int8](name, c)
}

func (c *Command) Int16Arg(name string) int16 {
return arg[int16](name, c)
}

func (c *Command) Int16Args(name string) []int16 {
return arg[[]int16](name, c)
}

func (c *Command) Int32Arg(name string) int32 {
return arg[int32](name, c)
}

func (c *Command) Int32Args(name string) []int32 {
return arg[[]int32](name, c)
}

func (c *Command) Int64Arg(name string) int64 {
return arg[int64](name, c)
}

func (c *Command) Int64Args(name string) []int64 {
return arg[[]int64](name, c)
}

func (c *Command) UintArg(name string) uint {
return arg[uint](name, c)
}

func (c *Command) Uint8Arg(name string) uint8 {
return arg[uint8](name, c)
}

func (c *Command) Uint16Arg(name string) uint16 {
return arg[uint16](name, c)
}

func (c *Command) Uint32Arg(name string) uint32 {
return arg[uint32](name, c)
}

func (c *Command) Uint64Arg(name string) uint64 {
return arg[uint64](name, c)
}

func (c *Command) UintArgs(name string) []uint {
return arg[[]uint](name, c)
}

func (c *Command) Uint8Args(name string) []uint8 {
return arg[[]uint8](name, c)
}

func (c *Command) Uint16Args(name string) []uint16 {
return arg[[]uint16](name, c)
}

func (c *Command) Uint32Args(name string) []uint32 {
return arg[[]uint32](name, c)
}

func (c *Command) Uint64Args(name string) []uint64 {
return arg[[]uint64](name, c)
}

func (c *Command) TimestampArg(name string) time.Time {
return arg[time.Time](name, c)
}

func (c *Command) TimestampArgs(name string) []time.Time {
return arg[[]time.Time](name, c)
}
Loading