##// END OF EJS Templates
pycompat: add maplist alias for old map behavior
Augie Fackler -
r31501:a1e40cee default
parent child Browse files
Show More
@@ -1,385 +1,387 b''
1 1 # pycompat.py - portability shim for python 3
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 """Mercurial portability shim for python 3.
7 7
8 8 This contains aliases to hide python version-specific details from the core.
9 9 """
10 10
11 11 from __future__ import absolute_import
12 12
13 13 import getopt
14 14 import os
15 15 import shlex
16 16 import sys
17 17
18 18 ispy3 = (sys.version_info[0] >= 3)
19 19
20 20 if not ispy3:
21 21 import cPickle as pickle
22 22 import httplib
23 23 import Queue as _queue
24 24 import SocketServer as socketserver
25 25 import urlparse
26 26 urlunquote = urlparse.unquote
27 27 import xmlrpclib
28 28 else:
29 29 import http.client as httplib
30 30 import pickle
31 31 import queue as _queue
32 32 import socketserver
33 33 import urllib.parse as urlparse
34 34 urlunquote = urlparse.unquote_to_bytes
35 35 import xmlrpc.client as xmlrpclib
36 36
37 37 if ispy3:
38 38 import builtins
39 39 import functools
40 40 import io
41 41 import struct
42 42
43 43 fsencode = os.fsencode
44 44 fsdecode = os.fsdecode
45 45 # A bytes version of os.name.
46 46 osname = os.name.encode('ascii')
47 47 ospathsep = os.pathsep.encode('ascii')
48 48 ossep = os.sep.encode('ascii')
49 49 osaltsep = os.altsep
50 50 if osaltsep:
51 51 osaltsep = osaltsep.encode('ascii')
52 52 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
53 53 # returns bytes.
54 54 getcwd = os.getcwdb
55 55 sysplatform = sys.platform.encode('ascii')
56 56 sysexecutable = sys.executable
57 57 if sysexecutable:
58 58 sysexecutable = os.fsencode(sysexecutable)
59 59 stringio = io.BytesIO
60 maplist = lambda *args: list(map(*args))
60 61
61 62 # TODO: .buffer might not exist if std streams were replaced; we'll need
62 63 # a silly wrapper to make a bytes stream backed by a unicode one.
63 64 stdin = sys.stdin.buffer
64 65 stdout = sys.stdout.buffer
65 66 stderr = sys.stderr.buffer
66 67
67 68 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
68 69 # we can use os.fsencode() to get back bytes argv.
69 70 #
70 71 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
71 72 #
72 73 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
73 74 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
74 75 if getattr(sys, 'argv', None) is not None:
75 76 sysargv = list(map(os.fsencode, sys.argv))
76 77
77 78 bytechr = struct.Struct('>B').pack
78 79
79 80 class bytestr(bytes):
80 81 """A bytes which mostly acts as a Python 2 str
81 82
82 83 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
83 84 (b'', b'foo', b'ascii', b'1')
84 85 >>> s = bytestr(b'foo')
85 86 >>> assert s is bytestr(s)
86 87
87 88 There's no implicit conversion from non-ascii str as its encoding is
88 89 unknown:
89 90
90 91 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
91 92 Traceback (most recent call last):
92 93 ...
93 94 UnicodeEncodeError: ...
94 95
95 96 Comparison between bytestr and bytes should work:
96 97
97 98 >>> assert bytestr(b'foo') == b'foo'
98 99 >>> assert b'foo' == bytestr(b'foo')
99 100 >>> assert b'f' in bytestr(b'foo')
100 101 >>> assert bytestr(b'f') in b'foo'
101 102
102 103 Sliced elements should be bytes, not integer:
103 104
104 105 >>> s[1], s[:2]
105 106 (b'o', b'fo')
106 107 >>> list(s), list(reversed(s))
107 108 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
108 109
109 110 As bytestr type isn't propagated across operations, you need to cast
110 111 bytes to bytestr explicitly:
111 112
112 113 >>> s = bytestr(b'foo').upper()
113 114 >>> t = bytestr(s)
114 115 >>> s[0], t[0]
115 116 (70, b'F')
116 117
117 118 Be careful to not pass a bytestr object to a function which expects
118 119 bytearray-like behavior.
119 120
120 121 >>> t = bytes(t) # cast to bytes
121 122 >>> assert type(t) is bytes
122 123 """
123 124
124 125 def __new__(cls, s=b''):
125 126 if isinstance(s, bytestr):
126 127 return s
127 128 if not isinstance(s, (bytes, bytearray)):
128 129 s = str(s).encode(u'ascii')
129 130 return bytes.__new__(cls, s)
130 131
131 132 def __getitem__(self, key):
132 133 s = bytes.__getitem__(self, key)
133 134 if not isinstance(s, bytes):
134 135 s = bytechr(s)
135 136 return s
136 137
137 138 def __iter__(self):
138 139 return iterbytestr(bytes.__iter__(self))
139 140
140 141 def iterbytestr(s):
141 142 """Iterate bytes as if it were a str object of Python 2"""
142 143 return map(bytechr, s)
143 144
144 145 def sysstr(s):
145 146 """Return a keyword str to be passed to Python functions such as
146 147 getattr() and str.encode()
147 148
148 149 This never raises UnicodeDecodeError. Non-ascii characters are
149 150 considered invalid and mapped to arbitrary but unique code points
150 151 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
151 152 """
152 153 if isinstance(s, builtins.str):
153 154 return s
154 155 return s.decode(u'latin-1')
155 156
156 157 def _wrapattrfunc(f):
157 158 @functools.wraps(f)
158 159 def w(object, name, *args):
159 160 return f(object, sysstr(name), *args)
160 161 return w
161 162
162 163 # these wrappers are automagically imported by hgloader
163 164 delattr = _wrapattrfunc(builtins.delattr)
164 165 getattr = _wrapattrfunc(builtins.getattr)
165 166 hasattr = _wrapattrfunc(builtins.hasattr)
166 167 setattr = _wrapattrfunc(builtins.setattr)
167 168 xrange = builtins.range
168 169
169 170 def open(name, mode='r', buffering=-1):
170 171 return builtins.open(name, sysstr(mode), buffering)
171 172
172 173 # getopt.getopt() on Python 3 deals with unicodes internally so we cannot
173 174 # pass bytes there. Passing unicodes will result in unicodes as return
174 175 # values which we need to convert again to bytes.
175 176 def getoptb(args, shortlist, namelist):
176 177 args = [a.decode('latin-1') for a in args]
177 178 shortlist = shortlist.decode('latin-1')
178 179 namelist = [a.decode('latin-1') for a in namelist]
179 180 opts, args = getopt.getopt(args, shortlist, namelist)
180 181 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
181 182 for a in opts]
182 183 args = [a.encode('latin-1') for a in args]
183 184 return opts, args
184 185
185 186 # keys of keyword arguments in Python need to be strings which are unicodes
186 187 # Python 3. This function takes keyword arguments, convert the keys to str.
187 188 def strkwargs(dic):
188 189 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
189 190 return dic
190 191
191 192 # keys of keyword arguments need to be unicode while passing into
192 193 # a function. This function helps us to convert those keys back to bytes
193 194 # again as we need to deal with bytes.
194 195 def byteskwargs(dic):
195 196 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
196 197 return dic
197 198
198 199 # shlex.split() accepts unicodes on Python 3. This function takes bytes
199 200 # argument, convert it into unicodes, pass into shlex.split(), convert the
200 201 # returned value to bytes and return that.
201 202 # TODO: handle shlex.shlex().
202 203 def shlexsplit(s):
203 204 ret = shlex.split(s.decode('latin-1'))
204 205 return [a.encode('latin-1') for a in ret]
205 206
206 207 else:
207 208 import cStringIO
208 209
209 210 bytechr = chr
210 211 bytestr = str
211 212 iterbytestr = iter
212 213
213 214 def sysstr(s):
214 215 return s
215 216
216 217 # Partial backport from os.py in Python 3, which only accepts bytes.
217 218 # In Python 2, our paths should only ever be bytes, a unicode path
218 219 # indicates a bug.
219 220 def fsencode(filename):
220 221 if isinstance(filename, str):
221 222 return filename
222 223 else:
223 224 raise TypeError(
224 225 "expect str, not %s" % type(filename).__name__)
225 226
226 227 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
227 228 # better not to touch Python 2 part as it's already working fine.
228 229 def fsdecode(filename):
229 230 return filename
230 231
231 232 def getoptb(args, shortlist, namelist):
232 233 return getopt.getopt(args, shortlist, namelist)
233 234
234 235 def strkwargs(dic):
235 236 return dic
236 237
237 238 def byteskwargs(dic):
238 239 return dic
239 240
240 241 osname = os.name
241 242 ospathsep = os.pathsep
242 243 ossep = os.sep
243 244 osaltsep = os.altsep
244 245 stdin = sys.stdin
245 246 stdout = sys.stdout
246 247 stderr = sys.stderr
247 248 if getattr(sys, 'argv', None) is not None:
248 249 sysargv = sys.argv
249 250 sysplatform = sys.platform
250 251 getcwd = os.getcwd
251 252 sysexecutable = sys.executable
252 253 shlexsplit = shlex.split
253 254 stringio = cStringIO.StringIO
255 maplist = map
254 256
255 257 empty = _queue.Empty
256 258 queue = _queue.Queue
257 259
258 260 class _pycompatstub(object):
259 261 def __init__(self):
260 262 self._aliases = {}
261 263
262 264 def _registeraliases(self, origin, items):
263 265 """Add items that will be populated at the first access"""
264 266 items = map(sysstr, items)
265 267 self._aliases.update(
266 268 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
267 269 for item in items)
268 270
269 271 def __getattr__(self, name):
270 272 try:
271 273 origin, item = self._aliases[name]
272 274 except KeyError:
273 275 raise AttributeError(name)
274 276 self.__dict__[name] = obj = getattr(origin, item)
275 277 return obj
276 278
277 279 httpserver = _pycompatstub()
278 280 urlreq = _pycompatstub()
279 281 urlerr = _pycompatstub()
280 282 if not ispy3:
281 283 import BaseHTTPServer
282 284 import CGIHTTPServer
283 285 import SimpleHTTPServer
284 286 import urllib2
285 287 import urllib
286 288 urlreq._registeraliases(urllib, (
287 289 "addclosehook",
288 290 "addinfourl",
289 291 "ftpwrapper",
290 292 "pathname2url",
291 293 "quote",
292 294 "splitattr",
293 295 "splitpasswd",
294 296 "splitport",
295 297 "splituser",
296 298 "unquote",
297 299 "url2pathname",
298 300 "urlencode",
299 301 ))
300 302 urlreq._registeraliases(urllib2, (
301 303 "AbstractHTTPHandler",
302 304 "BaseHandler",
303 305 "build_opener",
304 306 "FileHandler",
305 307 "FTPHandler",
306 308 "HTTPBasicAuthHandler",
307 309 "HTTPDigestAuthHandler",
308 310 "HTTPHandler",
309 311 "HTTPPasswordMgrWithDefaultRealm",
310 312 "HTTPSHandler",
311 313 "install_opener",
312 314 "ProxyHandler",
313 315 "Request",
314 316 "urlopen",
315 317 ))
316 318 urlerr._registeraliases(urllib2, (
317 319 "HTTPError",
318 320 "URLError",
319 321 ))
320 322 httpserver._registeraliases(BaseHTTPServer, (
321 323 "HTTPServer",
322 324 "BaseHTTPRequestHandler",
323 325 ))
324 326 httpserver._registeraliases(SimpleHTTPServer, (
325 327 "SimpleHTTPRequestHandler",
326 328 ))
327 329 httpserver._registeraliases(CGIHTTPServer, (
328 330 "CGIHTTPRequestHandler",
329 331 ))
330 332
331 333 else:
332 334 import urllib.parse
333 335 urlreq._registeraliases(urllib.parse, (
334 336 "splitattr",
335 337 "splitpasswd",
336 338 "splitport",
337 339 "splituser",
338 340 "unquote",
339 341 ))
340 342 import urllib.request
341 343 urlreq._registeraliases(urllib.request, (
342 344 "AbstractHTTPHandler",
343 345 "BaseHandler",
344 346 "build_opener",
345 347 "FileHandler",
346 348 "FTPHandler",
347 349 "ftpwrapper",
348 350 "HTTPHandler",
349 351 "HTTPSHandler",
350 352 "install_opener",
351 353 "pathname2url",
352 354 "HTTPBasicAuthHandler",
353 355 "HTTPDigestAuthHandler",
354 356 "HTTPPasswordMgrWithDefaultRealm",
355 357 "ProxyHandler",
356 358 "Request",
357 359 "url2pathname",
358 360 "urlopen",
359 361 ))
360 362 import urllib.response
361 363 urlreq._registeraliases(urllib.response, (
362 364 "addclosehook",
363 365 "addinfourl",
364 366 ))
365 367 import urllib.error
366 368 urlerr._registeraliases(urllib.error, (
367 369 "HTTPError",
368 370 "URLError",
369 371 ))
370 372 import http.server
371 373 httpserver._registeraliases(http.server, (
372 374 "HTTPServer",
373 375 "BaseHTTPRequestHandler",
374 376 "SimpleHTTPRequestHandler",
375 377 "CGIHTTPRequestHandler",
376 378 ))
377 379
378 380 # urllib.parse.quote() accepts both str and bytes, decodes bytes
379 381 # (if necessary), and returns str. This is wonky. We provide a custom
380 382 # implementation that only accepts bytes and emits bytes.
381 383 def quote(s, safe=r'/'):
382 384 s = urllib.parse.quote_from_bytes(s, safe=safe)
383 385 return s.encode('ascii', 'strict')
384 386
385 387 urlreq.quote = quote
General Comments 0
You need to be logged in to leave comments. Login now