diff --git a/doc/THIRD-PARTY.md b/doc/THIRD-PARTY.md index be1fdda4c..9c1718c39 100644 --- a/doc/THIRD-PARTY.md +++ b/doc/THIRD-PARTY.md @@ -15,9 +15,6 @@ This file lists bundled packages and their associated licensing terms. Copyright (C) 2013, Jonathan Hartley. * The Fcrypt library located under thirdparty/fcrypt/. Copyright (C) 2000, 2001, 2004 Carey Evans. -* The Oset library located under thirdparty/oset/. - Copyright (C) 2010, BlueDynamics Alliance, Austria. - Copyright (C) 2009, Raymond Hettinger, and others. * The PrettyPrint library located under thirdparty/prettyprint/. Copyright (C) 2010, Chris Hall. * The SocksiPy library located under thirdparty/socks/. diff --git a/lib/core/datatype.py b/lib/core/datatype.py index cfc4dd02f..2285948c3 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -5,6 +5,7 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/) See the file 'LICENSE' for copying permission """ +import collections import copy import types @@ -140,3 +141,61 @@ class LRUDict(object): def keys(self): return self.cache.keys() + +# Reference: https://code.activestate.com/recipes/576694/ +class OrderedSet(collections.MutableSet): + def __init__(self, iterable=None): + self.end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.map = {} # key --> [key, prev, next] + if iterable is not None: + self |= iterable + + def __len__(self): + return len(self.map) + + def __contains__(self, key): + return key in self.map + + def add(self, key): + if key not in self.map: + end = self.end + curr = end[1] + curr[2] = end[1] = self.map[key] = [key, curr, end] + + def discard(self, key): + if key in self.map: + key, prev, next = self.map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def pop(self, last=True): + if not self: + raise KeyError('set is empty') + key = self.end[1][0] if last else self.end[2][0] + self.discard(key) + return key + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + if isinstance(other, OrderedSet): + return len(self) == len(other) and list(self) == list(other) + return set(self) == set(other) diff --git a/lib/core/option.py b/lib/core/option.py index 3c331d3d6..adb3291e0 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -16,7 +16,6 @@ import sys import tempfile import threading import time -import urllib2 import lib.controller.checks import lib.core.common @@ -66,6 +65,7 @@ from lib.core.data import mergedOptions from lib.core.data import queries from lib.core.datatype import AttribDict from lib.core.datatype import InjectionDict +from lib.core.datatype import OrderedSet from lib.core.defaults import defaults from lib.core.dicts import DBMS_DICT from lib.core.dicts import DUMP_REPLACEMENTS @@ -149,7 +149,6 @@ from lib.utils.search import search from lib.utils.purge import purge from thirdparty.keepalive import keepalive from thirdparty.multipart import multipartpost -from thirdparty.oset.pyoset import oset from thirdparty.six.moves import http_client as _http_client from thirdparty.six.moves import http_cookiejar as _http_cookiejar from thirdparty.six.moves import urllib as _urllib @@ -2023,7 +2022,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.preprocessFunctions = [] kb.skipVulnHost = None kb.tamperFunctions = [] - kb.targets = oset() + kb.targets = OrderedSet() kb.testedParams = set() kb.userAgents = None kb.vainRun = True diff --git a/lib/core/settings.py b/lib/core/settings.py index 658386be1..df16b96a4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -17,7 +17,7 @@ from lib.core.enums import DBMS_DIRECTORY_NAME from lib.core.enums import OS # sqlmap version (...) -VERSION = "1.3.3.60" +VERSION = "1.3.3.61" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/core/subprocessng.py b/lib/core/subprocessng.py index ed7be6fdb..19fd1a06d 100644 --- a/lib/core/subprocessng.py +++ b/lib/core/subprocessng.py @@ -85,7 +85,7 @@ class Popen(subprocess.Popen): getattr(self, which).close() setattr(self, which, None) - if subprocess.mswindows: + if IS_WIN: def send(self, input): if not self.stdin: return None diff --git a/lib/parse/sitemap.py b/lib/parse/sitemap.py index a3a1d2cd0..b369ab17a 100644 --- a/lib/parse/sitemap.py +++ b/lib/parse/sitemap.py @@ -10,9 +10,9 @@ import re from lib.core.common import readInput from lib.core.data import kb from lib.core.data import logger +from lib.core.datatype import OrderedSet from lib.core.exception import SqlmapSyntaxException from lib.request.connect import Connect as Request -from thirdparty.oset.pyoset import oset from thirdparty.six.moves import http_client as _http_client abortedFlag = None @@ -26,7 +26,7 @@ def parseSitemap(url, retVal=None): try: if retVal is None: abortedFlag = False - retVal = oset() + retVal = OrderedSet() try: content = Request.getPage(url=url, raise404=True)[0] if not abortedFlag else "" diff --git a/lib/takeover/web.py b/lib/takeover/web.py index 72842f5a5..21b3b5fc7 100644 --- a/lib/takeover/web.py +++ b/lib/takeover/web.py @@ -37,6 +37,7 @@ from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.data import paths +from lib.core.datatype import OrderedSet from lib.core.enums import DBMS from lib.core.enums import HTTP_HEADER from lib.core.enums import OS @@ -50,7 +51,6 @@ from lib.core.settings import SHELL_RUNCMD_EXE_TAG from lib.core.settings import SHELL_WRITABLE_DIR_TAG from lib.core.settings import VIEWSTATE_REGEX from lib.request.connect import Connect as Request -from thirdparty.oset.pyoset import oset from thirdparty.six.moves import urllib as _urllib class Web: @@ -254,7 +254,7 @@ class Web: directories = list(arrayizeValue(getManualDirectories())) directories.extend(getAutoDirectories()) - directories = list(oset(directories)) + directories = list(OrderedSet(directories)) path = _urllib.parse.urlparse(conf.url).path or '/' path = re.sub(r"/[^/]*\.\w+\Z", '/', path) diff --git a/lib/utils/crawler.py b/lib/utils/crawler.py index c44735f86..f6a75f91d 100644 --- a/lib/utils/crawler.py +++ b/lib/utils/crawler.py @@ -22,6 +22,7 @@ from lib.core.common import urldecode from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger +from lib.core.datatype import OrderedSet from lib.core.enums import MKSTEMP_PREFIX from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapSyntaxException @@ -31,7 +32,6 @@ from lib.core.threads import runThreads from lib.parse.sitemap import parseSitemap from lib.request.connect import Connect as Request from thirdparty.beautifulsoup.beautifulsoup import BeautifulSoup -from thirdparty.oset.pyoset import oset from thirdparty.six.moves import http_client as _http_client from thirdparty.six.moves import urllib as _urllib @@ -39,7 +39,7 @@ def crawl(target): try: visited = set() threadData = getCurrentThreadData() - threadData.shared.value = oset() + threadData.shared.value = OrderedSet() def crawlThread(): threadData = getCurrentThreadData() diff --git a/lib/utils/hash.py b/lib/utils/hash.py index 21dc6b965..968ca6772 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -45,7 +45,6 @@ from hashlib import sha224 from hashlib import sha256 from hashlib import sha384 from hashlib import sha512 -from Queue import Queue from lib.core.common import Backend from lib.core.common import checkFile @@ -68,6 +67,7 @@ from lib.core.convert import utf8encode from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger +from lib.core.datatype import OrderedSet from lib.core.enums import DBMS from lib.core.enums import HASH from lib.core.enums import MKSTEMP_PREFIX @@ -87,9 +87,9 @@ from lib.core.settings import UNICODE_ENCODING from lib.core.settings import ROTATING_CHARS from lib.core.wordlist import Wordlist from thirdparty.colorama.initialise import init as coloramainit -from thirdparty.oset.pyoset import oset from thirdparty.pydes.pyDes import des from thirdparty.pydes.pyDes import CBC +from thirdparty.six.moves import queue as _queue def mysql_passwd(password, uppercase=True): """ @@ -561,7 +561,7 @@ def storeHashesToFile(attack_dict): if not attack_dict: return - items = oset() + items = OrderedSet() for user, hashes in attack_dict.items(): for hash_ in hashes: @@ -1059,7 +1059,7 @@ def dictionaryAttack(attack_dict): warnMsg += "not supported on this platform" singleTimeWarnMessage(warnMsg) - retVal = Queue() + retVal = _queue.Queue() _bruteProcessVariantA(attack_info, hash_regex, suffix, retVal, 0, 1, kb.wordlists, custom_wordlist, conf.api) except KeyboardInterrupt: @@ -1150,7 +1150,7 @@ def dictionaryAttack(attack_dict): class Value(): pass - retVal = Queue() + retVal = _queue.Queue() found_ = Value() found_.value = False diff --git a/thirdparty/oset/LICENSE.txt b/thirdparty/oset/LICENSE.txt deleted file mode 100644 index aef85dda3..000000000 --- a/thirdparty/oset/LICENSE.txt +++ /dev/null @@ -1,29 +0,0 @@ -License -======= - -Copyright (c) 2009, Raymond Hettinger, and others -All rights reserved. - -Package structured based on the one developed to odict -Copyright (c) 2010, BlueDynamics Alliance, Austria - - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. -* Neither the name of the BlueDynamics Alliance nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY BlueDynamics Alliance ``AS IS`` AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL BlueDynamics Alliance BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/thirdparty/oset/__init__.py b/thirdparty/oset/__init__.py deleted file mode 100644 index 688b31e92..000000000 --- a/thirdparty/oset/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Main Ordered Set module """ - -from pyoset import oset diff --git a/thirdparty/oset/_abc.py b/thirdparty/oset/_abc.py deleted file mode 100644 index ae6db9706..000000000 --- a/thirdparty/oset/_abc.py +++ /dev/null @@ -1,476 +0,0 @@ -#!/usr/bin/env python2 -# -*- mode:python; tab-width: 2; coding: utf-8 -*- - -"""Partially backported python ABC classes""" - -from __future__ import absolute_import - -import sys -import types - -if sys.version_info > (2, 6): - raise ImportError("Use native ABC classes istead of this one.") - - -# Instance of old-style class -class _C: - pass - -_InstanceType = type(_C()) - - -def abstractmethod(funcobj): - """A decorator indicating abstract methods. - - Requires that the metaclass is ABCMeta or derived from it. A - class that has a metaclass derived from ABCMeta cannot be - instantiated unless all of its abstract methods are overridden. - The abstract methods can be called using any of the normal - 'super' call mechanisms. - - Usage: - - class C: - __metaclass__ = ABCMeta - @abstractmethod - def my_abstract_method(self, ...): - ... - """ - funcobj.__isabstractmethod__ = True - return funcobj - - -class ABCMeta(type): - - """Metaclass for defining Abstract Base Classes (ABCs). - - Use this metaclass to create an ABC. An ABC can be subclassed - directly, and then acts as a mix-in class. You can also register - unrelated concrete classes (even built-in classes) and unrelated - ABCs as 'virtual subclasses' -- these and their descendants will - be considered subclasses of the registering ABC by the built-in - issubclass() function, but the registering ABC won't show up in - their MRO (Method Resolution Order) nor will method - implementations defined by the registering ABC be callable (not - even via super()). - - """ - - # A global counter that is incremented each time a class is - # registered as a virtual subclass of anything. It forces the - # negative cache to be cleared before its next use. - _abc_invalidation_counter = 0 - - def __new__(mcls, name, bases, namespace): - cls = super(ABCMeta, mcls).__new__(mcls, name, bases, namespace) - # Compute set of abstract method names - abstracts = set(name - for name, value in namespace.items() - if getattr(value, "__isabstractmethod__", False)) - for base in bases: - for name in getattr(base, "__abstractmethods__", set()): - value = getattr(cls, name, None) - if getattr(value, "__isabstractmethod__", False): - abstracts.add(name) - cls.__abstractmethods__ = frozenset(abstracts) - # Set up inheritance registry - cls._abc_registry = set() - cls._abc_cache = set() - cls._abc_negative_cache = set() - cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter - return cls - - def register(cls, subclass): - """Register a virtual subclass of an ABC.""" - if not isinstance(subclass, (type, types.ClassType)): - raise TypeError("Can only register classes") - if issubclass(subclass, cls): - return # Already a subclass - # Subtle: test for cycles *after* testing for "already a subclass"; - # this means we allow X.register(X) and interpret it as a no-op. - if issubclass(cls, subclass): - # This would create a cycle, which is bad for the algorithm below - raise RuntimeError("Refusing to create an inheritance cycle") - cls._abc_registry.add(subclass) - ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache - - def _dump_registry(cls, file=None): - """Debug helper to print the ABC registry.""" - print >> file, "Class: %s.%s" % (cls.__module__, cls.__name__) - print >> file, "Inv.counter: %s" % ABCMeta._abc_invalidation_counter - for name in sorted(cls.__dict__.keys()): - if name.startswith("_abc_"): - value = getattr(cls, name) - print >> file, "%s: %r" % (name, value) - - def __instancecheck__(cls, instance): - """Override for isinstance(instance, cls).""" - # Inline the cache checking when it's simple. - subclass = getattr(instance, '__class__', None) - if subclass in cls._abc_cache: - return True - subtype = type(instance) - # Old-style instances - if subtype is _InstanceType: - subtype = subclass - if subtype is subclass or subclass is None: - if (cls._abc_negative_cache_version == - ABCMeta._abc_invalidation_counter and - subtype in cls._abc_negative_cache): - return False - # Fall back to the subclass check. - return cls.__subclasscheck__(subtype) - return (cls.__subclasscheck__(subclass) or - cls.__subclasscheck__(subtype)) - - def __subclasscheck__(cls, subclass): - """Override for issubclass(subclass, cls).""" - # Check cache - if subclass in cls._abc_cache: - return True - # Check negative cache; may have to invalidate - if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: - # Invalidate the negative cache - cls._abc_negative_cache = set() - cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter - elif subclass in cls._abc_negative_cache: - return False - # Check the subclass hook - ok = cls.__subclasshook__(subclass) - if ok is not NotImplemented: - assert isinstance(ok, bool) - if ok: - cls._abc_cache.add(subclass) - else: - cls._abc_negative_cache.add(subclass) - return ok - # Check if it's a direct subclass - if cls in getattr(subclass, '__mro__', ()): - cls._abc_cache.add(subclass) - return True - # Check if it's a subclass of a registered class (recursive) - for rcls in cls._abc_registry: - if issubclass(subclass, rcls): - cls._abc_cache.add(subclass) - return True - # Check if it's a subclass of a subclass (recursive) - for scls in cls.__subclasses__(): - if issubclass(subclass, scls): - cls._abc_cache.add(subclass) - return True - # No dice; update negative cache - cls._abc_negative_cache.add(subclass) - return False - - -def _hasattr(C, attr): - try: - return any(attr in B.__dict__ for B in C.__mro__) - except AttributeError: - # Old-style class - return hasattr(C, attr) - - -class Sized: - __metaclass__ = ABCMeta - - @abstractmethod - def __len__(self): - return 0 - - @classmethod - def __subclasshook__(cls, C): - if cls is Sized: - if _hasattr(C, "__len__"): - return True - return NotImplemented - - -class Container: - __metaclass__ = ABCMeta - - @abstractmethod - def __contains__(self, x): - return False - - @classmethod - def __subclasshook__(cls, C): - if cls is Container: - if _hasattr(C, "__contains__"): - return True - return NotImplemented - - -class Iterable: - __metaclass__ = ABCMeta - - @abstractmethod - def __iter__(self): - while False: - yield None - - @classmethod - def __subclasshook__(cls, C): - if cls is Iterable: - if _hasattr(C, "__iter__"): - return True - return NotImplemented - -Iterable.register(str) - - -class Set(Sized, Iterable, Container): - """A set is a finite, iterable container. - - This class provides concrete generic implementations of all - methods except for __contains__, __iter__ and __len__. - - To override the comparisons (presumably for speed, as the - semantics are fixed), all you have to do is redefine __le__ and - then the other operations will automatically follow suit. - """ - - def __le__(self, other): - if not isinstance(other, Set): - return NotImplemented - if len(self) > len(other): - return False - for elem in self: - if elem not in other: - return False - return True - - def __lt__(self, other): - if not isinstance(other, Set): - return NotImplemented - return len(self) < len(other) and self.__le__(other) - - def __gt__(self, other): - if not isinstance(other, Set): - return NotImplemented - return other < self - - def __ge__(self, other): - if not isinstance(other, Set): - return NotImplemented - return other <= self - - def __eq__(self, other): - if not isinstance(other, Set): - return NotImplemented - return len(self) == len(other) and self.__le__(other) - - def __ne__(self, other): - return not (self == other) - - @classmethod - def _from_iterable(cls, it): - '''Construct an instance of the class from any iterable input. - - Must override this method if the class constructor signature - does not accept an iterable for an input. - ''' - return cls(it) - - def __and__(self, other): - if not isinstance(other, Iterable): - return NotImplemented - return self._from_iterable(value for value in other if value in self) - - def isdisjoint(self, other): - for value in other: - if value in self: - return False - return True - - def __or__(self, other): - if not isinstance(other, Iterable): - return NotImplemented - chain = (e for s in (self, other) for e in s) - return self._from_iterable(chain) - - def __sub__(self, other): - if not isinstance(other, Set): - if not isinstance(other, Iterable): - return NotImplemented - other = self._from_iterable(other) - return self._from_iterable(value for value in self - if value not in other) - - def __xor__(self, other): - if not isinstance(other, Set): - if not isinstance(other, Iterable): - return NotImplemented - other = self._from_iterable(other) - return (self - other) | (other - self) - - # Sets are not hashable by default, but subclasses can change this - __hash__ = None - - def _hash(self): - """Compute the hash value of a set. - - Note that we don't define __hash__: not all sets are hashable. - But if you define a hashable set type, its __hash__ should - call this function. - - This must be compatible __eq__. - - All sets ought to compare equal if they contain the same - elements, regardless of how they are implemented, and - regardless of the order of the elements; so there's not much - freedom for __eq__ or __hash__. We match the algorithm used - by the built-in frozenset type. - """ - MAX = sys.maxint - MASK = 2 * MAX + 1 - n = len(self) - h = 1927868237 * (n + 1) - h &= MASK - for x in self: - hx = hash(x) - h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167 - h &= MASK - h = h * 69069 + 907133923 - h &= MASK - if h > MAX: - h -= MASK + 1 - if h == -1: - h = 590923713 - return h - -Set.register(frozenset) - - -class MutableSet(Set): - - @abstractmethod - def add(self, value): - """Add an element.""" - raise NotImplementedError - - @abstractmethod - def discard(self, value): - """Remove an element. Do not raise an exception if absent.""" - raise NotImplementedError - - def remove(self, value): - """Remove an element. If not a member, raise a KeyError.""" - if value not in self: - raise KeyError(value) - self.discard(value) - - def pop(self): - """Return the popped value. Raise KeyError if empty.""" - it = iter(self) - try: - value = next(it) - except StopIteration: - raise KeyError - self.discard(value) - return value - - def clear(self): - """This is slow (creates N new iterators!) but effective.""" - try: - while True: - self.pop() - except KeyError: - pass - - def __ior__(self, it): - for value in it: - self.add(value) - return self - - def __iand__(self, it): - for value in (self - it): - self.discard(value) - return self - - def __ixor__(self, it): - if not isinstance(it, Set): - it = self._from_iterable(it) - for value in it: - if value in self: - self.discard(value) - else: - self.add(value) - return self - - def __isub__(self, it): - for value in it: - self.discard(value) - return self - -MutableSet.register(set) - - -class OrderedSet(MutableSet): - - def __init__(self, iterable=None): - self.end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.map = {} # key --> [key, prev, next] - if iterable is not None: - self |= iterable - - def __len__(self): - return len(self.map) - - def __contains__(self, key): - return key in self.map - - def __getitem__(self, key): - return list(self)[key] - - def add(self, key): - if key not in self.map: - end = self.end - curr = end[PREV] - curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end] - - def discard(self, key): - if key in self.map: - key, prev, next = self.map.pop(key) - prev[NEXT] = next - next[PREV] = prev - - def __iter__(self): - end = self.end - curr = end[NEXT] - while curr is not end: - yield curr[KEY] - curr = curr[NEXT] - - def __reversed__(self): - end = self.end - curr = end[PREV] - while curr is not end: - yield curr[KEY] - curr = curr[PREV] - - def pop(self, last=True): - if not self: - raise KeyError('set is empty') - key = next(reversed(self)) if last else next(iter(self)) - self.discard(key) - return key - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, list(self)) - - def __eq__(self, other): - if isinstance(other, OrderedSet): - return len(self) == len(other) and list(self) == list(other) - return set(self) == set(other) - - def __del__(self): - if all([KEY, PREV, NEXT]): - self.clear() # remove circular references - -if __name__ == '__main__': - print(OrderedSet('abracadaba')) - print(OrderedSet('simsalabim')) diff --git a/thirdparty/oset/pyoset.py b/thirdparty/oset/pyoset.py deleted file mode 100644 index 944d75c82..000000000 --- a/thirdparty/oset/pyoset.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python2 -# -*- mode:python; tab-width: 2; coding: utf-8 -*- - -"""Partially backported python ABC classes""" - -from __future__ import absolute_import - -try: - from collections import MutableSet -except ImportError: - # Running in Python <= 2.5 - from ._abc import MutableSet - - -KEY, PREV, NEXT = range(3) - - -class OrderedSet(MutableSet): - - def __init__(self, iterable=None): - self.end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.map = {} # key --> [key, prev, next] - if iterable is not None: - self |= iterable - - def __len__(self): - return len(self.map) - - def __contains__(self, key): - return key in self.map - - def __getitem__(self, key): - return list(self)[key] - - def add(self, key): - if key not in self.map: - end = self.end - curr = end[PREV] - curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end] - - def discard(self, key): - if key in self.map: - key, prev, next = self.map.pop(key) - prev[NEXT] = next - next[PREV] = prev - - def __iter__(self): - end = self.end - curr = end[NEXT] - while curr is not end: - yield curr[KEY] - curr = curr[NEXT] - - def __reversed__(self): - end = self.end - curr = end[PREV] - while curr is not end: - yield curr[KEY] - curr = curr[PREV] - - def pop(self, last=True): - if not self: - raise KeyError('set is empty') - key = next(reversed(self)) if last else next(iter(self)) - self.discard(key) - return key - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, list(self)) - - def __eq__(self, other): - if isinstance(other, OrderedSet): - return len(self) == len(other) and list(self) == list(other) - return set(self) == set(other) - - def __del__(self): - if all([KEY, PREV, NEXT]): - self.clear() # remove circular references - -oset = OrderedSet