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