Package portage :: Module output
[hide private]

Source Code for Module portage.output

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