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.
10 # Copyright (C) 2009, Thomas Leonard
11 # See the README file for details, or visit http://0install.net.
13 from zeroinstall
import _
, logger
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
23 if os
.path
.isabs(prog
): return prog
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
):
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
42 got
= os
.read(fd
, nbytes
)
44 if null_ok
and not data
:
46 raise Exception(_("Unexpected end-of-stream. Data so far %(data)s; expecting %(bytes)d bytes more.")
47 % {'data': repr(data
), 'bytes': nbytes
})
50 logger
.debug(_("Message received: %r"), 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
63 return _('%d bytes') % size
65 for unit
in (_('KB'), _('MB'), _('GB'), _('TB')):
69 return _('%(size).1f %(unit)s') % {'size': size
, 'unit': unit
}
72 """Like shutil.rmtree, except that we also delete read-only items.
73 @param root: the root of the subtree to remove
78 if (os
.getcwd() + os
.path
.sep
).startswith(root
+ os
.path
.sep
):
80 warnings
.warn("Removing tree ({tree}) containing the current directory ({cwd}) - this will not work on Windows".format(cwd
= os
.getcwd(), tree
= root
), stacklevel
= 2)
82 if platform
.system() == 'Windows':
83 for main
, dirs
, files
in os
.walk(root
):
84 for i
in files
+ dirs
:
85 os
.chmod(os
.path
.join(main
, i
), 0o700)
88 for main
, dirs
, files
in os
.walk(root
):
92 def raise_with_traceback(ex
, tb
):
93 """Raise an exception in a way that works on Python 2 and Python 3"""
94 if hasattr(ex
, 'with_traceback'):
96 exec("raise ex, None, tb", {'ex': ex
, 'tb': tb
}) # Python 2
99 def portable_rename(src
, dst
):
100 """Rename 'src' to 'dst', which must be on the same filesystem.
101 On POSIX systems, this operation is atomic.
102 On Windows, do the best we can by deleting dst and then renaming.
104 if os
.name
== "nt" and os
.path
.exists(dst
):
108 def windows_args_escape(args
):
109 """Combines multiple strings into one for use as a Windows command-line argument.
110 This coressponds to Windows' handling of command-line arguments as specified in: http://msdn.microsoft.com/library/17w5ykft.
113 # Add leading quotation mark if there are whitespaces
115 contains_whitespace
= any(whitespace
in arg
for whitespace
in string
.whitespace
)
116 result
= '"' if contains_whitespace
else ''
118 # Split by quotation marks
119 parts
= arg
.split('"')
120 for i
, part
in enumerate(parts
):
121 # Count slashes preceeding the quotation mark
122 slashes_count
= len(part
) - len(part
.rstrip('\\'))
124 result
= result
+ part
125 if i
< len(parts
) - 1:
127 result
= result
+ ("\\" * slashes_count
) # Double number of slashes
128 result
= result
+ "\\" + '"' # Escaped quotation mark
129 elif contains_whitespace
:
130 # Last part if there are whitespaces
131 result
= result
+ ("\\" * slashes_count
) # Double number of slashes
132 result
= result
+ '"' # Non-escaped quotation mark
136 return ' '.join(map(_escape
, args
))
138 if sys
.version_info
[0] > 2:
146 from urllib
import parse
147 return parse
.urlparse(url
)
150 unicode = unicode # (otherwise it can't be imported)
151 basestring
= basestring
153 raw_input = raw_input
157 return urlparse
.urlparse(url
)