# -*- test-case-name: omnipresence.test.test_case_mapping -*-
"""Operations on IRC case mappings."""
from collections import MutableMapping
from string import maketrans, ascii_lowercase, ascii_uppercase
from twisted.python.util import InsensitiveDict
KNOWN_CASE_MAPPINGS = {
'ascii': (ascii_lowercase, ascii_uppercase),
'rfc1459': (ascii_lowercase + r'|{}~', ascii_uppercase + r'\[]^'),
'strict-rfc1459': (ascii_lowercase + r'|{}', ascii_uppercase + r'\[]')}
[docs]class CaseMapping(object):
"""Provides convenience methods for bidirectional string translation
given a mapping of characters from *lower* to *upper*."""
def __init__(self, lower, upper):
self.lower_trans = maketrans(upper, lower)
self.upper_trans = maketrans(lower, upper)
@classmethod
[docs] def by_name(cls, name):
"""Return an IRC case mapping given the *name* used in the
``CASEMAPPING`` parameter of a ``RPL_ISUPPORT`` IRC message
(numeric 005). The following mapping names are recognized:
.. describe:: ascii
Treats the letters *A-Z* as uppercase versions of the letters
*a-z*.
.. describe:: strict-rfc1459
Extends the ``ascii`` case mapping to further treat *{}|* as
the lowercase versions of *\\[\\]\\\\*. This matches the
rules specified in :rfc:`1459#section-2.2`.
.. describe:: rfc1459
Extends the ``strict-rfc1459`` case mapping to further treat
*~* as the lowercase version of *^*. This corresponds to
most servers' actual implementation of the RFC 1459 rules.
`ValueError` is raised on an unrecognized mapping name.
"""
if name in KNOWN_CASE_MAPPINGS:
return cls(*KNOWN_CASE_MAPPINGS[name])
raise ValueError('unrecognized case mapping "{}"'.format(name))
def __hash__(self):
# Translation tables are just strings, which are hashable.
return hash(self.lower_trans)
def __eq__(self, other):
if isinstance(other, CaseMapping):
return self.lower_trans == other.lower_trans
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
[docs] def equates(self, one, two):
"""Return a boolean value indicating whether *a* and *b* are
equal under this case mapping."""
return (one.translate(self.lower_trans) ==
two.translate(self.lower_trans))
[docs] def lower(self, string):
"""Return a copy of *string* with uppercase characters converted
to lowercase according to this case mapping."""
return string.translate(self.lower_trans)
[docs] def upper(self, string):
"""Return a copy of *string* with lowercase characters converted
to uppercase according to this case mapping."""
return string.translate(self.upper_trans)
[docs]class CaseMappedDict(InsensitiveDict, MutableMapping):
"""A dictionary whose keys are treated case-insensitively according
to a `.CaseMapping` or mapping name string (as given to `.by_name`)
provided on instantiation."""
def __init__(self, initial=None, case_mapping=None):
if case_mapping is None:
case_mapping = CaseMapping.by_name('rfc1459')
elif isinstance(case_mapping, basestring):
case_mapping = CaseMapping.by_name(case_mapping)
self.case_mapping = case_mapping
InsensitiveDict.__init__(self, initial, preserve=1)
def __iter__(self):
return self.iterkeys()
def _lowerOrReturn(self, key):
"""Return a lowercase version of *key* according to the case
mapping in effect for this object."""
# Why would anyone use this for non-string keys? Whatever.
if isinstance(key, basestring):
return self.case_mapping.lower(key)
return key