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