Skip to content

Commit e4cc308

Browse files
committed
Relax the enum type constraint
This is a POC that it's possible to relax the `constraint.Integer` constraint into simply `constraint.Ordered`. This means in particular that string enum can be accepted, which I found to be a common case. It seems to work, but I may not have covered all the cases properly.
1 parent 9d40a41 commit e4cc308

File tree

9 files changed

+32
-27
lines changed

9 files changed

+32
-27
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ enumeration values either with a single flag `--mode=foo,bar` or multiple flag
2424
calls, such as `--mode=foo --mode=bar`.
2525

2626
Application programmers then simply deal with enumeration values in form of
27-
uints (or ints, _erm_, anything that satisfies `constraints.Integer`s),
27+
uints (or ints, _erm_, anything that satisfies `constraints.Ordered`s),
2828
liberated from parsing strings and validating enumeration flags.
2929

3030
For devcontainer instructions, please see the [section "DevContainer"
@@ -76,7 +76,7 @@ import (
7676
)
7777

7878
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
79-
// but it doesn't need to be as long as it satisfies constraints.Integer.
79+
// but it doesn't need to be as long as it satisfies constraints.Ordered.
8080
type FooMode enumflag.Flag
8181

8282
// ② Define the enumeration values for FooMode.
@@ -306,7 +306,7 @@ import (
306306
)
307307

308308
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
309-
// but it doesn't need to be as long as it satisfies constraints.Integer.
309+
// but it doesn't need to be as long as it satisfies constraints.Ordered.
310310
type MooMode enumflag.Flag
311311

312312
// ② Define the enumeration values for FooMode.

completion.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
// value prefix. The reason is that enumflag will automatically register the
2525
// correct (erm, “complete”) completion text. Please note that it isn't
2626
// necessary to supply any help texts in order to register enum flag completion.
27-
type Help[E constraints.Integer] map[E]string
27+
type Help[E constraints.Ordered] map[E]string
2828

2929
// Completor tells cobra how to complete a flag. See also cobra's [dynamic flag
3030
// completion] documentation.

example_nodefault_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import (
44
"fmt"
55

66
"github.com/spf13/cobra"
7+
78
"github.com/thediveo/enumflag/v2"
89
)
910

1011
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
11-
// but it doesn't need to be as long as it satisfies constraints.Integer.
12+
// but it doesn't need to be as long as it satisfies constraints.Ordered.
1213
type BarMode enumflag.Flag
1314

1415
// ② Define the enumeration values for BarMode.

example_slice_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import (
44
"fmt"
55

66
"github.com/spf13/cobra"
7+
78
"github.com/thediveo/enumflag/v2"
89
)
910

1011
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
11-
// but it doesn't need to be as long as it satisfies constraints.Integer.
12+
// but it doesn't need to be as long as it satisfies constraints.Ordered.
1213
type MooMode enumflag.Flag
1314

1415
// ② Define the enumeration values for FooMode.

example_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import (
44
"fmt"
55

66
"github.com/spf13/cobra"
7+
78
"github.com/thediveo/enumflag/v2"
89
)
910

1011
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
11-
// but it doesn't need to be as long as it satisfies constraints.Integer.
12+
// but it doesn't need to be as long as it satisfies constraints.Ordered.
1213
type FooMode enumflag.Flag
1314

1415
// ② Define the enumeration values for FooMode.

flag.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import (
3131
// However, applications don't need to base their own enum types on Flag. The
3232
// only requirement for user-defined enumeration flags is that they must be
3333
// (“somewhat”) compatible with the Flag type, or more precise: user-defined
34-
// enumerations must satisfy [constraints.Integer].
34+
// enumerations must satisfy [constraints.Ordered].
3535
type Flag uint
3636

3737
// EnumCaseSensitivity specifies whether the textual representations of enum
@@ -46,11 +46,11 @@ const (
4646
)
4747

4848
// EnumFlagValue wraps a user-defined enum type value satisfying
49-
// [constraints.Integer] or [][constraints.Integer]. It implements the
49+
// [constraints.Ordered] or [][constraints.Ordered]. It implements the
5050
// [github.com/spf13/pflag.Value] interface, so the user-defined enum type value
5151
// can directly be used with the fine pflag drop-in package for Golang CLI
5252
// flags.
53-
type EnumFlagValue[E constraints.Integer] struct {
53+
type EnumFlagValue[E constraints.Ordered] struct {
5454
value enumValue[E] // enum value of a user-defined enum scalar or slice type.
5555
enumtype string // user-friendly name of the user-defined enum type.
5656
names enumMapper[E] // enum value names.
@@ -63,26 +63,26 @@ type EnumFlagValue[E constraints.Integer] struct {
6363
// code”: by just moving the interface type from the source file with the struct
6464
// types to the source file with the consumer we achieve immediate Go
6565
// perfectness! Strike!
66-
type enumValue[E constraints.Integer] interface {
66+
type enumValue[E constraints.Ordered] interface {
6767
Get() any
6868
Set(val string, names enumMapper[E]) error
6969
String(names enumMapper[E]) string
7070
NewCompletor(enums EnumIdentifiers[E], help Help[E]) Completor
7171
}
7272

73-
// New wraps a given enum variable (satisfying [constraints.Integer]) so that it
73+
// New wraps a given enum variable (satisfying [constraints.Ordered]) so that it
7474
// can be used as a flag Value with [github.com/spf13/pflag.Var] and
7575
// [github.com/spf13/pflag.VarP]. In case no default enum value should be set
7676
// and therefore no default shown in [spf13/cobra], use [NewWithoutDefault]
7777
// instead.
7878
//
7979
// [spf13/cobra]: https://github.com/spf13/cobra
80-
func New[E constraints.Integer](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
80+
func New[E constraints.Ordered](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
8181
return new("New", flag, typename, mapping, sensitivity, false)
8282
}
8383

8484
// NewWithoutDefault wraps a given enum variable (satisfying
85-
// [constraints.Integer]) so that it can be used as a flag Value with
85+
// [constraints.Ordered]) so that it can be used as a flag Value with
8686
// [github.com/spf13/pflag.Var] and [github.com/spf13/pflag.VarP]. Please note
8787
// that the zero enum value must not be mapped and thus not be assigned to any
8888
// enum value textual representation.
@@ -91,14 +91,14 @@ func New[E constraints.Integer](flag *E, typename string, mapping EnumIdentifier
9191
// created with NewWithoutDefault.
9292
//
9393
// [spf13/cobra]: https://github.com/spf13/cobra
94-
func NewWithoutDefault[E constraints.Integer](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
94+
func NewWithoutDefault[E constraints.Ordered](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
9595
return new("NewWithoutDefault", flag, typename, mapping, sensitivity, true)
9696
}
9797

9898
// new returns a new enum variable to be used with pflag.Var and pflag.VarP.
99-
func new[E constraints.Integer](ctor string, flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity, nodefault bool) *EnumFlagValue[E] {
99+
func new[E constraints.Ordered](ctor string, flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity, nodefault bool) *EnumFlagValue[E] {
100100
if flag == nil {
101-
panic(fmt.Sprintf("%s requires flag to be a non-nil pointer to an enum value satisfying constraints.Integer", ctor))
101+
panic(fmt.Sprintf("%s requires flag to be a non-nil pointer to an enum value satisfying constraints.Ordered", ctor))
102102
}
103103
if mapping == nil {
104104
panic(fmt.Sprintf("%s requires mapping not to be nil", ctor))
@@ -110,12 +110,12 @@ func new[E constraints.Integer](ctor string, flag *E, typename string, mapping E
110110
}
111111
}
112112

113-
// NewSlice wraps a given enum slice variable (satisfying [constraints.Integer])
113+
// NewSlice wraps a given enum slice variable (satisfying [constraints.Ordered])
114114
// so that it can be used as a flag Value with [github.com/spf13/pflag.Var] and
115115
// [github.com/spf13/pflag.VarP].
116-
func NewSlice[E constraints.Integer](flag *[]E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
116+
func NewSlice[E constraints.Ordered](flag *[]E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
117117
if flag == nil {
118-
panic("NewSlice requires flag to be a non-nil pointer to an enum value slice satisfying []constraints.Integer")
118+
panic("NewSlice requires flag to be a non-nil pointer to an enum value slice satisfying []any")
119119
}
120120
if mapping == nil {
121121
panic("NewSlice requires mapping not to be nil")

mapper.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@ import (
2929
// representation (identifier). If more than one textual representation exists
3030
// for the same enumeration value, then the first textual representation is
3131
// considered to be the canonical one.
32-
type EnumIdentifiers[E constraints.Integer] map[E][]string
32+
type EnumIdentifiers[E constraints.Ordered] map[E][]string
3333

3434
// enumMapper is an optionally case insensitive map from enum values to their
3535
// corresponding textual representations.
36-
type enumMapper[E constraints.Integer] struct {
36+
type enumMapper[E constraints.Ordered] struct {
3737
m EnumIdentifiers[E]
3838
sensitivity EnumCaseSensitivity
3939
}
4040

4141
// newEnumMapper returns a new enumMapper for the given mapping and case
4242
// sensitivity or insensitivity.
43-
func newEnumMapper[E constraints.Integer](mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) enumMapper[E] {
43+
func newEnumMapper[E constraints.Ordered](mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) enumMapper[E] {
4444
return enumMapper[E]{
4545
m: mapping,
4646
sensitivity: sensitivity,
@@ -81,7 +81,8 @@ func (m enumMapper[E]) ValueOf(name string) (E, error) {
8181
allids = append(allids, strings.Join(s, "/"))
8282
}
8383
sort.Strings(allids)
84-
return 0, fmt.Errorf("must be %s", strings.Join(allids, ", "))
84+
var zero E
85+
return zero, fmt.Errorf("must be %s", strings.Join(allids, ", "))
8586
}
8687

8788
// Mapping returns the mapping of enum values to their names.

value_scalar.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const unknown = "<unknown>"
2626

2727
// enumScalar represents a mutable, single enumeration value that can be
2828
// retrieved, set, and stringified.
29-
type enumScalar[E constraints.Integer] struct {
29+
type enumScalar[E constraints.Ordered] struct {
3030
v *E
3131
nodefault bool // opts in to accepting a zero enum value as the "none"
3232
}
@@ -60,7 +60,8 @@ func (s *enumScalar[E]) String(names enumMapper[E]) string {
6060
if ids := names.Lookup(*s.v); len(ids) > 0 {
6161
return ids[0]
6262
}
63-
if *s.v == 0 && s.nodefault {
63+
var zero E
64+
if *s.v == zero && s.nodefault {
6465
return ""
6566
}
6667
return unknown

value_slice.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424

2525
// enumSlice represents a slice of enumeration values that can be retrieved,
2626
// set, and stringified.
27-
type enumSlice[E constraints.Integer] struct {
27+
type enumSlice[E constraints.Ordered] struct {
2828
v *[]E
2929
merge bool // replace the complete slice or merge values?
3030
}

0 commit comments

Comments
 (0)