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