/bin/markdown/odict.py

https://github.com/straup/recipes
Python | 189 lines | 144 code | 27 blank | 18 comment | 31 complexity | bb10f2f2ef27bccb68989762ef65c209 MD5 | raw file
  1. from __future__ import unicode_literals
  2. from __future__ import absolute_import
  3. from . import util
  4. from copy import deepcopy
  5. class OrderedDict(dict):
  6. """
  7. A dictionary that keeps its keys in the order in which they're inserted.
  8. Copied from Django's SortedDict with some modifications.
  9. """
  10. def __new__(cls, *args, **kwargs):
  11. instance = super(OrderedDict, cls).__new__(cls, *args, **kwargs)
  12. instance.keyOrder = []
  13. return instance
  14. def __init__(self, data=None):
  15. if data is None or isinstance(data, dict):
  16. data = data or []
  17. super(OrderedDict, self).__init__(data)
  18. self.keyOrder = list(data) if data else []
  19. else:
  20. super(OrderedDict, self).__init__()
  21. super_set = super(OrderedDict, self).__setitem__
  22. for key, value in data:
  23. # Take the ordering from first key
  24. if key not in self:
  25. self.keyOrder.append(key)
  26. # But override with last value in data (dict() does this)
  27. super_set(key, value)
  28. def __deepcopy__(self, memo):
  29. return self.__class__([(key, deepcopy(value, memo))
  30. for key, value in self.items()])
  31. def __copy__(self):
  32. # The Python's default copy implementation will alter the state
  33. # of self. The reason for this seems complex but is likely related to
  34. # subclassing dict.
  35. return self.copy()
  36. def __setitem__(self, key, value):
  37. if key not in self:
  38. self.keyOrder.append(key)
  39. super(OrderedDict, self).__setitem__(key, value)
  40. def __delitem__(self, key):
  41. super(OrderedDict, self).__delitem__(key)
  42. self.keyOrder.remove(key)
  43. def __iter__(self):
  44. return iter(self.keyOrder)
  45. def __reversed__(self):
  46. return reversed(self.keyOrder)
  47. def pop(self, k, *args):
  48. result = super(OrderedDict, self).pop(k, *args)
  49. try:
  50. self.keyOrder.remove(k)
  51. except ValueError:
  52. # Key wasn't in the dictionary in the first place. No problem.
  53. pass
  54. return result
  55. def popitem(self):
  56. result = super(OrderedDict, self).popitem()
  57. self.keyOrder.remove(result[0])
  58. return result
  59. def _iteritems(self):
  60. for key in self.keyOrder:
  61. yield key, self[key]
  62. def _iterkeys(self):
  63. for key in self.keyOrder:
  64. yield key
  65. def _itervalues(self):
  66. for key in self.keyOrder:
  67. yield self[key]
  68. if util.PY3: #pragma: no cover
  69. items = _iteritems
  70. keys = _iterkeys
  71. values = _itervalues
  72. else: #pragma: no cover
  73. iteritems = _iteritems
  74. iterkeys = _iterkeys
  75. itervalues = _itervalues
  76. def items(self):
  77. return [(k, self[k]) for k in self.keyOrder]
  78. def keys(self):
  79. return self.keyOrder[:]
  80. def values(self):
  81. return [self[k] for k in self.keyOrder]
  82. def update(self, dict_):
  83. for k in dict_:
  84. self[k] = dict_[k]
  85. def setdefault(self, key, default):
  86. if key not in self:
  87. self.keyOrder.append(key)
  88. return super(OrderedDict, self).setdefault(key, default)
  89. def value_for_index(self, index):
  90. """Returns the value of the item at the given zero-based index."""
  91. return self[self.keyOrder[index]]
  92. def insert(self, index, key, value):
  93. """Inserts the key, value pair before the item with the given index."""
  94. if key in self.keyOrder:
  95. n = self.keyOrder.index(key)
  96. del self.keyOrder[n]
  97. if n < index:
  98. index -= 1
  99. self.keyOrder.insert(index, key)
  100. super(OrderedDict, self).__setitem__(key, value)
  101. def copy(self):
  102. """Returns a copy of this object."""
  103. # This way of initializing the copy means it works for subclasses, too.
  104. return self.__class__(self)
  105. def __repr__(self):
  106. """
  107. Replaces the normal dict.__repr__ with a version that returns the keys
  108. in their Ordered order.
  109. """
  110. return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self._iteritems()])
  111. def clear(self):
  112. super(OrderedDict, self).clear()
  113. self.keyOrder = []
  114. def index(self, key):
  115. """ Return the index of a given key. """
  116. try:
  117. return self.keyOrder.index(key)
  118. except ValueError:
  119. raise ValueError("Element '%s' was not found in OrderedDict" % key)
  120. def index_for_location(self, location):
  121. """ Return index or None for a given location. """
  122. if location == '_begin':
  123. i = 0
  124. elif location == '_end':
  125. i = None
  126. elif location.startswith('<') or location.startswith('>'):
  127. i = self.index(location[1:])
  128. if location.startswith('>'):
  129. if i >= len(self):
  130. # last item
  131. i = None
  132. else:
  133. i += 1
  134. else:
  135. raise ValueError('Not a valid location: "%s". Location key '
  136. 'must start with a ">" or "<".' % location)
  137. return i
  138. def add(self, key, value, location):
  139. """ Insert by key location. """
  140. i = self.index_for_location(location)
  141. if i is not None:
  142. self.insert(i, key, value)
  143. else:
  144. self.__setitem__(key, value)
  145. def link(self, key, location):
  146. """ Change location of an existing item. """
  147. n = self.keyOrder.index(key)
  148. del self.keyOrder[n]
  149. try:
  150. i = self.index_for_location(location)
  151. if i is not None:
  152. self.keyOrder.insert(i, key)
  153. else:
  154. self.keyOrder.append(key)
  155. except Exception as e:
  156. # restore to prevent data loss and reraise
  157. self.keyOrder.insert(n, key)
  158. raise e