Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: orsinium-labs/enum
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.2.0
Choose a base ref
...
head repository: orsinium-labs/enum
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 9 commits
  • 5 files changed
  • 3 contributors

Commits on Aug 31, 2023

  1. Add enum.Builder

    orsinium committed Aug 31, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    97661b1 View commit details

Commits on Sep 3, 2023

  1. Merge pull request #7 from orsinium-labs/builder

    Add enum.Builder
    orsinium authored Sep 3, 2023
    Copy the full SHA
    b7aa223 View commit details
  2. Correct a comment

    xyproto authored Sep 3, 2023
    Copy the full SHA
    0287064 View commit details
  3. Merge pull request #8 from xyproto/patch-1

    Correct a comment
    orsinium authored Sep 3, 2023
    Copy the full SHA
    716dd44 View commit details

Commits on Apr 12, 2024

  1. ✨ (enum.go): Add Matcher interface and Match method to Enum for custo…

    …m matching logic
    
    ✅ (enum_test.go): Add tests for new Match method in Enum, testing with Book and Color enums
    iwata committed Apr 12, 2024
    Copy the full SHA
    6217b7d View commit details
  2. ♻️ (enum.go): Rename Matcher interface to Equaler and Match method to…

    … Equal for better clarity
    
    ✨ (enum.go): Add Parse function to replace Match method for better separation of concerns
    🐛 (enum_test.go): Update tests to reflect changes in enum.go, replacing Match with Parse and updating test names
    iwata committed Apr 12, 2024
    Copy the full SHA
    e2d3c6e View commit details
  3. Merge pull request #9 from iwata/master

    Add Enum.Match
    orsinium authored Apr 12, 2024
    Copy the full SHA
    2a22286 View commit details
  4. Provide examples for enum.Parse

    orsinium committed Apr 12, 2024
    Copy the full SHA
    74393ed View commit details
  5. Merge pull request #10 from orsinium-labs/parse-examples

    Provide examples for enum.Parse
    orsinium authored Apr 12, 2024
    Copy the full SHA
    4d8fb06 View commit details
Showing with 157 additions and 1 deletion.
  1. +1 −0 .golangci.yaml
  2. +14 −0 README.md
  3. +48 −0 enum.go
  4. +50 −1 enum_test.go
  5. +44 −0 example_test.go
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
linters:
disable:
- dupword
- depguard
presets:
- bugs
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -111,6 +111,20 @@ var (
fmt.Println(Red.Value.UI)
```

If the enum has lots of members and new ones may be added over time, it's easy to forget to register all members in the enum. To prevent this, use enum.Builder to define an enum:

```go
type Color enum.Member[string]

var (
b = enum.NewBuilder[string, Color]()
Red = b.Add(Color{"red"})
Green = b.Add(Color{"green"})
Blue = b.Add(Color{"blue"})
Colors = b.Enum()
)
```

## 🤔 QnA

1. **What happens when enums are added in Go itself?** I'll keep it alive until someone uses it but I expect the project popularity to quickly die out when there is native language support for enums. When you can mess with the compiler itself, you can do more. For example, this package can't provide an exhaustiveness check for switch statements using enums (maybe only by implementing a linter) but proper language-level enums would most likely have it.
48 changes: 48 additions & 0 deletions enum.go
Original file line number Diff line number Diff line change
@@ -12,6 +12,12 @@ type Member[T comparable] struct {
Value T
}

// Equaler check if the two values of the same type are equal.
type Equaler[V comparable] interface {
Equal(other V) bool
comparable
}

// iMember is the type constraint for Member used by Enum.
//
// We can't use Member directly in type constraints
@@ -149,3 +155,45 @@ func (e Enum[M, V]) GoString() string {
joined := strings.Join(values, ", ")
return fmt.Sprintf("enum.New(%s)", joined)
}

// Parse is like [Enum.Parse] but finds the member for the value using [Equaler] comparator.
func Parse[M iMember[V], V Equaler[V]](e Enum[M, V], value V) *M {
for v, m := range e.v2m {
if v.Equal(value) {
return m
}
}
return nil
}

// Builder is a constructor for an [Enum].
//
// Use [Builder.Add] to add new members to the future enum
// and then call [Builder.Enum] to create a new [Enum] with all added members.
//
// Builder is useful for when you have lots of enum members, and new ones
// are added over time, as the project grows. In such scenario, it's easy to forget
// to add in the [Enum] a newly created [Member].
// The builder is designed to prevent that.
type Builder[M iMember[V], V comparable] struct {
members []M
finished bool
}

// NewBuilder creates a new [Builder], a constructor for an [Enum].
func NewBuilder[V comparable, M iMember[V]]() Builder[M, V] {
return Builder[M, V]{make([]M, 0), false}
}

// Add registers a new [Member] in the builder.
func (b *Builder[M, V]) Add(m M) M {
b.members = append(b.members, m)
return m
}

// Enum creates a new [Enum] with all members registered using [Builder.Add].
func (b *Builder[M, V]) Enum() Enum[M, V] {
b.finished = true
e := New(b.members...)
return e
}
51 changes: 50 additions & 1 deletion enum_test.go
Original file line number Diff line number Diff line change
@@ -81,7 +81,7 @@ func TestEnum_Choice(t *testing.T) {
m = Colors.Choice(42)
is.True(m != nil)
is.Equal(*m, Blue)
// Check that selecting a random member from an empty Enum returns an error
// Selecting a random member from an empty Enum returns nil
emptyEnums := enum.New[string, Color]()
is.True(emptyEnums.Choice(0) == nil)
}
@@ -112,3 +112,52 @@ func TestEnum_Index_Panic(t *testing.T) {
}()
Colors.Index(Color{"purple"})
}

func TestBuilder(t *testing.T) {
is := is.New(t)
type Country enum.Member[string]
var (
b = enum.NewBuilder[string, Country]()
NL = b.Add(Country{"Netherlands"})
FR = b.Add(Country{"France"})
BE = b.Add(Country{"Belgium"})
Countries = b.Enum()
)
is.Equal(Countries.Members(), []Country{NL, FR, BE})
}

type BookValue struct {
Title string
ISBN string
}

type Book enum.Member[BookValue]

var (
EfficientGo = Book{BookValue{"Efficient Go", "978-1098105716"}}
ConcurrencyInGo = Book{BookValue{"Concurrency in Go", "978-1491941195"}}
Books = enum.New(EfficientGo, ConcurrencyInGo)
)

func (b BookValue) Equal(v BookValue) bool {
return b.ISBN == v.ISBN
}

func TestParse(t *testing.T) {
is := is.New(t)
tests := []struct {
isbn string
want *Book
}{
{"978-1098105716", &EfficientGo},
{"978-1491941195", &ConcurrencyInGo},
{"invalid-isbn", nil},
}
for _, tt := range tests {
t.Run(tt.isbn, func(t *testing.T) {
v := BookValue{ISBN: tt.isbn}
got := enum.Parse(Books, v)
is.Equal(got, tt.want)
})
}
}
44 changes: 44 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package enum_test

import (
"fmt"
"strings"

"github.com/orsinium-labs/enum"
)
@@ -182,3 +183,46 @@ func ExampleEnum_TypeName() {
fmt.Println(tname)
// Output: string
}

func ExampleNewBuilder() {
type Color enum.Member[string]
var (
b = enum.NewBuilder[string, Color]()
Red = b.Add(Color{"red"})
Green = b.Add(Color{"green"})
Blue = b.Add(Color{"blue"})
Colors = b.Enum()
)

fmt.Println(
Colors.Contains(Red),
Colors.Contains(Green),
Colors.Contains(Blue),
)
// Output:
// true true true
}

type FoldedString string

// Equal implements [enum.Equaler].
//
// Compare strings ignoring the case.
func (s FoldedString) Equal(other FoldedString) bool {
return strings.EqualFold(string(s), string(other))
}

func ExampleParse() {
type Color enum.Member[FoldedString]

var (
Red = Color{"red"}
Green = Color{"green"}
Blue = Color{"blue"}
Colors = enum.New(Red, Green, Blue)
)

parsed := enum.Parse(Colors, "RED")
fmt.Printf("%#v\n", parsed)
// Output: &enum_test.Color{Value:"red"}
}