├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── default.go ├── dispatcher.go ├── event.go └── event_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alexander Gromov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-event 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/AlexanderGrom/go-event)](https://goreportcard.com/report/github.com/AlexanderGrom/go-event) [![GoDoc](https://godoc.org/github.com/AlexanderGrom/go-event?status.svg)](https://godoc.org/github.com/AlexanderGrom/go-event) 3 | 4 | Go-event is a simple event system. 5 | 6 | ## Get the package 7 | ```bash 8 | $ go get -u github.com/AlexanderGrom/go-event 9 | ``` 10 | 11 | ## Examples 12 | ```go 13 | e := event.New() 14 | e.On("my.event.name.1", func() error { 15 | fmt.Println("Fire event") 16 | return nil 17 | }) 18 | 19 | e.On("my.event.name.2", func(text string) error { 20 | fmt.Println("Fire", text) 21 | return nil 22 | }) 23 | 24 | e.On("my.event.name.3", func(i, j int) error { 25 | fmt.Println("Fire", i+j) 26 | return nil 27 | }) 28 | 29 | e.On("my.event.name.4", func(name string, params ...string) error { 30 | fmt.Println(name, params) 31 | return nil 32 | }) 33 | 34 | e.Go("my.event.name.1") // Print: Fire event 35 | e.Go("my.event.name.2", "some event") // Print: Fire some event 36 | e.Go("my.event.name.3", 1, 2) // Print: Fire 3 37 | e.Go("my.event.name.4", "params:", "a", "b", "c") // Print: params: [a b c] 38 | ``` 39 | 40 | A couple more examples 41 | ```go 42 | package main 43 | 44 | import ( 45 | "fmt" 46 | 47 | "github.com/AlexanderGrom/go-event" 48 | ) 49 | 50 | func EventFunc(text string) error { 51 | fmt.Println("Fire:", text, "1") 52 | return nil 53 | } 54 | 55 | type EventStruct struct{} 56 | 57 | func (e *EventStruct) EventFunc(text string) error { 58 | fmt.Println("Fire:", text, "2") 59 | return nil 60 | } 61 | 62 | func main() { 63 | event.On("my.event.name.1", EventFunc) 64 | event.On("my.event.name.1", (&EventStruct{}).EventFunc) 65 | 66 | event.Go("my.event.name.1", "event") 67 | // Print: Fire event 1 68 | // Print: Fire event 2 69 | } 70 | ``` 71 | 72 | ### A more complex example 73 | 74 | ```go 75 | package main 76 | 77 | import ( 78 | "fmt" 79 | 80 | "github.com/AlexanderGrom/go-event" 81 | ) 82 | 83 | type ( 84 | Listener interface { 85 | Name() string 86 | Handle(e event.Eventer) error 87 | } 88 | ) 89 | 90 | // ... 91 | 92 | type ( 93 | fooEvent struct { 94 | event.Event 95 | 96 | i, j int 97 | } 98 | 99 | fooListener struct { 100 | name string 101 | } 102 | ) 103 | 104 | func NewFooEvent(i, j int) event.Eventer { 105 | return &fooEvent{i:i, j:j} 106 | } 107 | 108 | func NewFooListener() Listener { 109 | return &fooListener{ 110 | name: "my.foo.event", 111 | } 112 | } 113 | 114 | func (l *fooListener) Name() string { 115 | return l.name 116 | } 117 | 118 | func (l *fooListener) Handle(e event.Eventer) error { 119 | ev := e.(*fooEvent) 120 | ev.StopPropagation() 121 | 122 | fmt.Println("Fire", ev.i+ev.j) 123 | return nil 124 | } 125 | 126 | // ... 127 | 128 | func main() { 129 | e := event.New() 130 | 131 | // Collection 132 | collect := []Listener{ 133 | NewFooListener(), 134 | // ... 135 | } 136 | 137 | // ... 138 | 139 | // Registration 140 | for _, l := range collect { 141 | e.On(l.Name(), l.Handle) 142 | } 143 | 144 | // ... 145 | 146 | // Call 147 | e.Go("my.foo.event", NewFooEvent(1, 2)) 148 | // Print: Fire 3 149 | } 150 | ``` -------------------------------------------------------------------------------- /default.go: -------------------------------------------------------------------------------- 1 | // Package event is a simple event system. 2 | package event 3 | 4 | // Dispatcher event interface 5 | type Dispatcher interface { 6 | On(name string, fn interface{}) error 7 | Go(name string, params ...interface{}) error 8 | Has(name string) bool 9 | List() []string 10 | Remove(names ...string) 11 | } 12 | 13 | // Default event instance 14 | var globalSource = New() 15 | 16 | // On set new listener from the default source. 17 | func On(name string, fn interface{}) error { 18 | return globalSource.On(name, fn) 19 | } 20 | 21 | // Go firing an event from the default source. 22 | func Go(name string, params ...interface{}) error { 23 | return globalSource.Go(name, params...) 24 | } 25 | 26 | // Has returns true if a event exists from the default source. 27 | func Has(name string) bool { 28 | return globalSource.Has(name) 29 | } 30 | 31 | // List returns list events from the default source. 32 | func List() []string { 33 | return globalSource.List() 34 | } 35 | 36 | // Remove delete events from the event list from the default source. 37 | func Remove(names ...string) { 38 | globalSource.Remove(names...) 39 | } 40 | -------------------------------------------------------------------------------- /dispatcher.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "sync" 7 | ) 8 | 9 | // Event implementation 10 | type event struct { 11 | sync.RWMutex 12 | 13 | events map[string][]interface{} 14 | } 15 | 16 | // New returns a new event.Dispatcher 17 | func New() Dispatcher { 18 | return &event{ 19 | events: make(map[string][]interface{}), 20 | } 21 | } 22 | 23 | // On set new listener 24 | func (e *event) On(name string, fn interface{}) error { 25 | e.Lock() 26 | defer e.Unlock() 27 | 28 | if fn == nil { 29 | return errors.New("fn is nil") 30 | } 31 | if _, ok := fn.(handle); ok { 32 | e.events[name] = append(e.events[name], fn) 33 | return nil 34 | } 35 | 36 | t := reflect.TypeOf(fn) 37 | if t.Kind() != reflect.Func { 38 | return errors.New("fn is not a function") 39 | } 40 | if t.NumOut() != 1 { 41 | return errors.New("fn must have one return value") 42 | } 43 | if t.Out(0) != reflect.TypeOf((*error)(nil)).Elem() { 44 | return errors.New("fn must return an error message") 45 | } 46 | if list, ok := e.events[name]; ok && len(list) > 0 { 47 | tt := reflect.TypeOf(list[0]) 48 | if tt.NumIn() != t.NumIn() { 49 | return errors.New("fn signature is not equal") 50 | } 51 | for i := 0; i < tt.NumIn(); i++ { 52 | if tt.In(i) != t.In(i) { 53 | return errors.New("fn signature is not equal") 54 | } 55 | } 56 | } 57 | 58 | e.events[name] = append(e.events[name], fn) 59 | return nil 60 | } 61 | 62 | // Go firing an event 63 | func (e *event) Go(name string, params ...interface{}) error { 64 | e.RLock() 65 | defer e.RUnlock() 66 | 67 | fns := e.events[name] 68 | for i := len(fns) - 1; i >= 0; i-- { 69 | stopped, err := e.call(fns[i], params...) 70 | if err != nil { 71 | return err 72 | } 73 | if stopped { 74 | break 75 | } 76 | } 77 | 78 | return nil 79 | } 80 | 81 | func (e *event) call(fn interface{}, params ...interface{}) (stopped bool, err error) { 82 | if f, ok := fn.(handle); ok { 83 | if len(params) != 1 { 84 | return stopped, errors.New("parameters mismatched") 85 | } 86 | event, ok := (params[0]).(Eventer) 87 | if !ok { 88 | return stopped, errors.New("parameters mismatched") 89 | } 90 | err = f(event) 91 | return event.IsPropagationStopped(), err 92 | } 93 | 94 | var ( 95 | f = reflect.ValueOf(fn) 96 | t = f.Type() 97 | numIn = t.NumIn() 98 | in = make([]reflect.Value, 0, numIn) 99 | ) 100 | 101 | if t.IsVariadic() { 102 | n := numIn - 1 103 | if len(params) < n { 104 | return stopped, errors.New("parameters mismatched") 105 | } 106 | for _, param := range params[:n] { 107 | in = append(in, reflect.ValueOf(param)) 108 | } 109 | s := reflect.MakeSlice(t.In(n), 0, len(params[n:])) 110 | for _, param := range params[n:] { 111 | s = reflect.Append(s, reflect.ValueOf(param)) 112 | } 113 | in = append(in, s) 114 | 115 | err, _ = f.CallSlice(in)[0].Interface().(error) 116 | return stopped, err 117 | } 118 | 119 | if len(params) != numIn { 120 | return stopped, errors.New("parameters mismatched") 121 | } 122 | for _, param := range params { 123 | in = append(in, reflect.ValueOf(param)) 124 | } 125 | 126 | err, _ = f.Call(in)[0].Interface().(error) 127 | return stopped, err 128 | } 129 | 130 | // Has returns true if a event exists 131 | func (e *event) Has(name string) bool { 132 | e.RLock() 133 | defer e.RUnlock() 134 | _, ok := e.events[name] 135 | return ok 136 | } 137 | 138 | // List returns list events 139 | func (e *event) List() []string { 140 | e.RLock() 141 | defer e.RUnlock() 142 | list := make([]string, 0, len(e.events)) 143 | for name := range e.events { 144 | list = append(list, name) 145 | } 146 | return list 147 | } 148 | 149 | // Remove delete events from the event list 150 | func (e *event) Remove(names ...string) { 151 | e.Lock() 152 | defer e.Unlock() 153 | if len(names) > 0 { 154 | for _, name := range names { 155 | delete(e.events, name) 156 | } 157 | return 158 | } 159 | e.events = make(map[string][]interface{}) 160 | } 161 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | // Package event is a simple event system. 2 | package event 3 | 4 | type ( 5 | // Eventer interface 6 | Eventer interface { 7 | StopPropagation() 8 | IsPropagationStopped() bool 9 | } 10 | 11 | // Event is the base class for classes containing event data 12 | Event struct { 13 | stopped bool 14 | } 15 | 16 | // handle aliase 17 | handle = func(Eventer) error 18 | ) 19 | 20 | // StopPropagation Stops the propagation of the event to further event listeners 21 | func (e *Event) StopPropagation() { 22 | e.stopped = true 23 | } 24 | 25 | // IsPropagationStopped returns whether further event listeners should be triggered 26 | func (e *Event) IsPropagationStopped() bool { 27 | return e.stopped 28 | } 29 | -------------------------------------------------------------------------------- /event_test.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "errors" 5 | "sort" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestOn(t *testing.T) { 12 | testCases := []struct { 13 | name string 14 | fn interface{} 15 | assertion assert.ErrorAssertionFunc 16 | }{ 17 | { 18 | name: "test.event.on.1", 19 | fn: func() error { return nil }, 20 | assertion: assert.NoError, 21 | }, { 22 | name: "test.event.on.2", 23 | fn: func(i int) error { return nil }, 24 | assertion: assert.NoError, 25 | }, { 26 | name: "test.event.on.2", 27 | fn: func(i int) error { return nil }, 28 | assertion: assert.NoError, 29 | }, { 30 | name: "test.event.on.2", 31 | fn: func(i string) error { return nil }, 32 | assertion: assert.Error, 33 | }, { 34 | name: "test.event.on.2", 35 | fn: func(i int, j string) error { return nil }, 36 | assertion: assert.Error, 37 | }, { 38 | name: "test.event.on.3", 39 | fn: func() int { return 0 }, 40 | assertion: assert.Error, 41 | }, { 42 | name: "test.event.on.4", 43 | fn: func() (int, error) { return 0, nil }, 44 | assertion: assert.Error, 45 | }, { 46 | name: "test.event.on.5", 47 | fn: func() {}, 48 | assertion: assert.Error, 49 | }, { 50 | name: "test.event.on.6", 51 | fn: nil, 52 | assertion: assert.Error, 53 | }, { 54 | name: "test.event.on.7", 55 | fn: "func", 56 | assertion: assert.Error, 57 | }, { 58 | name: "test.event.on.8", 59 | fn: func(e Event) error { return nil }, 60 | assertion: assert.NoError, 61 | }, 62 | } 63 | 64 | e := New() 65 | for _, tt := range testCases { 66 | t.Run(tt.name, func(t *testing.T) { 67 | err := e.On(tt.name, tt.fn) 68 | tt.assertion(t, err) 69 | }) 70 | } 71 | } 72 | 73 | func TestGo(t *testing.T) { 74 | type str struct { 75 | count int 76 | } 77 | type event struct { 78 | Event 79 | count int 80 | } 81 | counter := 0 82 | testCases := []struct { 83 | name string 84 | fn interface{} 85 | params []interface{} 86 | count int 87 | assertion assert.ErrorAssertionFunc 88 | }{ 89 | { 90 | name: "test.event.go.1", 91 | fn: func() error { counter++; return nil }, 92 | params: []interface{}{}, 93 | count: 1, 94 | assertion: assert.NoError, 95 | }, { 96 | name: "test.event.go.1", 97 | fn: func() error { counter++; return nil }, 98 | params: []interface{}{}, 99 | count: 3, 100 | assertion: assert.NoError, 101 | }, { 102 | name: "test.event.go.2", 103 | fn: func() error { counter = 0; return nil }, 104 | params: []interface{}{}, 105 | count: 0, 106 | assertion: assert.NoError, 107 | }, { 108 | name: "test.event.go.3", 109 | fn: func() error { return errors.New("some error") }, 110 | params: []interface{}{}, 111 | count: 0, 112 | assertion: assert.Error, 113 | }, { 114 | name: "test.event.go.4", 115 | fn: func(i int) error { counter = i; return nil }, 116 | params: []interface{}{1}, 117 | count: 1, 118 | assertion: assert.NoError, 119 | }, { 120 | name: "test.event.go.5", 121 | fn: func(i int, j int) error { counter = i + j; return nil }, 122 | params: []interface{}{1, 2}, 123 | count: 3, 124 | assertion: assert.NoError, 125 | }, { 126 | name: "test.event.go.6", 127 | fn: func(i int, j ...int) error { counter = len(j); return nil }, 128 | params: []interface{}{1, 2, 3}, 129 | count: 2, 130 | assertion: assert.NoError, 131 | }, { 132 | name: "test.event.go.7", 133 | fn: func(i string, j ...int) error { counter = len(j); return nil }, 134 | params: []interface{}{"A", 1, 2}, 135 | count: 2, 136 | assertion: assert.NoError, 137 | }, { 138 | name: "test.event.go.8", 139 | fn: func(i str, j *str, k ...int) error { counter = len(k) + j.count; return nil }, 140 | params: []interface{}{str{1}, &str{2}, 3, 4}, 141 | count: 4, 142 | assertion: assert.NoError, 143 | }, { 144 | name: "test.event.go.9", 145 | fn: func(i ...int) error { counter = len(i); return nil }, 146 | params: []interface{}{}, 147 | count: 0, 148 | assertion: assert.NoError, 149 | }, { 150 | name: "test.event.go.10", 151 | fn: func(e Eventer) error { ev, _ := e.(*event); counter = ev.count; counter++; return nil }, 152 | params: []interface{}{&event{count: 1}}, 153 | count: 2, 154 | assertion: assert.NoError, 155 | }, { 156 | name: "test.event.go.10", 157 | fn: func(e Eventer) error { 158 | ev, _ := e.(*event) 159 | ev.StopPropagation() 160 | counter = 0 161 | return nil 162 | }, 163 | params: []interface{}{&event{}}, 164 | count: 0, 165 | assertion: assert.NoError, 166 | }, { 167 | name: "test.event.go.11", 168 | fn: func(e Eventer) error { return nil }, 169 | params: []interface{}{}, 170 | count: 0, 171 | assertion: assert.Error, 172 | }, { 173 | name: "test.event.go.12", 174 | fn: func(e Eventer) error { return nil }, 175 | params: []interface{}{str{}}, 176 | count: 0, 177 | assertion: assert.Error, 178 | }, { 179 | name: "test.event.go.13", 180 | fn: func(i int, j ...int) error { return nil }, 181 | params: []interface{}{}, 182 | count: 0, 183 | assertion: assert.Error, 184 | }, { 185 | name: "test.event.go.14", 186 | fn: func(i int) error { return nil }, 187 | params: []interface{}{}, 188 | count: 0, 189 | assertion: assert.Error, 190 | }, 191 | } 192 | 193 | e := New() 194 | for _, tt := range testCases { 195 | t.Run(tt.name, func(t *testing.T) { 196 | err := e.On(tt.name, tt.fn) 197 | assert.NoError(t, err) 198 | 199 | err = e.Go(tt.name, tt.params...) 200 | tt.assertion(t, err) 201 | 202 | assert.Equal(t, tt.count, counter) 203 | }) 204 | } 205 | } 206 | 207 | func TestHas(t *testing.T) { 208 | e := New() 209 | e.On("test.event.has.1", func() error { return nil }) 210 | e.On("test.event.has.2", func() error { return nil }) 211 | assert.True(t, e.Has("test.event.has.1")) 212 | assert.False(t, e.Has("test.event.has.3")) 213 | } 214 | 215 | func TestList(t *testing.T) { 216 | e := New() 217 | e.On("test.event.ls.1", func() error { return nil }) 218 | e.On("test.event.ls.1", func() error { return nil }) 219 | e.On("test.event.ls.2", func() error { return nil }) 220 | e.On("test.event.ls.3", func() error { return nil }) 221 | list := e.List() 222 | assert.Equal(t, 3, len(list)) 223 | sort.Strings(list) 224 | assert.Equal(t, []string{"test.event.ls.1", "test.event.ls.2", "test.event.ls.3"}, list) 225 | } 226 | 227 | func TestRemove(t *testing.T) { 228 | e := New() 229 | e.On("test.event.has.1", func() error { return nil }) 230 | e.On("test.event.has.2", func() error { return nil }) 231 | e.On("test.event.has.3", func() error { return nil }) 232 | assert.Equal(t, 3, len(e.List())) 233 | e.Remove("test.event.has.2") 234 | assert.Equal(t, 2, len(e.List())) 235 | assert.False(t, e.Has("test.event.has.2")) 236 | e.Remove("test.event.has.2") 237 | assert.Equal(t, 2, len(e.List())) 238 | e.Remove() 239 | assert.Equal(t, 0, len(e.List())) 240 | } 241 | --------------------------------------------------------------------------------