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