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