Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
Signed-off-by: George Lemon <georgelemon@protonmail.com>
  • Loading branch information
georgelemon committed Mar 30, 2024
1 parent c986c2c commit 0708559
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 84 deletions.
3 changes: 2 additions & 1 deletion emitter.nimble
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Package

version = "0.1.0"
version = "0.1.1"
author = "George Lemon"
description = "Supranim's Event Emitter - Subscribe & listen for various events within your application"
license = "MIT"
Expand All @@ -10,6 +10,7 @@ srcDir = "src"
# Dependencies

requires "nim >= 1.4.0"
requires "malebolgia#head"

task docgen, "Generate API documentation":
exec "nim doc --project --index:on --outdir:htmldocs src/emitter.nim"
Expand Down
172 changes: 89 additions & 83 deletions src/emitter.nim
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,107 +1,113 @@
# Supranim's Event Emitter - Subscribe & listen for
# various events within your application.
#
# This package is part of Supranim Hyper Framework.
#
# Supranim is a simple MVC-style web framework for building
# fast web applications, REST API microservices and other cool things.
#
# (c) 2021 Events is released under MIT License
# Made by Humans from OpenPeep
#
# https://github.com/supranim
# https://supranim.com

import std/[tables, macros, typeinfo]
import std/[tables, sequtils, macros, typeinfo]
from std/strutils import count, startsWith

# import std/threadpool
# import pkg/malebolgia

export tables, typeinfo

type
RunType* = enum
Anytime, Once
RunType* = enum
Anytime, Once

Arg* = ref object
value*: Any

Arg* = ref object
value*: Any
Callback* = proc(args: seq[Arg]) {.nimcall.}

Callback* = proc(args: varargs[Arg] = @[]) {.nimcall.}
Listener* = tuple[id: string, runCallable: Callback, runType: RunType]

Listener* = tuple[id: string, runCallable: Callback, runType: RunType]
EventEmitter = ref object
## Main object that holds all events and listeners,
## available as a Singleton as ``Event``
subscribers: Table[string, seq[Listener]]
# m: Master

EventEmitter = object
## Main object that holds all events and listeners,
## available as a Singleton as ``Event``
subscribers: Table[string, seq[Listener]]

EventEmitterError = object of CatchableError
EventEmitterError = object of CatchableError

when compileOption("threads"):
var Event* {.threadvar.}: EventEmitter
else:
var Event*: EventEmitter
# when compileOption("threads"):
# var Event* {.threadvar.}: EventEmitter
# else:
var Event*: EventEmitter

proc init*[E: EventEmitter](e: var E) =
Event = EventEmitter()
proc init*(e: var EventEmitter) =
Event = EventEmitter()

template newArg*(argValue: auto): untyped =
## Create a new Arg object instance based on
## ``Any`` value from ``std/typeinfo``
var
vany: Any
val = argValue
proc initNewArg(v: Any): Arg =
return Arg(value: v)
vany = toAny(val)
initNewArg(vany)

proc registerListener[E: EventEmitter](emitter: var E, key: string, handler: Callback, runType: RunType) =
## Main proc for registering listeners to a specific events by ``key``.
## TODO allow only lowercase keys, separated by dot, e.g. "user.update.account"
# if count(listener.id, '*') != 1:
# raise newException(EventEmitterError, "A key cannot contain more than one wildcard") # for now
if not emitter.subscribers.hasKey(key):
emitter.subscribers[key] = newSeq[Listener]()
emitter.subscribers[key].add (id: key, runCallable: handler, runType: runType)

template listen*[E: EventEmitter](emitter: var E, key: string, handler: Callback): untyped =
## Subscribe to a specific event with a runnable callback.
##
## You may want to register a listener using the ``*``
## as a wildcard for ``key`` parameter. This allows you to
## register same listener to multiple events.
runnableExamples:
Event.listen("account.update.email") do(args: varargs[Arg]):
echo "Email address has been changed."

Event.listen("account.*") do(args: varargs[Arg]):
echo "Listening for any events related to `account.`"

registerListener(emitter, key, handler, Anytime)

template listenOnce*[E: EventEmitter](emitter: var E, key: string, handler: Callback): untyped =
## Same as ``listen`` proc, the only difference is
## that this listener can run only once
registerListener(emitter, key, handler, Once)

template emit*[E: EventEmitter](emitter: var E, eventId: string, args: varargs[Arg] = @[]): untyped =
## Call an event by ``id`` and trigger all registered listeners
## related to the event. You can pass one or more ``args``
## to listener callback using the ``newArg`` procedure.
runnableExamples:
Event.emit("account.update.email", newArg("new.address@example.com"), newArg("192.168.1.1"))

if emitter.subscribers.hasKey(eventId):
for i, listener in pairs(emitter.subscribers[eventId]):
## Create a new Arg object instance based on
## ``Any`` value from ``std/typeinfo``
var
vany: Any
val = argValue
proc initNewArg(v: Any): Arg =
return Arg(value: v)
vany = toAny(val)
initNewArg(vany)


proc registerListener(emitter: var EventEmitter, key: string, handler: Callback, runType: RunType) =
## Main proc for registering listeners to a specific events by ``key``.
## TODO allow only lowercase keys, separated by dot, e.g. "user.update.account"
# if count(listener.id, '*') != 1:
# raise newException(EventEmitterError, "A key cannot contain more than one wildcard") # for now
if not emitter.subscribers.hasKey(key):
emitter.subscribers[key] = newSeq[Listener]()
emitter.subscribers[key].add (id: key, runCallable: handler, runType: runType)

proc listen*(emitter: var EventEmitter, key: string, handler: Callback) =
## Subscribe to a specific event with a runnable callback.
##
## You may want to register a listener using the ``*``
## as a wildcard for ``key`` parameter. This allows you to
## register same listener to multiple events.
runnableExamples:
Event.listen("account.update.email") do(args: seq[Arg]):
echo "Email address has been changed."

Event.listen("account.*") do(args: seq[Arg]):
echo "Listening for any events related to `account.`"

registerListener(emitter, key, handler, Anytime)

template listenOnce*(emitter: var EventEmitter, key: string, handler: Callback) =
## Same as ``listen`` proc, the only difference is
## that this listener can run only once
registerListener(emitter, key, handler, Once)

proc runCallback(args: ptr seq[Arg], listen: ptr Listener) =
{.gcsafe.}:
listen[].runCallable(@[])

proc emit*(emitter: var EventEmitter, eventId: string, args: seq[Arg] = @[]) =
## Call an event by ``id`` and trigger all registered listeners
## related to the event. You can pass one or more ``args``
## to listener callback using the ``newArg`` procedure.
runnableExamples:
Event.emit("account.update.email", newArg("new.address@example.com"), newArg("192.168.1.1"))
if emitter.subscribers.hasKey(eventId):
for i, listener in pairs(emitter.subscribers[eventId]):
let x = args
# spawn runCallback(x.addr, listener.addr)
runCallback(x.addr, listener.addr)
if likely(listener.runType == Once):
emitter.subscribers[eventId].delete(i)
else:
for subId, event in pairs(emitter.subscribers):
if likely(subId[^1] == '*'):
for i, listener in pairs(emitter.subscribers[subId]):
if startsWith(eventId, subId[0 .. ^2]):
listener.runCallable(args)
if likely(listener.runType == Once):
emitter.subscribers[eventId].delete(i)
else:
for subId, event in pairs(emitter.subscribers):
if likely(subId[^1] == '*'):
for i, listener in pairs(emitter.subscribers[subId]):
if startsWith(eventId, subId[0 .. ^2]):
listener.runCallable(args)
if likely(listener.runType == Once):
emitter.subscribers[subId].delete(i)
else: continue
else: continue
if likely(listener.runType == Once):
emitter.subscribers[subId].delete(i)
else: continue
else: continue

0 comments on commit 0708559

Please sign in to comment.