helpers.py 7.5 KB

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