1 """
2 Useful support routines (for internal use).
3
4 These functions aren't really Zero Install specific; they're things we might
5 wish were in the standard library.
6
7 @since: 0.27
8 """
9
10
11
12
13 from zeroinstall import _, logger
14 import sys, os
15
17 """Search $PATH for prog.
18 If prog is an absolute path, return it unmodified.
19 @param prog: name of executable to find
20 @type prog: str
21 @return: the full path of prog, or None if not found
22 @rtype: str
23 @since: 0.27"""
24 if os.path.isabs(prog): return prog
25 if os.name == "nt":
26 prog += '.exe'
27 for d in os.environ.get('PATH', '/bin:/usr/bin').split(os.pathsep):
28 path = os.path.join(d, prog)
29 if os.path.isfile(path):
30 return path
31 return None
32
34 """Read exactly nbytes from fd.
35 @param fd: file descriptor to read from
36 @type fd: int
37 @param nbytes: number of bytes to read
38 @type nbytes: int
39 @param null_ok: if True, it's OK to receive EOF immediately (we then return None)
40 @type null_ok: bool
41 @return: the bytes read
42 @rtype: bytes
43 @raise Exception: if we received less than nbytes of data"""
44 data = b''
45 while nbytes:
46 got = os.read(fd, nbytes)
47 if not got:
48 if null_ok and not data:
49 return None
50 raise Exception(_("Unexpected end-of-stream. Data so far %(data)s; expecting %(bytes)d bytes more.")
51 % {'data': repr(data), 'bytes': nbytes})
52 data += got
53 nbytes -= len(got)
54 logger.debug(_("Message received: %r"), data)
55 return data
56
58 """Format a size for printing.
59 @param size: the size in bytes
60 @type size: int (or None)
61 @return: the formatted size
62 @rtype: str
63 @since: 0.27"""
64 if size is None:
65 return '?'
66 if size < 2048:
67 return _('%d bytes') % size
68 size = float(size)
69 for unit in (_('KB'), _('MB'), _('GB'), _('TB')):
70 size /= 1024
71 if size < 2048:
72 break
73 return _('%(size).1f %(unit)s') % {'size': size, 'unit': unit}
74
76 """Like shutil.rmtree, except that we also delete read-only items.
77 @param root: the root of the subtree to remove
78 @type root: str
79 @since: 0.28"""
80 import shutil
81 import platform
82 if (os.getcwd() + os.path.sep).startswith(root + os.path.sep):
83 import warnings
84 warnings.warn("Removing tree ({tree}) containing the current directory ({cwd}) - this will not work on Windows".format(cwd = os.getcwd(), tree = root), stacklevel = 2)
85
86 if os.path.isfile(root):
87 os.chmod(root, 0o700)
88 os.remove(root)
89 else:
90 if platform.system() == 'Windows':
91 for main, dirs, files in os.walk(root):
92 for i in files + dirs:
93 os.chmod(os.path.join(main, i), 0o700)
94 os.chmod(root, 0o700)
95 else:
96 for main, dirs, files in os.walk(root):
97 os.chmod(main, 0o700)
98 shutil.rmtree(root)
99
101 """Raise an exception in a way that works on Python 2 and Python 3
102 @type ex: BaseException"""
103 if hasattr(ex, 'with_traceback'):
104 raise ex
105 exec("raise ex, None, tb", {'ex': ex, 'tb': tb})
106 assert 0
107
109 """Rename 'src' to 'dst', which must be on the same filesystem.
110 On POSIX systems, this operation is atomic.
111 On Windows, do the best we can by deleting dst and then renaming.
112 @type src: str
113 @type dst: str
114 @since: 1.9"""
115 if os.name == "nt" and os.path.exists(dst):
116 os.unlink(dst)
117 os.rename(src, dst)
118
120 """Combines multiple strings into one for use as a Windows command-line argument.
121 This coressponds to Windows' handling of command-line arguments as specified in: http://msdn.microsoft.com/library/17w5ykft.
122 @type args: [str]
123 @rtype: str
124 @since: 1.11"""
125 def _escape(arg):
126
127 import string
128 contains_whitespace = any(whitespace in arg for whitespace in string.whitespace)
129 result = '"' if contains_whitespace else ''
130
131
132 parts = arg.split('"')
133 for i, part in enumerate(parts):
134
135 slashes_count = len(part) - len(part.rstrip('\\'))
136
137 result = result + part
138 if i < len(parts) - 1:
139
140 result = result + ("\\" * slashes_count)
141 result = result + "\\" + '"'
142 elif contains_whitespace:
143
144 result = result + ("\\" * slashes_count)
145 result = result + '"'
146
147 return result
148
149 return ' '.join(map(_escape, args))
150
151 if sys.version_info[0] > 2:
152
153 unicode = str
154 basestring = str
155 intern = sys.intern
156 raw_input = input
157
159 """@type url: str
160 @rtype: ParseResult"""
161 from urllib import parse
162 return parse.urlparse(url)
163 else:
164
165 unicode = unicode
166 basestring = basestring
167 intern = intern
168 raw_input = raw_input
169
175