mirror of
https://github.com/aewens/ircevents
synced 2024-06-15 05:46:36 +00:00
Refactored process function to split concerns, made namespaces optional if only one is given, event loop will iterate over recv if iterable
This commit is contained in:
parent
ab10722f88
commit
2fa9de3c6d
|
@ -1,6 +1,7 @@
|
|||
from .helpers import infinitedict, trap
|
||||
|
||||
from collections import namedtuple
|
||||
from collections.abc import Iterable
|
||||
from functools import wraps
|
||||
from threading import Thread, Event
|
||||
from queue import Queue, Empty
|
||||
|
@ -36,8 +37,8 @@ class StateManager:
|
|||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get(self, key):
|
||||
getattr(self, key, None)
|
||||
def get(self, key, default=None):
|
||||
getattr(self, key, default)
|
||||
|
||||
def set(self, key, value):
|
||||
return setattr(self, key, value)
|
||||
|
@ -64,6 +65,7 @@ class Engine:
|
|||
self._recv_kwargs = dict()
|
||||
|
||||
self._states = defaultdict(lambda: StateManager())
|
||||
self._mutations = dict()
|
||||
|
||||
self._namespaces = set()
|
||||
self._whens = set()
|
||||
|
@ -93,10 +95,114 @@ class Engine:
|
|||
# Returns both the key and value to return like a dict
|
||||
yield (attr_name, attribute)
|
||||
|
||||
def ns_get(self, namespace, key):
|
||||
self._states[namespace].get(key)
|
||||
def _apply_mutations(self):
|
||||
"""
|
||||
Applies mutations and passes them on as generator
|
||||
"""
|
||||
|
||||
for using in self._using:
|
||||
mutation = using.callback(raw_line)
|
||||
self._mutations[using.namespace] = mutation
|
||||
yield (using, mutation)
|
||||
|
||||
def _process_mutations(self, requires, skip_whens):
|
||||
"""
|
||||
Run all mutation variables against callbacks to see if applicable
|
||||
"""
|
||||
|
||||
for (using, mutation) in self._apply_mutations():
|
||||
for (key, value} in self._get_variables(mutation):
|
||||
for using_whens in self._whens_map[key]:
|
||||
for using_when in using_whens:
|
||||
# Already been triggers, skip
|
||||
if using_when in skip_whens:
|
||||
continue
|
||||
|
||||
self._process_when(using_when, requires)
|
||||
|
||||
def _process_when(self, when, requires):
|
||||
"""
|
||||
Determines if required keys are present to check callback
|
||||
If so, will run the callback with the mutation and namespace state
|
||||
"""
|
||||
|
||||
# Check if all required fields are found
|
||||
whens_requires = requires.get(when)
|
||||
if whens_requires is None:
|
||||
requires[whens] = set(self._whens._fields)
|
||||
whens_requires = requires.get(whens)
|
||||
|
||||
whens_requires.remove(key)
|
||||
if len(whens_requires) > 0:
|
||||
return None
|
||||
|
||||
triggered = self._check_when(when)
|
||||
if not triggered:
|
||||
return None
|
||||
|
||||
state = self._states[namespace]
|
||||
func = self._whens_funcs.get(when)
|
||||
if func is None:
|
||||
return None
|
||||
|
||||
# Run callback using mutation data and state manager
|
||||
func(data, state)
|
||||
|
||||
def _check_when(self, when):
|
||||
"""
|
||||
Checks if mutation state will trigger callback
|
||||
"""
|
||||
|
||||
# If all requirements are found, stop checking this
|
||||
skip_whens.add(when)
|
||||
|
||||
namespace = self._whens_namespaces.get(when)
|
||||
data = mutations.get(namespace)
|
||||
if None in [namespace, data]:
|
||||
continue
|
||||
|
||||
trigger_when = True
|
||||
|
||||
# Use magic _always pair to always trigger callback
|
||||
if when.get("_always") is True:
|
||||
return trigger_when
|
||||
|
||||
# Check if conditions match mutation
|
||||
for when_key, when_value in when.items():
|
||||
when_path = when_key.split("__")
|
||||
pointer = data
|
||||
for wpath in when_path:
|
||||
if not isinstance(pointer, dict):
|
||||
eprint(f"Invalid path: {when_key}")
|
||||
break
|
||||
|
||||
pointer = pointer.get(wpath)
|
||||
|
||||
when_status = False
|
||||
|
||||
# Value can be a function complex checks
|
||||
if callable(when_value):
|
||||
when_status = when_value(pointer)
|
||||
|
||||
else:
|
||||
when_status = pointer == when_value
|
||||
|
||||
if not when_status:
|
||||
trigger_when = False
|
||||
break
|
||||
|
||||
return trigger_when
|
||||
|
||||
def ns_get(self, namespace, key, default=None):
|
||||
"""
|
||||
Shortcut to get namespace value outside of callback
|
||||
"""
|
||||
self._states[namespace].get(key, default)
|
||||
|
||||
def ns_set(self, namespace, key, value):
|
||||
"""
|
||||
Shortcut to set namespace value outside of callback
|
||||
"""
|
||||
self._states[namespace].set(key, value)
|
||||
|
||||
def use(self, namespace, callback):
|
||||
|
@ -107,13 +213,17 @@ class Engine:
|
|||
Mutation = namedtuple("Mutation", ["name", "callback"])
|
||||
self._using.add(Mutation(namespace, callback))
|
||||
|
||||
def when(self, namespace, **when_kwargs):
|
||||
def when(self, namespace=None, **when_kwargs):
|
||||
"""
|
||||
Decorator used to flag callback functions that the engine will use
|
||||
The namespace decides what scope of object to pass to callback
|
||||
The when keyword arguments determine what will trigger the callback
|
||||
"""
|
||||
|
||||
# Namespaces are optional if only one is given
|
||||
if namespace is None and len(self._namespaces) == 1:
|
||||
namespace = self._namespaces[0]
|
||||
|
||||
assert namespace in self._namespaces, f"Invalid namespace: {namespace}"
|
||||
|
||||
# Make hashable for set
|
||||
|
@ -145,80 +255,18 @@ class Engine:
|
|||
Applies mutations to the raw IRC text and checks it against callbacks
|
||||
"""
|
||||
|
||||
mutations = dict()
|
||||
# Clear out previous mutations
|
||||
self._mutations = dict()
|
||||
|
||||
requires = dict()
|
||||
triggered_whens = set()
|
||||
skip_whens = set()
|
||||
|
||||
# Apply mutations
|
||||
for using in self._using:
|
||||
mutation = using.callback(raw_line)
|
||||
mutations[using.namespace] = mutation
|
||||
for (key, value} in self._get_variables(mutation):
|
||||
for using_whens in self._whens_map[key]:
|
||||
for using_when in using_whens:
|
||||
# Already been triggers, skip
|
||||
if using_when in skip_whens:
|
||||
continue
|
||||
|
||||
# Check if all required fields are found
|
||||
whens_requires = requires.get(using_when)
|
||||
if whens_requires is None:
|
||||
requires[using_whens] = set(self._whens._fields)
|
||||
whens_requires = requires.get(using_whens)
|
||||
|
||||
whens_requires.remove(key)
|
||||
|
||||
if len(whens_requires) == 0:
|
||||
# If all requirements are found, stop checking this
|
||||
skip_whens.add(using_when)
|
||||
|
||||
namespace = self._whens_namespaces.get(using_when)
|
||||
data = mutations.get(namespace)
|
||||
if None in [namespace, data]:
|
||||
continue
|
||||
|
||||
# Check if conditions match mutation
|
||||
trigger_when = True
|
||||
for when_key, when_value in using_when.items():
|
||||
when_path = when_key.split("__")
|
||||
pointer = data
|
||||
for wpath in when_path:
|
||||
if not isinstance(pointer, dict):
|
||||
eprint(f"Invalid path: {when_key}")
|
||||
break
|
||||
|
||||
pointer = pointer.get(wpath)
|
||||
|
||||
when_status = False
|
||||
|
||||
# Value can be a function complex checks
|
||||
if callable(when_value):
|
||||
when_status = when_value(pointer)
|
||||
|
||||
else:
|
||||
when_status = pointer == when_value
|
||||
|
||||
if not when_status:
|
||||
trigger_when = False
|
||||
break
|
||||
|
||||
if not trigger_when:
|
||||
continue
|
||||
|
||||
triggered_whens.add(using_when)
|
||||
state = self._states[namespace]
|
||||
func = self._whens_funcs.get(using_when)
|
||||
if func is None:
|
||||
continue
|
||||
|
||||
# Run callback using mutation data and state manager
|
||||
func(data, state)
|
||||
self._process_mutations(requires, skip_whens)
|
||||
|
||||
def pre_process(self, callback, *args, **kwargs):
|
||||
"""
|
||||
Anything that needs to be run before each new line is processed
|
||||
"""
|
||||
|
||||
assert callable(callback), f"Expected function but got: {callback}"
|
||||
self._pre_callback = callback
|
||||
self._pre_args = args
|
||||
|
@ -228,6 +276,7 @@ class Engine:
|
|||
"""
|
||||
Anything that needs to be run after each new line is processed
|
||||
"""
|
||||
|
||||
assert callable(callback), f"Expected function but got: {callback}"
|
||||
self._post_callback = callback
|
||||
self._post_args = args
|
||||
|
@ -237,6 +286,7 @@ class Engine:
|
|||
"""
|
||||
What to run against the source to receive data
|
||||
"""
|
||||
|
||||
assert callable(callback), f"Expected function but got: {callback}"
|
||||
self._recv_callback = callback
|
||||
self._recv_args = args
|
||||
|
@ -246,6 +296,7 @@ class Engine:
|
|||
"""
|
||||
Passes stop signal to event loop in run function
|
||||
"""
|
||||
|
||||
self._running.set()
|
||||
|
||||
def run(self):
|
||||
|
@ -253,14 +304,16 @@ class Engine:
|
|||
The event loop that drives the engine
|
||||
Will loop indefinitely until the stopped or gets an exception
|
||||
"""
|
||||
|
||||
# Run until stopped
|
||||
while not self._running.is_set():
|
||||
# Run pre callback before processing
|
||||
self._pre_callback(*self._pre_args, **self._pre_kwargs)
|
||||
self._pre_callback(self._source, *self._pre_args,
|
||||
**self._pre_kwargs)
|
||||
|
||||
try:
|
||||
# Extract raw text from source using recv callback
|
||||
raw_text = self._recv_callback(self._source, *self._recv_args,
|
||||
# Extract receive data from source using recv callback
|
||||
recv_data = self._recv_callback(self._source, *self._recv_args,
|
||||
**self._recv_kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
@ -268,8 +321,15 @@ class Engine:
|
|||
eprint(format_exc())
|
||||
break
|
||||
|
||||
# Process raw text
|
||||
self.process(raw_text)
|
||||
# Process received data
|
||||
if isinstance(recv_data, Iterable):
|
||||
# If iterable, iterate over lines
|
||||
for recv in recv_data:
|
||||
self.process(recv)
|
||||
|
||||
else:
|
||||
self.process(recv_data)
|
||||
|
||||
# Run post callback before processing
|
||||
self._post_callback(*self._post_args, **self._post_kwargs)
|
||||
self._post_callback(self._source, *self._post_args,
|
||||
**self._post_kwargs)
|
||||
|
|
Loading…
Reference in New Issue
Block a user