up: update name match logic and add more tests

This commit is contained in:
Inhere
2023-06-08 00:46:02 +08:00
parent 6a692b41b4
commit 65c875403f
6 changed files with 178 additions and 49 deletions

View File

@@ -10,10 +10,12 @@ Lightweight event management, dispatch tool library implemented by Go
- Support for custom definition event objects
- Support for adding multiple listeners to an event
- Supports setting the priority of the event listener. The higher the priority, the higher the trigger.
- Support setting the priority of the event listener, the higher the priority, the first to trigger
- Support for a set of event listeners based on the event name prefix `PREFIX.*`.
- add `app.*` event listen, trigger `app.run` `app.end`, Both will trigger the `app.*` event at the same time
- `ModeSimple` - `app.*` event listen, trigger `app.run` `app.end`, Both will trigger the `app.*` event at the same time
- `ModePath` - `db.**` event listen, trigger `db.run` `db.end`, Only trigger the `db.**` event once
- Support for using the wildcard `*` to listen for triggers for all events
- Support async trigger event by `go` channel consumers. use `Async(), FireAsync()`
- Complete unit testing, unit coverage `> 95%`
## [中文说明](README.zh-CN.md)
@@ -22,7 +24,7 @@ Lightweight event management, dispatch tool library implemented by Go
## GoDoc
- [Godoc for github](https://pkg.go.dev/github.com/gookit/event)
- [Godoc for GitHub](https://pkg.go.dev/github.com/gookit/event)
## Install
@@ -38,7 +40,9 @@ go get github.com/gookit/event
- `MustTrigger/MustFire(name string, params M) Event` Trigger event, there will be panic if there is an error
- `FireEvent(e Event) (err error)` Trigger an event based on a given event instance
- `FireBatch(es ...interface{}) (ers []error)` Trigger multiple events at once
- `AsyncFire(e Event)` Async fire event by 'go' keywords
- `Async/FireC(name string, params M)` Push event to `chan`, asynchronous consumption processing
- `FireAsync(e Event)` Push event to `chan`, asynchronous consumption processing
- `AsyncFire(e Event)` Async fire event by 'go' keywords
## Quick start

View File

@@ -6,14 +6,16 @@
[![Coverage Status](https://coveralls.io/repos/github/gookit/event/badge.svg?branch=master)](https://coveralls.io/github/gookit/event?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/gookit/event)](https://goreportcard.com/report/github.com/gookit/event)
Go 实现的轻量级的事件管理、调度工具库
`event` Go 实现的轻量级的事件管理、调度工具库
- 支持自定义定义事件对象
- 支持自定义创建预定义事件对象
- 支持对一个事件添加多个监听器
- 支持设置事件监听器的优先级,优先级越高越先触发
- 支持根据事件名称前缀 `PREFIX.*` 来进行一组事件监听.
- 注册`app.*` 事件的监听,触发 `app.run` `app.end` 时,都将同时会触发 `app.*` 事件
- 支持使用通配符 `*` 来监听全部事件的触发
- 支持通过通配符 `*` 来进行一组事件的匹配监听.
- `ModeSimple` - 注册 `app.*` 事件的监听,触发 `app.run` `app.end` 时,都将同时会触发 `app.*` 事件
- `ModePath` - **NEW** `*` 只匹配一段非 `.` 的字符,可以进行更精细的监听; `**` 匹配任意多个字符,只能用于开头或结尾
- 支持直接使用通配符 `*` 来监听全部事件的触发
- 支持触发事件时投递到 `chan`, 异步进行消费处理. 触发: `Async(), FireAsync()`
- 完善的单元测试,单元覆盖率 `> 95%`
## [English](README.md)
@@ -22,7 +24,7 @@ English introduction, please see **[EN README](README.md)**
## GoDoc
- [Godoc for github](https://pkg.go.dev/github.com/gookit/event)
- [Godoc for GitHub](https://pkg.go.dev/github.com/gookit/event)
## 安装
@@ -35,10 +37,12 @@ go get github.com/gookit/event
- `On/Listen(name string, listener Listener, priority ...int)` 注册事件监听
- `Subscribe/AddSubscriber(sbr Subscriber)` 订阅,支持注册多个事件监听
- `Trigger/Fire(name string, params M) (error, Event)` 触发事件
- `MustTrigger/MustFire(name string, params M) Event` 触发事件有错误则会panic
- `FireEvent(e Event) (err error)` 根据给定的事件实例,触发事件
- `MustTrigger/MustFire(name string, params M) Event` 触发事件有错误则会panic
- `FireEvent(e Event) (err error)` 根据给定的事件实例,触发事件
- `FireBatch(es ...interface{}) (ers []error)` 一次触发多个事件
- `AsyncFire(e Event)` Async fire event by 'go' keywords
- `Async/FireC(name string, params M)` 投递触事件到 `chan`,异步消费处理
- `FireAsync(e Event)` 投递触事件到 `chan`,异步消费处理
- `AsyncFire(e Event)` 简单的通过 `go` 异步触发事件
## 快速使用

View File

@@ -1,10 +1,12 @@
// Package event is lightweight event manager and dispatcher implements by Go.
package event
// Wildcard event name
const Wildcard = "*"
const AnyNode = "*"
const AllNode = "**"
// wildcard event name
const (
Wildcard = "*"
AnyNode = "*"
AllNode = "**"
)
const (
// ModeSimple old mode, simple match group listener.
@@ -44,8 +46,8 @@ type ManagerFace interface {
type Options struct {
// EnableLock enable lock on fire event.
EnableLock bool
// ChanLength for fire events by goroutine
ChanLength int
// ChannelSize for fire events by goroutine
ChannelSize int
ConsumerNum int
// MatchMode event name match mode. default is ModeSimple
MatchMode uint8

View File

@@ -6,6 +6,11 @@ import (
"sync"
)
const (
defaultChannelSize = 100
defaultConsumerNum = 5
)
// Manager event manager definition. for manage events and listeners
type Manager struct {
Options
@@ -44,8 +49,8 @@ func NewManager(name string, fns ...OptionFn) *Manager {
}
// for async fire by goroutine
em.ConsumerNum = 5
em.ChanLength = 10
em.ConsumerNum = defaultConsumerNum
em.ChannelSize = defaultChannelSize
// apply options
return em.WithOptions(fns...)
@@ -154,6 +159,29 @@ func (em *Manager) Trigger(name string, params M) (error, Event) {
// Fire trigger event by name. if not found listener, will return (nil, nil)
func (em *Manager) Fire(name string, params M) (err error, e Event) {
// call listeners handle event
e, err = em.fireByName(name, params, false)
return
}
// Async fire event by go channel.
func (em *Manager) Async(name string, params M) {
_, _ = em.fireByName(name, params, true)
}
// FireC async fire event by go channel. alias of the method Async()
func (em *Manager) FireC(name string, params M) {
_, _ = em.fireByName(name, params, true)
}
// fire event by name.
//
// if useCh is true, will async fire by channel. always return (nil, nil)
//
// On useCh=false:
// - will call listeners handle event.
// - if not found listener, will return (nil, nil)
func (em *Manager) fireByName(name string, params M, useCh bool) (e Event, err error) {
name = goodName(name, false)
// use pre-defined Event
@@ -163,10 +191,16 @@ func (em *Manager) Fire(name string, params M) (err error, e Event) {
e.SetData(params)
}
} else {
// create a basic event instance
// create new basic event instance
e = em.newBasicEvent(name, params)
}
// fire by channel
if useCh {
em.FireAsync(e)
return nil, nil
}
// call listeners handle event
err = em.FireEvent(e)
return
@@ -183,17 +217,6 @@ func (em *Manager) FireEvent(e Event) (err error) {
e.Abort(false)
name := e.Name()
// fire direct matched listeners. eg: db.user.add
if lq, ok := em.listeners[name]; ok {
// sort by priority before call.
for _, li := range lq.Sort().Items() {
err = li.Listener.Handle(e)
if err != nil || e.IsAborted() {
return
}
}
}
// fire group listeners by wildcard. eg "db.user.*"
if em.MatchMode == ModePath {
err = em.firePathMode(name, e)
@@ -223,6 +246,17 @@ func (em *Manager) FireEvent(e Event) (err error) {
// Example:
// - event "db.user.add" will trigger listeners on the "db.user.*"
func (em *Manager) fireSimpleMode(name string, e Event) (err error) {
// fire direct matched listeners. eg: db.user.add
if lq, ok := em.listeners[name]; ok {
// sort by priority before call.
for _, li := range lq.Sort().Items() {
err = li.Listener.Handle(e)
if err != nil || e.IsAborted() {
return
}
}
}
pos := strings.LastIndexByte(name, '.')
if pos > 0 && pos < len(name) {
@@ -247,12 +281,8 @@ func (em *Manager) fireSimpleMode(name string, e Event) (err error) {
// - event "db.user.add" will trigger listeners on the "db.**"
// - event "db.user.add" will trigger listeners on the "db.user.*"
func (em *Manager) firePathMode(name string, e Event) (err error) {
if strings.IndexByte(name, '.') < 0 {
return nil
}
for pattern, lq := range em.listeners {
if pattern != name && matchNodePath(pattern, name, ".") {
if pattern == name || matchNodePath(pattern, name, ".") {
for _, li := range lq.Sort().Items() {
err = li.Listener.Handle(e)
if err != nil || e.IsAborted() {
@@ -265,14 +295,18 @@ func (em *Manager) firePathMode(name string, e Event) (err error) {
return nil
}
// Fire2 alias of the method Fire()
func (em *Manager) Fire2(name string, params M) (error, Event) {
return em.Fire(name, params)
}
// FireAsync async fire event by go channel.
//
// Note: if you want to use this method, you should call the method Close() after all events are fired.
func (em *Manager) FireAsync(e Event) {
if em.ConsumerNum <= 0 {
em.ConsumerNum = defaultConsumerNum
}
if em.ChannelSize <= 0 {
em.ChannelSize = defaultChannelSize
}
// FireByChan async fire event by go channel
func (em *Manager) FireByChan(e Event) {
// once make
// once make consumers
em.once.Do(func() {
em.makeConsumers()
})
@@ -283,11 +317,12 @@ func (em *Manager) FireByChan(e Event) {
// async fire event by 'go' keywords
func (em *Manager) makeConsumers() {
em.ch = make(chan Event, em.ChanLength)
em.ch = make(chan Event, em.ChannelSize)
// make event consumers
for i := 0; i < em.ConsumerNum; i++ {
go func() {
// keep running until channel closed
for e := range em.ch {
_ = em.FireEvent(e) // ignore async fire error
}
@@ -295,6 +330,14 @@ func (em *Manager) makeConsumers() {
}
}
// Close manager and event channel
func (em *Manager) Close() error {
if em.ch != nil {
close(em.ch)
}
return nil
}
// FireBatch fire multi event at once.
//
// Usage:
@@ -316,7 +359,7 @@ func (em *Manager) FireBatch(es ...any) (ers []error) {
return
}
// AsyncFire async fire event by 'go' keywords
// AsyncFire simple async fire event by 'go' keywords
func (em *Manager) AsyncFire(e Event) {
go func(e Event) {
_ = em.FireEvent(e)

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"testing"
"time"
"github.com/gookit/event"
"github.com/stretchr/testify/assert"
@@ -77,4 +78,73 @@ func TestManager_Fire_usePathMode(t *testing.T) {
assert.Contains(t, str, "db.*.update|")
assert.True(t, strings.Count(str, "|") == 3)
})
buf.Reset()
t.Run("not-exist", func(t *testing.T) {
err, e := em.Fire("not-exist", event.M{"user": "inhere"})
assert.NoError(t, err)
assert.Equal(t, "inhere", e.Get("user"))
str := buf.String()
fmt.Println(str)
assert.Equal(t, "*|", str)
})
}
func TestManager_Fire_AllNode(t *testing.T) {
em := event.NewManager("test", event.UsePathMode, event.EnableLock)
buf := new(bytes.Buffer)
em.Listen("**.add", event.ListenerFunc(func(e event.Event) error {
_, _ = buf.WriteString("**.add|")
return nil
}))
err, e := em.Trigger("db.user.add", event.M{"user": "inhere"})
assert.NoError(t, err)
assert.Equal(t, "inhere", e.Get("user"))
str := buf.String()
assert.Equal(t, "**.add|", str)
}
func TestManager_FireC(t *testing.T) {
em := event.NewManager("test", event.UsePathMode, event.EnableLock)
defer func(em *event.Manager) {
_ = em.Close()
}(em)
buf := new(bytes.Buffer)
em.Listen("db.user.*", event.ListenerFunc(func(e event.Event) error {
_, _ = buf.WriteString("db.user.*|")
return nil
}))
em.Listen("db.**", event.ListenerFunc(func(e event.Event) error {
_, _ = buf.WriteString("db.**|")
return nil
}), 1)
em.Listen("db.user.add", event.ListenerFunc(func(e event.Event) error {
_, _ = buf.WriteString("db.user.add|")
return nil
}), 2)
assert.True(t, em.HasListeners("db.user.*"))
em.FireC("db.user.add", event.M{"user": "inhere"})
time.Sleep(time.Millisecond * 100) // wait for async
str := buf.String()
fmt.Println(str)
assert.Contains(t, str, "db.**|")
assert.Contains(t, str, "db.user.*|")
assert.Contains(t, str, "db.user.add|")
assert.True(t, strings.Count(str, "|") == 3)
buf.Reset()
em.WithOptions(func(o *event.Options) {
o.ChannelSize = 0
o.ConsumerNum = 0
})
em.Async("not-exist", event.M{"user": "inhere"})
time.Sleep(time.Millisecond * 100) // wait for async
assert.Empty(t, buf.String())
}

10
util.go
View File

@@ -46,8 +46,14 @@ func goodName(name string, isReg bool) string {
panic("event: the event name cannot be empty")
}
if isReg && (name == AllNode || name == Wildcard) {
return Wildcard
// on add listener
if isReg {
if name == AllNode || name == Wildcard {
return Wildcard
}
if strings.HasPrefix(name, AllNode) {
return name
}
}
if !goodNameReg.MatchString(name) {