You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
385 lines
9.3 KiB
385 lines
9.3 KiB
8 months ago
|
# Copyright (c) 2006-2021 Andrey Golovizin
|
||
|
#
|
||
|
# Permission is hereby granted, free of charge, to any person obtaining
|
||
|
# a copy of this software and associated documentation files (the
|
||
|
# "Software"), to deal in the Software without restriction, including
|
||
|
# without limitation the rights to use, copy, modify, merge, publish,
|
||
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
# permit persons to whom the Software is furnished to do so, subject to
|
||
|
# the following conditions:
|
||
|
#
|
||
|
# The above copyright notice and this permission notice shall be
|
||
|
# included in all copies or substantial portions of the Software.
|
||
|
#
|
||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||
|
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||
|
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||
|
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
|
||
|
|
||
|
"""Miscellaneous small utils."""
|
||
|
from __future__ import print_function, unicode_literals
|
||
|
|
||
|
import itertools
|
||
|
from collections import OrderedDict, deque
|
||
|
from functools import wraps
|
||
|
from types import GeneratorType
|
||
|
try:
|
||
|
from collections.abc import MutableMapping, MutableSet, Sequence
|
||
|
except ImportError:
|
||
|
from collections import MutableMapping, MutableSet, Sequence
|
||
|
|
||
|
from six.moves import zip_longest
|
||
|
|
||
|
from .py3compat import fix_unicode_literals_in_doctest
|
||
|
|
||
|
|
||
|
def deprecated(since, reason=None):
|
||
|
def decorator(f):
|
||
|
@wraps(f)
|
||
|
def new_f(*args, **kwargs):
|
||
|
import warnings
|
||
|
message = u'{0}() is deprecated since {1}'.format(f.__name__, since)
|
||
|
if reason:
|
||
|
message += ': {0}'.format(reason)
|
||
|
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
||
|
return f(*args, **kwargs)
|
||
|
return new_f
|
||
|
return decorator
|
||
|
|
||
|
|
||
|
def memoize(f, capacity=1024):
|
||
|
memory = {}
|
||
|
history = deque()
|
||
|
|
||
|
@wraps(f)
|
||
|
def new_f(*args):
|
||
|
if args not in memory:
|
||
|
if len(history) >= capacity:
|
||
|
del memory[history.popleft()]
|
||
|
memory[args] = f(*args)
|
||
|
history.append(args)
|
||
|
return memory[args]
|
||
|
return new_f
|
||
|
|
||
|
|
||
|
def collect_iterable(f):
|
||
|
@wraps(f)
|
||
|
def new_f(*args, **kwargs):
|
||
|
return list(f(*args, **kwargs))
|
||
|
return new_f
|
||
|
|
||
|
|
||
|
def pairwise(iterable):
|
||
|
a, b = itertools.tee(iterable)
|
||
|
next(b, None)
|
||
|
return zip_longest(a, b)
|
||
|
|
||
|
|
||
|
@fix_unicode_literals_in_doctest
|
||
|
class CaseInsensitiveDict(MutableMapping):
|
||
|
"""A dict with case-insensitive lookup.
|
||
|
|
||
|
>>> d = CaseInsensitiveDict(TesT='passed')
|
||
|
>>> d
|
||
|
CaseInsensitiveDict({'TesT': u'passed'})
|
||
|
>>> d.lower()
|
||
|
CaseInsensitiveDict({'test': u'passed'})
|
||
|
>>> print(d['TesT'])
|
||
|
passed
|
||
|
>>> print(d['test'])
|
||
|
passed
|
||
|
>>> print(d['Test'])
|
||
|
passed
|
||
|
>>> print(d.get('test'))
|
||
|
passed
|
||
|
>>> print(d.get('Test'))
|
||
|
passed
|
||
|
|
||
|
>>> d['Test'] = 'passed again'
|
||
|
>>> print(d['test'])
|
||
|
passed again
|
||
|
>>> 'test' in d
|
||
|
True
|
||
|
>>> 'Test' in d
|
||
|
True
|
||
|
>>> list(d.keys())
|
||
|
[u'Test']
|
||
|
>>> for key in d:
|
||
|
... print(key)
|
||
|
Test
|
||
|
>>> for key, value in d.items():
|
||
|
... print(key, value)
|
||
|
Test passed again
|
||
|
>>> bool(d)
|
||
|
True
|
||
|
>>> len(d)
|
||
|
1
|
||
|
|
||
|
>>> del d['test']
|
||
|
>>> len(d)
|
||
|
0
|
||
|
>>> bool(d)
|
||
|
False
|
||
|
>>> 'test' in d
|
||
|
False
|
||
|
>>> 'Test' in d
|
||
|
False
|
||
|
>>> print(d.get('test'))
|
||
|
None
|
||
|
>>> print(d.get('Test'))
|
||
|
None
|
||
|
>>> print(d.get('Test', 'failed'))
|
||
|
failed
|
||
|
|
||
|
>>> CaseInsensitiveDict(
|
||
|
... (key, value) for key, value in [('a', 'b')]
|
||
|
... )
|
||
|
CaseInsensitiveDict({u'a': u'b'})
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
initial = dict(*args, **kwargs)
|
||
|
self._dict = dict((key.lower(), value) for key, value in initial.items())
|
||
|
self._keys = dict((key.lower(), key) for key in initial)
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self._dict)
|
||
|
|
||
|
def __iter__(self):
|
||
|
return iter(self._keys.values())
|
||
|
|
||
|
def __setitem__(self, key, value):
|
||
|
"""To implement lowercase keys."""
|
||
|
key_lower = key.lower()
|
||
|
self._dict[key_lower] = value
|
||
|
self._keys[key_lower] = key
|
||
|
|
||
|
def __getitem__(self, key):
|
||
|
return self._dict[key.lower()]
|
||
|
|
||
|
def __delitem__(self, key):
|
||
|
key_lower = key.lower()
|
||
|
del self._dict[key_lower]
|
||
|
del self._keys[key_lower]
|
||
|
|
||
|
def __contains__(self, key):
|
||
|
return key.lower() in self._dict
|
||
|
|
||
|
def __repr__(self):
|
||
|
"""A caselessDict version of __repr__ """
|
||
|
dct = dict((key, self[key]) for key in self)
|
||
|
return '{0}({1})'.format(
|
||
|
type(self).__name__, repr(dct),
|
||
|
)
|
||
|
|
||
|
def items_lower(self):
|
||
|
return ((key.lower(), value) for key, value in self.items())
|
||
|
|
||
|
def lower(self):
|
||
|
return type(self)(self.items_lower())
|
||
|
|
||
|
|
||
|
class CaseInsensitiveDefaultDict(CaseInsensitiveDict):
|
||
|
"""CaseInseisitiveDict with default factory, like collections.defaultdict
|
||
|
|
||
|
>>> d = CaseInsensitiveDefaultDict(int)
|
||
|
>>> d['a']
|
||
|
0
|
||
|
>>> d['a'] += 1
|
||
|
>>> d['a']
|
||
|
1
|
||
|
>>> d['A']
|
||
|
1
|
||
|
>>> d['a'] = 3
|
||
|
>>> d['a']
|
||
|
3
|
||
|
>>> d['B'] += 10
|
||
|
>>> d['b']
|
||
|
10
|
||
|
|
||
|
"""
|
||
|
def __init__(self, default_factory):
|
||
|
super(CaseInsensitiveDefaultDict, self).__init__()
|
||
|
self.default_factory = default_factory
|
||
|
|
||
|
def __getitem__(self, key):
|
||
|
try:
|
||
|
return super(CaseInsensitiveDefaultDict, self).__getitem__(key)
|
||
|
except KeyError:
|
||
|
return self.default_factory()
|
||
|
|
||
|
|
||
|
@fix_unicode_literals_in_doctest
|
||
|
class OrderedCaseInsensitiveDict(CaseInsensitiveDict):
|
||
|
""" An (incomplete) ordered case-insensitive dict.
|
||
|
|
||
|
>>> d = OrderedCaseInsensitiveDict([
|
||
|
... ('Uno', 1),
|
||
|
... ('Dos', 2),
|
||
|
... ('Tres', 3),
|
||
|
... ])
|
||
|
>>> d
|
||
|
OrderedCaseInsensitiveDict([(u'Uno', 1), (u'Dos', 2), (u'Tres', 3)])
|
||
|
>>> d.lower()
|
||
|
OrderedCaseInsensitiveDict([(u'uno', 1), (u'dos', 2), (u'tres', 3)])
|
||
|
>>> list(d.keys())
|
||
|
[u'Uno', u'Dos', u'Tres']
|
||
|
>>> list(d.items())
|
||
|
[(u'Uno', 1), (u'Dos', 2), (u'Tres', 3)]
|
||
|
>>> list(d.values())
|
||
|
[1, 2, 3]
|
||
|
>>> d['Cuatro'] = 4
|
||
|
>>> list(d.keys())
|
||
|
[u'Uno', u'Dos', u'Tres', u'Cuatro']
|
||
|
>>> list(d.items())
|
||
|
[(u'Uno', 1), (u'Dos', 2), (u'Tres', 3), (u'Cuatro', 4)]
|
||
|
>>> list(d.values())
|
||
|
[1, 2, 3, 4]
|
||
|
>>> list(d)
|
||
|
[u'Uno', u'Dos', u'Tres', u'Cuatro']
|
||
|
>>> 'Uno' in d
|
||
|
True
|
||
|
>>> 'uno' in d
|
||
|
True
|
||
|
>>> d['Uno']
|
||
|
1
|
||
|
>>> d['uno']
|
||
|
1
|
||
|
>>> d['UNO']
|
||
|
1
|
||
|
>>> 'Cuatro' in d
|
||
|
True
|
||
|
>>> 'CUATRO' in d
|
||
|
True
|
||
|
>>> d['Cuatro']
|
||
|
4
|
||
|
>>> d['cuatro']
|
||
|
4
|
||
|
>>> d['UNO'] = 'one'
|
||
|
>>> d['uno']
|
||
|
u'one'
|
||
|
>>> d['Uno']
|
||
|
u'one'
|
||
|
>>> list(d.keys())
|
||
|
[u'UNO', u'Dos', u'Tres', u'Cuatro']
|
||
|
>>> d['cuatro'] = 'four'
|
||
|
>>> d['Cuatro']
|
||
|
u'four'
|
||
|
>>> d['cuatro']
|
||
|
u'four'
|
||
|
>>> list(d.keys())
|
||
|
[u'UNO', u'Dos', u'Tres', u'cuatro']
|
||
|
>>> list(d.values())
|
||
|
[u'one', 2, 3, u'four']
|
||
|
>>> del d['dos']
|
||
|
>>> list(d.keys())
|
||
|
[u'UNO', u'Tres', u'cuatro']
|
||
|
>>> list(d.values())
|
||
|
[u'one', 3, u'four']
|
||
|
"""
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
initial = OrderedDict(*args, **kwargs)
|
||
|
self._dict = dict((key.lower(), value) for key, value in initial.items())
|
||
|
self._keys = OrderedDict((key.lower(), key) for key in initial)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '{0}({1})'.format(
|
||
|
type(self).__name__, list(self.items()),
|
||
|
)
|
||
|
|
||
|
|
||
|
@fix_unicode_literals_in_doctest
|
||
|
class CaseInsensitiveSet(MutableSet):
|
||
|
"""A very basic case-insensitive set.
|
||
|
|
||
|
>>> s = CaseInsensitiveSet()
|
||
|
>>> len(s)
|
||
|
0
|
||
|
>>> 'a' in s
|
||
|
False
|
||
|
>>> list(CaseInsensitiveSet(['aaa', 'Aaa', 'AAA']))
|
||
|
[u'aaa']
|
||
|
>>> s = CaseInsensitiveSet(['Aaa', 'Bbb'])
|
||
|
>>> s
|
||
|
CaseInsensitiveSet([u'Aaa', u'Bbb'])
|
||
|
>>> s.lower()
|
||
|
CaseInsensitiveSet([u'aaa', u'bbb'])
|
||
|
>>> len(s)
|
||
|
2
|
||
|
>>> 'aaa' in s
|
||
|
True
|
||
|
>>> 'Aaa' in s
|
||
|
True
|
||
|
>>> 'AAA' in s
|
||
|
True
|
||
|
>>> 'bbb' in s
|
||
|
True
|
||
|
>>> 'Bbb' in s
|
||
|
True
|
||
|
>>> 'abc' in s
|
||
|
False
|
||
|
>>> s.add('ccc')
|
||
|
>>> len(s)
|
||
|
3
|
||
|
>>> 'aaa' in s
|
||
|
True
|
||
|
>>> 'ccc' in s
|
||
|
True
|
||
|
>>> s.remove('AAA')
|
||
|
>>> len(s)
|
||
|
2
|
||
|
>>> 'aaa' in s
|
||
|
False
|
||
|
|
||
|
>>> bool(CaseInsensitiveSet(['a']))
|
||
|
True
|
||
|
>>> bool(CaseInsensitiveSet([]))
|
||
|
False
|
||
|
>>> bool(CaseInsensitiveSet())
|
||
|
False
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __init__(self, iterable=()):
|
||
|
self._set = set()
|
||
|
self._keys = dict()
|
||
|
for item in iterable:
|
||
|
self.add(item)
|
||
|
|
||
|
def __contains__(self, key):
|
||
|
return key.lower() in self._set
|
||
|
|
||
|
def __iter__(self):
|
||
|
return iter(self._set)
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self._set)
|
||
|
|
||
|
def __repr__(self):
|
||
|
"""A caselessDict version of __repr__ """
|
||
|
return '{0}({1})'.format(
|
||
|
type(self).__name__, repr(sorted(self._keys.values()))
|
||
|
)
|
||
|
|
||
|
def add(self, key):
|
||
|
key_lower = key.lower()
|
||
|
self._set.add(key_lower)
|
||
|
self._keys[key_lower] = key
|
||
|
|
||
|
def discard(self, key):
|
||
|
key_lower = key.lower()
|
||
|
self._set.discard(key_lower)
|
||
|
self._keys.pop(key_lower, None)
|
||
|
|
||
|
def get_canonical_key(self, key):
|
||
|
return self._keys[key.lower()]
|
||
|
|
||
|
def lower(self):
|
||
|
return type(self)(self._set)
|