_compat.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # _compat.py -- For dealing with python2.4 oddness
  2. # Copyright (C) 2008 Canonical Ltd.
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; version 2
  7. # of the License or (at your option) a later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  17. # MA 02110-1301, USA.
  18. """Misc utilities to work with python <2.6.
  19. These utilities can all be deleted when dulwich decides it wants to stop
  20. support for python <2.6.
  21. """
  22. try:
  23. import hashlib
  24. except ImportError:
  25. import sha
  26. try:
  27. from urlparse import parse_qs
  28. except ImportError:
  29. from cgi import parse_qs
  30. try:
  31. from os import SEEK_END
  32. except ImportError:
  33. SEEK_END = 2
  34. import struct
  35. class defaultdict(dict):
  36. """A python 2.4 equivalent of collections.defaultdict."""
  37. def __init__(self, default_factory=None, *a, **kw):
  38. if (default_factory is not None and
  39. not hasattr(default_factory, '__call__')):
  40. raise TypeError('first argument must be callable')
  41. dict.__init__(self, *a, **kw)
  42. self.default_factory = default_factory
  43. def __getitem__(self, key):
  44. try:
  45. return dict.__getitem__(self, key)
  46. except KeyError:
  47. return self.__missing__(key)
  48. def __missing__(self, key):
  49. if self.default_factory is None:
  50. raise KeyError(key)
  51. self[key] = value = self.default_factory()
  52. return value
  53. def __reduce__(self):
  54. if self.default_factory is None:
  55. args = tuple()
  56. else:
  57. args = self.default_factory,
  58. return type(self), args, None, None, self.items()
  59. def copy(self):
  60. return self.__copy__()
  61. def __copy__(self):
  62. return type(self)(self.default_factory, self)
  63. def __deepcopy__(self, memo):
  64. import copy
  65. return type(self)(self.default_factory,
  66. copy.deepcopy(self.items()))
  67. def __repr__(self):
  68. return 'defaultdict(%s, %s)' % (self.default_factory,
  69. dict.__repr__(self))
  70. def make_sha(source=''):
  71. """A python2.4 workaround for the sha/hashlib module fiasco."""
  72. try:
  73. return hashlib.sha1(source)
  74. except NameError:
  75. sha1 = sha.sha(source)
  76. return sha1
  77. def unpack_from(fmt, buf, offset=0):
  78. """A python2.4 workaround for struct missing unpack_from."""
  79. try:
  80. return struct.unpack_from(fmt, buf, offset)
  81. except AttributeError:
  82. b = buf[offset:offset+struct.calcsize(fmt)]
  83. return struct.unpack(fmt, b)
  84. try:
  85. from itertools import permutations
  86. except ImportError:
  87. # Implementation of permutations from Python 2.6 documentation:
  88. # http://docs.python.org/2.6/library/itertools.html#itertools.permutations
  89. # Copyright (c) 2001-2010 Python Software Foundation; All Rights Reserved
  90. # Modified syntax slightly to run under Python 2.4.
  91. def permutations(iterable, r=None):
  92. # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
  93. # permutations(range(3)) --> 012 021 102 120 201 210
  94. pool = tuple(iterable)
  95. n = len(pool)
  96. if r is None:
  97. r = n
  98. if r > n:
  99. return
  100. indices = range(n)
  101. cycles = range(n, n-r, -1)
  102. yield tuple(pool[i] for i in indices[:r])
  103. while n:
  104. for i in reversed(range(r)):
  105. cycles[i] -= 1
  106. if cycles[i] == 0:
  107. indices[i:] = indices[i+1:] + indices[i:i+1]
  108. cycles[i] = n - i
  109. else:
  110. j = cycles[i]
  111. indices[i], indices[-j] = indices[-j], indices[i]
  112. yield tuple(pool[i] for i in indices[:r])
  113. break
  114. else:
  115. return
  116. try:
  117. from collections import namedtuple
  118. TreeEntryTuple = namedtuple('TreeEntryTuple', ['path', 'mode', 'sha'])
  119. TreeChangeTuple = namedtuple('TreeChangeTuple', ['type', 'old', 'new'])
  120. except ImportError:
  121. # Provide manual implementations of namedtuples for Python <2.5.
  122. # If the class definitions change, be sure to keep these in sync by running
  123. # namedtuple(..., verbose=True) in a recent Python and pasting the output.
  124. # Necessary globals go here.
  125. _tuple = tuple
  126. _property = property
  127. from operator import itemgetter as _itemgetter
  128. class TreeEntryTuple(tuple):
  129. 'TreeEntryTuple(path, mode, sha)'
  130. __slots__ = ()
  131. _fields = ('path', 'mode', 'sha')
  132. def __new__(_cls, path, mode, sha):
  133. return _tuple.__new__(_cls, (path, mode, sha))
  134. @classmethod
  135. def _make(cls, iterable, new=tuple.__new__, len=len):
  136. 'Make a new TreeEntryTuple object from a sequence or iterable'
  137. result = new(cls, iterable)
  138. if len(result) != 3:
  139. raise TypeError('Expected 3 arguments, got %d' % len(result))
  140. return result
  141. def __repr__(self):
  142. return 'TreeEntryTuple(path=%r, mode=%r, sha=%r)' % self
  143. def _asdict(t):
  144. 'Return a new dict which maps field names to their values'
  145. return {'path': t[0], 'mode': t[1], 'sha': t[2]}
  146. def _replace(_self, **kwds):
  147. 'Return a new TreeEntryTuple object replacing specified fields with new values'
  148. result = _self._make(map(kwds.pop, ('path', 'mode', 'sha'), _self))
  149. if kwds:
  150. raise ValueError('Got unexpected field names: %r' % kwds.keys())
  151. return result
  152. def __getnewargs__(self):
  153. return tuple(self)
  154. path = _property(_itemgetter(0))
  155. mode = _property(_itemgetter(1))
  156. sha = _property(_itemgetter(2))
  157. class TreeChangeTuple(tuple):
  158. 'TreeChangeTuple(type, old, new)'
  159. __slots__ = ()
  160. _fields = ('type', 'old', 'new')
  161. def __new__(_cls, type, old, new):
  162. return _tuple.__new__(_cls, (type, old, new))
  163. @classmethod
  164. def _make(cls, iterable, new=tuple.__new__, len=len):
  165. 'Make a new TreeChangeTuple object from a sequence or iterable'
  166. result = new(cls, iterable)
  167. if len(result) != 3:
  168. raise TypeError('Expected 3 arguments, got %d' % len(result))
  169. return result
  170. def __repr__(self):
  171. return 'TreeChangeTuple(type=%r, old=%r, new=%r)' % self
  172. def _asdict(t):
  173. 'Return a new dict which maps field names to their values'
  174. return {'type': t[0], 'old': t[1], 'new': t[2]}
  175. def _replace(_self, **kwds):
  176. 'Return a new TreeChangeTuple object replacing specified fields with new values'
  177. result = _self._make(map(kwds.pop, ('type', 'old', 'new'), _self))
  178. if kwds:
  179. raise ValueError('Got unexpected field names: %r' % kwds.keys())
  180. return result
  181. def __getnewargs__(self):
  182. return tuple(self)
  183. type = _property(_itemgetter(0))
  184. old = _property(_itemgetter(1))
  185. new = _property(_itemgetter(2))