##// END OF EJS Templates
Fix wrong module reference in copyfile exception
Andrei Vermel -
r4067:c620376b default
parent child Browse files
Show More
@@ -1,1343 +1,1343
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import gettext as _
16 16 from demandload import *
17 17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 18 demandload(globals(), "os threading time calendar ConfigParser locale glob")
19 19
20 20 try:
21 21 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
22 22 or "ascii"
23 23 except locale.Error:
24 24 _encoding = 'ascii'
25 25 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
26 26 _fallbackencoding = 'ISO-8859-1'
27 27
28 28 def tolocal(s):
29 29 """
30 30 Convert a string from internal UTF-8 to local encoding
31 31
32 32 All internal strings should be UTF-8 but some repos before the
33 33 implementation of locale support may contain latin1 or possibly
34 34 other character sets. We attempt to decode everything strictly
35 35 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
36 36 replace unknown characters.
37 37 """
38 38 for e in ('UTF-8', _fallbackencoding):
39 39 try:
40 40 u = s.decode(e) # attempt strict decoding
41 41 return u.encode(_encoding, "replace")
42 42 except LookupError, k:
43 43 raise Abort(_("%s, please check your locale settings") % k)
44 44 except UnicodeDecodeError:
45 45 pass
46 46 u = s.decode("utf-8", "replace") # last ditch
47 47 return u.encode(_encoding, "replace")
48 48
49 49 def fromlocal(s):
50 50 """
51 51 Convert a string from the local character encoding to UTF-8
52 52
53 53 We attempt to decode strings using the encoding mode set by
54 54 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
55 55 characters will cause an error message. Other modes include
56 56 'replace', which replaces unknown characters with a special
57 57 Unicode character, and 'ignore', which drops the character.
58 58 """
59 59 try:
60 60 return s.decode(_encoding, _encodingmode).encode("utf-8")
61 61 except UnicodeDecodeError, inst:
62 62 sub = s[max(0, inst.start-10):inst.start+10]
63 63 raise Abort("decoding near '%s': %s!" % (sub, inst))
64 64 except LookupError, k:
65 65 raise Abort(_("%s, please check your locale settings") % k)
66 66
67 67 def locallen(s):
68 68 """Find the length in characters of a local string"""
69 69 return len(s.decode(_encoding, "replace"))
70 70
71 71 def localsub(s, a, b=None):
72 72 try:
73 73 u = s.decode(_encoding, _encodingmode)
74 74 if b is not None:
75 75 u = u[a:b]
76 76 else:
77 77 u = u[:a]
78 78 return u.encode(_encoding, _encodingmode)
79 79 except UnicodeDecodeError, inst:
80 80 sub = s[max(0, inst.start-10), inst.start+10]
81 81 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
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 '%b %d %H:%M:%S %Y',
97 97 '%b %d %I:%M:%S%p %Y',
98 98 '%b %d %H:%M:%S',
99 99 '%b %d %I:%M:%S%p',
100 100 '%b %d %H:%M',
101 101 '%b %d %I:%M%p',
102 102 '%b %d %Y',
103 103 '%b %d',
104 104 '%H:%M:%S',
105 105 '%I:%M:%SP',
106 106 '%H:%M',
107 107 '%I:%M%p',
108 108 )
109 109
110 110 extendeddateformats = defaultdateformats + (
111 111 "%Y",
112 112 "%Y-%m",
113 113 "%b",
114 114 "%b %Y",
115 115 )
116 116
117 117 class SignalInterrupt(Exception):
118 118 """Exception raised on SIGTERM and SIGHUP."""
119 119
120 120 # like SafeConfigParser but with case-sensitive keys
121 121 class configparser(ConfigParser.SafeConfigParser):
122 122 def optionxform(self, optionstr):
123 123 return optionstr
124 124
125 125 def cachefunc(func):
126 126 '''cache the result of function calls'''
127 127 # XXX doesn't handle keywords args
128 128 cache = {}
129 129 if func.func_code.co_argcount == 1:
130 130 # we gain a small amount of time because
131 131 # we don't need to pack/unpack the list
132 132 def f(arg):
133 133 if arg not in cache:
134 134 cache[arg] = func(arg)
135 135 return cache[arg]
136 136 else:
137 137 def f(*args):
138 138 if args not in cache:
139 139 cache[args] = func(*args)
140 140 return cache[args]
141 141
142 142 return f
143 143
144 144 def pipefilter(s, cmd):
145 145 '''filter string S through command CMD, returning its output'''
146 146 (pout, pin) = popen2.popen2(cmd, -1, 'b')
147 147 def writer():
148 148 try:
149 149 pin.write(s)
150 150 pin.close()
151 151 except IOError, inst:
152 152 if inst.errno != errno.EPIPE:
153 153 raise
154 154
155 155 # we should use select instead on UNIX, but this will work on most
156 156 # systems, including Windows
157 157 w = threading.Thread(target=writer)
158 158 w.start()
159 159 f = pout.read()
160 160 pout.close()
161 161 w.join()
162 162 return f
163 163
164 164 def tempfilter(s, cmd):
165 165 '''filter string S through a pair of temporary files with CMD.
166 166 CMD is used as a template to create the real command to be run,
167 167 with the strings INFILE and OUTFILE replaced by the real names of
168 168 the temporary files generated.'''
169 169 inname, outname = None, None
170 170 try:
171 171 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
172 172 fp = os.fdopen(infd, 'wb')
173 173 fp.write(s)
174 174 fp.close()
175 175 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
176 176 os.close(outfd)
177 177 cmd = cmd.replace('INFILE', inname)
178 178 cmd = cmd.replace('OUTFILE', outname)
179 179 code = os.system(cmd)
180 180 if code: raise Abort(_("command '%s' failed: %s") %
181 181 (cmd, explain_exit(code)))
182 182 return open(outname, 'rb').read()
183 183 finally:
184 184 try:
185 185 if inname: os.unlink(inname)
186 186 except: pass
187 187 try:
188 188 if outname: os.unlink(outname)
189 189 except: pass
190 190
191 191 filtertable = {
192 192 'tempfile:': tempfilter,
193 193 'pipe:': pipefilter,
194 194 }
195 195
196 196 def filter(s, cmd):
197 197 "filter a string through a command that transforms its input to its output"
198 198 for name, fn in filtertable.iteritems():
199 199 if cmd.startswith(name):
200 200 return fn(s, cmd[len(name):].lstrip())
201 201 return pipefilter(s, cmd)
202 202
203 203 def find_in_path(name, path, default=None):
204 204 '''find name in search path. path can be string (will be split
205 205 with os.pathsep), or iterable thing that returns strings. if name
206 206 found, return path to name. else return default.'''
207 207 if isinstance(path, str):
208 208 path = path.split(os.pathsep)
209 209 for p in path:
210 210 p_name = os.path.join(p, name)
211 211 if os.path.exists(p_name):
212 212 return p_name
213 213 return default
214 214
215 215 def binary(s):
216 216 """return true if a string is binary data using diff's heuristic"""
217 217 if s and '\0' in s[:4096]:
218 218 return True
219 219 return False
220 220
221 221 def unique(g):
222 222 """return the uniq elements of iterable g"""
223 223 seen = {}
224 224 l = []
225 225 for f in g:
226 226 if f not in seen:
227 227 seen[f] = 1
228 228 l.append(f)
229 229 return l
230 230
231 231 class Abort(Exception):
232 232 """Raised if a command needs to print an error and exit."""
233 233
234 234 class UnexpectedOutput(Abort):
235 235 """Raised to print an error with part of output and exit."""
236 236
237 237 def always(fn): return True
238 238 def never(fn): return False
239 239
240 240 def expand_glob(pats):
241 241 '''On Windows, expand the implicit globs in a list of patterns'''
242 242 if os.name != 'nt':
243 243 return list(pats)
244 244 ret = []
245 245 for p in pats:
246 246 kind, name = patkind(p, None)
247 247 if kind is None:
248 248 globbed = glob.glob(name)
249 249 if globbed:
250 250 ret.extend(globbed)
251 251 continue
252 252 # if we couldn't expand the glob, just keep it around
253 253 ret.append(p)
254 254 return ret
255 255
256 256 def patkind(name, dflt_pat='glob'):
257 257 """Split a string into an optional pattern kind prefix and the
258 258 actual pattern."""
259 259 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
260 260 if name.startswith(prefix + ':'): return name.split(':', 1)
261 261 return dflt_pat, name
262 262
263 263 def globre(pat, head='^', tail='$'):
264 264 "convert a glob pattern into a regexp"
265 265 i, n = 0, len(pat)
266 266 res = ''
267 267 group = False
268 268 def peek(): return i < n and pat[i]
269 269 while i < n:
270 270 c = pat[i]
271 271 i = i+1
272 272 if c == '*':
273 273 if peek() == '*':
274 274 i += 1
275 275 res += '.*'
276 276 else:
277 277 res += '[^/]*'
278 278 elif c == '?':
279 279 res += '.'
280 280 elif c == '[':
281 281 j = i
282 282 if j < n and pat[j] in '!]':
283 283 j += 1
284 284 while j < n and pat[j] != ']':
285 285 j += 1
286 286 if j >= n:
287 287 res += '\\['
288 288 else:
289 289 stuff = pat[i:j].replace('\\','\\\\')
290 290 i = j + 1
291 291 if stuff[0] == '!':
292 292 stuff = '^' + stuff[1:]
293 293 elif stuff[0] == '^':
294 294 stuff = '\\' + stuff
295 295 res = '%s[%s]' % (res, stuff)
296 296 elif c == '{':
297 297 group = True
298 298 res += '(?:'
299 299 elif c == '}' and group:
300 300 res += ')'
301 301 group = False
302 302 elif c == ',' and group:
303 303 res += '|'
304 304 elif c == '\\':
305 305 p = peek()
306 306 if p:
307 307 i += 1
308 308 res += re.escape(p)
309 309 else:
310 310 res += re.escape(c)
311 311 else:
312 312 res += re.escape(c)
313 313 return head + res + tail
314 314
315 315 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
316 316
317 317 def pathto(n1, n2):
318 318 '''return the relative path from one place to another.
319 319 n1 should use os.sep to separate directories
320 320 n2 should use "/" to separate directories
321 321 returns an os.sep-separated path.
322 322 '''
323 323 if not n1: return localpath(n2)
324 324 a, b = n1.split(os.sep), n2.split('/')
325 325 a.reverse()
326 326 b.reverse()
327 327 while a and b and a[-1] == b[-1]:
328 328 a.pop()
329 329 b.pop()
330 330 b.reverse()
331 331 return os.sep.join((['..'] * len(a)) + b)
332 332
333 333 def canonpath(root, cwd, myname):
334 334 """return the canonical path of myname, given cwd and root"""
335 335 if root == os.sep:
336 336 rootsep = os.sep
337 337 elif root.endswith(os.sep):
338 338 rootsep = root
339 339 else:
340 340 rootsep = root + os.sep
341 341 name = myname
342 342 if not os.path.isabs(name):
343 343 name = os.path.join(root, cwd, name)
344 344 name = os.path.normpath(name)
345 345 if name != rootsep and name.startswith(rootsep):
346 346 name = name[len(rootsep):]
347 347 audit_path(name)
348 348 return pconvert(name)
349 349 elif name == root:
350 350 return ''
351 351 else:
352 352 # Determine whether `name' is in the hierarchy at or beneath `root',
353 353 # by iterating name=dirname(name) until that causes no change (can't
354 354 # check name == '/', because that doesn't work on windows). For each
355 355 # `name', compare dev/inode numbers. If they match, the list `rel'
356 356 # holds the reversed list of components making up the relative file
357 357 # name we want.
358 358 root_st = os.stat(root)
359 359 rel = []
360 360 while True:
361 361 try:
362 362 name_st = os.stat(name)
363 363 except OSError:
364 364 break
365 365 if samestat(name_st, root_st):
366 366 rel.reverse()
367 367 name = os.path.join(*rel)
368 368 audit_path(name)
369 369 return pconvert(name)
370 370 dirname, basename = os.path.split(name)
371 371 rel.append(basename)
372 372 if dirname == name:
373 373 break
374 374 name = dirname
375 375
376 376 raise Abort('%s not under root' % myname)
377 377
378 378 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
379 379 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
380 380
381 381 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='',
382 382 src=None, globbed=False):
383 383 if not globbed:
384 384 names = expand_glob(names)
385 385 return _matcher(canonroot, cwd, names, inc, exc, head, 'relpath', src)
386 386
387 387 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
388 388 """build a function to match a set of file patterns
389 389
390 390 arguments:
391 391 canonroot - the canonical root of the tree you're matching against
392 392 cwd - the current working directory, if relevant
393 393 names - patterns to find
394 394 inc - patterns to include
395 395 exc - patterns to exclude
396 396 head - a regex to prepend to patterns to control whether a match is rooted
397 397
398 398 a pattern is one of:
399 399 'glob:<rooted glob>'
400 400 're:<rooted regexp>'
401 401 'path:<rooted path>'
402 402 'relglob:<relative glob>'
403 403 'relpath:<relative path>'
404 404 'relre:<relative regexp>'
405 405 '<rooted path or regexp>'
406 406
407 407 returns:
408 408 a 3-tuple containing
409 409 - list of explicit non-pattern names passed in
410 410 - a bool match(filename) function
411 411 - a bool indicating if any patterns were passed in
412 412
413 413 todo:
414 414 make head regex a rooted bool
415 415 """
416 416
417 417 def contains_glob(name):
418 418 for c in name:
419 419 if c in _globchars: return True
420 420 return False
421 421
422 422 def regex(kind, name, tail):
423 423 '''convert a pattern into a regular expression'''
424 424 if kind == 're':
425 425 return name
426 426 elif kind == 'path':
427 427 return '^' + re.escape(name) + '(?:/|$)'
428 428 elif kind == 'relglob':
429 429 return head + globre(name, '(?:|.*/)', tail)
430 430 elif kind == 'relpath':
431 431 return head + re.escape(name) + tail
432 432 elif kind == 'relre':
433 433 if name.startswith('^'):
434 434 return name
435 435 return '.*' + name
436 436 return head + globre(name, '', tail)
437 437
438 438 def matchfn(pats, tail):
439 439 """build a matching function from a set of patterns"""
440 440 if not pats:
441 441 return
442 442 matches = []
443 443 for k, p in pats:
444 444 try:
445 445 pat = '(?:%s)' % regex(k, p, tail)
446 446 matches.append(re.compile(pat).match)
447 447 except re.error:
448 448 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
449 449 else: raise Abort("invalid pattern (%s): %s" % (k, p))
450 450
451 451 def buildfn(text):
452 452 for m in matches:
453 453 r = m(text)
454 454 if r:
455 455 return r
456 456
457 457 return buildfn
458 458
459 459 def globprefix(pat):
460 460 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
461 461 root = []
462 462 for p in pat.split(os.sep):
463 463 if contains_glob(p): break
464 464 root.append(p)
465 465 return '/'.join(root)
466 466
467 467 pats = []
468 468 files = []
469 469 roots = []
470 470 for kind, name in [patkind(p, dflt_pat) for p in names]:
471 471 if kind in ('glob', 'relpath'):
472 472 name = canonpath(canonroot, cwd, name)
473 473 if name == '':
474 474 kind, name = 'glob', '**'
475 475 if kind in ('glob', 'path', 're'):
476 476 pats.append((kind, name))
477 477 if kind == 'glob':
478 478 root = globprefix(name)
479 479 if root: roots.append(root)
480 480 elif kind == 'relpath':
481 481 files.append((kind, name))
482 482 roots.append(name)
483 483
484 484 patmatch = matchfn(pats, '$') or always
485 485 filematch = matchfn(files, '(?:/|$)') or always
486 486 incmatch = always
487 487 if inc:
488 488 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
489 489 incmatch = matchfn(inckinds, '(?:/|$)')
490 490 excmatch = lambda fn: False
491 491 if exc:
492 492 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
493 493 excmatch = matchfn(exckinds, '(?:/|$)')
494 494
495 495 return (roots,
496 496 lambda fn: (incmatch(fn) and not excmatch(fn) and
497 497 (fn.endswith('/') or
498 498 (not pats and not files) or
499 499 (pats and patmatch(fn)) or
500 500 (files and filematch(fn)))),
501 501 (inc or exc or (pats and pats != [('glob', '**')])) and True)
502 502
503 503 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
504 504 '''enhanced shell command execution.
505 505 run with environment maybe modified, maybe in different dir.
506 506
507 507 if command fails and onerr is None, return status. if ui object,
508 508 print error message and return status, else raise onerr object as
509 509 exception.'''
510 510 def py2shell(val):
511 511 'convert python object into string that is useful to shell'
512 512 if val in (None, False):
513 513 return '0'
514 514 if val == True:
515 515 return '1'
516 516 return str(val)
517 517 oldenv = {}
518 518 for k in environ:
519 519 oldenv[k] = os.environ.get(k)
520 520 if cwd is not None:
521 521 oldcwd = os.getcwd()
522 522 origcmd = cmd
523 523 if os.name == 'nt':
524 524 cmd = '"%s"' % cmd
525 525 try:
526 526 for k, v in environ.iteritems():
527 527 os.environ[k] = py2shell(v)
528 528 if cwd is not None and oldcwd != cwd:
529 529 os.chdir(cwd)
530 530 rc = os.system(cmd)
531 531 if rc and onerr:
532 532 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
533 533 explain_exit(rc)[0])
534 534 if errprefix:
535 535 errmsg = '%s: %s' % (errprefix, errmsg)
536 536 try:
537 537 onerr.warn(errmsg + '\n')
538 538 except AttributeError:
539 539 raise onerr(errmsg)
540 540 return rc
541 541 finally:
542 542 for k, v in oldenv.iteritems():
543 543 if v is None:
544 544 del os.environ[k]
545 545 else:
546 546 os.environ[k] = v
547 547 if cwd is not None and oldcwd != cwd:
548 548 os.chdir(oldcwd)
549 549
550 550 def rename(src, dst):
551 551 """forcibly rename a file"""
552 552 try:
553 553 os.rename(src, dst)
554 554 except OSError, err:
555 555 # on windows, rename to existing file is not allowed, so we
556 556 # must delete destination first. but if file is open, unlink
557 557 # schedules it for delete but does not delete it. rename
558 558 # happens immediately even for open files, so we create
559 559 # temporary file, delete it, rename destination to that name,
560 560 # then delete that. then rename is safe to do.
561 561 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
562 562 os.close(fd)
563 563 os.unlink(temp)
564 564 os.rename(dst, temp)
565 565 os.unlink(temp)
566 566 os.rename(src, dst)
567 567
568 568 def unlink(f):
569 569 """unlink and remove the directory if it is empty"""
570 570 os.unlink(f)
571 571 # try removing directories that might now be empty
572 572 try:
573 573 os.removedirs(os.path.dirname(f))
574 574 except OSError:
575 575 pass
576 576
577 577 def copyfile(src, dest):
578 578 "copy a file, preserving mode"
579 579 try:
580 580 shutil.copyfile(src, dest)
581 581 shutil.copymode(src, dest)
582 582 except shutil.Error, inst:
583 raise util.Abort(str(inst))
583 raise Abort(str(inst))
584 584
585 585 def copyfiles(src, dst, hardlink=None):
586 586 """Copy a directory tree using hardlinks if possible"""
587 587
588 588 if hardlink is None:
589 589 hardlink = (os.stat(src).st_dev ==
590 590 os.stat(os.path.dirname(dst)).st_dev)
591 591
592 592 if os.path.isdir(src):
593 593 os.mkdir(dst)
594 594 for name in os.listdir(src):
595 595 srcname = os.path.join(src, name)
596 596 dstname = os.path.join(dst, name)
597 597 copyfiles(srcname, dstname, hardlink)
598 598 else:
599 599 if hardlink:
600 600 try:
601 601 os_link(src, dst)
602 602 except (IOError, OSError):
603 603 hardlink = False
604 604 shutil.copy(src, dst)
605 605 else:
606 606 shutil.copy(src, dst)
607 607
608 608 def audit_path(path):
609 609 """Abort if path contains dangerous components"""
610 610 parts = os.path.normcase(path).split(os.sep)
611 611 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
612 612 or os.pardir in parts):
613 613 raise Abort(_("path contains illegal component: %s\n") % path)
614 614
615 615 def _makelock_file(info, pathname):
616 616 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
617 617 os.write(ld, info)
618 618 os.close(ld)
619 619
620 620 def _readlock_file(pathname):
621 621 return posixfile(pathname).read()
622 622
623 623 def nlinks(pathname):
624 624 """Return number of hardlinks for the given file."""
625 625 return os.lstat(pathname).st_nlink
626 626
627 627 if hasattr(os, 'link'):
628 628 os_link = os.link
629 629 else:
630 630 def os_link(src, dst):
631 631 raise OSError(0, _("Hardlinks not supported"))
632 632
633 633 def fstat(fp):
634 634 '''stat file object that may not have fileno method.'''
635 635 try:
636 636 return os.fstat(fp.fileno())
637 637 except AttributeError:
638 638 return os.stat(fp.name)
639 639
640 640 posixfile = file
641 641
642 642 def is_win_9x():
643 643 '''return true if run on windows 95, 98 or me.'''
644 644 try:
645 645 return sys.getwindowsversion()[3] == 1
646 646 except AttributeError:
647 647 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
648 648
649 649 getuser_fallback = None
650 650
651 651 def getuser():
652 652 '''return name of current user'''
653 653 try:
654 654 return getpass.getuser()
655 655 except ImportError:
656 656 # import of pwd will fail on windows - try fallback
657 657 if getuser_fallback:
658 658 return getuser_fallback()
659 659 # raised if win32api not available
660 660 raise Abort(_('user name not available - set USERNAME '
661 661 'environment variable'))
662 662
663 663 def username(uid=None):
664 664 """Return the name of the user with the given uid.
665 665
666 666 If uid is None, return the name of the current user."""
667 667 try:
668 668 import pwd
669 669 if uid is None:
670 670 uid = os.getuid()
671 671 try:
672 672 return pwd.getpwuid(uid)[0]
673 673 except KeyError:
674 674 return str(uid)
675 675 except ImportError:
676 676 return None
677 677
678 678 def groupname(gid=None):
679 679 """Return the name of the group with the given gid.
680 680
681 681 If gid is None, return the name of the current group."""
682 682 try:
683 683 import grp
684 684 if gid is None:
685 685 gid = os.getgid()
686 686 try:
687 687 return grp.getgrgid(gid)[0]
688 688 except KeyError:
689 689 return str(gid)
690 690 except ImportError:
691 691 return None
692 692
693 693 # File system features
694 694
695 695 def checkfolding(path):
696 696 """
697 697 Check whether the given path is on a case-sensitive filesystem
698 698
699 699 Requires a path (like /foo/.hg) ending with a foldable final
700 700 directory component.
701 701 """
702 702 s1 = os.stat(path)
703 703 d, b = os.path.split(path)
704 704 p2 = os.path.join(d, b.upper())
705 705 if path == p2:
706 706 p2 = os.path.join(d, b.lower())
707 707 try:
708 708 s2 = os.stat(p2)
709 709 if s2 == s1:
710 710 return False
711 711 return True
712 712 except:
713 713 return True
714 714
715 715 # Platform specific variants
716 716 if os.name == 'nt':
717 717 demandload(globals(), "msvcrt")
718 718 nulldev = 'NUL:'
719 719
720 720 class winstdout:
721 721 '''stdout on windows misbehaves if sent through a pipe'''
722 722
723 723 def __init__(self, fp):
724 724 self.fp = fp
725 725
726 726 def __getattr__(self, key):
727 727 return getattr(self.fp, key)
728 728
729 729 def close(self):
730 730 try:
731 731 self.fp.close()
732 732 except: pass
733 733
734 734 def write(self, s):
735 735 try:
736 736 return self.fp.write(s)
737 737 except IOError, inst:
738 738 if inst.errno != 0: raise
739 739 self.close()
740 740 raise IOError(errno.EPIPE, 'Broken pipe')
741 741
742 742 sys.stdout = winstdout(sys.stdout)
743 743
744 744 def system_rcpath():
745 745 try:
746 746 return system_rcpath_win32()
747 747 except:
748 748 return [r'c:\mercurial\mercurial.ini']
749 749
750 750 def os_rcpath():
751 751 '''return default os-specific hgrc search path'''
752 752 path = system_rcpath()
753 753 path.append(user_rcpath())
754 754 userprofile = os.environ.get('USERPROFILE')
755 755 if userprofile:
756 756 path.append(os.path.join(userprofile, 'mercurial.ini'))
757 757 return path
758 758
759 759 def user_rcpath():
760 760 '''return os-specific hgrc search path to the user dir'''
761 761 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
762 762
763 763 def parse_patch_output(output_line):
764 764 """parses the output produced by patch and returns the file name"""
765 765 pf = output_line[14:]
766 766 if pf[0] == '`':
767 767 pf = pf[1:-1] # Remove the quotes
768 768 return pf
769 769
770 770 def testpid(pid):
771 771 '''return False if pid dead, True if running or not known'''
772 772 return True
773 773
774 774 def is_exec(f, last):
775 775 return last
776 776
777 777 def set_exec(f, mode):
778 778 pass
779 779
780 780 def set_binary(fd):
781 781 msvcrt.setmode(fd.fileno(), os.O_BINARY)
782 782
783 783 def pconvert(path):
784 784 return path.replace("\\", "/")
785 785
786 786 def localpath(path):
787 787 return path.replace('/', '\\')
788 788
789 789 def normpath(path):
790 790 return pconvert(os.path.normpath(path))
791 791
792 792 makelock = _makelock_file
793 793 readlock = _readlock_file
794 794
795 795 def samestat(s1, s2):
796 796 return False
797 797
798 798 def shellquote(s):
799 799 return '"%s"' % s.replace('"', '\\"')
800 800
801 801 def explain_exit(code):
802 802 return _("exited with status %d") % code, code
803 803
804 804 # if you change this stub into a real check, please try to implement the
805 805 # username and groupname functions above, too.
806 806 def isowner(fp, st=None):
807 807 return True
808 808
809 809 try:
810 810 # override functions with win32 versions if possible
811 811 from util_win32 import *
812 812 if not is_win_9x():
813 813 posixfile = posixfile_nt
814 814 except ImportError:
815 815 pass
816 816
817 817 else:
818 818 nulldev = '/dev/null'
819 819
820 820 def rcfiles(path):
821 821 rcs = [os.path.join(path, 'hgrc')]
822 822 rcdir = os.path.join(path, 'hgrc.d')
823 823 try:
824 824 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
825 825 if f.endswith(".rc")])
826 826 except OSError:
827 827 pass
828 828 return rcs
829 829
830 830 def os_rcpath():
831 831 '''return default os-specific hgrc search path'''
832 832 path = []
833 833 # old mod_python does not set sys.argv
834 834 if len(getattr(sys, 'argv', [])) > 0:
835 835 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
836 836 '/../etc/mercurial'))
837 837 path.extend(rcfiles('/etc/mercurial'))
838 838 path.append(os.path.expanduser('~/.hgrc'))
839 839 path = [os.path.normpath(f) for f in path]
840 840 return path
841 841
842 842 def parse_patch_output(output_line):
843 843 """parses the output produced by patch and returns the file name"""
844 844 pf = output_line[14:]
845 845 if pf.startswith("'") and pf.endswith("'") and " " in pf:
846 846 pf = pf[1:-1] # Remove the quotes
847 847 return pf
848 848
849 849 def is_exec(f, last):
850 850 """check whether a file is executable"""
851 851 return (os.lstat(f).st_mode & 0100 != 0)
852 852
853 853 def set_exec(f, mode):
854 854 s = os.lstat(f).st_mode
855 855 if (s & 0100 != 0) == mode:
856 856 return
857 857 if mode:
858 858 # Turn on +x for every +r bit when making a file executable
859 859 # and obey umask.
860 860 umask = os.umask(0)
861 861 os.umask(umask)
862 862 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
863 863 else:
864 864 os.chmod(f, s & 0666)
865 865
866 866 def set_binary(fd):
867 867 pass
868 868
869 869 def pconvert(path):
870 870 return path
871 871
872 872 def localpath(path):
873 873 return path
874 874
875 875 normpath = os.path.normpath
876 876 samestat = os.path.samestat
877 877
878 878 def makelock(info, pathname):
879 879 try:
880 880 os.symlink(info, pathname)
881 881 except OSError, why:
882 882 if why.errno == errno.EEXIST:
883 883 raise
884 884 else:
885 885 _makelock_file(info, pathname)
886 886
887 887 def readlock(pathname):
888 888 try:
889 889 return os.readlink(pathname)
890 890 except OSError, why:
891 891 if why.errno == errno.EINVAL:
892 892 return _readlock_file(pathname)
893 893 else:
894 894 raise
895 895
896 896 def shellquote(s):
897 897 return "'%s'" % s.replace("'", "'\\''")
898 898
899 899 def testpid(pid):
900 900 '''return False if pid dead, True if running or not sure'''
901 901 try:
902 902 os.kill(pid, 0)
903 903 return True
904 904 except OSError, inst:
905 905 return inst.errno != errno.ESRCH
906 906
907 907 def explain_exit(code):
908 908 """return a 2-tuple (desc, code) describing a process's status"""
909 909 if os.WIFEXITED(code):
910 910 val = os.WEXITSTATUS(code)
911 911 return _("exited with status %d") % val, val
912 912 elif os.WIFSIGNALED(code):
913 913 val = os.WTERMSIG(code)
914 914 return _("killed by signal %d") % val, val
915 915 elif os.WIFSTOPPED(code):
916 916 val = os.WSTOPSIG(code)
917 917 return _("stopped by signal %d") % val, val
918 918 raise ValueError(_("invalid exit code"))
919 919
920 920 def isowner(fp, st=None):
921 921 """Return True if the file object f belongs to the current user.
922 922
923 923 The return value of a util.fstat(f) may be passed as the st argument.
924 924 """
925 925 if st is None:
926 926 st = fstat(fp)
927 927 return st.st_uid == os.getuid()
928 928
929 929 def _buildencodefun():
930 930 e = '_'
931 931 win_reserved = [ord(x) for x in '\\:*?"<>|']
932 932 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
933 933 for x in (range(32) + range(126, 256) + win_reserved):
934 934 cmap[chr(x)] = "~%02x" % x
935 935 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
936 936 cmap[chr(x)] = e + chr(x).lower()
937 937 dmap = {}
938 938 for k, v in cmap.iteritems():
939 939 dmap[v] = k
940 940 def decode(s):
941 941 i = 0
942 942 while i < len(s):
943 943 for l in xrange(1, 4):
944 944 try:
945 945 yield dmap[s[i:i+l]]
946 946 i += l
947 947 break
948 948 except KeyError:
949 949 pass
950 950 else:
951 951 raise KeyError
952 952 return (lambda s: "".join([cmap[c] for c in s]),
953 953 lambda s: "".join(list(decode(s))))
954 954
955 955 encodefilename, decodefilename = _buildencodefun()
956 956
957 957 def encodedopener(openerfn, fn):
958 958 def o(path, *args, **kw):
959 959 return openerfn(fn(path), *args, **kw)
960 960 return o
961 961
962 962 def opener(base, audit=True):
963 963 """
964 964 return a function that opens files relative to base
965 965
966 966 this function is used to hide the details of COW semantics and
967 967 remote file access from higher level code.
968 968 """
969 969 p = base
970 970 audit_p = audit
971 971
972 972 def mktempcopy(name):
973 973 d, fn = os.path.split(name)
974 974 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
975 975 os.close(fd)
976 976 ofp = posixfile(temp, "wb")
977 977 try:
978 978 try:
979 979 ifp = posixfile(name, "rb")
980 980 except IOError, inst:
981 981 if not getattr(inst, 'filename', None):
982 982 inst.filename = name
983 983 raise
984 984 for chunk in filechunkiter(ifp):
985 985 ofp.write(chunk)
986 986 ifp.close()
987 987 ofp.close()
988 988 except:
989 989 try: os.unlink(temp)
990 990 except: pass
991 991 raise
992 992 st = os.lstat(name)
993 993 os.chmod(temp, st.st_mode)
994 994 return temp
995 995
996 996 class atomictempfile(posixfile):
997 997 """the file will only be copied when rename is called"""
998 998 def __init__(self, name, mode):
999 999 self.__name = name
1000 1000 self.temp = mktempcopy(name)
1001 1001 posixfile.__init__(self, self.temp, mode)
1002 1002 def rename(self):
1003 1003 if not self.closed:
1004 1004 posixfile.close(self)
1005 1005 rename(self.temp, localpath(self.__name))
1006 1006 def __del__(self):
1007 1007 if not self.closed:
1008 1008 try:
1009 1009 os.unlink(self.temp)
1010 1010 except: pass
1011 1011 posixfile.close(self)
1012 1012
1013 1013 class atomicfile(atomictempfile):
1014 1014 """the file will only be copied on close"""
1015 1015 def __init__(self, name, mode):
1016 1016 atomictempfile.__init__(self, name, mode)
1017 1017 def close(self):
1018 1018 self.rename()
1019 1019 def __del__(self):
1020 1020 self.rename()
1021 1021
1022 1022 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1023 1023 if audit_p:
1024 1024 audit_path(path)
1025 1025 f = os.path.join(p, path)
1026 1026
1027 1027 if not text:
1028 1028 mode += "b" # for that other OS
1029 1029
1030 1030 if mode[0] != "r":
1031 1031 try:
1032 1032 nlink = nlinks(f)
1033 1033 except OSError:
1034 1034 d = os.path.dirname(f)
1035 1035 if not os.path.isdir(d):
1036 1036 os.makedirs(d)
1037 1037 else:
1038 1038 if atomic:
1039 1039 return atomicfile(f, mode)
1040 1040 elif atomictemp:
1041 1041 return atomictempfile(f, mode)
1042 1042 if nlink > 1:
1043 1043 rename(mktempcopy(f), f)
1044 1044 return posixfile(f, mode)
1045 1045
1046 1046 return o
1047 1047
1048 1048 class chunkbuffer(object):
1049 1049 """Allow arbitrary sized chunks of data to be efficiently read from an
1050 1050 iterator over chunks of arbitrary size."""
1051 1051
1052 1052 def __init__(self, in_iter, targetsize = 2**16):
1053 1053 """in_iter is the iterator that's iterating over the input chunks.
1054 1054 targetsize is how big a buffer to try to maintain."""
1055 1055 self.in_iter = iter(in_iter)
1056 1056 self.buf = ''
1057 1057 self.targetsize = int(targetsize)
1058 1058 if self.targetsize <= 0:
1059 1059 raise ValueError(_("targetsize must be greater than 0, was %d") %
1060 1060 targetsize)
1061 1061 self.iterempty = False
1062 1062
1063 1063 def fillbuf(self):
1064 1064 """Ignore target size; read every chunk from iterator until empty."""
1065 1065 if not self.iterempty:
1066 1066 collector = cStringIO.StringIO()
1067 1067 collector.write(self.buf)
1068 1068 for ch in self.in_iter:
1069 1069 collector.write(ch)
1070 1070 self.buf = collector.getvalue()
1071 1071 self.iterempty = True
1072 1072
1073 1073 def read(self, l):
1074 1074 """Read L bytes of data from the iterator of chunks of data.
1075 1075 Returns less than L bytes if the iterator runs dry."""
1076 1076 if l > len(self.buf) and not self.iterempty:
1077 1077 # Clamp to a multiple of self.targetsize
1078 1078 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1079 1079 collector = cStringIO.StringIO()
1080 1080 collector.write(self.buf)
1081 1081 collected = len(self.buf)
1082 1082 for chunk in self.in_iter:
1083 1083 collector.write(chunk)
1084 1084 collected += len(chunk)
1085 1085 if collected >= targetsize:
1086 1086 break
1087 1087 if collected < targetsize:
1088 1088 self.iterempty = True
1089 1089 self.buf = collector.getvalue()
1090 1090 s, self.buf = self.buf[:l], buffer(self.buf, l)
1091 1091 return s
1092 1092
1093 1093 def filechunkiter(f, size=65536, limit=None):
1094 1094 """Create a generator that produces the data in the file size
1095 1095 (default 65536) bytes at a time, up to optional limit (default is
1096 1096 to read all data). Chunks may be less than size bytes if the
1097 1097 chunk is the last chunk in the file, or the file is a socket or
1098 1098 some other type of file that sometimes reads less data than is
1099 1099 requested."""
1100 1100 assert size >= 0
1101 1101 assert limit is None or limit >= 0
1102 1102 while True:
1103 1103 if limit is None: nbytes = size
1104 1104 else: nbytes = min(limit, size)
1105 1105 s = nbytes and f.read(nbytes)
1106 1106 if not s: break
1107 1107 if limit: limit -= len(s)
1108 1108 yield s
1109 1109
1110 1110 def makedate():
1111 1111 lt = time.localtime()
1112 1112 if lt[8] == 1 and time.daylight:
1113 1113 tz = time.altzone
1114 1114 else:
1115 1115 tz = time.timezone
1116 1116 return time.mktime(lt), tz
1117 1117
1118 1118 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1119 1119 """represent a (unixtime, offset) tuple as a localized time.
1120 1120 unixtime is seconds since the epoch, and offset is the time zone's
1121 1121 number of seconds away from UTC. if timezone is false, do not
1122 1122 append time zone to string."""
1123 1123 t, tz = date or makedate()
1124 1124 s = time.strftime(format, time.gmtime(float(t) - tz))
1125 1125 if timezone:
1126 1126 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1127 1127 return s
1128 1128
1129 1129 def strdate(string, format, defaults):
1130 1130 """parse a localized time string and return a (unixtime, offset) tuple.
1131 1131 if the string cannot be parsed, ValueError is raised."""
1132 1132 def timezone(string):
1133 1133 tz = string.split()[-1]
1134 1134 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1135 1135 tz = int(tz)
1136 1136 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1137 1137 return offset
1138 1138 if tz == "GMT" or tz == "UTC":
1139 1139 return 0
1140 1140 return None
1141 1141
1142 1142 # NOTE: unixtime = localunixtime + offset
1143 1143 offset, date = timezone(string), string
1144 1144 if offset != None:
1145 1145 date = " ".join(string.split()[:-1])
1146 1146
1147 1147 # add missing elements from defaults
1148 1148 for part in defaults:
1149 1149 found = [True for p in part if ("%"+p) in format]
1150 1150 if not found:
1151 1151 date += "@" + defaults[part]
1152 1152 format += "@%" + part[0]
1153 1153
1154 1154 timetuple = time.strptime(date, format)
1155 1155 localunixtime = int(calendar.timegm(timetuple))
1156 1156 if offset is None:
1157 1157 # local timezone
1158 1158 unixtime = int(time.mktime(timetuple))
1159 1159 offset = unixtime - localunixtime
1160 1160 else:
1161 1161 unixtime = localunixtime + offset
1162 1162 return unixtime, offset
1163 1163
1164 1164 def parsedate(string, formats=None, defaults=None):
1165 1165 """parse a localized time string and return a (unixtime, offset) tuple.
1166 1166 The date may be a "unixtime offset" string or in one of the specified
1167 1167 formats."""
1168 1168 if not string:
1169 1169 return 0, 0
1170 1170 if not formats:
1171 1171 formats = defaultdateformats
1172 1172 string = string.strip()
1173 1173 try:
1174 1174 when, offset = map(int, string.split(' '))
1175 1175 except ValueError:
1176 1176 # fill out defaults
1177 1177 if not defaults:
1178 1178 defaults = {}
1179 1179 now = makedate()
1180 1180 for part in "d mb yY HI M S".split():
1181 1181 if part not in defaults:
1182 1182 if part[0] in "HMS":
1183 1183 defaults[part] = "00"
1184 1184 elif part[0] in "dm":
1185 1185 defaults[part] = "1"
1186 1186 else:
1187 1187 defaults[part] = datestr(now, "%" + part[0], False)
1188 1188
1189 1189 for format in formats:
1190 1190 try:
1191 1191 when, offset = strdate(string, format, defaults)
1192 1192 except ValueError:
1193 1193 pass
1194 1194 else:
1195 1195 break
1196 1196 else:
1197 1197 raise Abort(_('invalid date: %r ') % string)
1198 1198 # validate explicit (probably user-specified) date and
1199 1199 # time zone offset. values must fit in signed 32 bits for
1200 1200 # current 32-bit linux runtimes. timezones go from UTC-12
1201 1201 # to UTC+14
1202 1202 if abs(when) > 0x7fffffff:
1203 1203 raise Abort(_('date exceeds 32 bits: %d') % when)
1204 1204 if offset < -50400 or offset > 43200:
1205 1205 raise Abort(_('impossible time zone offset: %d') % offset)
1206 1206 return when, offset
1207 1207
1208 1208 def matchdate(date):
1209 1209 """Return a function that matches a given date match specifier
1210 1210
1211 1211 Formats include:
1212 1212
1213 1213 '{date}' match a given date to the accuracy provided
1214 1214
1215 1215 '<{date}' on or before a given date
1216 1216
1217 1217 '>{date}' on or after a given date
1218 1218
1219 1219 """
1220 1220
1221 1221 def lower(date):
1222 1222 return parsedate(date, extendeddateformats)[0]
1223 1223
1224 1224 def upper(date):
1225 1225 d = dict(mb="12", HI="23", M="59", S="59")
1226 1226 for days in "31 30 29".split():
1227 1227 try:
1228 1228 d["d"] = days
1229 1229 return parsedate(date, extendeddateformats, d)[0]
1230 1230 except:
1231 1231 pass
1232 1232 d["d"] = "28"
1233 1233 return parsedate(date, extendeddateformats, d)[0]
1234 1234
1235 1235 if date[0] == "<":
1236 1236 when = upper(date[1:])
1237 1237 return lambda x: x <= when
1238 1238 elif date[0] == ">":
1239 1239 when = lower(date[1:])
1240 1240 return lambda x: x >= when
1241 1241 elif date[0] == "-":
1242 1242 try:
1243 1243 days = int(date[1:])
1244 1244 except ValueError:
1245 1245 raise Abort(_("invalid day spec: %s") % date[1:])
1246 1246 when = makedate()[0] - days * 3600 * 24
1247 1247 return lambda x: x >= when
1248 1248 elif " to " in date:
1249 1249 a, b = date.split(" to ")
1250 1250 start, stop = lower(a), upper(b)
1251 1251 return lambda x: x >= start and x <= stop
1252 1252 else:
1253 1253 start, stop = lower(date), upper(date)
1254 1254 return lambda x: x >= start and x <= stop
1255 1255
1256 1256 def shortuser(user):
1257 1257 """Return a short representation of a user name or email address."""
1258 1258 f = user.find('@')
1259 1259 if f >= 0:
1260 1260 user = user[:f]
1261 1261 f = user.find('<')
1262 1262 if f >= 0:
1263 1263 user = user[f+1:]
1264 1264 f = user.find(' ')
1265 1265 if f >= 0:
1266 1266 user = user[:f]
1267 1267 f = user.find('.')
1268 1268 if f >= 0:
1269 1269 user = user[:f]
1270 1270 return user
1271 1271
1272 1272 def ellipsis(text, maxlength=400):
1273 1273 """Trim string to at most maxlength (default: 400) characters."""
1274 1274 if len(text) <= maxlength:
1275 1275 return text
1276 1276 else:
1277 1277 return "%s..." % (text[:maxlength-3])
1278 1278
1279 1279 def walkrepos(path):
1280 1280 '''yield every hg repository under path, recursively.'''
1281 1281 def errhandler(err):
1282 1282 if err.filename == path:
1283 1283 raise err
1284 1284
1285 1285 for root, dirs, files in os.walk(path, onerror=errhandler):
1286 1286 for d in dirs:
1287 1287 if d == '.hg':
1288 1288 yield root
1289 1289 dirs[:] = []
1290 1290 break
1291 1291
1292 1292 _rcpath = None
1293 1293
1294 1294 def rcpath():
1295 1295 '''return hgrc search path. if env var HGRCPATH is set, use it.
1296 1296 for each item in path, if directory, use files ending in .rc,
1297 1297 else use item.
1298 1298 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1299 1299 if no HGRCPATH, use default os-specific path.'''
1300 1300 global _rcpath
1301 1301 if _rcpath is None:
1302 1302 if 'HGRCPATH' in os.environ:
1303 1303 _rcpath = []
1304 1304 for p in os.environ['HGRCPATH'].split(os.pathsep):
1305 1305 if not p: continue
1306 1306 if os.path.isdir(p):
1307 1307 for f in os.listdir(p):
1308 1308 if f.endswith('.rc'):
1309 1309 _rcpath.append(os.path.join(p, f))
1310 1310 else:
1311 1311 _rcpath.append(p)
1312 1312 else:
1313 1313 _rcpath = os_rcpath()
1314 1314 return _rcpath
1315 1315
1316 1316 def bytecount(nbytes):
1317 1317 '''return byte count formatted as readable string, with units'''
1318 1318
1319 1319 units = (
1320 1320 (100, 1<<30, _('%.0f GB')),
1321 1321 (10, 1<<30, _('%.1f GB')),
1322 1322 (1, 1<<30, _('%.2f GB')),
1323 1323 (100, 1<<20, _('%.0f MB')),
1324 1324 (10, 1<<20, _('%.1f MB')),
1325 1325 (1, 1<<20, _('%.2f MB')),
1326 1326 (100, 1<<10, _('%.0f KB')),
1327 1327 (10, 1<<10, _('%.1f KB')),
1328 1328 (1, 1<<10, _('%.2f KB')),
1329 1329 (1, 1, _('%.0f bytes')),
1330 1330 )
1331 1331
1332 1332 for multiplier, divisor, format in units:
1333 1333 if nbytes >= divisor * multiplier:
1334 1334 return format % (nbytes / float(divisor))
1335 1335 return units[-1][2] % nbytes
1336 1336
1337 1337 def drop_scheme(scheme, path):
1338 1338 sc = scheme + ':'
1339 1339 if path.startswith(sc):
1340 1340 path = path[len(sc):]
1341 1341 if path.startswith('//'):
1342 1342 path = path[2:]
1343 1343 return path
General Comments 0
You need to be logged in to leave comments. Login now