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