Skip to content

Commit a1fe710

Browse files
committed
initial
0 parents  commit a1fe710

8 files changed

+331
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

README.md

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Bit Flags
2+
3+
A simple package to store up to 64 boolean flags in one uint field.
4+
5+
Requires Go version 1.18+ (uses generics)
6+
7+
For usage example see [example dir](example)
8+
9+
```go
10+
import (
11+
"strings"
12+
13+
bf "github.com/goiste/bit_flags"
14+
)
15+
16+
const (
17+
Messages = 1 << iota // 1
18+
Replies // 2
19+
Likes // 4
20+
NewArticles // ... powers of 2
21+
News
22+
23+
ByEmail
24+
BySms
25+
ByTelegram
26+
27+
AllNotifications = Messages | Replies | Likes | NewArticles | News
28+
AllMethods = ByEmail | BySms | ByTelegram
29+
)
30+
31+
type Notification struct {
32+
SomeOtherFields string
33+
Flags bf.BitFlags[uint8]
34+
}
35+
36+
func New() *Notification { ... }
37+
38+
func (n *Notification) SetDefaultFlags() {
39+
n.Flags.Set(Messages | Replies | Likes | ByEmail)
40+
}
41+
42+
func (n *Notification) SetAll() {
43+
n.Flags.Set(AllNotifications | AllMethods)
44+
}
45+
46+
func (n *Notification) SetNone() {
47+
n.Flags.Reset()
48+
}
49+
50+
...
51+
```
52+
[full notification.go](example/notification.go)
53+
```go
54+
func main() {
55+
ntf := notification.New()
56+
fmt.Println(ntf.Flags.Get()) // 39
57+
fmt.Println(ntf.FlagsToString()) // Messages, Replies, Likes, By email
58+
59+
ntf.Flags.Add(notification.News | notification.BySms)
60+
ntf.Flags.Remove(notification.Replies | notification.Likes)
61+
fmt.Println(ntf.FlagsToString()) // Messages, News, By email, By sms
62+
fmt.Println(ntf.HasFlag(notification.Messages)) // true
63+
fmt.Println(ntf.HasFlag(notification.Likes)) // false
64+
65+
ntf.Flags.Reset()
66+
fmt.Println(ntf.Flags.Get()) // 0
67+
fmt.Println(ntf.GetFlagsNames()) // []
68+
69+
ntf.SetAll()
70+
fmt.Println(ntf.Flags.Get()) // 255
71+
fmt.Println(ntf.FlagsToString()) // Messages, Replies, Likes, New articles, News, By email, By sms, By telegram
72+
}
73+
```
74+
[full main.go](example/main.go)

bit_flags.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package bit_flags
2+
3+
import "math/bits"
4+
5+
type uints interface {
6+
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
7+
}
8+
9+
// BitFlags stores boolean flags
10+
//
11+
// Use uintN type to store up to N flags, e.g. uint8 to <=8 flags
12+
type BitFlags[T uints] struct {
13+
flags T
14+
}
15+
16+
func New[T uints]() *BitFlags[T] {
17+
return new(BitFlags[T])
18+
}
19+
20+
// Set sets the stored flags from the specified
21+
func (f *BitFlags[T]) Set(flags T) {
22+
f.flags = flags
23+
}
24+
25+
// Get returns the stored flags
26+
func (f BitFlags[T]) Get() T {
27+
return f.flags
28+
}
29+
30+
// Add adds the specified flags to the stored
31+
func (f *BitFlags[T]) Add(flags T) {
32+
f.flags |= flags
33+
}
34+
35+
// Remove deletes the specified flags from the stored
36+
func (f *BitFlags[T]) Remove(flags T) {
37+
f.flags ^= flags
38+
}
39+
40+
// Reset sets the stored flags to zero
41+
func (f *BitFlags[T]) Reset() {
42+
f.flags = 0
43+
}
44+
45+
// Has checks if all the specified flags exists in the stored
46+
func (f BitFlags[T]) Has(flags T) bool {
47+
return f.flags&flags == flags
48+
}
49+
50+
// List returns a list of the stored flags
51+
func (f BitFlags[T]) List() []T {
52+
result := make([]T, 0, bits.Len64(uint64(f.flags)))
53+
for i := 0; i < cap(result); i++ {
54+
pow := T(1 << i)
55+
if f.flags&pow == pow {
56+
result = append(result, pow)
57+
}
58+
}
59+
return result
60+
}

bit_flags_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package bit_flags
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
const (
10+
testProperty1 = 1 << iota
11+
testProperty2
12+
testProperty3
13+
testProperty4
14+
testProperty5
15+
testProperty6
16+
)
17+
18+
func TestBitProperties_Set(t *testing.T) {
19+
bf := New[uint8]()
20+
bf.Set(testProperty1 | testProperty5)
21+
require.Equal(t, uint8(17), bf.Get())
22+
}
23+
24+
func TestBitProperties_Add(t *testing.T) {
25+
bf := New[uint8]()
26+
bf.Set(testProperty1 | testProperty5)
27+
bf.Add(testProperty2 | testProperty3)
28+
require.Equal(t, uint8(23), bf.Get())
29+
}
30+
31+
func TestBitProperties_Remove(t *testing.T) {
32+
bf := New[uint8]()
33+
bf.Set(testProperty1 | testProperty5 | testProperty4 | testProperty6)
34+
bf.Remove(testProperty5)
35+
require.Equal(t, uint8(41), bf.Get())
36+
require.Equal(t, false, bf.Has(testProperty5|testProperty1))
37+
require.Equal(t, true, bf.Has(testProperty1))
38+
}
39+
40+
func TestBitProperties_Reset(t *testing.T) {
41+
bf := New[uint8]()
42+
bf.Set(testProperty1 | testProperty5)
43+
bf.Reset()
44+
require.Equal(t, uint8(0), bf.Get())
45+
}
46+
47+
func TestBitProperties_Has(t *testing.T) {
48+
bf := New[uint8]()
49+
bf.Set(testProperty1 | testProperty5 | testProperty4 | testProperty6)
50+
require.Equal(t, true, bf.Has(testProperty5|testProperty1))
51+
require.Equal(t, false, bf.Has(testProperty2))
52+
}
53+
54+
func TestBitProperties_List(t *testing.T) {
55+
bf := New[uint8]()
56+
bf.Set(testProperty1 | testProperty5 | testProperty4 | testProperty6)
57+
exp := []uint8{testProperty1, testProperty4, testProperty5, testProperty6}
58+
require.Equal(t, exp, bf.List())
59+
}

example/main.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
func main() {
8+
ntf := New()
9+
fmt.Println(ntf.Flags.Get()) // 39
10+
fmt.Println(ntf.FlagsToString()) // Messages, Replies, Likes, By email
11+
12+
ntf.Flags.Add(News | BySms)
13+
ntf.Flags.Remove(Replies | Likes)
14+
fmt.Println(ntf.FlagsToString()) // Messages, News, By email, By sms
15+
fmt.Println(ntf.HasFlag(Messages)) // true
16+
fmt.Println(ntf.HasFlag(Likes)) // false
17+
18+
ntf.Flags.Reset()
19+
fmt.Println(ntf.Flags.Get()) // 0
20+
fmt.Println(ntf.GetFlagsNames()) // []
21+
22+
ntf.SetAll()
23+
fmt.Println(ntf.Flags.Get()) // 255
24+
fmt.Println(ntf.FlagsToString()) // Messages, Replies, Likes, New articles, News, By email, By sms, By telegram
25+
}

example/notification.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package main
2+
3+
import (
4+
"strings"
5+
6+
bf "github.com/goiste/bit_flags"
7+
)
8+
9+
const (
10+
Messages = 1 << iota // 1
11+
Replies // 2
12+
Likes // 4
13+
NewArticles // ... powers of 2
14+
News
15+
16+
ByEmail
17+
BySms
18+
ByTelegram
19+
20+
AllNotifications = Messages | Replies | Likes | NewArticles | News
21+
AllMethods = ByEmail | BySms | ByTelegram
22+
)
23+
24+
type Notification struct {
25+
SomeOtherFields string
26+
Flags bf.BitFlags[uint8]
27+
}
28+
29+
func New() *Notification {
30+
n := &Notification{}
31+
n.SetDefaultFlags()
32+
return n
33+
}
34+
35+
func (n *Notification) SetDefaultFlags() {
36+
n.Flags.Set(Messages | Replies | Likes | ByEmail)
37+
}
38+
39+
func (n *Notification) SetAll() {
40+
n.Flags.Set(AllNotifications | AllMethods)
41+
}
42+
43+
func (n *Notification) SetNone() {
44+
n.Flags.Reset()
45+
}
46+
47+
func (n Notification) HasFlag(flag uint8) bool {
48+
return n.Flags.Has(flag)
49+
}
50+
51+
func (n Notification) FlagsToString() string {
52+
return strings.Join(n.GetFlagsNames(), ", ")
53+
}
54+
55+
func (n Notification) GetFlagsNames() (names []string) {
56+
for _, p := range n.Flags.List() {
57+
name := getFlagName(p)
58+
if name == "" {
59+
continue
60+
}
61+
names = append(names, name)
62+
}
63+
return
64+
}
65+
66+
func getFlagName(flag uint8) string {
67+
switch flag {
68+
case Messages:
69+
return "Messages"
70+
case Replies:
71+
return "Replies"
72+
case Likes:
73+
return "Likes"
74+
case NewArticles:
75+
return "New articles"
76+
case News:
77+
return "News"
78+
case ByEmail:
79+
return "By email"
80+
case BySms:
81+
return "By sms"
82+
case ByTelegram:
83+
return "By telegram"
84+
}
85+
return ""
86+
}

go.mod

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/goiste/bit_flags
2+
3+
go 1.18
4+
5+
require github.com/stretchr/testify v1.8.0
6+
7+
require (
8+
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/pmezard/go-difflib v1.0.0 // indirect
10+
gopkg.in/yaml.v3 v3.0.1 // indirect
11+
)

go.sum

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
5+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
8+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
9+
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
10+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
11+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
12+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
13+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
14+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
15+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)