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