Add <rename> recipe step
[zeroinstall/solver.git] / zeroinstall / support / __init__.py
blobe6327f1bc6b0d5c128b60a13443ca0e5350fb93e
1 """
2 Useful support routines (for internal use).
4 These functions aren't really Zero Install specific; they're things we might
5 wish were in the standard library.
7 @since: 0.27
8 """
10 # Copyright (C) 2009, Thomas Leonard
11 # See the README file for details, or visit http://0install.net.
13 from zeroinstall import _
14 import os, logging
16 def find_in_path(prog):
17 """Search $PATH for prog.
18 If prog is an absolute path, return it unmodified.
19 @param prog: name of executable to find
20 @return: the full path of prog, or None if not found
21 @since: 0.27
22 """
23 if os.path.isabs(prog): return prog
24 if os.name == "nt":
25 prog += '.exe'
26 for d in os.environ.get('PATH', '/bin:/usr/bin').split(os.pathsep):
27 path = os.path.join(d, prog)
28 if os.path.isfile(path):
29 return path
30 return None
32 def read_bytes(fd, nbytes, null_ok = False):
33 """Read exactly nbytes from fd.
34 @param fd: file descriptor to read from
35 @param nbytes: number of bytes to read
36 @param null_ok: if True, it's OK to receive EOF immediately (we then return None)
37 @return: the bytes read
38 @raise Exception: if we received less than nbytes of data
39 """
40 data = ''
41 while nbytes:
42 got = os.read(fd, nbytes)
43 if not got:
44 if null_ok and not data:
45 return None
46 raise Exception(_("Unexpected end-of-stream. Data so far %(data)s; expecting %(bytes)d bytes more.")
47 % {'data': repr(data), 'bytes': nbytes})
48 data += got
49 nbytes -= len(got)
50 logging.debug(_("Message received: %r"), data)
51 return data
53 def pretty_size(size):
54 """Format a size for printing.
55 @param size: the size in bytes
56 @type size: int (or None)
57 @return: the formatted size
58 @rtype: str
59 @since: 0.27"""
60 if size is None:
61 return '?'
62 if size < 2048:
63 return _('%d bytes') % size
64 size = float(size)
65 for unit in (_('KB'), _('MB'), _('GB'), _('TB')):
66 size /= 1024
67 if size < 2048:
68 break
69 return _('%(size).1f %(unit)s') % {'size': size, 'unit': unit}
71 def ro_rmtree(root):
72 """Like shutil.rmtree, except that we also delete read-only items.
73 @param root: the root of the subtree to remove
74 @type root: str
75 @since: 0.28"""
76 import shutil
77 import platform
78 if platform.system() == 'Windows':
79 for main, dirs, files in os.walk(root):
80 for i in files + dirs:
81 os.chmod(os.path.join(main, i), 0o700)
82 os.chmod(root, 0o700)
83 else:
84 for main, dirs, files in os.walk(root):
85 os.chmod(main, 0o700)
86 shutil.rmtree(root)
88 def raise_with_traceback(ex, tb):
89 """Raise an exception in a way that works on Python 2 and Python 3"""
90 if hasattr(ex, 'with_traceback'):
91 raise ex.with_traceback(tb) # Python 3
92 exec("raise ex, None, tb", {'ex': ex, 'tb': tb}) # Python 2
93 assert 0
95 def portable_rename(src, dst):
96 """Rename 'src' to 'dst', which must be on the same filesystem.
97 On POSIX systems, this operation is atomic.
98 On Windows, do the best we can by deleting dst and then renaming.
99 @since: 1.9"""
100 if os.name == "nt" and os.path.exists(dst):
101 os.unlink(dst)
102 os.rename(src, dst)