mirror of https://github.com/H-uru/korman.git
Browse Source
This changeset ensures that if a dependant modifier's dependency is deleted that the aforementioned modifier is deleted as well. Since this is something of a large change, we ask for the user to confirm the operation before actually doing so.pull/38/head
Adam Johnson
9 years ago
3 changed files with 257 additions and 5 deletions
@ -0,0 +1,200 @@ |
|||||||
|
""" |
||||||
|
An OrderedSet is a custom MutableSet that remembers its order, so that every |
||||||
|
entry has an index that can be looked up. |
||||||
|
|
||||||
|
Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, |
||||||
|
and released under the MIT license. |
||||||
|
|
||||||
|
Rob Speer's changes are as follows: |
||||||
|
|
||||||
|
- changed the content from a doubly-linked list to a regular Python list. |
||||||
|
Seriously, who wants O(1) deletes but O(N) lookups by index? |
||||||
|
- add() returns the index of the added item |
||||||
|
- index() just returns the index of an item |
||||||
|
- added a __getstate__ and __setstate__ so it can be pickled |
||||||
|
- added __getitem__ |
||||||
|
|
||||||
|
Updated for Python 3.5 by Adam Johnson |
||||||
|
""" |
||||||
|
import collections.abc |
||||||
|
|
||||||
|
SLICE_ALL = slice(None) |
||||||
|
__version__ = '2.0.1' |
||||||
|
|
||||||
|
|
||||||
|
def is_iterable(obj): |
||||||
|
""" |
||||||
|
Are we being asked to look up a list of things, instead of a single thing? |
||||||
|
We check for the `__iter__` attribute so that this can cover types that |
||||||
|
don't have to be known by this module, such as NumPy arrays. |
||||||
|
|
||||||
|
Strings, however, should be considered as atomic values to look up, not |
||||||
|
iterables. The same goes for tuples, since they are immutable and therefore |
||||||
|
valid entries. |
||||||
|
|
||||||
|
We don't need to check for the Python 2 `unicode` type, because it doesn't |
||||||
|
have an `__iter__` attribute anyway. |
||||||
|
""" |
||||||
|
return hasattr(obj, '__iter__') and not isinstance(obj, str) and not isinstance(obj, tuple) |
||||||
|
|
||||||
|
|
||||||
|
class OrderedSet(collections.abc.MutableSet): |
||||||
|
""" |
||||||
|
An OrderedSet is a custom MutableSet that remembers its order, so that |
||||||
|
every entry has an index that can be looked up. |
||||||
|
""" |
||||||
|
def __init__(self, iterable=None): |
||||||
|
self.items = [] |
||||||
|
self.map = {} |
||||||
|
if iterable is not None: |
||||||
|
self |= iterable |
||||||
|
|
||||||
|
def __len__(self): |
||||||
|
return len(self.items) |
||||||
|
|
||||||
|
def __getitem__(self, index): |
||||||
|
""" |
||||||
|
Get the item at a given index. |
||||||
|
|
||||||
|
If `index` is a slice, you will get back that slice of items. If it's |
||||||
|
the slice [:], exactly the same object is returned. (If you want an |
||||||
|
independent copy of an OrderedSet, use `OrderedSet.copy()`.) |
||||||
|
|
||||||
|
If `index` is an iterable, you'll get the OrderedSet of items |
||||||
|
corresponding to those indices. This is similar to NumPy's |
||||||
|
"fancy indexing". |
||||||
|
""" |
||||||
|
if index == SLICE_ALL: |
||||||
|
return self |
||||||
|
elif hasattr(index, '__index__') or isinstance(index, slice): |
||||||
|
result = self.items[index] |
||||||
|
if isinstance(result, list): |
||||||
|
return OrderedSet(result) |
||||||
|
else: |
||||||
|
return result |
||||||
|
elif is_iterable(index): |
||||||
|
return OrderedSet([self.items[i] for i in index]) |
||||||
|
else: |
||||||
|
raise TypeError("Don't know how to index an OrderedSet by %r" % |
||||||
|
index) |
||||||
|
|
||||||
|
def copy(self): |
||||||
|
return OrderedSet(self) |
||||||
|
|
||||||
|
def __getstate__(self): |
||||||
|
if len(self) == 0: |
||||||
|
# The state can't be an empty list. |
||||||
|
# We need to return a truthy value, or else __setstate__ won't be run. |
||||||
|
# |
||||||
|
# This could have been done more gracefully by always putting the state |
||||||
|
# in a tuple, but this way is backwards- and forwards- compatible with |
||||||
|
# previous versions of OrderedSet. |
||||||
|
return (None,) |
||||||
|
else: |
||||||
|
return list(self) |
||||||
|
|
||||||
|
def __setstate__(self, state): |
||||||
|
if state == (None,): |
||||||
|
self.__init__([]) |
||||||
|
else: |
||||||
|
self.__init__(state) |
||||||
|
|
||||||
|
def __contains__(self, key): |
||||||
|
return key in self.map |
||||||
|
|
||||||
|
def add(self, key): |
||||||
|
""" |
||||||
|
Add `key` as an item to this OrderedSet, then return its index. |
||||||
|
|
||||||
|
If `key` is already in the OrderedSet, return the index it already |
||||||
|
had. |
||||||
|
""" |
||||||
|
if key not in self.map: |
||||||
|
self.map[key] = len(self.items) |
||||||
|
self.items.append(key) |
||||||
|
return self.map[key] |
||||||
|
append = add |
||||||
|
|
||||||
|
def update(self, sequence): |
||||||
|
""" |
||||||
|
Update the set with the given iterable sequence, then return the index |
||||||
|
of the last element inserted. |
||||||
|
""" |
||||||
|
item_index = None |
||||||
|
try: |
||||||
|
for item in sequence: |
||||||
|
item_index = self.add(item) |
||||||
|
except TypeError: |
||||||
|
raise ValueError('Argument needs to be an iterable, got %s' % type(sequence)) |
||||||
|
return item_index |
||||||
|
|
||||||
|
def index(self, key): |
||||||
|
""" |
||||||
|
Get the index of a given entry, raising an IndexError if it's not |
||||||
|
present. |
||||||
|
|
||||||
|
`key` can be an iterable of entries that is not a string, in which case |
||||||
|
this returns a list of indices. |
||||||
|
""" |
||||||
|
if is_iterable(key): |
||||||
|
return [self.index(subkey) for subkey in key] |
||||||
|
return self.map[key] |
||||||
|
|
||||||
|
def pop(self): |
||||||
|
""" |
||||||
|
Remove and return the last element from the set. |
||||||
|
|
||||||
|
Raises KeyError if the set is empty. |
||||||
|
""" |
||||||
|
if not self.items: |
||||||
|
raise KeyError('Set is empty') |
||||||
|
|
||||||
|
elem = self.items[-1] |
||||||
|
del self.items[-1] |
||||||
|
del self.map[elem] |
||||||
|
return elem |
||||||
|
|
||||||
|
def discard(self, key): |
||||||
|
""" |
||||||
|
Remove an element. Do not raise an exception if absent. |
||||||
|
|
||||||
|
The MutableSet mixin uses this to implement the .remove() method, which |
||||||
|
*does* raise an error when asked to remove a non-existent item. |
||||||
|
""" |
||||||
|
if key in self: |
||||||
|
i = self.items.index(key) |
||||||
|
del self.items[i] |
||||||
|
del self.map[key] |
||||||
|
for k, v in self.map.items(): |
||||||
|
if v >= i: |
||||||
|
self.map[k] = v - 1 |
||||||
|
|
||||||
|
def clear(self): |
||||||
|
""" |
||||||
|
Remove all items from this OrderedSet. |
||||||
|
""" |
||||||
|
del self.items[:] |
||||||
|
self.map.clear() |
||||||
|
|
||||||
|
def __iter__(self): |
||||||
|
return iter(self.items) |
||||||
|
|
||||||
|
def __reversed__(self): |
||||||
|
return reversed(self.items) |
||||||
|
|
||||||
|
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 self.items == other.items |
||||||
|
try: |
||||||
|
other_as_set = set(other) |
||||||
|
except TypeError: |
||||||
|
# If `other` can't be converted into a set, it's not equal. |
||||||
|
return False |
||||||
|
else: |
||||||
|
return set(self) == other_as_set |
||||||
|
|
Loading…
Reference in new issue