helpers.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import argparse
  2. from datetime import datetime
  3. from fnmatch import fnmatchcase
  4. import grp
  5. import os
  6. import pwd
  7. import re
  8. import stat
  9. import struct
  10. def read_set(path):
  11. """Read set from disk (as int32s)
  12. """
  13. with open(path, 'rb') as fd:
  14. data = fd.read()
  15. return set(struct.unpack('<%di' % (len(data) / 4), data))
  16. def write_set(s, path):
  17. """Write set to disk (as int32s)
  18. """
  19. with open(path, 'wb') as fd:
  20. fd.write(struct.pack('<%di' % len(s), *s))
  21. def encode_long(v):
  22. bytes = []
  23. while True:
  24. if v > 0x7f:
  25. bytes.append(0x80 | (v % 0x80))
  26. v >>= 7
  27. else:
  28. bytes.append(v)
  29. return ''.join(chr(x) for x in bytes)
  30. def decode_long(bytes):
  31. v = 0
  32. base = 0
  33. for x in bytes:
  34. b = ord(x)
  35. if b & 0x80:
  36. v += (b & 0x7f) << base
  37. base += 7
  38. else:
  39. return v + (b << base)
  40. def zero_pad(data, length):
  41. """Make sure data is `length` bytes long by prepending zero bytes
  42. >>> zero_pad('foo', 5)
  43. '\\x00\\x00foo'
  44. >>> zero_pad('foo', 3)
  45. 'foo'
  46. """
  47. return '\0' * (length - len(data)) + data
  48. def exclude_path(path, patterns):
  49. """Used by create and extract sub-commands to determine
  50. if an item should be processed or not
  51. """
  52. for pattern in (patterns or []):
  53. if pattern.match(path):
  54. return isinstance(pattern, ExcludePattern)
  55. return False
  56. class IncludePattern(object):
  57. """--include PATTERN
  58. >>> py = IncludePattern('*.py')
  59. >>> foo = IncludePattern('/foo')
  60. >>> py.match('/foo/foo.py')
  61. True
  62. >>> py.match('/bar/foo.java')
  63. False
  64. >>> foo.match('/foo/foo.py')
  65. True
  66. >>> foo.match('/bar/foo.java')
  67. False
  68. >>> foo.match('/foobar/foo.py')
  69. False
  70. >>> foo.match('/foo')
  71. True
  72. """
  73. def __init__(self, pattern):
  74. self.pattern = self.dirpattern = pattern
  75. if not pattern.endswith(os.path.sep):
  76. self.dirpattern += os.path.sep
  77. def match(self, path):
  78. dir, name = os.path.split(path)
  79. return (path == self.pattern
  80. or (dir + os.path.sep).startswith(self.dirpattern)
  81. or fnmatchcase(name, self.pattern))
  82. def __repr__(self):
  83. return '%s(%s)' % (type(self), self.pattern)
  84. class ExcludePattern(IncludePattern):
  85. """
  86. """
  87. def walk_path(path, skip_inodes=None):
  88. st = os.lstat(path)
  89. if skip_inodes and (st.st_ino, st.st_dev) in skip_inodes:
  90. return
  91. yield path, st
  92. if stat.S_ISDIR(st.st_mode):
  93. for f in os.listdir(path):
  94. for x in walk_path(os.path.join(path, f), skip_inodes):
  95. yield x
  96. def format_time(t):
  97. """Format datetime suitable for fixed length list output
  98. """
  99. if (datetime.now() - t).days < 365:
  100. return t.strftime('%b %d %H:%M')
  101. else:
  102. return t.strftime('%b %d %Y')
  103. def format_file_mode(mod):
  104. """Format file mode bits for list output
  105. """
  106. def x(v):
  107. return ''.join(v & m and s or '-'
  108. for m, s in ((4, 'r'), (2, 'w'), (1, 'x')))
  109. return '%s%s%s' % (x(mod / 64), x(mod / 8), x(mod))
  110. def format_file_size(v):
  111. """Format file size into a human friendly format
  112. """
  113. if v > 1024 * 1024 * 1024:
  114. return '%.2f GB' % (v / 1024. / 1024. / 1024.)
  115. elif v > 1024 * 1024:
  116. return '%.2f MB' % (v / 1024. / 1024.)
  117. elif v > 1024:
  118. return '%.2f kB' % (v / 1024.)
  119. else:
  120. return str(v)
  121. class IntegrityError(Exception):
  122. """
  123. """
  124. def memoize(function):
  125. cache = {}
  126. def decorated_function(*args):
  127. try:
  128. return cache[args]
  129. except KeyError:
  130. val = function(*args)
  131. cache[args] = val
  132. return val
  133. return decorated_function
  134. @memoize
  135. def uid2user(uid):
  136. try:
  137. return pwd.getpwuid(uid).pw_name
  138. except KeyError:
  139. return None
  140. @memoize
  141. def user2uid(user):
  142. try:
  143. return pwd.getpwnam(user).pw_uid
  144. except KeyError:
  145. return None
  146. @memoize
  147. def gid2group(gid):
  148. try:
  149. return grp.getgrgid(gid).gr_name
  150. except KeyError:
  151. return None
  152. @memoize
  153. def group2gid(group):
  154. try:
  155. return grp.getgrnam(group).gr_gid
  156. except KeyError:
  157. return None
  158. class Location(object):
  159. """Object representing a store / archive location
  160. >>> Location('ssh://user@host:1234/some/path::archive')
  161. Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')
  162. >>> Location('file:///some/path::archive')
  163. Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')
  164. >>> Location('user@host:/some/path::archive')
  165. Location(proto='ssh', user='user', host='host', port=22, path='/some/path', archive='archive')
  166. >>> Location('/some/path::archive')
  167. Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')
  168. """
  169. proto = user = host = port = path = archive = None
  170. ssh_re = re.compile(r'(?P<proto>ssh)://(?:(?P<user>[^@]+)@)?'
  171. r'(?P<host>[^:/#]+)(?::(?P<port>\d+))?'
  172. r'(?P<path>[^:]*)(?:::(?P<archive>.+))?')
  173. file_re = re.compile(r'(?P<proto>file)://'
  174. r'(?P<path>[^:]*)(?:::(?P<archive>.+))?')
  175. scp_re = re.compile(r'((?:(?P<user>[^@]+)@)?(?P<host>[^:/]+):)?'
  176. r'(?P<path>[^:]*)(?:::(?P<archive>.+))?')
  177. def __init__(self, text):
  178. if not self.parse(text):
  179. raise ValueError
  180. def parse(self, text):
  181. m = self.ssh_re.match(text)
  182. if m:
  183. self.proto = m.group('proto')
  184. self.user = m.group('user')
  185. self.host = m.group('host')
  186. self.port = m.group('port') and int(m.group('port')) or 22
  187. self.path = m.group('path')
  188. self.archive = m.group('archive')
  189. return True
  190. m = self.file_re.match(text)
  191. if m:
  192. self.proto = m.group('proto')
  193. self.path = m.group('path')
  194. self.archive = m.group('archive')
  195. return True
  196. m = self.scp_re.match(text)
  197. if m:
  198. self.user = m.group('user')
  199. self.host = m.group('host')
  200. self.path = m.group('path')
  201. self.archive = m.group('archive')
  202. self.proto = self.host and 'ssh' or 'file'
  203. if self.proto == 'ssh':
  204. self.port = 22
  205. return True
  206. return False
  207. def __str__(self):
  208. items = []
  209. items.append('proto=%r' % self.proto)
  210. items.append('user=%r' % self.user)
  211. items.append('host=%r' % self.host)
  212. items.append('port=%r' % self.port)
  213. items.append('path=%r'% self.path)
  214. items.append('archive=%r' % self.archive)
  215. return ', '.join(items)
  216. def __repr__(self):
  217. return "Location(%s)" % self
  218. def location_validator(archive=None):
  219. def validator(text):
  220. try:
  221. loc = Location(text)
  222. except ValueError:
  223. raise argparse.ArgumentTypeError('Invalid location format: "%s"' % text)
  224. if archive is True and not loc.archive:
  225. raise argparse.ArgumentTypeError('"%s": No archive specified' % text)
  226. elif archive is False and loc.archive:
  227. raise argparse.ArgumentTypeError('"%s" No archive can be specified' % text)
  228. return loc
  229. return validator