diff --git a/youtube_dl_gui/utils.py b/youtube_dl_gui/utils.py index c7745b7..f87ed16 100644 --- a/youtube_dl_gui/utils.py +++ b/youtube_dl_gui/utils.py @@ -3,7 +3,7 @@ """Youtubedlg module that contains util functions. Attributes: - RANDOM_OBJECT (object): Object that it's used as a default parameter. + _RANDOM_OBJECT (object): Object that it's used as a default parameter. YOUTUBEDL_BIN (string): Youtube-dl binary filename. @@ -15,7 +15,7 @@ import sys import subprocess -RANDOM_OBJECT = object() +_RANDOM_OBJECT = object() YOUTUBEDL_BIN = 'youtube-dl' @@ -163,7 +163,7 @@ def get_icon_file(): return None -class TwoWayOrderedDict(object): +class TwoWayOrderedDict(dict): """Custom data structure which implements a two way ordrered dictionary. @@ -171,7 +171,7 @@ class TwoWayOrderedDict(object): key:value relationship but you can also get the value:key relationship. It also remembers the order in which the items were inserted and supports almost all the features of the build-in dict. - + Note: Ways to create a new dictionary. @@ -188,200 +188,178 @@ class TwoWayOrderedDict(object): >>> d[1] 'a' >>> print d - {'a': 1, 'b': 2} + TwoWayOrderedDict([('a', 1), ('b', 2)]) """ - def __init__(self, *args, **kwargs): - self._items = list() - self._load_into_dict(args, kwargs) + _PREV = 0 + _KEY = 1 + _NEXT = 2 - def __getitem__(self, key): - try: - item = self._find_item(key) - - return self._get_value(key, item) - except KeyError as error: - raise error + def __init__(self, *args, **kwargs): + self._items = item = [] + self._items += [item, None, item] # Double linked list [prev, key, next] + self._items_map = {} # Map link list items into keys to speed up lookup + self._load(args, kwargs) def __setitem__(self, key, value): - index = 0 - - while index < len(self._items): - item = self._items[index] + if key in self: + # If self[key] == key for example {'b': 'b'} and we + # do d['b'] = 2 then we dont want to remove the 'b' + # from our linked list because we will lose the order + if self[key] in self._items_map and key != self[key]: + self._remove_mapped_key(self[key]) + + dict.__delitem__(self, self[key]) + + if value in self: + # If value == key we dont have to remove the + # value from the items_map because the value is + # the key and we want to keep the key in our + # linked list in order to keep the order. + if value in self._items_map and key != value: + self._remove_mapped_key(value) + + if self[value] in self._items_map: + self._remove_mapped_key(self[value]) + + # Check if self[value] is in the dict + # for cases like {'a': 'a'} where we + # have only one copy instead of {'a': 1, 1: 'a'} + if self[value] in self: + dict.__delitem__(self, self[value]) + + if key not in self._items_map: + last = self._items[self._PREV] # self._items prev always points to the last item + last[self._NEXT] = self._items[self._PREV] = self._items_map[key] = [last, key, self._items] + + dict.__setitem__(self, key, value) + dict.__setitem__(self, value, key) - if (key == item[0] or key == item[1] or - value == item[0] or value == item[1]): - self._items.remove(item) - else: - index += 1 + def __delitem__(self, key): + if self[key] in self._items_map: + self._remove_mapped_key(self[key]) - self._items.append((key, value)) + if key in self._items_map: + self._remove_mapped_key(key) - def __delitem__(self, key): - try: - item = self._find_item(key) + dict.__delitem__(self, self[key]) - self._items.remove(item) - except KeyError as error: - raise error + # Check if key is in the dict + # for cases like {'a': 'a'} where we + # have only one copy instead of {'a': 1, 1: 'a'} + if key in self: + dict.__delitem__(self, key) def __len__(self): - return len(self._items) + return len(self._items_map) def __iter__(self): - for item in self._items: - yield item[0] - - def __contains__(self, item): - """Return True if the item matches either a - dictionary key or value else False. """ - try: - self._find_item(item) - except KeyError: - return False + curr = self._items[self._NEXT] + while curr is not self._items: + yield curr[self._KEY] + curr = curr[self._NEXT] - return True + def __reversed__(self): + curr = self._items[self._PREV] + while curr is not self._items: + yield curr[self._KEY] + curr = curr[self._PREV] def __repr__(self): - return str(self._items) - - def __str__(self): - return str(dict(self._items)) - - def _get_value(self, key, item): - """Return the key:value or value:key relationship - for the given item. """ - if key == item[0]: - return item[1] - - if key == item[1]: - return item[0] - - def _find_item(self, key): - """Search for the item which contains the given key. - - This method will compare the key both with the key and the value - of the item until it finds a match else it will raise a KeyError - exception. - - Returns: - item (tuple): Tuple which contains the (key, value). - - Raises: - KeyError - - """ - for item in self._items: - if key == item[0] or key == item[1]: - return item + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.items() == other.items() + return False - raise KeyError(key) + def __ne__(self, other): + return not self == other - def _load_into_dict(self, args, kwargs): - """Load new items into the dictionary. This method handles the - items insertion for the __init__ and update methods. """ + def _remove_mapped_key(self, key): + """Remove the given key both from the linked list + and the map dictionary. """ + prev, __, next = self._items_map.pop(key) + prev[self._NEXT] = next + next[self._PREV] = prev + + def _load(self, args, kwargs): + """Load items into our dictionary. """ for item in args: if type(item) == dict: - item = item.items() + item = item.iteritems() for key, value in item: - self.__setitem__(key, value) + self[key] = value for key, value in kwargs.items(): - self.__setitem__(key, value) + self[key] = value def items(self): - """Return the item list instead of returning the dict view. """ - return self._items + return [(key, self[key]) for key in self] def values(self): - """Return a list with all the values of the dictionary instead of - returning the dict view for the values. """ - return [item[1] for item in self._items] + return [self[key] for key in self] def keys(self): - """Return a list with all the keys of the dictionary instead of - returning the dict view for the keys. """ - return [item[0] for item in self._items] - - def get(self, key, default=None): - """Return the value for key or the key for value if key - is in the dictionary else default. - - Note: - This method does NOT raise a KeyError. - - """ - try: - return self.__getitem__(key) - except KeyError: - return default + return list(self) - def pop(self, key, default=RANDOM_OBJECT): - """If key is in the dictionary remove it and return its value - else return default. If default is not given and key is not in - the dictionary a KeyError is raised. """ + def pop(self, key, default=_RANDOM_OBJECT): try: - item = self._find_item(key) - value = self._get_value(key, item) + value = self[key] - self._items.remove(item) + del self[key] except KeyError as error: - if default == RANDOM_OBJECT: + if default == _RANDOM_OBJECT: raise error value = default return value - def popitem(self): + def popitem(self, last=True): """Remove and return a (key, value) pair from the dictionary. If the dictionary is empty calling popitem() raises a KeyError. - + + Args: + last (bool): When False popitem() will remove the first item + from the list. + Note: popitem() is useful to destructively iterate over a dictionary. - + Raises: KeyError - + """ - if len(self._items) == 0: + if not self: raise KeyError('popitem(): dictionary is empty') - return self._items.pop() + if last: + __, key, __ = self._items[self._PREV] + else: + __, key, __ = self._items[self._NEXT] + + value = self.pop(key) + + return key, value + + def update(self, *args, **kwargs): + self._load(args, kwargs) def setdefault(self, key, default=None): - """If key is in the dictionary return its value else - insert a new key with a value of default and return default. """ try: - return self.__getitem__(key) + return self[key] except KeyError: - self.__setitem__(key, default) + self[key] = default return default - def update(self, *args, **kwargs): - """Update the dictionary with the (key, value) pairs - overwriting existing keys. - - Example: - >>d = TwoWayOrderedDict(a=1, b=2) - >>print d - {'a': 1, 'b': 2} - - >>d.update({'a': 0, 'b': 1, 'c': 2}) - {'a': 0, 'b': 1, 'c': 2} - - >>d.update(d=3) - {'a': 0, 'b': 1, 'c': 2, 'd': 3} - - """ - self._load_into_dict(args, kwargs) - def copy(self): - """Return a copy of our custom dictionary. """ - return TwoWayOrderedDict(self._items) + return self.__class__(self.items()) def clear(self): - """Remove all items from the dictionary. """ - del self._items[:] + self._items = item = [] + self._items += [item, None, item] + self._items_map = {} + dict.clear(self)