<filename>pythonosc/dispatcher.py<gh_stars>1-10
"""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
