Package portage :: Module output
[hide private]

Source Code for Module portage.output

  1  # Copyright 1998-2011 Gentoo Foundation 
  2  # Distributed under the terms of the GNU General Public License v2 
  3   
  4  __docformat__ = "epytext" 
  5   
  6  import errno 
  7  import io 
  8  import formatter 
  9  import re 
 10  import subprocess 
 11  import sys 
 12   
 13  import portage 
 14  portage.proxy.lazyimport.lazyimport(globals(), 
 15          'portage.util:writemsg', 
 16  ) 
 17   
 18  from portage import os 
 19  from portage import _encodings 
 20  from portage import _unicode_encode 
 21  from portage import _unicode_decode 
 22  from portage.const import COLOR_MAP_FILE 
 23  from portage.exception import CommandNotFound, FileNotFound, \ 
 24          ParseError, PermissionDenied, PortageException 
 25  from portage.localization import _ 
 26   
 27  havecolor=1 
 28  dotitles=1 
 29   
 30  _styles = {} 
 31  """Maps style class to tuple of attribute names.""" 
 32   
 33  codes = {} 
 34  """Maps attribute name to ansi code.""" 
 35   
 36  esc_seq = "\x1b[" 
 37   
 38  codes["normal"]       =  esc_seq + "0m" 
 39  codes["reset"]        =  esc_seq + "39;49;00m" 
 40   
 41  codes["bold"]         =  esc_seq + "01m" 
 42  codes["faint"]        =  esc_seq + "02m" 
 43  codes["standout"]     =  esc_seq + "03m" 
 44  codes["underline"]    =  esc_seq + "04m" 
 45  codes["blink"]        =  esc_seq + "05m" 
 46  codes["overline"]     =  esc_seq + "06m" 
 47  codes["reverse"]      =  esc_seq + "07m" 
 48  codes["invisible"]    =  esc_seq + "08m" 
 49   
 50  codes["no-attr"]      = esc_seq + "22m" 
 51  codes["no-standout"]  = esc_seq + "23m" 
 52  codes["no-underline"] = esc_seq + "24m" 
 53  codes["no-blink"]     = esc_seq + "25m" 
 54  codes["no-overline"]  = esc_seq + "26m" 
 55  codes["no-reverse"]   = esc_seq + "27m" 
 56   
 57  codes["bg_black"]      = esc_seq + "40m" 
 58  codes["bg_darkred"]    = esc_seq + "41m" 
 59  codes["bg_darkgreen"]  = esc_seq + "42m" 
 60  codes["bg_brown"]      = esc_seq + "43m" 
 61  codes["bg_darkblue"]   = esc_seq + "44m" 
 62  codes["bg_purple"]     = esc_seq + "45m" 
 63  codes["bg_teal"]       = esc_seq + "46m" 
 64  codes["bg_lightgray"]  = esc_seq + "47m" 
 65  codes["bg_default"]    = esc_seq + "49m" 
 66  codes["bg_darkyellow"] = codes["bg_brown"] 
67 68 -def color(fg, bg="default", attr=["normal"]):
69 mystr = codes[fg] 70 for x in [bg]+attr: 71 mystr += codes[x] 72 return mystr
73 74 75 ansi_codes = [] 76 for x in range(30, 38): 77 ansi_codes.append("%im" % x) 78 ansi_codes.append("%i;01m" % x) 79 80 rgb_ansi_colors = ['0x000000', '0x555555', '0xAA0000', '0xFF5555', '0x00AA00', 81 '0x55FF55', '0xAA5500', '0xFFFF55', '0x0000AA', '0x5555FF', '0xAA00AA', 82 '0xFF55FF', '0x00AAAA', '0x55FFFF', '0xAAAAAA', '0xFFFFFF'] 83 84 for x in range(len(rgb_ansi_colors)): 85 codes[rgb_ansi_colors[x]] = esc_seq + ansi_codes[x] 86 87 del x 88 89 codes["black"] = codes["0x000000"] 90 codes["darkgray"] = codes["0x555555"] 91 92 codes["red"] = codes["0xFF5555"] 93 codes["darkred"] = codes["0xAA0000"] 94 95 codes["green"] = codes["0x55FF55"] 96 codes["darkgreen"] = codes["0x00AA00"] 97 98 codes["yellow"] = codes["0xFFFF55"] 99 codes["brown"] = codes["0xAA5500"] 100 101 codes["blue"] = codes["0x5555FF"] 102 codes["darkblue"] = codes["0x0000AA"] 103 104 codes["fuchsia"] = codes["0xFF55FF"] 105 codes["purple"] = codes["0xAA00AA"] 106 107 codes["turquoise"] = codes["0x55FFFF"] 108 codes["teal"] = codes["0x00AAAA"] 109 110 codes["white"] = codes["0xFFFFFF"] 111 codes["lightgray"] = codes["0xAAAAAA"] 112 113 codes["darkteal"] = codes["turquoise"] 114 # Some terminals have darkyellow instead of brown. 115 codes["0xAAAA00"] = codes["brown"] 116 codes["darkyellow"] = codes["0xAAAA00"] 117 118 119 120 # Colors from /etc/init.d/functions.sh 121 _styles["NORMAL"] = ( "normal", ) 122 _styles["GOOD"] = ( "green", ) 123 _styles["WARN"] = ( "yellow", ) 124 _styles["BAD"] = ( "red", ) 125 _styles["HILITE"] = ( "teal", ) 126 _styles["BRACKET"] = ( "blue", ) 127 128 # Portage functions 129 _styles["INFORM"] = ( "darkgreen", ) 130 _styles["UNMERGE_WARN"] = ( "red", ) 131 _styles["SECURITY_WARN"] = ( "red", ) 132 _styles["MERGE_LIST_PROGRESS"] = ( "yellow", ) 133 _styles["PKG_BLOCKER"] = ( "red", ) 134 _styles["PKG_BLOCKER_SATISFIED"] = ( "darkblue", ) 135 _styles["PKG_MERGE"] = ( "darkgreen", ) 136 _styles["PKG_MERGE_SYSTEM"] = ( "darkgreen", ) 137 _styles["PKG_MERGE_WORLD"] = ( "green", ) 138 _styles["PKG_BINARY_MERGE"] = ( "purple", ) 139 _styles["PKG_BINARY_MERGE_SYSTEM"] = ( "purple", ) 140 _styles["PKG_BINARY_MERGE_WORLD"] = ( "fuchsia", ) 141 _styles["PKG_UNINSTALL"] = ( "red", ) 142 _styles["PKG_NOMERGE"] = ( "darkblue", ) 143 _styles["PKG_NOMERGE_SYSTEM"] = ( "darkblue", ) 144 _styles["PKG_NOMERGE_WORLD"] = ( "blue", ) 145 _styles["PROMPT_CHOICE_DEFAULT"] = ( "green", ) 146 _styles["PROMPT_CHOICE_OTHER"] = ( "red", )
147 148 -def _parse_color_map(config_root='/', onerror=None):
149 """ 150 Parse /etc/portage/color.map and return a dict of error codes. 151 152 @param onerror: an optional callback to handle any ParseError that would 153 otherwise be raised 154 @type onerror: callable 155 @rtype: dict 156 @return: a dictionary mapping color classes to color codes 157 """ 158 global codes, _styles 159 myfile = os.path.join(config_root, COLOR_MAP_FILE) 160 ansi_code_pattern = re.compile("^[0-9;]*m$") 161 quotes = '\'"' 162 def strip_quotes(token): 163 if token[0] in quotes and token[0] == token[-1]: 164 token = token[1:-1] 165 return token
166 167 f = None 168 try: 169 f = io.open(_unicode_encode(myfile, 170 encoding=_encodings['fs'], errors='strict'), 171 mode='r', encoding=_encodings['content'], errors='replace') 172 lineno = 0 173 for line in f: 174 lineno += 1 175 176 commenter_pos = line.find("#") 177 line = line[:commenter_pos].strip() 178 179 if len(line) == 0: 180 continue 181 182 split_line = line.split("=") 183 if len(split_line) != 2: 184 e = ParseError(_("'%s', line %s: expected exactly one occurrence of '=' operator") % \ 185 (myfile, lineno)) 186 raise e 187 if onerror: 188 onerror(e) 189 else: 190 raise e 191 continue 192 193 k = strip_quotes(split_line[0].strip()) 194 v = strip_quotes(split_line[1].strip()) 195 if not k in _styles and not k in codes: 196 e = ParseError(_("'%s', line %s: Unknown variable: '%s'") % \ 197 (myfile, lineno, k)) 198 if onerror: 199 onerror(e) 200 else: 201 raise e 202 continue 203 if ansi_code_pattern.match(v): 204 if k in _styles: 205 _styles[k] = ( esc_seq + v, ) 206 elif k in codes: 207 codes[k] = esc_seq + v 208 else: 209 code_list = [] 210 for x in v.split(): 211 if x in codes: 212 if k in _styles: 213 code_list.append(x) 214 elif k in codes: 215 code_list.append(codes[x]) 216 else: 217 e = ParseError(_("'%s', line %s: Undefined: '%s'") % \ 218 (myfile, lineno, x)) 219 if onerror: 220 onerror(e) 221 else: 222 raise e 223 if k in _styles: 224 _styles[k] = tuple(code_list) 225 elif k in codes: 226 codes[k] = "".join(code_list) 227 except (IOError, OSError) as e: 228 if e.errno == errno.ENOENT: 229 raise FileNotFound(myfile) 230 elif e.errno == errno.EACCES: 231 raise PermissionDenied(myfile) 232 raise 233 finally: 234 if f is not None: 235 f.close() 236
237 -def nc_len(mystr):
238 tmp = re.sub(esc_seq + "^m]+m", "", mystr); 239 return len(tmp)
240 241 _legal_terms_re = re.compile(r'^(xterm|xterm-color|Eterm|aterm|rxvt|screen|kterm|rxvt-unicode|gnome|interix)') 242 _disable_xtermTitle = None 243 _max_xtermTitle_len = 253
244 245 -def xtermTitle(mystr, raw=False):
246 global _disable_xtermTitle 247 if _disable_xtermTitle is None: 248 _disable_xtermTitle = not (sys.stderr.isatty() and \ 249 'TERM' in os.environ and \ 250 _legal_terms_re.match(os.environ['TERM']) is not None) 251 252 if dotitles and not _disable_xtermTitle: 253 # If the title string is too big then the terminal can 254 # misbehave. Therefore, truncate it if it's too big. 255 if len(mystr) > _max_xtermTitle_len: 256 mystr = mystr[:_max_xtermTitle_len] 257 if not raw: 258 mystr = '\x1b]0;%s\x07' % mystr 259 260 # avoid potential UnicodeEncodeError 261 mystr = _unicode_encode(mystr, 262 encoding=_encodings['stdio'], errors='backslashreplace') 263 f = sys.stderr 264 if sys.hexversion >= 0x3000000: 265 f = f.buffer 266 f.write(mystr) 267 f.flush()
268 269 default_xterm_title = None
270 271 -def xtermTitleReset():
272 global default_xterm_title 273 if default_xterm_title is None: 274 prompt_command = os.environ.get('PROMPT_COMMAND') 275 if prompt_command == "": 276 default_xterm_title = "" 277 elif prompt_command is not None: 278 if dotitles and \ 279 'TERM' in os.environ and \ 280 _legal_terms_re.match(os.environ['TERM']) is not None and \ 281 sys.stderr.isatty(): 282 from portage.process import find_binary, spawn 283 shell = os.environ.get("SHELL") 284 if not shell or not os.access(shell, os.EX_OK): 285 shell = find_binary("sh") 286 if shell: 287 spawn([shell, "-c", prompt_command], env=os.environ, 288 fd_pipes={0:sys.stdin.fileno(),1:sys.stderr.fileno(), 289 2:sys.stderr.fileno()}) 290 else: 291 os.system(prompt_command) 292 return 293 else: 294 pwd = os.environ.get('PWD','') 295 home = os.environ.get('HOME', '') 296 if home != '' and pwd.startswith(home): 297 pwd = '~' + pwd[len(home):] 298 default_xterm_title = '\x1b]0;%s@%s:%s\x07' % ( 299 os.environ.get('LOGNAME', ''), 300 os.environ.get('HOSTNAME', '').split('.', 1)[0], pwd) 301 xtermTitle(default_xterm_title, raw=True)
302
303 -def notitles():
304 "turn off title setting" 305 dotitles=0
306
307 -def nocolor():
308 "turn off colorization" 309 global havecolor 310 havecolor=0
311
312 -def resetColor():
313 return codes["reset"]
314
315 -def style_to_ansi_code(style):
316 """ 317 @param style: A style name 318 @type style: String 319 @rtype: String 320 @return: A string containing one or more ansi escape codes that are 321 used to render the given style. 322 """ 323 ret = "" 324 for attr_name in _styles[style]: 325 # allow stuff that has found it's way through ansi_code_pattern 326 ret += codes.get(attr_name, attr_name) 327 return ret
328
329 -def colormap():
330 mycolors = [] 331 for c in ("GOOD", "WARN", "BAD", "HILITE", "BRACKET", "NORMAL"): 332 mycolors.append("%s=$'%s'" % (c, style_to_ansi_code(c))) 333 return "\n".join(mycolors)
334
335 -def colorize(color_key, text):
336 global havecolor 337 if havecolor: 338 if color_key in codes: 339 return codes[color_key] + text + codes["reset"] 340 elif color_key in _styles: 341 return style_to_ansi_code(color_key) + text + codes["reset"] 342 else: 343 return text 344 else: 345 return text
346 347 compat_functions_colors = ["bold","white","teal","turquoise","darkteal", 348 "fuchsia","purple","blue","darkblue","green","darkgreen","yellow", 349 "brown","darkyellow","red","darkred"]
350 351 -class create_color_func(object):
352 __slots__ = ("_color_key",)
353 - def __init__(self, color_key):
354 self._color_key = color_key
355 - def __call__(self, text):
356 return colorize(self._color_key, text)
357 358 for c in compat_functions_colors: 359 globals()[c] = create_color_func(c)
360 361 -class ConsoleStyleFile(object):
362 """ 363 A file-like object that behaves something like 364 the colorize() function. Style identifiers 365 passed in via the new_styles() method will be used to 366 apply console codes to output. 367 """
368 - def __init__(self, f):
369 self._file = f 370 self._styles = None 371 self.write_listener = None
372
373 - def new_styles(self, styles):
374 self._styles = styles
375
376 - def write(self, s):
377 # In python-2.6, DumbWriter.send_line_break() can write 378 # non-unicode '\n' which fails with TypeError if self._file 379 # is a text stream such as io.StringIO. Therefore, make sure 380 # input is converted to unicode when necessary. 381 s = _unicode_decode(s) 382 global havecolor 383 if havecolor and self._styles: 384 styled_s = [] 385 for style in self._styles: 386 styled_s.append(style_to_ansi_code(style)) 387 styled_s.append(s) 388 styled_s.append(codes["reset"]) 389 self._write(self._file, "".join(styled_s)) 390 else: 391 self._write(self._file, s) 392 if self.write_listener: 393 self._write(self.write_listener, s)
394
395 - def _write(self, f, s):
396 # avoid potential UnicodeEncodeError 397 if f in (sys.stdout, sys.stderr): 398 s = _unicode_encode(s, 399 encoding=_encodings['stdio'], errors='backslashreplace') 400 if sys.hexversion >= 0x3000000: 401 f = f.buffer 402 f.write(s)
403
404 - def writelines(self, lines):
405 for s in lines: 406 self.write(s)
407
408 - def flush(self):
409 self._file.flush()
410
411 - def close(self):
412 self._file.close()
413
414 -class StyleWriter(formatter.DumbWriter):
415 """ 416 This is just a DumbWriter with a hook in the new_styles() method 417 that passes a styles tuple as a single argument to a callable 418 style_listener attribute. 419 """
420 - def __init__(self, **kwargs):
421 formatter.DumbWriter.__init__(self, **kwargs) 422 self.style_listener = None
423
424 - def new_styles(self, styles):
425 formatter.DumbWriter.new_styles(self, styles) 426 if self.style_listener: 427 self.style_listener(styles)
428
429 -def get_term_size(fd=None):
430 """ 431 Get the number of lines and columns of the tty that is connected to 432 fd. Returns a tuple of (lines, columns) or (0, 0) if an error 433 occurs. The curses module is used if available, otherwise the output of 434 `stty size` is parsed. The lines and columns values are guaranteed to be 435 greater than or equal to zero, since a negative COLUMNS variable is 436 known to prevent some commands from working (see bug #394091). 437 """ 438 if fd is None: 439 fd = sys.stdout 440 if not hasattr(fd, 'isatty') or not fd.isatty(): 441 return (0, 0) 442 try: 443 import curses 444 try: 445 curses.setupterm(term=os.environ.get("TERM", "unknown"), 446 fd=fd.fileno()) 447 return curses.tigetnum('lines'), curses.tigetnum('cols') 448 except curses.error: 449 pass 450 except ImportError: 451 pass 452 453 try: 454 proc = subprocess.Popen(["stty", "size"], 455 stdout=subprocess.PIPE, stderr=fd) 456 except EnvironmentError as e: 457 if e.errno != errno.ENOENT: 458 raise 459 # stty command not found 460 return (0, 0) 461 462 out = _unicode_decode(proc.communicate()[0]) 463 if proc.wait() == os.EX_OK: 464 out = out.split() 465 if len(out) == 2: 466 try: 467 val = (int(out[0]), int(out[1])) 468 except ValueError: 469 pass 470 else: 471 if val[0] >= 0 and val[1] >= 0: 472 return val 473 return (0, 0)
474
475 -def set_term_size(lines, columns, fd):
476 """ 477 Set the number of lines and columns for the tty that is connected to fd. 478 For portability, this simply calls `stty rows $lines columns $columns`. 479 """ 480 from portage.process import spawn 481 cmd = ["stty", "rows", str(lines), "columns", str(columns)] 482 try: 483 spawn(cmd, env=os.environ, fd_pipes={0:fd}) 484 except CommandNotFound: 485 writemsg(_("portage: stty: command not found\n"), noiselevel=-1)
486
487 -class EOutput(object):
488 """ 489 Performs fancy terminal formatting for status and informational messages. 490 491 The provided methods produce identical terminal output to the eponymous 492 functions in the shell script C{/sbin/functions.sh} and also accept 493 identical parameters. 494 495 This is not currently a drop-in replacement however, as the output-related 496 functions in C{/sbin/functions.sh} are oriented for use mainly by system 497 init scripts and ebuilds and their output can be customized via certain 498 C{RC_*} environment variables (see C{/etc/conf.d/rc}). B{EOutput} is not 499 customizable in this manner since it's intended for more general uses. 500 Likewise, no logging is provided. 501 502 @ivar quiet: Specifies if output should be silenced. 503 @type quiet: BooleanType 504 @ivar term_columns: Width of terminal in characters. Defaults to the value 505 specified by the shell's C{COLUMNS} variable, else to the queried tty 506 size, else to C{80}. 507 @type term_columns: IntType 508 """ 509
510 - def __init__(self, quiet=False):
511 self.__last_e_cmd = "" 512 self.__last_e_len = 0 513 self.quiet = quiet 514 lines, columns = get_term_size() 515 if columns <= 0: 516 columns = 80 517 self.term_columns = columns 518 sys.stdout.flush() 519 sys.stderr.flush()
520
521 - def _write(self, f, s):
522 # avoid potential UnicodeEncodeError 523 writemsg(s, noiselevel=-1, fd=f)
524
525 - def __eend(self, caller, errno, msg):
526 if errno == 0: 527 status_brackets = colorize("BRACKET", "[ ") + colorize("GOOD", "ok") + colorize("BRACKET", " ]") 528 else: 529 status_brackets = colorize("BRACKET", "[ ") + colorize("BAD", "!!") + colorize("BRACKET", " ]") 530 if msg: 531 if caller == "eend": 532 self.eerror(msg[0]) 533 elif caller == "ewend": 534 self.ewarn(msg[0]) 535 if self.__last_e_cmd != "ebegin": 536 self.__last_e_len = 0 537 if not self.quiet: 538 out = sys.stdout 539 self._write(out, 540 "%*s%s\n" % ((self.term_columns - self.__last_e_len - 7), 541 "", status_brackets))
542
543 - def ebegin(self, msg):
544 """ 545 Shows a message indicating the start of a process. 546 547 @param msg: A very brief (shorter than one line) description of the 548 starting process. 549 @type msg: StringType 550 """ 551 msg += " ..." 552 if not self.quiet: 553 self.einfon(msg) 554 self.__last_e_len = len(msg) + 3 555 self.__last_e_cmd = "ebegin"
556
557 - def eend(self, errno, *msg):
558 """ 559 Indicates the completion of a process, optionally displaying a message 560 via L{eerror} if the process's exit status isn't C{0}. 561 562 @param errno: A standard UNIX C{errno} code returned by processes upon 563 exit. 564 @type errno: IntType 565 @param msg: I{(optional)} An error message, typically a standard UNIX 566 error string corresponding to C{errno}. 567 @type msg: StringType 568 """ 569 if not self.quiet: 570 self.__eend("eend", errno, msg) 571 self.__last_e_cmd = "eend"
572
573 - def eerror(self, msg):
574 """ 575 Shows an error message. 576 577 @param msg: A very brief (shorter than one line) error message. 578 @type msg: StringType 579 """ 580 out = sys.stderr 581 if not self.quiet: 582 if self.__last_e_cmd == "ebegin": 583 self._write(out, "\n") 584 self._write(out, colorize("BAD", " * ") + msg + "\n") 585 self.__last_e_cmd = "eerror"
586
587 - def einfo(self, msg):
588 """ 589 Shows an informative message terminated with a newline. 590 591 @param msg: A very brief (shorter than one line) informative message. 592 @type msg: StringType 593 """ 594 out = sys.stdout 595 if not self.quiet: 596 if self.__last_e_cmd == "ebegin": 597 self._write(out, "\n") 598 self._write(out, colorize("GOOD", " * ") + msg + "\n") 599 self.__last_e_cmd = "einfo"
600
601 - def einfon(self, msg):
602 """ 603 Shows an informative message terminated without a newline. 604 605 @param msg: A very brief (shorter than one line) informative message. 606 @type msg: StringType 607 """ 608 out = sys.stdout 609 if not self.quiet: 610 if self.__last_e_cmd == "ebegin": 611 self._write(out, "\n") 612 self._write(out, colorize("GOOD", " * ") + msg) 613 self.__last_e_cmd = "einfon"
614
615 - def ewarn(self, msg):
616 """ 617 Shows a warning message. 618 619 @param msg: A very brief (shorter than one line) warning message. 620 @type msg: StringType 621 """ 622 out = sys.stderr 623 if not self.quiet: 624 if self.__last_e_cmd == "ebegin": 625 self._write(out, "\n") 626 self._write(out, colorize("WARN", " * ") + msg + "\n") 627 self.__last_e_cmd = "ewarn"
628
629 - def ewend(self, errno, *msg):
630 """ 631 Indicates the completion of a process, optionally displaying a message 632 via L{ewarn} if the process's exit status isn't C{0}. 633 634 @param errno: A standard UNIX C{errno} code returned by processes upon 635 exit. 636 @type errno: IntType 637 @param msg: I{(optional)} A warning message, typically a standard UNIX 638 error string corresponding to C{errno}. 639 @type msg: StringType 640 """ 641 if not self.quiet: 642 self.__eend("ewend", errno, msg) 643 self.__last_e_cmd = "ewend"
644
645 -class ProgressBar(object):
646 """The interface is copied from the ProgressBar class from the EasyDialogs 647 module (which is Mac only)."""
648 - def __init__(self, title=None, maxval=0, label=None, max_desc_length=25):
649 self._title = title or "" 650 self._maxval = maxval 651 self._label = label or "" 652 self._curval = 0 653 self._desc = "" 654 self._desc_max_length = max_desc_length 655 self._set_desc()
656 657 @property
658 - def curval(self):
659 """ 660 The current value (of type integer or long integer) of the progress 661 bar. The normal access methods coerce curval between 0 and maxval. This 662 attribute should not be altered directly. 663 """ 664 return self._curval
665 666 @property
667 - def maxval(self):
668 """ 669 The maximum value (of type integer or long integer) of the progress 670 bar; the progress bar (thermometer style) is full when curval equals 671 maxval. If maxval is 0, the bar will be indeterminate (barber-pole). 672 This attribute should not be altered directly. 673 """ 674 return self._maxval
675
676 - def title(self, newstr):
677 """Sets the text in the title bar of the progress dialog to newstr.""" 678 self._title = newstr 679 self._set_desc()
680
681 - def label(self, newstr):
682 """Sets the text in the progress box of the progress dialog to newstr.""" 683 self._label = newstr 684 self._set_desc()
685
686 - def _set_desc(self):
687 self._desc = "%s%s" % ( 688 "%s: " % self._title if self._title else "", 689 "%s" % self._label if self._label else "" 690 ) 691 if len(self._desc) > self._desc_max_length: # truncate if too long 692 self._desc = "%s..." % self._desc[:self._desc_max_length - 3] 693 if len(self._desc): 694 self._desc = self._desc.ljust(self._desc_max_length)
695 696
697 - def set(self, value, maxval=None):
698 """ 699 Sets the progress bar's curval to value, and also maxval to max if the 700 latter is provided. value is first coerced between 0 and maxval. The 701 thermometer bar is updated to reflect the changes, including a change 702 from indeterminate to determinate or vice versa. 703 """ 704 if maxval is not None: 705 self._maxval = maxval 706 if value < 0: 707 value = 0 708 elif value > self._maxval: 709 value = self._maxval 710 self._curval = value
711
712 - def inc(self, n=1):
713 """Increments the progress bar's curval by n, or by 1 if n is not 714 provided. (Note that n may be negative, in which case the effect is a 715 decrement.) The progress bar is updated to reflect the change. If the 716 bar is indeterminate, this causes one ``spin'' of the barber pole. The 717 resulting curval is coerced between 0 and maxval if incrementing causes 718 it to fall outside this range. 719 """ 720 self.set(self._curval+n)
721
722 -class TermProgressBar(ProgressBar):
723 """A tty progress bar similar to wget's."""
724 - def __init__(self, fd=sys.stdout, **kwargs):
725 ProgressBar.__init__(self, **kwargs) 726 lines, self.term_columns = get_term_size(fd) 727 self.file = fd 728 self._min_columns = 11 729 self._max_columns = 80 730 # for indeterminate mode, ranges from 0.0 to 1.0 731 self._position = 0.0
732
733 - def set(self, value, maxval=None):
736
737 - def _display_image(self, image):
738 self.file.write('\r') 739 self.file.write(image) 740 self.file.flush()
741
742 - def _create_image(self):
743 cols = self.term_columns 744 if cols > self._max_columns: 745 cols = self._max_columns 746 min_columns = self._min_columns 747 curval = self._curval 748 maxval = self._maxval 749 position = self._position 750 percentage_str_width = 5 751 square_brackets_width = 2 752 if cols < percentage_str_width: 753 return "" 754 bar_space = cols - percentage_str_width - square_brackets_width - 1 755 if self._desc: 756 bar_space -= self._desc_max_length 757 if maxval == 0: 758 max_bar_width = bar_space-3 759 _percent = "".ljust(percentage_str_width) 760 if cols < min_columns: 761 return "" 762 if position <= 0.5: 763 offset = 2 * position 764 else: 765 offset = 2 * (1 - position) 766 delta = 0.5 / max_bar_width 767 position += delta 768 if position >= 1.0: 769 position = 0.0 770 # make sure it touches the ends 771 if 1.0 - position < delta: 772 position = 1.0 773 if position < 0.5 and 0.5 - position < delta: 774 position = 0.5 775 self._position = position 776 bar_width = int(offset * max_bar_width) 777 image = "%s%s%s" % (self._desc, _percent, 778 "[" + (bar_width * " ") + \ 779 "<=>" + ((max_bar_width - bar_width) * " ") + "]") 780 return image 781 else: 782 percentage = int(100 * float(curval) / maxval) 783 max_bar_width = bar_space - 1 784 _percent = ("%d%% " % percentage).rjust(percentage_str_width) 785 image = "%s%s" % (self._desc, _percent) 786 787 if cols < min_columns: 788 return image 789 offset = float(curval) / maxval 790 bar_width = int(offset * max_bar_width) 791 image = image + "[" + (bar_width * "=") + \ 792 ">" + ((max_bar_width - bar_width) * " ") + "]" 793 return image
794 795 _color_map_loaded = False
796 797 -def _init(config_root='/'):
798 """ 799 Load color.map from the given config_root. This is called automatically 800 on first access of the codes or _styles attributes (unless it has already 801 been called for some other reason). 802 """ 803 804 global _color_map_loaded, codes, _styles 805 if _color_map_loaded: 806 return 807 808 _color_map_loaded = True 809 codes = object.__getattribute__(codes, '_attr') 810 _styles = object.__getattribute__(_styles, '_attr') 811 812 for k, v in codes.items(): 813 codes[k] = _unicode_decode(v) 814 815 for k, v in _styles.items(): 816 _styles[k] = _unicode_decode(v) 817 818 try: 819 _parse_color_map(config_root=config_root, 820 onerror=lambda e: writemsg("%s\n" % str(e), noiselevel=-1)) 821 except FileNotFound: 822 pass 823 except PermissionDenied as e: 824 writemsg(_("Permission denied: '%s'\n") % str(e), noiselevel=-1) 825 del e 826 except PortageException as e: 827 writemsg("%s\n" % str(e), noiselevel=-1) 828 del e
829
830 -class _LazyInitColorMap(portage.proxy.objectproxy.ObjectProxy):
831 832 __slots__ = ('_attr',) 833
834 - def __init__(self, attr):
835 portage.proxy.objectproxy.ObjectProxy.__init__(self) 836 object.__setattr__(self, '_attr', attr)
837
838 - def _get_target(self):
839 _init() 840 return object.__getattribute__(self, '_attr')
841 842 codes = _LazyInitColorMap(codes) 843 _styles = _LazyInitColorMap(_styles) 844