##// END OF EJS Templates
util: use safehasattr or getattr instead of hasattr
Augie Fackler -
r14968:b7dbe957 default
parent child Browse files
Show More
@@ -1,1645 +1,1646
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, time, calendar, textwrap, unicodedata, signal
20 20 import imp, socket, urllib
21 21
22 22 if os.name == 'nt':
23 23 import windows as platform
24 24 else:
25 25 import posix as platform
26 26
27 27 cachestat = platform.cachestat
28 28 checkexec = platform.checkexec
29 29 checklink = platform.checklink
30 30 executablepath = platform.executablepath
31 31 expandglobs = platform.expandglobs
32 32 explainexit = platform.explainexit
33 33 findexe = platform.findexe
34 34 gethgcmd = platform.gethgcmd
35 35 getuser = platform.getuser
36 36 groupmembers = platform.groupmembers
37 37 groupname = platform.groupname
38 38 hidewindow = platform.hidewindow
39 39 isexec = platform.isexec
40 40 isowner = platform.isowner
41 41 localpath = platform.localpath
42 42 lookupreg = platform.lookupreg
43 43 makedir = platform.makedir
44 44 nlinks = platform.nlinks
45 45 normpath = platform.normpath
46 46 nulldev = platform.nulldev
47 47 openhardlinks = platform.openhardlinks
48 48 oslink = platform.oslink
49 49 parsepatchoutput = platform.parsepatchoutput
50 50 pconvert = platform.pconvert
51 51 popen = platform.popen
52 52 posixfile = platform.posixfile
53 53 quotecommand = platform.quotecommand
54 54 realpath = platform.realpath
55 55 rename = platform.rename
56 56 samedevice = platform.samedevice
57 57 samefile = platform.samefile
58 58 samestat = platform.samestat
59 59 setbinary = platform.setbinary
60 60 setflags = platform.setflags
61 61 setsignalhandler = platform.setsignalhandler
62 62 shellquote = platform.shellquote
63 63 spawndetached = platform.spawndetached
64 64 sshargs = platform.sshargs
65 65 statfiles = platform.statfiles
66 66 termwidth = platform.termwidth
67 67 testpid = platform.testpid
68 68 umask = platform.umask
69 69 unlink = platform.unlink
70 70 unlinkpath = platform.unlinkpath
71 71 username = platform.username
72 72
73 73 # Python compatibility
74 74
75 75 def sha1(s):
76 76 return _fastsha1(s)
77 77
78 78 _notset = object()
79 79 def safehasattr(thing, attr):
80 80 return getattr(thing, attr, _notset) is not _notset
81 81
82 82 def _fastsha1(s):
83 83 # This function will import sha1 from hashlib or sha (whichever is
84 84 # available) and overwrite itself with it on the first call.
85 85 # Subsequent calls will go directly to the imported function.
86 86 if sys.version_info >= (2, 5):
87 87 from hashlib import sha1 as _sha1
88 88 else:
89 89 from sha import sha as _sha1
90 90 global _fastsha1, sha1
91 91 _fastsha1 = sha1 = _sha1
92 92 return _sha1(s)
93 93
94 94 import __builtin__
95 95
96 96 if sys.version_info[0] < 3:
97 97 def fakebuffer(sliceable, offset=0):
98 98 return sliceable[offset:]
99 99 else:
100 100 def fakebuffer(sliceable, offset=0):
101 101 return memoryview(sliceable)[offset:]
102 102 try:
103 103 buffer
104 104 except NameError:
105 105 __builtin__.buffer = fakebuffer
106 106
107 107 import subprocess
108 108 closefds = os.name == 'posix'
109 109
110 110 def popen2(cmd, env=None, newlines=False):
111 111 # Setting bufsize to -1 lets the system decide the buffer size.
112 112 # The default for bufsize is 0, meaning unbuffered. This leads to
113 113 # poor performance on Mac OS X: http://bugs.python.org/issue4194
114 114 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
115 115 close_fds=closefds,
116 116 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
117 117 universal_newlines=newlines,
118 118 env=env)
119 119 return p.stdin, p.stdout
120 120
121 121 def popen3(cmd, env=None, newlines=False):
122 122 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
123 123 close_fds=closefds,
124 124 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
125 125 stderr=subprocess.PIPE,
126 126 universal_newlines=newlines,
127 127 env=env)
128 128 return p.stdin, p.stdout, p.stderr
129 129
130 130 def version():
131 131 """Return version information if available."""
132 132 try:
133 133 import __version__
134 134 return __version__.version
135 135 except ImportError:
136 136 return 'unknown'
137 137
138 138 # used by parsedate
139 139 defaultdateformats = (
140 140 '%Y-%m-%d %H:%M:%S',
141 141 '%Y-%m-%d %I:%M:%S%p',
142 142 '%Y-%m-%d %H:%M',
143 143 '%Y-%m-%d %I:%M%p',
144 144 '%Y-%m-%d',
145 145 '%m-%d',
146 146 '%m/%d',
147 147 '%m/%d/%y',
148 148 '%m/%d/%Y',
149 149 '%a %b %d %H:%M:%S %Y',
150 150 '%a %b %d %I:%M:%S%p %Y',
151 151 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
152 152 '%b %d %H:%M:%S %Y',
153 153 '%b %d %I:%M:%S%p %Y',
154 154 '%b %d %H:%M:%S',
155 155 '%b %d %I:%M:%S%p',
156 156 '%b %d %H:%M',
157 157 '%b %d %I:%M%p',
158 158 '%b %d %Y',
159 159 '%b %d',
160 160 '%H:%M:%S',
161 161 '%I:%M:%S%p',
162 162 '%H:%M',
163 163 '%I:%M%p',
164 164 )
165 165
166 166 extendeddateformats = defaultdateformats + (
167 167 "%Y",
168 168 "%Y-%m",
169 169 "%b",
170 170 "%b %Y",
171 171 )
172 172
173 173 def cachefunc(func):
174 174 '''cache the result of function calls'''
175 175 # XXX doesn't handle keywords args
176 176 cache = {}
177 177 if func.func_code.co_argcount == 1:
178 178 # we gain a small amount of time because
179 179 # we don't need to pack/unpack the list
180 180 def f(arg):
181 181 if arg not in cache:
182 182 cache[arg] = func(arg)
183 183 return cache[arg]
184 184 else:
185 185 def f(*args):
186 186 if args not in cache:
187 187 cache[args] = func(*args)
188 188 return cache[args]
189 189
190 190 return f
191 191
192 192 def lrucachefunc(func):
193 193 '''cache most recent results of function calls'''
194 194 cache = {}
195 195 order = []
196 196 if func.func_code.co_argcount == 1:
197 197 def f(arg):
198 198 if arg not in cache:
199 199 if len(cache) > 20:
200 200 del cache[order.pop(0)]
201 201 cache[arg] = func(arg)
202 202 else:
203 203 order.remove(arg)
204 204 order.append(arg)
205 205 return cache[arg]
206 206 else:
207 207 def f(*args):
208 208 if args not in cache:
209 209 if len(cache) > 20:
210 210 del cache[order.pop(0)]
211 211 cache[args] = func(*args)
212 212 else:
213 213 order.remove(args)
214 214 order.append(args)
215 215 return cache[args]
216 216
217 217 return f
218 218
219 219 class propertycache(object):
220 220 def __init__(self, func):
221 221 self.func = func
222 222 self.name = func.__name__
223 223 def __get__(self, obj, type=None):
224 224 result = self.func(obj)
225 225 setattr(obj, self.name, result)
226 226 return result
227 227
228 228 def pipefilter(s, cmd):
229 229 '''filter string S through command CMD, returning its output'''
230 230 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
231 231 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
232 232 pout, perr = p.communicate(s)
233 233 return pout
234 234
235 235 def tempfilter(s, cmd):
236 236 '''filter string S through a pair of temporary files with CMD.
237 237 CMD is used as a template to create the real command to be run,
238 238 with the strings INFILE and OUTFILE replaced by the real names of
239 239 the temporary files generated.'''
240 240 inname, outname = None, None
241 241 try:
242 242 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
243 243 fp = os.fdopen(infd, 'wb')
244 244 fp.write(s)
245 245 fp.close()
246 246 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
247 247 os.close(outfd)
248 248 cmd = cmd.replace('INFILE', inname)
249 249 cmd = cmd.replace('OUTFILE', outname)
250 250 code = os.system(cmd)
251 251 if sys.platform == 'OpenVMS' and code & 1:
252 252 code = 0
253 253 if code:
254 254 raise Abort(_("command '%s' failed: %s") %
255 255 (cmd, explainexit(code)))
256 256 fp = open(outname, 'rb')
257 257 r = fp.read()
258 258 fp.close()
259 259 return r
260 260 finally:
261 261 try:
262 262 if inname:
263 263 os.unlink(inname)
264 264 except OSError:
265 265 pass
266 266 try:
267 267 if outname:
268 268 os.unlink(outname)
269 269 except OSError:
270 270 pass
271 271
272 272 filtertable = {
273 273 'tempfile:': tempfilter,
274 274 'pipe:': pipefilter,
275 275 }
276 276
277 277 def filter(s, cmd):
278 278 "filter a string through a command that transforms its input to its output"
279 279 for name, fn in filtertable.iteritems():
280 280 if cmd.startswith(name):
281 281 return fn(s, cmd[len(name):].lstrip())
282 282 return pipefilter(s, cmd)
283 283
284 284 def binary(s):
285 285 """return true if a string is binary data"""
286 286 return bool(s and '\0' in s)
287 287
288 288 def increasingchunks(source, min=1024, max=65536):
289 289 '''return no less than min bytes per chunk while data remains,
290 290 doubling min after each chunk until it reaches max'''
291 291 def log2(x):
292 292 if not x:
293 293 return 0
294 294 i = 0
295 295 while x:
296 296 x >>= 1
297 297 i += 1
298 298 return i - 1
299 299
300 300 buf = []
301 301 blen = 0
302 302 for chunk in source:
303 303 buf.append(chunk)
304 304 blen += len(chunk)
305 305 if blen >= min:
306 306 if min < max:
307 307 min = min << 1
308 308 nmin = 1 << log2(blen)
309 309 if nmin > min:
310 310 min = nmin
311 311 if min > max:
312 312 min = max
313 313 yield ''.join(buf)
314 314 blen = 0
315 315 buf = []
316 316 if buf:
317 317 yield ''.join(buf)
318 318
319 319 Abort = error.Abort
320 320
321 321 def always(fn):
322 322 return True
323 323
324 324 def never(fn):
325 325 return False
326 326
327 327 def pathto(root, n1, n2):
328 328 '''return the relative path from one place to another.
329 329 root should use os.sep to separate directories
330 330 n1 should use os.sep to separate directories
331 331 n2 should use "/" to separate directories
332 332 returns an os.sep-separated path.
333 333
334 334 If n1 is a relative path, it's assumed it's
335 335 relative to root.
336 336 n2 should always be relative to root.
337 337 '''
338 338 if not n1:
339 339 return localpath(n2)
340 340 if os.path.isabs(n1):
341 341 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
342 342 return os.path.join(root, localpath(n2))
343 343 n2 = '/'.join((pconvert(root), n2))
344 344 a, b = splitpath(n1), n2.split('/')
345 345 a.reverse()
346 346 b.reverse()
347 347 while a and b and a[-1] == b[-1]:
348 348 a.pop()
349 349 b.pop()
350 350 b.reverse()
351 351 return os.sep.join((['..'] * len(a)) + b) or '.'
352 352
353 353 _hgexecutable = None
354 354
355 355 def mainfrozen():
356 356 """return True if we are a frozen executable.
357 357
358 358 The code supports py2exe (most common, Windows only) and tools/freeze
359 359 (portable, not much used).
360 360 """
361 return (hasattr(sys, "frozen") or # new py2exe
362 hasattr(sys, "importers") or # old py2exe
361 return (safehasattr(sys, "frozen") or # new py2exe
362 safehasattr(sys, "importers") or # old py2exe
363 363 imp.is_frozen("__main__")) # tools/freeze
364 364
365 365 def hgexecutable():
366 366 """return location of the 'hg' executable.
367 367
368 368 Defaults to $HG or 'hg' in the search path.
369 369 """
370 370 if _hgexecutable is None:
371 371 hg = os.environ.get('HG')
372 372 if hg:
373 373 _sethgexecutable(hg)
374 374 elif mainfrozen():
375 375 _sethgexecutable(sys.executable)
376 376 else:
377 377 exe = findexe('hg') or os.path.basename(sys.argv[0])
378 378 _sethgexecutable(exe)
379 379 return _hgexecutable
380 380
381 381 def _sethgexecutable(path):
382 382 """set location of the 'hg' executable"""
383 383 global _hgexecutable
384 384 _hgexecutable = path
385 385
386 386 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
387 387 '''enhanced shell command execution.
388 388 run with environment maybe modified, maybe in different dir.
389 389
390 390 if command fails and onerr is None, return status. if ui object,
391 391 print error message and return status, else raise onerr object as
392 392 exception.
393 393
394 394 if out is specified, it is assumed to be a file-like object that has a
395 395 write() method. stdout and stderr will be redirected to out.'''
396 396 try:
397 397 sys.stdout.flush()
398 398 except Exception:
399 399 pass
400 400 def py2shell(val):
401 401 'convert python object into string that is useful to shell'
402 402 if val is None or val is False:
403 403 return '0'
404 404 if val is True:
405 405 return '1'
406 406 return str(val)
407 407 origcmd = cmd
408 408 cmd = quotecommand(cmd)
409 409 env = dict(os.environ)
410 410 env.update((k, py2shell(v)) for k, v in environ.iteritems())
411 411 env['HG'] = hgexecutable()
412 412 if out is None or out == sys.__stdout__:
413 413 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
414 414 env=env, cwd=cwd)
415 415 else:
416 416 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
417 417 env=env, cwd=cwd, stdout=subprocess.PIPE,
418 418 stderr=subprocess.STDOUT)
419 419 for line in proc.stdout:
420 420 out.write(line)
421 421 proc.wait()
422 422 rc = proc.returncode
423 423 if sys.platform == 'OpenVMS' and rc & 1:
424 424 rc = 0
425 425 if rc and onerr:
426 426 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
427 427 explainexit(rc)[0])
428 428 if errprefix:
429 429 errmsg = '%s: %s' % (errprefix, errmsg)
430 430 try:
431 431 onerr.warn(errmsg + '\n')
432 432 except AttributeError:
433 433 raise onerr(errmsg)
434 434 return rc
435 435
436 436 def checksignature(func):
437 437 '''wrap a function with code to check for calling errors'''
438 438 def check(*args, **kwargs):
439 439 try:
440 440 return func(*args, **kwargs)
441 441 except TypeError:
442 442 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
443 443 raise error.SignatureError
444 444 raise
445 445
446 446 return check
447 447
448 448 def copyfile(src, dest):
449 449 "copy a file, preserving mode and atime/mtime"
450 450 if os.path.islink(src):
451 451 try:
452 452 os.unlink(dest)
453 453 except OSError:
454 454 pass
455 455 os.symlink(os.readlink(src), dest)
456 456 else:
457 457 try:
458 458 shutil.copyfile(src, dest)
459 459 shutil.copymode(src, dest)
460 460 except shutil.Error, inst:
461 461 raise Abort(str(inst))
462 462
463 463 def copyfiles(src, dst, hardlink=None):
464 464 """Copy a directory tree using hardlinks if possible"""
465 465
466 466 if hardlink is None:
467 467 hardlink = (os.stat(src).st_dev ==
468 468 os.stat(os.path.dirname(dst)).st_dev)
469 469
470 470 num = 0
471 471 if os.path.isdir(src):
472 472 os.mkdir(dst)
473 473 for name, kind in osutil.listdir(src):
474 474 srcname = os.path.join(src, name)
475 475 dstname = os.path.join(dst, name)
476 476 hardlink, n = copyfiles(srcname, dstname, hardlink)
477 477 num += n
478 478 else:
479 479 if hardlink:
480 480 try:
481 481 oslink(src, dst)
482 482 except (IOError, OSError):
483 483 hardlink = False
484 484 shutil.copy(src, dst)
485 485 else:
486 486 shutil.copy(src, dst)
487 487 num += 1
488 488
489 489 return hardlink, num
490 490
491 491 _winreservednames = '''con prn aux nul
492 492 com1 com2 com3 com4 com5 com6 com7 com8 com9
493 493 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
494 494 _winreservedchars = ':*?"<>|'
495 495 def checkwinfilename(path):
496 496 '''Check that the base-relative path is a valid filename on Windows.
497 497 Returns None if the path is ok, or a UI string describing the problem.
498 498
499 499 >>> checkwinfilename("just/a/normal/path")
500 500 >>> checkwinfilename("foo/bar/con.xml")
501 501 "filename contains 'con', which is reserved on Windows"
502 502 >>> checkwinfilename("foo/con.xml/bar")
503 503 "filename contains 'con', which is reserved on Windows"
504 504 >>> checkwinfilename("foo/bar/xml.con")
505 505 >>> checkwinfilename("foo/bar/AUX/bla.txt")
506 506 "filename contains 'AUX', which is reserved on Windows"
507 507 >>> checkwinfilename("foo/bar/bla:.txt")
508 508 "filename contains ':', which is reserved on Windows"
509 509 >>> checkwinfilename("foo/bar/b\07la.txt")
510 510 "filename contains '\\\\x07', which is invalid on Windows"
511 511 >>> checkwinfilename("foo/bar/bla ")
512 512 "filename ends with ' ', which is not allowed on Windows"
513 513 '''
514 514 for n in path.replace('\\', '/').split('/'):
515 515 if not n:
516 516 continue
517 517 for c in n:
518 518 if c in _winreservedchars:
519 519 return _("filename contains '%s', which is reserved "
520 520 "on Windows") % c
521 521 if ord(c) <= 31:
522 522 return _("filename contains %r, which is invalid "
523 523 "on Windows") % c
524 524 base = n.split('.')[0]
525 525 if base and base.lower() in _winreservednames:
526 526 return _("filename contains '%s', which is reserved "
527 527 "on Windows") % base
528 528 t = n[-1]
529 529 if t in '. ':
530 530 return _("filename ends with '%s', which is not allowed "
531 531 "on Windows") % t
532 532
533 533 if os.name == 'nt':
534 534 checkosfilename = checkwinfilename
535 535 else:
536 536 checkosfilename = platform.checkosfilename
537 537
538 538 def makelock(info, pathname):
539 539 try:
540 540 return os.symlink(info, pathname)
541 541 except OSError, why:
542 542 if why.errno == errno.EEXIST:
543 543 raise
544 544 except AttributeError: # no symlink in os
545 545 pass
546 546
547 547 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
548 548 os.write(ld, info)
549 549 os.close(ld)
550 550
551 551 def readlock(pathname):
552 552 try:
553 553 return os.readlink(pathname)
554 554 except OSError, why:
555 555 if why.errno not in (errno.EINVAL, errno.ENOSYS):
556 556 raise
557 557 except AttributeError: # no symlink in os
558 558 pass
559 559 fp = posixfile(pathname)
560 560 r = fp.read()
561 561 fp.close()
562 562 return r
563 563
564 564 def fstat(fp):
565 565 '''stat file object that may not have fileno method.'''
566 566 try:
567 567 return os.fstat(fp.fileno())
568 568 except AttributeError:
569 569 return os.stat(fp.name)
570 570
571 571 # File system features
572 572
573 573 def checkcase(path):
574 574 """
575 575 Check whether the given path is on a case-sensitive filesystem
576 576
577 577 Requires a path (like /foo/.hg) ending with a foldable final
578 578 directory component.
579 579 """
580 580 s1 = os.stat(path)
581 581 d, b = os.path.split(path)
582 582 p2 = os.path.join(d, b.upper())
583 583 if path == p2:
584 584 p2 = os.path.join(d, b.lower())
585 585 try:
586 586 s2 = os.stat(p2)
587 587 if s2 == s1:
588 588 return False
589 589 return True
590 590 except OSError:
591 591 return True
592 592
593 593 _fspathcache = {}
594 594 def fspath(name, root):
595 595 '''Get name in the case stored in the filesystem
596 596
597 597 The name is either relative to root, or it is an absolute path starting
598 598 with root. Note that this function is unnecessary, and should not be
599 599 called, for case-sensitive filesystems (simply because it's expensive).
600 600 '''
601 601 # If name is absolute, make it relative
602 602 if name.lower().startswith(root.lower()):
603 603 l = len(root)
604 604 if name[l] == os.sep or name[l] == os.altsep:
605 605 l = l + 1
606 606 name = name[l:]
607 607
608 608 if not os.path.lexists(os.path.join(root, name)):
609 609 return None
610 610
611 611 seps = os.sep
612 612 if os.altsep:
613 613 seps = seps + os.altsep
614 614 # Protect backslashes. This gets silly very quickly.
615 615 seps.replace('\\','\\\\')
616 616 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
617 617 dir = os.path.normcase(os.path.normpath(root))
618 618 result = []
619 619 for part, sep in pattern.findall(name):
620 620 if sep:
621 621 result.append(sep)
622 622 continue
623 623
624 624 if dir not in _fspathcache:
625 625 _fspathcache[dir] = os.listdir(dir)
626 626 contents = _fspathcache[dir]
627 627
628 628 lpart = part.lower()
629 629 lenp = len(part)
630 630 for n in contents:
631 631 if lenp == len(n) and n.lower() == lpart:
632 632 result.append(n)
633 633 break
634 634 else:
635 635 # Cannot happen, as the file exists!
636 636 result.append(part)
637 637 dir = os.path.join(dir, lpart)
638 638
639 639 return ''.join(result)
640 640
641 641 def checknlink(testfile):
642 642 '''check whether hardlink count reporting works properly'''
643 643
644 644 # testfile may be open, so we need a separate file for checking to
645 645 # work around issue2543 (or testfile may get lost on Samba shares)
646 646 f1 = testfile + ".hgtmp1"
647 647 if os.path.lexists(f1):
648 648 return False
649 649 try:
650 650 posixfile(f1, 'w').close()
651 651 except IOError:
652 652 return False
653 653
654 654 f2 = testfile + ".hgtmp2"
655 655 fd = None
656 656 try:
657 657 try:
658 658 oslink(f1, f2)
659 659 except OSError:
660 660 return False
661 661
662 662 # nlinks() may behave differently for files on Windows shares if
663 663 # the file is open.
664 664 fd = posixfile(f2)
665 665 return nlinks(f2) > 1
666 666 finally:
667 667 if fd is not None:
668 668 fd.close()
669 669 for f in (f1, f2):
670 670 try:
671 671 os.unlink(f)
672 672 except OSError:
673 673 pass
674 674
675 675 return False
676 676
677 677 def endswithsep(path):
678 678 '''Check path ends with os.sep or os.altsep.'''
679 679 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
680 680
681 681 def splitpath(path):
682 682 '''Split path by os.sep.
683 683 Note that this function does not use os.altsep because this is
684 684 an alternative of simple "xxx.split(os.sep)".
685 685 It is recommended to use os.path.normpath() before using this
686 686 function if need.'''
687 687 return path.split(os.sep)
688 688
689 689 def gui():
690 690 '''Are we running in a GUI?'''
691 691 if sys.platform == 'darwin':
692 692 if 'SSH_CONNECTION' in os.environ:
693 693 # handle SSH access to a box where the user is logged in
694 694 return False
695 695 elif getattr(osutil, 'isgui', None):
696 696 # check if a CoreGraphics session is available
697 697 return osutil.isgui()
698 698 else:
699 699 # pure build; use a safe default
700 700 return True
701 701 else:
702 702 return os.name == "nt" or os.environ.get("DISPLAY")
703 703
704 704 def mktempcopy(name, emptyok=False, createmode=None):
705 705 """Create a temporary file with the same contents from name
706 706
707 707 The permission bits are copied from the original file.
708 708
709 709 If the temporary file is going to be truncated immediately, you
710 710 can use emptyok=True as an optimization.
711 711
712 712 Returns the name of the temporary file.
713 713 """
714 714 d, fn = os.path.split(name)
715 715 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
716 716 os.close(fd)
717 717 # Temporary files are created with mode 0600, which is usually not
718 718 # what we want. If the original file already exists, just copy
719 719 # its mode. Otherwise, manually obey umask.
720 720 try:
721 721 st_mode = os.lstat(name).st_mode & 0777
722 722 except OSError, inst:
723 723 if inst.errno != errno.ENOENT:
724 724 raise
725 725 st_mode = createmode
726 726 if st_mode is None:
727 727 st_mode = ~umask
728 728 st_mode &= 0666
729 729 os.chmod(temp, st_mode)
730 730 if emptyok:
731 731 return temp
732 732 try:
733 733 try:
734 734 ifp = posixfile(name, "rb")
735 735 except IOError, inst:
736 736 if inst.errno == errno.ENOENT:
737 737 return temp
738 738 if not getattr(inst, 'filename', None):
739 739 inst.filename = name
740 740 raise
741 741 ofp = posixfile(temp, "wb")
742 742 for chunk in filechunkiter(ifp):
743 743 ofp.write(chunk)
744 744 ifp.close()
745 745 ofp.close()
746 746 except:
747 747 try: os.unlink(temp)
748 748 except: pass
749 749 raise
750 750 return temp
751 751
752 752 class atomictempfile(object):
753 753 '''writeable file object that atomically updates a file
754 754
755 755 All writes will go to a temporary copy of the original file. Call
756 756 rename() when you are done writing, and atomictempfile will rename
757 757 the temporary copy to the original name, making the changes visible.
758 758
759 759 Unlike other file-like objects, close() discards your writes by
760 760 simply deleting the temporary file.
761 761 '''
762 762 def __init__(self, name, mode='w+b', createmode=None):
763 763 self.__name = name # permanent name
764 764 self._tempname = mktempcopy(name, emptyok=('w' in mode),
765 765 createmode=createmode)
766 766 self._fp = posixfile(self._tempname, mode)
767 767
768 768 # delegated methods
769 769 self.write = self._fp.write
770 770 self.fileno = self._fp.fileno
771 771
772 772 def rename(self):
773 773 if not self._fp.closed:
774 774 self._fp.close()
775 775 rename(self._tempname, localpath(self.__name))
776 776
777 777 def close(self):
778 778 if not self._fp.closed:
779 779 try:
780 780 os.unlink(self._tempname)
781 781 except OSError:
782 782 pass
783 783 self._fp.close()
784 784
785 785 def __del__(self):
786 if hasattr(self, '_fp'): # constructor actually did something
786 if safehasattr(self, '_fp'): # constructor actually did something
787 787 self.close()
788 788
789 789 def makedirs(name, mode=None):
790 790 """recursive directory creation with parent mode inheritance"""
791 791 parent = os.path.abspath(os.path.dirname(name))
792 792 try:
793 793 os.mkdir(name)
794 794 if mode is not None:
795 795 os.chmod(name, mode)
796 796 return
797 797 except OSError, err:
798 798 if err.errno == errno.EEXIST:
799 799 return
800 800 if not name or parent == name or err.errno != errno.ENOENT:
801 801 raise
802 802 makedirs(parent, mode)
803 803 makedirs(name, mode)
804 804
805 805 def readfile(path):
806 806 fp = open(path, 'rb')
807 807 try:
808 808 return fp.read()
809 809 finally:
810 810 fp.close()
811 811
812 812 def writefile(path, text):
813 813 fp = open(path, 'wb')
814 814 try:
815 815 fp.write(text)
816 816 finally:
817 817 fp.close()
818 818
819 819 def appendfile(path, text):
820 820 fp = open(path, 'ab')
821 821 try:
822 822 fp.write(text)
823 823 finally:
824 824 fp.close()
825 825
826 826 class chunkbuffer(object):
827 827 """Allow arbitrary sized chunks of data to be efficiently read from an
828 828 iterator over chunks of arbitrary size."""
829 829
830 830 def __init__(self, in_iter):
831 831 """in_iter is the iterator that's iterating over the input chunks.
832 832 targetsize is how big a buffer to try to maintain."""
833 833 def splitbig(chunks):
834 834 for chunk in chunks:
835 835 if len(chunk) > 2**20:
836 836 pos = 0
837 837 while pos < len(chunk):
838 838 end = pos + 2 ** 18
839 839 yield chunk[pos:end]
840 840 pos = end
841 841 else:
842 842 yield chunk
843 843 self.iter = splitbig(in_iter)
844 844 self._queue = []
845 845
846 846 def read(self, l):
847 847 """Read L bytes of data from the iterator of chunks of data.
848 848 Returns less than L bytes if the iterator runs dry."""
849 849 left = l
850 850 buf = ''
851 851 queue = self._queue
852 852 while left > 0:
853 853 # refill the queue
854 854 if not queue:
855 855 target = 2**18
856 856 for chunk in self.iter:
857 857 queue.append(chunk)
858 858 target -= len(chunk)
859 859 if target <= 0:
860 860 break
861 861 if not queue:
862 862 break
863 863
864 864 chunk = queue.pop(0)
865 865 left -= len(chunk)
866 866 if left < 0:
867 867 queue.insert(0, chunk[left:])
868 868 buf += chunk[:left]
869 869 else:
870 870 buf += chunk
871 871
872 872 return buf
873 873
874 874 def filechunkiter(f, size=65536, limit=None):
875 875 """Create a generator that produces the data in the file size
876 876 (default 65536) bytes at a time, up to optional limit (default is
877 877 to read all data). Chunks may be less than size bytes if the
878 878 chunk is the last chunk in the file, or the file is a socket or
879 879 some other type of file that sometimes reads less data than is
880 880 requested."""
881 881 assert size >= 0
882 882 assert limit is None or limit >= 0
883 883 while True:
884 884 if limit is None:
885 885 nbytes = size
886 886 else:
887 887 nbytes = min(limit, size)
888 888 s = nbytes and f.read(nbytes)
889 889 if not s:
890 890 break
891 891 if limit:
892 892 limit -= len(s)
893 893 yield s
894 894
895 895 def makedate():
896 896 lt = time.localtime()
897 897 if lt[8] == 1 and time.daylight:
898 898 tz = time.altzone
899 899 else:
900 900 tz = time.timezone
901 901 t = time.mktime(lt)
902 902 if t < 0:
903 903 hint = _("check your clock")
904 904 raise Abort(_("negative timestamp: %d") % t, hint=hint)
905 905 return t, tz
906 906
907 907 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
908 908 """represent a (unixtime, offset) tuple as a localized time.
909 909 unixtime is seconds since the epoch, and offset is the time zone's
910 910 number of seconds away from UTC. if timezone is false, do not
911 911 append time zone to string."""
912 912 t, tz = date or makedate()
913 913 if t < 0:
914 914 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
915 915 tz = 0
916 916 if "%1" in format or "%2" in format:
917 917 sign = (tz > 0) and "-" or "+"
918 918 minutes = abs(tz) // 60
919 919 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
920 920 format = format.replace("%2", "%02d" % (minutes % 60))
921 921 s = time.strftime(format, time.gmtime(float(t) - tz))
922 922 return s
923 923
924 924 def shortdate(date=None):
925 925 """turn (timestamp, tzoff) tuple into iso 8631 date."""
926 926 return datestr(date, format='%Y-%m-%d')
927 927
928 928 def strdate(string, format, defaults=[]):
929 929 """parse a localized time string and return a (unixtime, offset) tuple.
930 930 if the string cannot be parsed, ValueError is raised."""
931 931 def timezone(string):
932 932 tz = string.split()[-1]
933 933 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
934 934 sign = (tz[0] == "+") and 1 or -1
935 935 hours = int(tz[1:3])
936 936 minutes = int(tz[3:5])
937 937 return -sign * (hours * 60 + minutes) * 60
938 938 if tz == "GMT" or tz == "UTC":
939 939 return 0
940 940 return None
941 941
942 942 # NOTE: unixtime = localunixtime + offset
943 943 offset, date = timezone(string), string
944 944 if offset is not None:
945 945 date = " ".join(string.split()[:-1])
946 946
947 947 # add missing elements from defaults
948 948 usenow = False # default to using biased defaults
949 949 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
950 950 found = [True for p in part if ("%"+p) in format]
951 951 if not found:
952 952 date += "@" + defaults[part][usenow]
953 953 format += "@%" + part[0]
954 954 else:
955 955 # We've found a specific time element, less specific time
956 956 # elements are relative to today
957 957 usenow = True
958 958
959 959 timetuple = time.strptime(date, format)
960 960 localunixtime = int(calendar.timegm(timetuple))
961 961 if offset is None:
962 962 # local timezone
963 963 unixtime = int(time.mktime(timetuple))
964 964 offset = unixtime - localunixtime
965 965 else:
966 966 unixtime = localunixtime + offset
967 967 return unixtime, offset
968 968
969 969 def parsedate(date, formats=None, bias={}):
970 970 """parse a localized date/time and return a (unixtime, offset) tuple.
971 971
972 972 The date may be a "unixtime offset" string or in one of the specified
973 973 formats. If the date already is a (unixtime, offset) tuple, it is returned.
974 974 """
975 975 if not date:
976 976 return 0, 0
977 977 if isinstance(date, tuple) and len(date) == 2:
978 978 return date
979 979 if not formats:
980 980 formats = defaultdateformats
981 981 date = date.strip()
982 982 try:
983 983 when, offset = map(int, date.split(' '))
984 984 except ValueError:
985 985 # fill out defaults
986 986 now = makedate()
987 987 defaults = {}
988 988 for part in ("d", "mb", "yY", "HI", "M", "S"):
989 989 # this piece is for rounding the specific end of unknowns
990 990 b = bias.get(part)
991 991 if b is None:
992 992 if part[0] in "HMS":
993 993 b = "00"
994 994 else:
995 995 b = "0"
996 996
997 997 # this piece is for matching the generic end to today's date
998 998 n = datestr(now, "%" + part[0])
999 999
1000 1000 defaults[part] = (b, n)
1001 1001
1002 1002 for format in formats:
1003 1003 try:
1004 1004 when, offset = strdate(date, format, defaults)
1005 1005 except (ValueError, OverflowError):
1006 1006 pass
1007 1007 else:
1008 1008 break
1009 1009 else:
1010 1010 raise Abort(_('invalid date: %r') % date)
1011 1011 # validate explicit (probably user-specified) date and
1012 1012 # time zone offset. values must fit in signed 32 bits for
1013 1013 # current 32-bit linux runtimes. timezones go from UTC-12
1014 1014 # to UTC+14
1015 1015 if abs(when) > 0x7fffffff:
1016 1016 raise Abort(_('date exceeds 32 bits: %d') % when)
1017 1017 if when < 0:
1018 1018 raise Abort(_('negative date value: %d') % when)
1019 1019 if offset < -50400 or offset > 43200:
1020 1020 raise Abort(_('impossible time zone offset: %d') % offset)
1021 1021 return when, offset
1022 1022
1023 1023 def matchdate(date):
1024 1024 """Return a function that matches a given date match specifier
1025 1025
1026 1026 Formats include:
1027 1027
1028 1028 '{date}' match a given date to the accuracy provided
1029 1029
1030 1030 '<{date}' on or before a given date
1031 1031
1032 1032 '>{date}' on or after a given date
1033 1033
1034 1034 >>> p1 = parsedate("10:29:59")
1035 1035 >>> p2 = parsedate("10:30:00")
1036 1036 >>> p3 = parsedate("10:30:59")
1037 1037 >>> p4 = parsedate("10:31:00")
1038 1038 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1039 1039 >>> f = matchdate("10:30")
1040 1040 >>> f(p1[0])
1041 1041 False
1042 1042 >>> f(p2[0])
1043 1043 True
1044 1044 >>> f(p3[0])
1045 1045 True
1046 1046 >>> f(p4[0])
1047 1047 False
1048 1048 >>> f(p5[0])
1049 1049 False
1050 1050 """
1051 1051
1052 1052 def lower(date):
1053 1053 d = dict(mb="1", d="1")
1054 1054 return parsedate(date, extendeddateformats, d)[0]
1055 1055
1056 1056 def upper(date):
1057 1057 d = dict(mb="12", HI="23", M="59", S="59")
1058 1058 for days in ("31", "30", "29"):
1059 1059 try:
1060 1060 d["d"] = days
1061 1061 return parsedate(date, extendeddateformats, d)[0]
1062 1062 except:
1063 1063 pass
1064 1064 d["d"] = "28"
1065 1065 return parsedate(date, extendeddateformats, d)[0]
1066 1066
1067 1067 date = date.strip()
1068 1068
1069 1069 if not date:
1070 1070 raise Abort(_("dates cannot consist entirely of whitespace"))
1071 1071 elif date[0] == "<":
1072 1072 if not date[1:]:
1073 1073 raise Abort(_("invalid day spec, use '<DATE'"))
1074 1074 when = upper(date[1:])
1075 1075 return lambda x: x <= when
1076 1076 elif date[0] == ">":
1077 1077 if not date[1:]:
1078 1078 raise Abort(_("invalid day spec, use '>DATE'"))
1079 1079 when = lower(date[1:])
1080 1080 return lambda x: x >= when
1081 1081 elif date[0] == "-":
1082 1082 try:
1083 1083 days = int(date[1:])
1084 1084 except ValueError:
1085 1085 raise Abort(_("invalid day spec: %s") % date[1:])
1086 1086 if days < 0:
1087 1087 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1088 1088 % date[1:])
1089 1089 when = makedate()[0] - days * 3600 * 24
1090 1090 return lambda x: x >= when
1091 1091 elif " to " in date:
1092 1092 a, b = date.split(" to ")
1093 1093 start, stop = lower(a), upper(b)
1094 1094 return lambda x: x >= start and x <= stop
1095 1095 else:
1096 1096 start, stop = lower(date), upper(date)
1097 1097 return lambda x: x >= start and x <= stop
1098 1098
1099 1099 def shortuser(user):
1100 1100 """Return a short representation of a user name or email address."""
1101 1101 f = user.find('@')
1102 1102 if f >= 0:
1103 1103 user = user[:f]
1104 1104 f = user.find('<')
1105 1105 if f >= 0:
1106 1106 user = user[f + 1:]
1107 1107 f = user.find(' ')
1108 1108 if f >= 0:
1109 1109 user = user[:f]
1110 1110 f = user.find('.')
1111 1111 if f >= 0:
1112 1112 user = user[:f]
1113 1113 return user
1114 1114
1115 1115 def email(author):
1116 1116 '''get email of author.'''
1117 1117 r = author.find('>')
1118 1118 if r == -1:
1119 1119 r = None
1120 1120 return author[author.find('<') + 1:r]
1121 1121
1122 1122 def _ellipsis(text, maxlength):
1123 1123 if len(text) <= maxlength:
1124 1124 return text, False
1125 1125 else:
1126 1126 return "%s..." % (text[:maxlength - 3]), True
1127 1127
1128 1128 def ellipsis(text, maxlength=400):
1129 1129 """Trim string to at most maxlength (default: 400) characters."""
1130 1130 try:
1131 1131 # use unicode not to split at intermediate multi-byte sequence
1132 1132 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1133 1133 maxlength)
1134 1134 if not truncated:
1135 1135 return text
1136 1136 return utext.encode(encoding.encoding)
1137 1137 except (UnicodeDecodeError, UnicodeEncodeError):
1138 1138 return _ellipsis(text, maxlength)[0]
1139 1139
1140 1140 def bytecount(nbytes):
1141 1141 '''return byte count formatted as readable string, with units'''
1142 1142
1143 1143 units = (
1144 1144 (100, 1 << 30, _('%.0f GB')),
1145 1145 (10, 1 << 30, _('%.1f GB')),
1146 1146 (1, 1 << 30, _('%.2f GB')),
1147 1147 (100, 1 << 20, _('%.0f MB')),
1148 1148 (10, 1 << 20, _('%.1f MB')),
1149 1149 (1, 1 << 20, _('%.2f MB')),
1150 1150 (100, 1 << 10, _('%.0f KB')),
1151 1151 (10, 1 << 10, _('%.1f KB')),
1152 1152 (1, 1 << 10, _('%.2f KB')),
1153 1153 (1, 1, _('%.0f bytes')),
1154 1154 )
1155 1155
1156 1156 for multiplier, divisor, format in units:
1157 1157 if nbytes >= divisor * multiplier:
1158 1158 return format % (nbytes / float(divisor))
1159 1159 return units[-1][2] % nbytes
1160 1160
1161 1161 def uirepr(s):
1162 1162 # Avoid double backslash in Windows path repr()
1163 1163 return repr(s).replace('\\\\', '\\')
1164 1164
1165 1165 # delay import of textwrap
1166 1166 def MBTextWrapper(**kwargs):
1167 1167 class tw(textwrap.TextWrapper):
1168 1168 """
1169 1169 Extend TextWrapper for double-width characters.
1170 1170
1171 1171 Some Asian characters use two terminal columns instead of one.
1172 1172 A good example of this behavior can be seen with u'\u65e5\u672c',
1173 1173 the two Japanese characters for "Japan":
1174 1174 len() returns 2, but when printed to a terminal, they eat 4 columns.
1175 1175
1176 1176 (Note that this has nothing to do whatsoever with unicode
1177 1177 representation, or encoding of the underlying string)
1178 1178 """
1179 1179 def __init__(self, **kwargs):
1180 1180 textwrap.TextWrapper.__init__(self, **kwargs)
1181 1181
1182 1182 def _cutdown(self, str, space_left):
1183 1183 l = 0
1184 1184 ucstr = unicode(str, encoding.encoding)
1185 1185 colwidth = unicodedata.east_asian_width
1186 1186 for i in xrange(len(ucstr)):
1187 1187 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1188 1188 if space_left < l:
1189 1189 return (ucstr[:i].encode(encoding.encoding),
1190 1190 ucstr[i:].encode(encoding.encoding))
1191 1191 return str, ''
1192 1192
1193 1193 # overriding of base class
1194 1194 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1195 1195 space_left = max(width - cur_len, 1)
1196 1196
1197 1197 if self.break_long_words:
1198 1198 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1199 1199 cur_line.append(cut)
1200 1200 reversed_chunks[-1] = res
1201 1201 elif not cur_line:
1202 1202 cur_line.append(reversed_chunks.pop())
1203 1203
1204 1204 global MBTextWrapper
1205 1205 MBTextWrapper = tw
1206 1206 return tw(**kwargs)
1207 1207
1208 1208 def wrap(line, width, initindent='', hangindent=''):
1209 1209 maxindent = max(len(hangindent), len(initindent))
1210 1210 if width <= maxindent:
1211 1211 # adjust for weird terminal size
1212 1212 width = max(78, maxindent + 1)
1213 1213 wrapper = MBTextWrapper(width=width,
1214 1214 initial_indent=initindent,
1215 1215 subsequent_indent=hangindent)
1216 1216 return wrapper.fill(line)
1217 1217
1218 1218 def iterlines(iterator):
1219 1219 for chunk in iterator:
1220 1220 for line in chunk.splitlines():
1221 1221 yield line
1222 1222
1223 1223 def expandpath(path):
1224 1224 return os.path.expanduser(os.path.expandvars(path))
1225 1225
1226 1226 def hgcmd():
1227 1227 """Return the command used to execute current hg
1228 1228
1229 1229 This is different from hgexecutable() because on Windows we want
1230 1230 to avoid things opening new shell windows like batch files, so we
1231 1231 get either the python call or current executable.
1232 1232 """
1233 1233 if mainfrozen():
1234 1234 return [sys.executable]
1235 1235 return gethgcmd()
1236 1236
1237 1237 def rundetached(args, condfn):
1238 1238 """Execute the argument list in a detached process.
1239 1239
1240 1240 condfn is a callable which is called repeatedly and should return
1241 1241 True once the child process is known to have started successfully.
1242 1242 At this point, the child process PID is returned. If the child
1243 1243 process fails to start or finishes before condfn() evaluates to
1244 1244 True, return -1.
1245 1245 """
1246 1246 # Windows case is easier because the child process is either
1247 1247 # successfully starting and validating the condition or exiting
1248 1248 # on failure. We just poll on its PID. On Unix, if the child
1249 1249 # process fails to start, it will be left in a zombie state until
1250 1250 # the parent wait on it, which we cannot do since we expect a long
1251 1251 # running process on success. Instead we listen for SIGCHLD telling
1252 1252 # us our child process terminated.
1253 1253 terminated = set()
1254 1254 def handler(signum, frame):
1255 1255 terminated.add(os.wait())
1256 1256 prevhandler = None
1257 if hasattr(signal, 'SIGCHLD'):
1258 prevhandler = signal.signal(signal.SIGCHLD, handler)
1257 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1258 if SIGCHLD is not None:
1259 prevhandler = signal.signal(SIGCHLD, handler)
1259 1260 try:
1260 1261 pid = spawndetached(args)
1261 1262 while not condfn():
1262 1263 if ((pid in terminated or not testpid(pid))
1263 1264 and not condfn()):
1264 1265 return -1
1265 1266 time.sleep(0.1)
1266 1267 return pid
1267 1268 finally:
1268 1269 if prevhandler is not None:
1269 1270 signal.signal(signal.SIGCHLD, prevhandler)
1270 1271
1271 1272 try:
1272 1273 any, all = any, all
1273 1274 except NameError:
1274 1275 def any(iterable):
1275 1276 for i in iterable:
1276 1277 if i:
1277 1278 return True
1278 1279 return False
1279 1280
1280 1281 def all(iterable):
1281 1282 for i in iterable:
1282 1283 if not i:
1283 1284 return False
1284 1285 return True
1285 1286
1286 1287 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1287 1288 """Return the result of interpolating items in the mapping into string s.
1288 1289
1289 1290 prefix is a single character string, or a two character string with
1290 1291 a backslash as the first character if the prefix needs to be escaped in
1291 1292 a regular expression.
1292 1293
1293 1294 fn is an optional function that will be applied to the replacement text
1294 1295 just before replacement.
1295 1296
1296 1297 escape_prefix is an optional flag that allows using doubled prefix for
1297 1298 its escaping.
1298 1299 """
1299 1300 fn = fn or (lambda s: s)
1300 1301 patterns = '|'.join(mapping.keys())
1301 1302 if escape_prefix:
1302 1303 patterns += '|' + prefix
1303 1304 if len(prefix) > 1:
1304 1305 prefix_char = prefix[1:]
1305 1306 else:
1306 1307 prefix_char = prefix
1307 1308 mapping[prefix_char] = prefix_char
1308 1309 r = re.compile(r'%s(%s)' % (prefix, patterns))
1309 1310 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1310 1311
1311 1312 def getport(port):
1312 1313 """Return the port for a given network service.
1313 1314
1314 1315 If port is an integer, it's returned as is. If it's a string, it's
1315 1316 looked up using socket.getservbyname(). If there's no matching
1316 1317 service, util.Abort is raised.
1317 1318 """
1318 1319 try:
1319 1320 return int(port)
1320 1321 except ValueError:
1321 1322 pass
1322 1323
1323 1324 try:
1324 1325 return socket.getservbyname(port)
1325 1326 except socket.error:
1326 1327 raise Abort(_("no port number associated with service '%s'") % port)
1327 1328
1328 1329 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1329 1330 '0': False, 'no': False, 'false': False, 'off': False,
1330 1331 'never': False}
1331 1332
1332 1333 def parsebool(s):
1333 1334 """Parse s into a boolean.
1334 1335
1335 1336 If s is not a valid boolean, returns None.
1336 1337 """
1337 1338 return _booleans.get(s.lower(), None)
1338 1339
1339 1340 _hexdig = '0123456789ABCDEFabcdef'
1340 1341 _hextochr = dict((a + b, chr(int(a + b, 16)))
1341 1342 for a in _hexdig for b in _hexdig)
1342 1343
1343 1344 def _urlunquote(s):
1344 1345 """unquote('abc%20def') -> 'abc def'."""
1345 1346 res = s.split('%')
1346 1347 # fastpath
1347 1348 if len(res) == 1:
1348 1349 return s
1349 1350 s = res[0]
1350 1351 for item in res[1:]:
1351 1352 try:
1352 1353 s += _hextochr[item[:2]] + item[2:]
1353 1354 except KeyError:
1354 1355 s += '%' + item
1355 1356 except UnicodeDecodeError:
1356 1357 s += unichr(int(item[:2], 16)) + item[2:]
1357 1358 return s
1358 1359
1359 1360 class url(object):
1360 1361 r"""Reliable URL parser.
1361 1362
1362 1363 This parses URLs and provides attributes for the following
1363 1364 components:
1364 1365
1365 1366 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1366 1367
1367 1368 Missing components are set to None. The only exception is
1368 1369 fragment, which is set to '' if present but empty.
1369 1370
1370 1371 If parsefragment is False, fragment is included in query. If
1371 1372 parsequery is False, query is included in path. If both are
1372 1373 False, both fragment and query are included in path.
1373 1374
1374 1375 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1375 1376
1376 1377 Note that for backward compatibility reasons, bundle URLs do not
1377 1378 take host names. That means 'bundle://../' has a path of '../'.
1378 1379
1379 1380 Examples:
1380 1381
1381 1382 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1382 1383 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1383 1384 >>> url('ssh://[::1]:2200//home/joe/repo')
1384 1385 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1385 1386 >>> url('file:///home/joe/repo')
1386 1387 <url scheme: 'file', path: '/home/joe/repo'>
1387 1388 >>> url('file:///c:/temp/foo/')
1388 1389 <url scheme: 'file', path: 'c:/temp/foo/'>
1389 1390 >>> url('bundle:foo')
1390 1391 <url scheme: 'bundle', path: 'foo'>
1391 1392 >>> url('bundle://../foo')
1392 1393 <url scheme: 'bundle', path: '../foo'>
1393 1394 >>> url(r'c:\foo\bar')
1394 1395 <url path: 'c:\\foo\\bar'>
1395 1396 >>> url(r'\\blah\blah\blah')
1396 1397 <url path: '\\\\blah\\blah\\blah'>
1397 1398
1398 1399 Authentication credentials:
1399 1400
1400 1401 >>> url('ssh://joe:xyz@x/repo')
1401 1402 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1402 1403 >>> url('ssh://joe@x/repo')
1403 1404 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1404 1405
1405 1406 Query strings and fragments:
1406 1407
1407 1408 >>> url('http://host/a?b#c')
1408 1409 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1409 1410 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1410 1411 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1411 1412 """
1412 1413
1413 1414 _safechars = "!~*'()+"
1414 1415 _safepchars = "/!~*'()+"
1415 1416 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1416 1417
1417 1418 def __init__(self, path, parsequery=True, parsefragment=True):
1418 1419 # We slowly chomp away at path until we have only the path left
1419 1420 self.scheme = self.user = self.passwd = self.host = None
1420 1421 self.port = self.path = self.query = self.fragment = None
1421 1422 self._localpath = True
1422 1423 self._hostport = ''
1423 1424 self._origpath = path
1424 1425
1425 1426 # special case for Windows drive letters and UNC paths
1426 1427 if hasdriveletter(path) or path.startswith(r'\\'):
1427 1428 self.path = path
1428 1429 return
1429 1430
1430 1431 # For compatibility reasons, we can't handle bundle paths as
1431 1432 # normal URLS
1432 1433 if path.startswith('bundle:'):
1433 1434 self.scheme = 'bundle'
1434 1435 path = path[7:]
1435 1436 if path.startswith('//'):
1436 1437 path = path[2:]
1437 1438 self.path = path
1438 1439 return
1439 1440
1440 1441 if self._matchscheme(path):
1441 1442 parts = path.split(':', 1)
1442 1443 if parts[0]:
1443 1444 self.scheme, path = parts
1444 1445 self._localpath = False
1445 1446
1446 1447 if not path:
1447 1448 path = None
1448 1449 if self._localpath:
1449 1450 self.path = ''
1450 1451 return
1451 1452 else:
1452 1453 if parsefragment and '#' in path:
1453 1454 path, self.fragment = path.split('#', 1)
1454 1455 if not path:
1455 1456 path = None
1456 1457 if self._localpath:
1457 1458 self.path = path
1458 1459 return
1459 1460
1460 1461 if parsequery and '?' in path:
1461 1462 path, self.query = path.split('?', 1)
1462 1463 if not path:
1463 1464 path = None
1464 1465 if not self.query:
1465 1466 self.query = None
1466 1467
1467 1468 # // is required to specify a host/authority
1468 1469 if path and path.startswith('//'):
1469 1470 parts = path[2:].split('/', 1)
1470 1471 if len(parts) > 1:
1471 1472 self.host, path = parts
1472 1473 path = path
1473 1474 else:
1474 1475 self.host = parts[0]
1475 1476 path = None
1476 1477 if not self.host:
1477 1478 self.host = None
1478 1479 if path and not hasdriveletter(path):
1479 1480 path = '/' + path
1480 1481
1481 1482 if self.host and '@' in self.host:
1482 1483 self.user, self.host = self.host.rsplit('@', 1)
1483 1484 if ':' in self.user:
1484 1485 self.user, self.passwd = self.user.split(':', 1)
1485 1486 if not self.host:
1486 1487 self.host = None
1487 1488
1488 1489 # Don't split on colons in IPv6 addresses without ports
1489 1490 if (self.host and ':' in self.host and
1490 1491 not (self.host.startswith('[') and self.host.endswith(']'))):
1491 1492 self._hostport = self.host
1492 1493 self.host, self.port = self.host.rsplit(':', 1)
1493 1494 if not self.host:
1494 1495 self.host = None
1495 1496
1496 1497 if (self.host and self.scheme == 'file' and
1497 1498 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1498 1499 raise Abort(_('file:// URLs can only refer to localhost'))
1499 1500
1500 1501 self.path = path
1501 1502
1502 1503 for a in ('user', 'passwd', 'host', 'port',
1503 1504 'path', 'query', 'fragment'):
1504 1505 v = getattr(self, a)
1505 1506 if v is not None:
1506 1507 setattr(self, a, _urlunquote(v))
1507 1508
1508 1509 def __repr__(self):
1509 1510 attrs = []
1510 1511 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1511 1512 'query', 'fragment'):
1512 1513 v = getattr(self, a)
1513 1514 if v is not None:
1514 1515 attrs.append('%s: %r' % (a, v))
1515 1516 return '<url %s>' % ', '.join(attrs)
1516 1517
1517 1518 def __str__(self):
1518 1519 r"""Join the URL's components back into a URL string.
1519 1520
1520 1521 Examples:
1521 1522
1522 1523 >>> str(url('http://user:pw@host:80/?foo#bar'))
1523 1524 'http://user:pw@host:80/?foo#bar'
1524 1525 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1525 1526 'ssh://user:pw@[::1]:2200//home/joe#'
1526 1527 >>> str(url('http://localhost:80//'))
1527 1528 'http://localhost:80//'
1528 1529 >>> str(url('http://localhost:80/'))
1529 1530 'http://localhost:80/'
1530 1531 >>> str(url('http://localhost:80'))
1531 1532 'http://localhost:80/'
1532 1533 >>> str(url('bundle:foo'))
1533 1534 'bundle:foo'
1534 1535 >>> str(url('bundle://../foo'))
1535 1536 'bundle:../foo'
1536 1537 >>> str(url('path'))
1537 1538 'path'
1538 1539 >>> str(url('file:///tmp/foo/bar'))
1539 1540 'file:///tmp/foo/bar'
1540 1541 >>> print url(r'bundle:foo\bar')
1541 1542 bundle:foo\bar
1542 1543 """
1543 1544 if self._localpath:
1544 1545 s = self.path
1545 1546 if self.scheme == 'bundle':
1546 1547 s = 'bundle:' + s
1547 1548 if self.fragment:
1548 1549 s += '#' + self.fragment
1549 1550 return s
1550 1551
1551 1552 s = self.scheme + ':'
1552 1553 if self.user or self.passwd or self.host:
1553 1554 s += '//'
1554 1555 elif self.scheme and (not self.path or self.path.startswith('/')):
1555 1556 s += '//'
1556 1557 if self.user:
1557 1558 s += urllib.quote(self.user, safe=self._safechars)
1558 1559 if self.passwd:
1559 1560 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1560 1561 if self.user or self.passwd:
1561 1562 s += '@'
1562 1563 if self.host:
1563 1564 if not (self.host.startswith('[') and self.host.endswith(']')):
1564 1565 s += urllib.quote(self.host)
1565 1566 else:
1566 1567 s += self.host
1567 1568 if self.port:
1568 1569 s += ':' + urllib.quote(self.port)
1569 1570 if self.host:
1570 1571 s += '/'
1571 1572 if self.path:
1572 1573 s += urllib.quote(self.path, safe=self._safepchars)
1573 1574 if self.query:
1574 1575 s += '?' + urllib.quote(self.query, safe=self._safepchars)
1575 1576 if self.fragment is not None:
1576 1577 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1577 1578 return s
1578 1579
1579 1580 def authinfo(self):
1580 1581 user, passwd = self.user, self.passwd
1581 1582 try:
1582 1583 self.user, self.passwd = None, None
1583 1584 s = str(self)
1584 1585 finally:
1585 1586 self.user, self.passwd = user, passwd
1586 1587 if not self.user:
1587 1588 return (s, None)
1588 1589 return (s, (None, (str(self), self.host),
1589 1590 self.user, self.passwd or ''))
1590 1591
1591 1592 def isabs(self):
1592 1593 if self.scheme and self.scheme != 'file':
1593 1594 return True # remote URL
1594 1595 if hasdriveletter(self.path):
1595 1596 return True # absolute for our purposes - can't be joined()
1596 1597 if self.path.startswith(r'\\'):
1597 1598 return True # Windows UNC path
1598 1599 if self.path.startswith('/'):
1599 1600 return True # POSIX-style
1600 1601 return False
1601 1602
1602 1603 def localpath(self):
1603 1604 if self.scheme == 'file' or self.scheme == 'bundle':
1604 1605 path = self.path or '/'
1605 1606 # For Windows, we need to promote hosts containing drive
1606 1607 # letters to paths with drive letters.
1607 1608 if hasdriveletter(self._hostport):
1608 1609 path = self._hostport + '/' + self.path
1609 1610 elif self.host is not None and self.path:
1610 1611 path = '/' + path
1611 1612 # We also need to handle the case of file:///C:/, which
1612 1613 # should return C:/, not /C:/.
1613 1614 elif hasdriveletter(path):
1614 1615 # Strip leading slash from paths with drive names
1615 1616 return path[1:]
1616 1617 return path
1617 1618 return self._origpath
1618 1619
1619 1620 def hasscheme(path):
1620 1621 return bool(url(path).scheme)
1621 1622
1622 1623 def hasdriveletter(path):
1623 1624 return path[1:2] == ':' and path[0:1].isalpha()
1624 1625
1625 1626 def urllocalpath(path):
1626 1627 return url(path, parsequery=False, parsefragment=False).localpath()
1627 1628
1628 1629 def hidepassword(u):
1629 1630 '''hide user credential in a url string'''
1630 1631 u = url(u)
1631 1632 if u.passwd:
1632 1633 u.passwd = '***'
1633 1634 return str(u)
1634 1635
1635 1636 def removeauth(u):
1636 1637 '''remove all authentication information from a url string'''
1637 1638 u = url(u)
1638 1639 u.user = u.passwd = None
1639 1640 return str(u)
1640 1641
1641 1642 def isatty(fd):
1642 1643 try:
1643 1644 return fd.isatty()
1644 1645 except AttributeError:
1645 1646 return False
General Comments 0
You need to be logged in to leave comments. Login now