"""Class that maps OSC addresses to handlers.""" import collections import logging import re Handler = collections.namedtuple( typename='Handler', field_names=('callback', 'args')) class Dispatcher(object): """Register addresses to handlers and can match vice-versa.""" def __init__(self): self._map = collections.defaultdict(list) self._default_handler = None def map(self, address, handler, *args): """Map a given address to a handler. Args: - address: An explicit endpoint. - handler: A function that will be run when the address matches with the OscMessage passed as parameter. - args: Any additional arguments that will be always passed to the handlers after the osc messages arguments if any. """ # TODO: Check the spec: # http://opensoundcontrol.org/spec-1_0 # regarding multiple mappings self._map[address].append(Handler(handler, list(args))) def handlers_for_address(self, address_pattern): """yields Handler namedtuples matching the given OSC pattern.""" # First convert the address_pattern into a matchable regexp. # '?' in the OSC Address Pattern matches any single character. # Let's consider numbers and _ "characters" too here, it's not said # explicitly in the specification but it sounds good. escaped_address_pattern = re.escape(address_pattern) pattern = escaped_address_pattern.replace('\\?', '\\w?') # '*' in the OSC Address Pattern matches any sequence of zero or more # characters. pattern = pattern.replace('\\*', '[\w|\+]*') # The rest of the syntax in the specification is like the re module so # we're fine. pattern = pattern + '$' pattern = re.compile(pattern) matched = False for addr, handlers in self._map.items(): if (pattern.match(addr) or (('*' in addr) and re.match(addr.replace('*','[^/]*?/*'), address_pattern))): for handler in handlers: yield handler matched = True if not matched and self._default_handler: logging.debug('No handler matched but default handler present, added it.') yield Handler(self._default_handler, []) def set_default_handler(self, handler): """Sets the default handler. Must be a function with the same constaints as with the self.map method or None to unset the default handler. """ self._default_handler = handler