Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/buildandtest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ jobs:
steps:

- name: Set up Go ${{matrix.go}}
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v4
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v5
with:
go-version: ${{matrix.go}}
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4

- name: Test
run: go test -v -p=1 -race ./...
19 changes: 0 additions & 19 deletions Makefile

This file was deleted.

25 changes: 3 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enumeration values either with a single flag `--mode=foo,bar` or multiple flag
calls, such as `--mode=foo --mode=bar`.

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

For devcontainer instructions, please see the [section "DevContainer"
Expand Down Expand Up @@ -76,7 +76,7 @@ import (
)

// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies constraints.Ordered.
// but it doesn't need to be as long as it satisfies comparable.
type FooMode enumflag.Flag

// ② Define the enumeration values for FooMode.
Expand Down Expand Up @@ -306,7 +306,7 @@ import (
)

// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies constraints.Ordered.
// but it doesn't need to be as long as it satisfies comparable.
type MooMode enumflag.Flag

// ② Define the enumeration values for FooMode.
Expand Down Expand Up @@ -357,25 +357,6 @@ func Example_slice() {
2. in VSCode: Ctrl+Shift+P, "Dev Containers: Open Workspace in Container..."
3. select `enumflag.code-workspace` and off you go...

## VSCode Tasks

The included `enumflag.code-workspace` defines the following tasks:

- **Build workspace** task: builds all, including the shared library test
plugin.

- **Run all tests with coverage** task: does what it says on the tin and runs
all tests with coverage.

## Make Targets

- `make`: lists available targets.
- `make test`: runs all tests.
- `make coverage`: deprecated, use the `gocover` CLI command in the devcontainer
instead.
- `make report`: deprecated, use the `goreportcard-cli` CLI command in the
devcontainer instead.

## Contributing

Please see [CONTRIBUTING.md](CONTRIBUTING.md).
Expand Down
3 changes: 1 addition & 2 deletions completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ package enumflag

import (
"github.com/spf13/cobra"
"golang.org/x/exp/constraints"
)

// Help maps enumeration values to their corresponding help descriptions. These
// descriptions should contain just the description but without any "foo\t" enum
// value prefix. The reason is that enumflag will automatically register the
// correct (erm, “complete”) completion text. Please note that it isn't
// necessary to supply any help texts in order to register enum flag completion.
type Help[E constraints.Ordered] map[E]string
type Help[E comparable] map[E]string

// Completor tells cobra how to complete a flag. See also cobra's [dynamic flag
// completion] documentation.
Expand Down
2 changes: 1 addition & 1 deletion completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (
"os"
"os/exec"
"path/filepath"
"slices"
"syscall"
"time"

"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
"golang.org/x/exp/slices"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down
26 changes: 9 additions & 17 deletions example_external_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,26 @@ package enumflag_test

import (
"fmt"
"os"
"log/slog"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)

func init() {
log.SetOutput(os.Stdout)
}

func Example_external() {
// ①+② skip "define your own enum flag type" and enumeration values, as we
// already have a 3rd party one.

// ③ Map 3rd party enumeration values to their textual representations
var LoglevelIds = map[log.Level][]string{
log.TraceLevel: {"trace"},
log.DebugLevel: {"debug"},
log.InfoLevel: {"info"},
log.WarnLevel: {"warning", "warn"},
log.ErrorLevel: {"error"},
log.FatalLevel: {"fatal"},
log.PanicLevel: {"panic"},
var LoglevelIds = map[slog.Level][]string{
slog.LevelDebug: {"debug"},
slog.LevelInfo: {"info"},
slog.LevelWarn: {"warning", "warn"},
slog.LevelError: {"error"},
}

// ④ Define your enum flag value and set the your logging default value.
var loglevel log.Level = log.WarnLevel
var loglevel = slog.LevelWarn

rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
Expand All @@ -49,6 +41,6 @@ func Example_external() {
rootCmd.SetArgs([]string{"--log", "debug"})
_ = rootCmd.Execute()
// Output:
// logging level is: 3="warning"
// logging level is: 5="debug"
// logging level is: 4="warning"
// logging level is: -4="debug"
}
2 changes: 1 addition & 1 deletion example_nodefault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies constraints.Ordered.
// but it doesn't need to be as long as it satisfies comparable.
type BarMode enumflag.Flag

// ② Define the enumeration values for BarMode.
Expand Down
2 changes: 1 addition & 1 deletion example_slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies constraints.Ordered.
// but it doesn't need to be as long as it satisfies comparable.
type MooMode enumflag.Flag

// ② Define the enumeration values for FooMode.
Expand Down
2 changes: 1 addition & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies constraints.Ordered.
// but it doesn't need to be as long as it satisfies comparable.
type FooMode enumflag.Flag

// ② Define the enumeration values for FooMode.
Expand Down
60 changes: 38 additions & 22 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"fmt"

"github.com/spf13/cobra"
"golang.org/x/exp/constraints"
)

// Flag represents a CLI (enumeration) flag which can take on only a single
Expand All @@ -31,7 +30,9 @@ import (
// However, applications don't need to base their own enum types on Flag. The
// only requirement for user-defined enumeration flags is that they must be
// (“somewhat”) compatible with the Flag type, or more precise: user-defined
// enumerations must satisfy [constraints.Ordered].
// enumerations must satisfy the predeclared type identifier [comparable].
//
// [comparable]: https://go.dev/blog/comparable
type Flag uint

// EnumCaseSensitivity specifies whether the textual representations of enum
Expand All @@ -45,12 +46,11 @@ const (
EnumCaseSensitive EnumCaseSensitivity = true
)

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

// New wraps a given enum variable (satisfying [constraints.Ordered]) so that it
// can be used as a flag Value with [github.com/spf13/pflag.Var] and
// [github.com/spf13/pflag.VarP]. In case no default enum value should be set
// and therefore no default shown in [spf13/cobra], use [NewWithoutDefault]
// instead.
// New wraps a given enum variable (satisfying the predeclared type identifier
// comparable) so that it can be used as a flag Value with
// [github.com/spf13/pflag.Var] and [github.com/spf13/pflag.VarP]. In case no
// default enum value should be set and therefore no default shown in
// [spf13/cobra], use [NewWithoutDefault] instead.
//
// [spf13/cobra]: https://github.com/spf13/cobra
func New[E constraints.Ordered](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
func New[E comparable](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
return new("New", flag, typename, mapping, sensitivity, false)
}

// NewWithoutDefault wraps a given enum variable (satisfying
// [constraints.Ordered]) so that it can be used as a flag Value with
// NewWithoutDefault wraps a given enum variable (satisfying the predeclared
// type identifier comparable) so that it can be used as a flag Value with
// [github.com/spf13/pflag.Var] and [github.com/spf13/pflag.VarP]. Please note
// that the zero enum value must not be mapped and thus not be assigned to any
// enum value textual representation.
Expand All @@ -91,14 +91,14 @@ func New[E constraints.Ordered](flag *E, typename string, mapping EnumIdentifier
// created with NewWithoutDefault.
//
// [spf13/cobra]: https://github.com/spf13/cobra
func NewWithoutDefault[E constraints.Ordered](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
func NewWithoutDefault[E comparable](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
return new("NewWithoutDefault", flag, typename, mapping, sensitivity, true)
}

// new returns a new enum variable to be used with pflag.Var and pflag.VarP.
func new[E constraints.Ordered](ctor string, flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity, nodefault bool) *EnumFlagValue[E] {
func new[E comparable](ctor string, flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity, nodefault bool) *EnumFlagValue[E] {
if flag == nil {
panic(fmt.Sprintf("%s requires flag to be a non-nil pointer to an enum value satisfying constraints.Ordered", ctor))
panic(fmt.Sprintf("%s requires flag to be a non-nil pointer to an enum value satisfying comparable", ctor))
}
if mapping == nil {
panic(fmt.Sprintf("%s requires mapping not to be nil", ctor))
Expand All @@ -110,10 +110,10 @@ func new[E constraints.Ordered](ctor string, flag *E, typename string, mapping E
}
}

// NewSlice wraps a given enum slice variable (satisfying [constraints.Ordered])
// NewSlice wraps a given enum slice variable (satisfying [comparable])
// so that it can be used as a flag Value with [github.com/spf13/pflag.Var] and
// [github.com/spf13/pflag.VarP].
func NewSlice[E constraints.Ordered](flag *[]E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
func NewSlice[E comparable](flag *[]E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
if flag == nil {
panic("NewSlice requires flag to be a non-nil pointer to an enum value slice satisfying []any")
}
Expand Down Expand Up @@ -154,3 +154,19 @@ func (e *EnumFlagValue[E]) RegisterCompletion(cmd *cobra.Command, name string, h
return cmd.RegisterFlagCompletionFunc(
name, e.value.NewCompletor(e.names.Mapping(), help))
}

// GetValue returns the (scalar) enum value of type E, otherwise it returns the
// zero value for type E.
func (e *EnumFlagValue[E]) GetValue() (v E) {
ev := e.Get() // returns E, not *E
v, _ = ev.(E)
return
}

// GetSliceValue returns the slice enum value of type []E, otherwise it returns
// the zero value for type []E.
func (e *EnumFlagValue[E]) GetSliceValue() (v []E) {
ev := e.Get() // returns []E, not *[]E
v, _ = ev.([]E)
return
}
21 changes: 20 additions & 1 deletion flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
package enumflag

import (
"github.com/spf13/cobra"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/spf13/cobra"
)

var _ = Describe("flag", func() {
Expand Down Expand Up @@ -60,6 +61,24 @@ var _ = Describe("flag", func() {

})

Context("retrieving the enum value", func() {

It("succeeds for scalar", func() {
var foomode = fmBar
val := New(&foomode, "mode", FooModeIdentifiersTest, EnumCaseSensitive)
Expect(val.GetValue()).To(Equal(fmBar))
Expect(val.GetSliceValue()).To(BeZero())
})

It("succeeds for slices", func() {
var foomodes = []FooModeTest{fmBar}
val := NewSlice(&foomodes, "mode", FooModeIdentifiersTest, EnumCaseSensitive)
Expect(val.GetValue()).To(BeZero())
Expect(val.GetSliceValue()).To(ConsistOf(fmBar))
})

})

When("passing nil", func() {

It("panics", func() {
Expand Down
Loading