Package portage :: Module output
[hide private]

Source Code for Module portage.output

  1  # Copyright 1998-2013 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={ 289 0: portage._get_stdin().fileno(), 290 1: sys.__stderr__.fileno(), 291 2: sys.__stderr__.fileno() 292 }) 293 else: 294 os.system(prompt_command) 295 return 296 else: 297 pwd = os.environ.get('PWD','') 298 home = os.environ.get('HOME', '') 299 if home != '' and pwd.startswith(home): 300 pwd = '~' + pwd[len(home):] 301 default_xterm_title = '\x1b]0;%s@%s:%s\x07' % ( 302 os.environ.get('LOGNAME', ''), 303 os.environ.get('HOSTNAME', '').split('.', 1)[0], pwd) 304 xtermTitle(default_xterm_title, raw=True)
305
306 -def notitles():
307 "turn off title setting" 308 dotitles=0
309
310 -def nocolor():
311 "turn off colorization" 312 global havecolor 313 havecolor=0
314
315 -def resetColor():
316 return codes["reset"]
317
318 -def style_to_ansi_code(style):
319 """ 320 @param style: A style name 321 @type style: String 322 @rtype: String 323 @return: A string containing one or more ansi escape codes that are 324 used to render the given style. 325 """ 326 ret = "" 327 for attr_name in _styles[style]: 328 # allow stuff that has found it's way through ansi_code_pattern 329 ret += codes.get(attr_name, attr_name) 330 return ret
331
332 -def colormap():
333 mycolors = [] 334 for c in ("GOOD", "WARN", "BAD", "HILITE", "BRACKET", "NORMAL"): 335 mycolors.append("%s=$'%s'" % (c, style_to_ansi_code(c))) 336 return "\n".join(mycolors)
337
338 -def colorize(color_key, text):
339 global havecolor 340 if havecolor: 341 if color_key in codes: 342 return codes[color_key] + text + codes["reset"] 343 elif color_key in _styles: 344 return style_to_ansi_code(color_key) + text + codes["reset"] 345 else: 346 return text 347 else: 348 return text
349 350 compat_functions_colors = ["bold","white","teal","turquoise","darkteal", 351 "fuchsia","purple","blue","darkblue","green","darkgreen","yellow", 352 "brown","darkyellow","red","darkred"]
353 354 -class create_color_func(object):
355 __slots__ = ("_color_key",)
356 - def __init__(self, color_key):
357 self._color_key = color_key
358 - def __call__(self, text):
359 return colorize(self._color_key, text)
360 361 for c in compat_functions_colors: 362 globals()[c] = create_color_func(c)
363 364 -class ConsoleStyleFile(object):
365 """ 366 A file-like object that behaves something like 367 the colorize() function. Style identifiers 368 passed in via the new_styles() method will be used to 369 apply console codes to output. 370 """
371 - def __init__(self, f):
372 self._file = f 373 self._styles = None 374 self.write_listener = None
375
376 - def new_styles(self, styles):
377 self._styles = styles
378
379 - def write(self, s):
380 # In python-2.6, DumbWriter.send_line_break() can write 381 # non-unicode '\n' which fails with TypeError if self._file 382 # is a text stream such as io.StringIO. Therefore, make sure 383 # input is converted to unicode when necessary. 384 s = _unicode_decode(s) 385 global havecolor 386 if havecolor and self._styles: 387 styled_s = [] 388 for style in self._styles: 389 styled_s.append(style_to_ansi_code(style)) 390 styled_s.append(s) 391 styled_s.append(codes["reset"]) 392 self._write(self._file, "".join(styled_s)) 393 else: 394 self._write(self._file, s) 395 if self.write_listener: 396 self._write(self.write_listener, s)
397
398 - def _write(self, f, s):
399 # avoid potential UnicodeEncodeError 400 if f in (sys.stdout, sys.stderr): 401 s = _unicode_encode(s, 402 encoding=_encodings['stdio'], errors='backslashreplace') 403 if sys.hexversion >= 0x3000000: 404 f = f.buffer 405 f.write(s)
406
407 - def writelines(self, lines):
408 for s in lines: 409 self.write(s)
410
411 - def flush(self):
412 self._file.flush()
413
414 - def close(self):
415 self._file.close()
416
417 -class StyleWriter(formatter.DumbWriter):
418 """ 419 This is just a DumbWriter with a hook in the new_styles() method 420 that passes a styles tuple as a single argument to a callable 421 style_listener attribute. 422 """
423 - def __init__(self, **kwargs):
424 formatter.DumbWriter.__init__(self, **kwargs) 425 self.style_listener = None
426
427 - def new_styles(self, styles):
428 formatter.DumbWriter.new_styles(self, styles) 429 if self.style_listener: 430 self.style_listener(styles)
431
432 -def get_term_size(fd=None):
433 """ 434 Get the number of lines and columns of the tty that is connected to 435 fd. Returns a tuple of (lines, columns) or (0, 0) if an error 436 occurs. The curses module is used if available, otherwise the output of 437 `stty size` is parsed. The lines and columns values are guaranteed to be 438 greater than or equal to zero, since a negative COLUMNS variable is 439 known to prevent some commands from working (see bug #394091). 440 """ 441 if fd is None: 442 fd = sys.stdout 443 if not hasattr(fd, 'isatty') or not fd.isatty(): 444 return (0, 0) 445 try: 446 import curses 447 try: 448 curses.setupterm(term=os.environ.get("TERM", "unknown"), 449 fd=fd.fileno()) 450 return curses.tigetnum('lines'), curses.tigetnum('cols') 451 except curses.error: 452 pass 453 except ImportError: 454 pass 455 456 try: 457 proc = subprocess.Popen(["stty", "size"], 458 stdout=subprocess.PIPE, stderr=fd) 459 except EnvironmentError as e: 460 if e.errno != errno.ENOENT: 461 raise 462 # stty command not found 463 return (0, 0) 464 465 out = _unicode_decode(proc.communicate()[0]) 466 if proc.wait() == os.EX_OK: 467 out = out.split() 468 if len(out) == 2: 469 try: 470 val = (int(out[0]), int(out[1])) 471 except ValueError: 472 pass 473 else: 474 if val[0] >= 0 and val[1] >= 0: 475 return val 476 return (0, 0)
477
478 -def set_term_size(lines, columns, fd):
479 """ 480 Set the number of lines and columns for the tty that is connected to fd. 481 For portability, this simply calls `stty rows $lines columns $columns`. 482 """ 483 from portage.process import spawn 484 cmd = ["stty", "rows", str(lines), "columns", str(columns)] 485 try: 486 spawn(cmd, env=os.environ, fd_pipes={0:fd}) 487 except CommandNotFound: 488 writemsg(_("portage: stty: command not found\n"), noiselevel=-1)
489
490 -class EOutput(object):
491 """ 492 Performs fancy terminal formatting for status and informational messages. 493 494 The provided methods produce identical terminal output to the eponymous 495 functions in the shell script C{/sbin/functions.sh} and also accept 496 identical parameters. 497 498 This is not currently a drop-in replacement however, as the output-related 499 functions in C{/sbin/functions.sh} are oriented for use mainly by system 500 init scripts and ebuilds and their output can be customized via certain 501 C{RC_*} environment variables (see C{/etc/conf.d/rc}). B{EOutput} is not 502 customizable in this manner since it's intended for more general uses. 503 Likewise, no logging is provided. 504 505 @ivar quiet: Specifies if output should be silenced. 506 @type quiet: BooleanType 507 @ivar term_columns: Width of terminal in characters. Defaults to the value 508 specified by the shell's C{COLUMNS} variable, else to the queried tty 509 size, else to C{80}. 510 @type term_columns: IntType 511 """ 512
513 - def __init__(self, quiet=False):
514 self.__last_e_cmd = "" 515 self.__last_e_len = 0 516 self.quiet = quiet 517 lines, columns = get_term_size() 518 if columns <= 0: 519 columns = 80 520 self.term_columns = columns 521 sys.stdout.flush() 522 sys.stderr.flush()
523
524 - def _write(self, f, s):
525 # avoid potential UnicodeEncodeError 526 writemsg(s, noiselevel=-1, fd=f)
527
528 - def __eend(self, caller, errno, msg):
529 if errno == 0: 530 status_brackets = colorize("BRACKET", "[ ") + colorize("GOOD", "ok") + colorize("BRACKET", " ]") 531 else: 532 status_brackets = colorize("BRACKET", "[ ") + colorize("BAD", "!!") + colorize("BRACKET", " ]") 533 if msg: 534 if caller == "eend": 535 self.eerror(msg[0]) 536 elif caller == "ewend": 537 self.ewarn(msg[0]) 538 if self.__last_e_cmd != "ebegin": 539 self.__last_e_len = 0 540 if not self.quiet: 541 out = sys.stdout 542 self._write(out, 543 "%*s%s\n" % ((self.term_columns - self.__last_e_len - 7), 544 "", status_brackets))
545
546 - def ebegin(self, msg):
547 """ 548 Shows a message indicating the start of a process. 549 550 @param msg: A very brief (shorter than one line) description of the 551 starting process. 552 @type msg: StringType 553 """ 554 msg += " ..." 555 if not self.quiet: 556 self.einfon(msg) 557 self.__last_e_len = len(msg) + 3 558 self.__last_e_cmd = "ebegin"
559
560 - def eend(self, errno, *msg):
561 """ 562 Indicates the completion of a process, optionally displaying a message 563 via L{eerror} if the process's exit status isn't C{0}. 564 565 @param errno: A standard UNIX C{errno} code returned by processes upon 566 exit. 567 @type errno: IntType 568 @param msg: I{(optional)} An error message, typically a standard UNIX 569 error string corresponding to C{errno}. 570 @type msg: StringType 571 """ 572 if not self.quiet: 573 self.__eend("eend", errno, msg) 574 self.__last_e_cmd = "eend"
575
576 - def eerror(self, msg):
577 """ 578 Shows an error message. 579 580 @param msg: A very brief (shorter than one line) error message. 581 @type msg: StringType 582 """ 583 out = sys.stderr 584 if not self.quiet: 585 if self.__last_e_cmd == "ebegin": 586 self._write(out, "\n") 587 self._write(out, colorize("BAD", " * ") + msg + "\n") 588 self.__last_e_cmd = "eerror"
589
590 - def einfo(self, msg):
591 """ 592 Shows an informative message terminated with a newline. 593 594 @param msg: A very brief (shorter than one line) informative message. 595 @type msg: StringType 596 """ 597 out = sys.stdout 598 if not self.quiet: 599 if self.__last_e_cmd == "ebegin": 600 self._write(out, "\n") 601 self._write(out, colorize("GOOD", " * ") + msg + "\n") 602 self.__last_e_cmd = "einfo"
603
604 - def einfon(self, msg):
605 """ 606 Shows an informative message terminated without a newline. 607 608 @param msg: A very brief (shorter than one line) informative message. 609 @type msg: StringType 610 """ 611 out = sys.stdout 612 if not self.quiet: 613 if self.__last_e_cmd == "ebegin": 614 self._write(out, "\n") 615 self._write(out, colorize("GOOD", " * ") + msg) 616 self.__last_e_cmd = "einfon"
617
618 - def ewarn(self, msg):
619 """ 620 Shows a warning message. 621 622 @param msg: A very brief (shorter than one line) warning message. 623 @type msg: StringType 624 """ 625 out = sys.stderr 626 if not self.quiet: 627 if self.__last_e_cmd == "ebegin": 628 self._write(out, "\n") 629 self._write(out, colorize("WARN", " * ") + msg + "\n") 630 self.__last_e_cmd = "ewarn"
631
632 - def ewend(self, errno, *msg):
633 """ 634 Indicates the completion of a process, optionally displaying a message 635 via L{ewarn} if the process's exit status isn't C{0}. 636 637 @param errno: A standard UNIX C{errno} code returned by processes upon 638 exit. 639 @type errno: IntType 640 @param msg: I{(optional)} A warning message, typically a standard UNIX 641 error string corresponding to C{errno}. 642 @type msg: StringType 643 """ 644 if not self.quiet: 645 self.__eend("ewend", errno, msg) 646 self.__last_e_cmd = "ewend"
647
648 -class ProgressBar(object):
649 """The interface is copied from the ProgressBar class from the EasyDialogs 650 module (which is Mac only)."""
651 - def __init__(self, title=None, maxval=0, label=None, max_desc_length=25):
652 self._title = title or "" 653 self._maxval = maxval 654 self._label = label or "" 655 self._curval = 0 656 self._desc = "" 657 self._desc_max_length = max_desc_length 658 self._set_desc()
659 660 @property
661 - def curval(self):
662 """ 663 The current value (of type integer or long integer) of the progress 664 bar. The normal access methods coerce curval between 0 and maxval. This 665 attribute should not be altered directly. 666 """ 667 return self._curval
668 669 @property
670 - def maxval(self):
671 """ 672 The maximum value (of type integer or long integer) of the progress 673 bar; the progress bar (thermometer style) is full when curval equals 674 maxval. If maxval is 0, the bar will be indeterminate (barber-pole). 675 This attribute should not be altered directly. 676 """ 677 return self._maxval
678
679 - def title(self, newstr):
680 """Sets the text in the title bar of the progress dialog to newstr.""" 681 self._title = newstr 682 self._set_desc()
683
684 - def label(self, newstr):
685 """Sets the text in the progress box of the progress dialog to newstr.""" 686 self._label = newstr 687 self._set_desc()
688
689 - def _set_desc(self):
690 self._desc = "%s%s" % ( 691 "%s: " % self._title if self._title else "", 692 "%s" % self._label if self._label else "" 693 ) 694 if len(self._desc) > self._desc_max_length: # truncate if too long 695 self._desc = "%s..." % self._desc[:self._desc_max_length - 3] 696 if len(self._desc): 697 self._desc = self._desc.ljust(self._desc_max_length)
698 699
700 - def set(self, value, maxval=None):
701 """ 702 Sets the progress bar's curval to value, and also maxval to max if the 703 latter is provided. value is first coerced between 0 and maxval. The 704 thermometer bar is updated to reflect the changes, including a change 705 from indeterminate to determinate or vice versa. 706 """ 707 if maxval is not None: 708 self._maxval = maxval 709 if value < 0: 710 value = 0 711 elif value > self._maxval: 712 value = self._maxval 713 self._curval = value
714
715 - def inc(self, n=1):
716 """Increments the progress bar's curval by n, or by 1 if n is not 717 provided. (Note that n may be negative, in which case the effect is a 718 decrement.) The progress bar is updated to reflect the change. If the 719 bar is indeterminate, this causes one ``spin'' of the barber pole. The 720 resulting curval is coerced between 0 and maxval if incrementing causes 721 it to fall outside this range. 722 """ 723 self.set(self._curval+n)
724
725 -class TermProgressBar(ProgressBar):
726 """A tty progress bar similar to wget's."""
727 - def __init__(self, fd=sys.stdout, **kwargs):
728 ProgressBar.__init__(self, **kwargs) 729 lines, self.term_columns = get_term_size(fd) 730 self.file = fd 731 self._min_columns = 11 732 self._max_columns = 80 733 # for indeterminate mode, ranges from 0.0 to 1.0 734 self._position = 0.0
735
736 - def set(self, value, maxval=None):
739
740 - def _display_image(self, image):
741 self.file.write('\r') 742 self.file.write(image) 743 self.file.flush()
744
745 - def _create_image(self):
746 cols = self.term_columns 747 if cols > self._max_columns: 748 cols = self._max_columns 749 min_columns = self._min_columns 750 curval = self._curval 751 maxval = self._maxval 752 position = self._position 753 percentage_str_width = 5 754 square_brackets_width = 2 755 if cols < percentage_str_width: 756 return "" 757 bar_space = cols - percentage_str_width - square_brackets_width - 1 758 if self._desc: 759 bar_space -= self._desc_max_length 760 if maxval == 0: 761 max_bar_width = bar_space-3 762 _percent = "".ljust(percentage_str_width) 763 if cols < min_columns: 764 return "" 765 if position <= 0.5: 766 offset = 2 * position 767 else: 768 offset = 2 * (1 - position) 769 delta = 0.5 / max_bar_width 770 position += delta 771 if position >= 1.0: 772 position = 0.0 773 # make sure it touches the ends 774 if 1.0 - position < delta: 775 position = 1.0 776 if position < 0.5 and 0.5 - position < delta: 777 position = 0.5 778 self._position = position 779 bar_width = int(offset * max_bar_width) 780 image = "%s%s%s" % (self._desc, _percent, 781 "[" + (bar_width * " ") + \ 782 "<=>" + ((max_bar_width - bar_width) * " ") + "]") 783 return image 784 else: 785 percentage = int(100 * float(curval) / maxval) 786 max_bar_width = bar_space - 1 787 _percent = ("%d%% " % percentage).rjust(percentage_str_width) 788 image = "%s%s" % (self._desc, _percent) 789 790 if cols < min_columns: 791 return image 792 offset = float(curval) / maxval 793 bar_width = int(offset * max_bar_width) 794 image = image + "[" + (bar_width * "=") + \ 795 ">" + ((max_bar_width - bar_width) * " ") + "]" 796 return image
797 798 _color_map_loaded = False
799 800 -def _init(config_root='/'):
801 """ 802 Load color.map from the given config_root. This is called automatically 803 on first access of the codes or _styles attributes (unless it has already 804 been called for some other reason). 805 """ 806 807 global _color_map_loaded, codes, _styles 808 if _color_map_loaded: 809 return 810 811 _color_map_loaded = True 812 codes = object.__getattribute__(codes, '_attr') 813 _styles = object.__getattribute__(_styles, '_attr') 814 815 for k, v in codes.items(): 816 codes[k] = _unicode_decode(v) 817 818 for k, v in _styles.items(): 819 _styles[k] = _unicode_decode(v) 820 821 try: 822 _parse_color_map(config_root=config_root, 823 onerror=lambda e: writemsg("%s\n" % str(e), noiselevel=-1)) 824 except FileNotFound: 825 pass 826 except PermissionDenied as e: 827 writemsg(_("Permission denied: '%s'\n") % str(e), noiselevel=-1) 828 del e 829 except PortageException as e: 830 writemsg("%s\n" % str(e), noiselevel=-1) 831 del e
832
833 -class _LazyInitColorMap(portage.proxy.objectproxy.ObjectProxy):
834 835 __slots__ = ('_attr',) 836
837 - def __init__(self, attr):
838 portage.proxy.objectproxy.ObjectProxy.__init__(self) 839 object.__setattr__(self, '_attr', attr)
840
841 - def _get_target(self):
842 _init() 843 return object.__getattribute__(self, '_attr')
844 845 codes = _LazyInitColorMap(codes) 846 _styles = _LazyInitColorMap(_styles) 847