2
0

_compat.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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_CUR, SEEK_END
  32. except ImportError:
  33. SEEK_CUR = 1
  34. SEEK_END = 2
  35. import struct
  36. class defaultdict(dict):
  37. """A python 2.4 equivalent of collections.defaultdict."""
  38. def __init__(self, default_factory=None, *a, **kw):
  39. if (default_factory is not None and
  40. not hasattr(default_factory, '__call__')):
  41. raise TypeError('first argument must be callable')
  42. dict.__init__(self, *a, **kw)
  43. self.default_factory = default_factory
  44. def __getitem__(self, key):
  45. try:
  46. return dict.__getitem__(self, key)
  47. except KeyError:
  48. return self.__missing__(key)
  49. def __missing__(self, key):
  50. if self.default_factory is None:
  51. raise KeyError(key)
  52. self[key] = value = self.default_factory()
  53. return value
  54. def __reduce__(self):
  55. if self.default_factory is None:
  56. args = tuple()
  57. else:
  58. args = self.default_factory,
  59. return type(self), args, None, None, self.items()
  60. def copy(self):
  61. return self.__copy__()
  62. def __copy__(self):
  63. return type(self)(self.default_factory, self)
  64. def __deepcopy__(self, memo):
  65. import copy
  66. return type(self)(self.default_factory,
  67. copy.deepcopy(self.items()))
  68. def __repr__(self):
  69. return 'defaultdict(%s, %s)' % (self.default_factory,
  70. dict.__repr__(self))
  71. def make_sha(source=''):
  72. """A python2.4 workaround for the sha/hashlib module fiasco."""
  73. try:
  74. return hashlib.sha1(source)
  75. except NameError:
  76. sha1 = sha.sha(source)
  77. return sha1
  78. def unpack_from(fmt, buf, offset=0):
  79. """A python2.4 workaround for struct missing unpack_from."""
  80. try:
  81. return struct.unpack_from(fmt, buf, offset)
  82. except AttributeError:
  83. b = buf[offset:offset+struct.calcsize(fmt)]
  84. return struct.unpack(fmt, b)
  85. try:
  86. from itertools import permutations
  87. except ImportError:
  88. # Implementation of permutations from Python 2.6 documentation:
  89. # http://docs.python.org/2.6/library/itertools.html#itertools.permutations
  90. # Copyright (c) 2001-2010 Python Software Foundation; All Rights Reserved
  91. # Modified syntax slightly to run under Python 2.4.
  92. def permutations(iterable, r=None):
  93. # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
  94. # permutations(range(3)) --> 012 021 102 120 201 210
  95. pool = tuple(iterable)
  96. n = len(pool)
  97. if r is None:
  98. r = n
  99. if r > n:
  100. return
  101. indices = range(n)
  102. cycles = range(n, n-r, -1)
  103. yield tuple(pool[i] for i in indices[:r])
  104. while n:
  105. for i in reversed(range(r)):
  106. cycles[i] -= 1
  107. if cycles[i] == 0:
  108. indices[i:] = indices[i+1:] + indices[i:i+1]
  109. cycles[i] = n - i
  110. else:
  111. j = cycles[i]
  112. indices[i], indices[-j] = indices[-j], indices[i]
  113. yield tuple(pool[i] for i in indices[:r])
  114. break
  115. else:
  116. return
  117. try:
  118. all = all
  119. except NameError:
  120. # Implementation of permutations from Python 2.6 documentation:
  121. # http://docs.python.org/2.6/library/functions.html#all
  122. # Copyright (c) 2001-2010 Python Software Foundation; All Rights Reserved
  123. # Licensed under the Python Software Foundation License.
  124. def all(iterable):
  125. for element in iterable:
  126. if not element:
  127. return False
  128. return True
  129. try:
  130. from collections import namedtuple
  131. except ImportError:
  132. # Recipe for namedtuple from http://code.activestate.com/recipes/500261/
  133. # Copyright (c) 2007 Python Software Foundation; All Rights Reserved
  134. # Licensed under the Python Software Foundation License.
  135. from operator import itemgetter as _itemgetter
  136. from keyword import iskeyword as _iskeyword
  137. import sys as _sys
  138. def namedtuple(typename, field_names, verbose=False, rename=False):
  139. """Returns a new subclass of tuple with named fields.
  140. >>> Point = namedtuple('Point', 'x y')
  141. >>> Point.__doc__ # docstring for the new class
  142. 'Point(x, y)'
  143. >>> p = Point(11, y=22) # instantiate with positional args or keywords
  144. >>> p[0] + p[1] # indexable like a plain tuple
  145. 33
  146. >>> x, y = p # unpack like a regular tuple
  147. >>> x, y
  148. (11, 22)
  149. >>> p.x + p.y # fields also accessable by name
  150. 33
  151. >>> d = p._asdict() # convert to a dictionary
  152. >>> d['x']
  153. 11
  154. >>> Point(**d) # convert from a dictionary
  155. Point(x=11, y=22)
  156. >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
  157. Point(x=100, y=22)
  158. """
  159. # Parse and validate the field names. Validation serves two purposes,
  160. # generating informative error messages and preventing template injection attacks.
  161. if isinstance(field_names, basestring):
  162. field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
  163. field_names = tuple(map(str, field_names))
  164. if rename:
  165. names = list(field_names)
  166. seen = set()
  167. for i, name in enumerate(names):
  168. if (not min(c.isalnum() or c=='_' for c in name) or _iskeyword(name)
  169. or not name or name[0].isdigit() or name.startswith('_')
  170. or name in seen):
  171. names[i] = '_%d' % i
  172. seen.add(name)
  173. field_names = tuple(names)
  174. for name in (typename,) + field_names:
  175. if not min(c.isalnum() or c=='_' for c in name):
  176. raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
  177. if _iskeyword(name):
  178. raise ValueError('Type names and field names cannot be a keyword: %r' % name)
  179. if name[0].isdigit():
  180. raise ValueError('Type names and field names cannot start with a number: %r' % name)
  181. seen_names = set()
  182. for name in field_names:
  183. if name.startswith('_') and not rename:
  184. raise ValueError('Field names cannot start with an underscore: %r' % name)
  185. if name in seen_names:
  186. raise ValueError('Encountered duplicate field name: %r' % name)
  187. seen_names.add(name)
  188. # Create and fill-in the class template
  189. numfields = len(field_names)
  190. argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes
  191. reprtxt = ', '.join('%s=%%r' % name for name in field_names)
  192. template = '''class %(typename)s(tuple):
  193. '%(typename)s(%(argtxt)s)' \n
  194. __slots__ = () \n
  195. _fields = %(field_names)r \n
  196. def __new__(_cls, %(argtxt)s):
  197. return _tuple.__new__(_cls, (%(argtxt)s)) \n
  198. @classmethod
  199. def _make(cls, iterable, new=tuple.__new__, len=len):
  200. 'Make a new %(typename)s object from a sequence or iterable'
  201. result = new(cls, iterable)
  202. if len(result) != %(numfields)d:
  203. raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
  204. return result \n
  205. def __repr__(self):
  206. return '%(typename)s(%(reprtxt)s)' %% self \n
  207. def _asdict(self):
  208. 'Return a new dict which maps field names to their values'
  209. return dict(zip(self._fields, self)) \n
  210. def _replace(_self, **kwds):
  211. 'Return a new %(typename)s object replacing specified fields with new values'
  212. result = _self._make(map(kwds.pop, %(field_names)r, _self))
  213. if kwds:
  214. raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
  215. return result \n
  216. def __getnewargs__(self):
  217. return tuple(self) \n\n''' % locals()
  218. for i, name in enumerate(field_names):
  219. template += ' %s = _property(_itemgetter(%d))\n' % (name, i)
  220. if verbose:
  221. print template
  222. # Execute the template string in a temporary namespace
  223. namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
  224. _property=property, _tuple=tuple)
  225. try:
  226. exec template in namespace
  227. except SyntaxError, e:
  228. raise SyntaxError(e.message + ':\n' + template)
  229. result = namespace[typename]
  230. # For pickling to work, the __module__ variable needs to be set to the frame
  231. # where the named tuple is created. Bypass this step in enviroments where
  232. # sys._getframe is not defined (Jython for example) or sys._getframe is not
  233. # defined for arguments greater than 0 (IronPython).
  234. try:
  235. result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
  236. except (AttributeError, ValueError):
  237. pass
  238. return result