source: yum/trunk/output.py@ 6

Last change on this file since 6 was 2, checked in by Yuri Dario, 15 years ago

Initial import for vendor code.

  • Property svn:eol-style set to native
File size: 74.2 KB
Line 
1#!/usr/bin/python -t
2
3"""This handles actual output from the cli"""
4
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Library General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18# Copyright 2005 Duke University
19
20import sys
21import time
22import logging
23import types
24import gettext
25import pwd
26import rpm
27
28import re # For YumTerm
29
30from weakref import proxy as weakref
31
32from urlgrabber.progress import TextMeter
33import urlgrabber.progress
34from urlgrabber.grabber import URLGrabError
35from yum.misc import prco_tuple_to_string
36from yum.i18n import to_str, to_utf8, to_unicode
37import yum.misc
38from rpmUtils.miscutils import checkSignals, formatRequire
39from yum.constants import *
40
41from yum import logginglevels, _
42from yum.rpmtrans import RPMBaseCallback
43from yum.packageSack import packagesNewestByNameArch
44import yum.packages
45
46from yum.i18n import utf8_width, utf8_width_fill, utf8_text_fill
47
48def _term_width():
49 """ Simple terminal width, limit to 20 chars. and make 0 == 80. """
50 if not hasattr(urlgrabber.progress, 'terminal_width_cached'):
51 return 80
52 ret = urlgrabber.progress.terminal_width_cached()
53 if ret == 0:
54 return 80
55 if ret < 20:
56 return 20
57 return ret
58
59
60class YumTextMeter(TextMeter):
61
62 """
63 Text progress bar output.
64 """
65
66 def update(self, amount_read, now=None):
67 checkSignals()
68 TextMeter.update(self, amount_read, now)
69
70class YumTerm:
71 """some terminal "UI" helpers based on curses"""
72
73 # From initial search for "terminfo and python" got:
74 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116
75 # ...it's probably not copyrightable, but if so ASPN says:
76 #
77 # Except where otherwise noted, recipes in the Python Cookbook are
78 # published under the Python license.
79
80 __enabled = True
81
82 if hasattr(urlgrabber.progress, 'terminal_width_cached'):
83 columns = property(lambda self: _term_width())
84
85 __cap_names = {
86 'underline' : 'smul',
87 'reverse' : 'rev',
88 'normal' : 'sgr0',
89 }
90
91 __colors = {
92 'black' : 0,
93 'blue' : 1,
94 'green' : 2,
95 'cyan' : 3,
96 'red' : 4,
97 'magenta' : 5,
98 'yellow' : 6,
99 'white' : 7
100 }
101 __ansi_colors = {
102 'black' : 0,
103 'red' : 1,
104 'green' : 2,
105 'yellow' : 3,
106 'blue' : 4,
107 'magenta' : 5,
108 'cyan' : 6,
109 'white' : 7
110 }
111 __ansi_forced_MODE = {
112 'bold' : '\x1b[1m',
113 'blink' : '\x1b[5m',
114 'dim' : '',
115 'reverse' : '\x1b[7m',
116 'underline' : '\x1b[4m',
117 'normal' : '\x1b(B\x1b[m'
118 }
119 __ansi_forced_FG_COLOR = {
120 'black' : '\x1b[30m',
121 'red' : '\x1b[31m',
122 'green' : '\x1b[32m',
123 'yellow' : '\x1b[33m',
124 'blue' : '\x1b[34m',
125 'magenta' : '\x1b[35m',
126 'cyan' : '\x1b[36m',
127 'white' : '\x1b[37m'
128 }
129 __ansi_forced_BG_COLOR = {
130 'black' : '\x1b[40m',
131 'red' : '\x1b[41m',
132 'green' : '\x1b[42m',
133 'yellow' : '\x1b[43m',
134 'blue' : '\x1b[44m',
135 'magenta' : '\x1b[45m',
136 'cyan' : '\x1b[46m',
137 'white' : '\x1b[47m'
138 }
139
140 def __forced_init(self):
141 self.MODE = self.__ansi_forced_MODE
142 self.FG_COLOR = self.__ansi_forced_FG_COLOR
143 self.BG_COLOR = self.__ansi_forced_BG_COLOR
144
145 def reinit(self, term_stream=None, color='auto'):
146 self.__enabled = True
147 if not hasattr(urlgrabber.progress, 'terminal_width_cached'):
148 self.columns = 80
149 self.lines = 24
150
151 if color == 'always':
152 self.__forced_init()
153 return
154
155 # Output modes:
156 self.MODE = {
157 'bold' : '',
158 'blink' : '',
159 'dim' : '',
160 'reverse' : '',
161 'underline' : '',
162 'normal' : ''
163 }
164
165 # Colours
166 self.FG_COLOR = {
167 'black' : '',
168 'blue' : '',
169 'green' : '',
170 'cyan' : '',
171 'red' : '',
172 'magenta' : '',
173 'yellow' : '',
174 'white' : ''
175 }
176
177 self.BG_COLOR = {
178 'black' : '',
179 'blue' : '',
180 'green' : '',
181 'cyan' : '',
182 'red' : '',
183 'magenta' : '',
184 'yellow' : '',
185 'white' : ''
186 }
187
188 if color == 'never':
189 self.__enabled = False
190 return
191 assert color == 'auto'
192
193 # Curses isn't available on all platforms
194 try:
195 import curses
196 except:
197 self.__enabled = False
198 return
199
200 # If the stream isn't a tty, then assume it has no capabilities.
201 if not term_stream:
202 term_stream = sys.stdout
203 if not term_stream.isatty():
204 self.__enabled = False
205 return
206
207 # Check the terminal type. If we fail, then assume that the
208 # terminal has no capabilities.
209 try:
210 curses.setupterm(fd=term_stream.fileno())
211 except:
212 self.__enabled = False
213 return
214 self._ctigetstr = curses.tigetstr
215
216 if not hasattr(urlgrabber.progress, 'terminal_width_cached'):
217 self.columns = curses.tigetnum('cols')
218 self.lines = curses.tigetnum('lines')
219
220 # Look up string capabilities.
221 for cap_name in self.MODE:
222 mode = cap_name
223 if cap_name in self.__cap_names:
224 cap_name = self.__cap_names[cap_name]
225 self.MODE[mode] = self._tigetstr(cap_name) or ''
226
227 # Colors
228 set_fg = self._tigetstr('setf')
229 if set_fg:
230 for (color, val) in self.__colors.items():
231 self.FG_COLOR[color] = curses.tparm(set_fg, val) or ''
232 set_fg_ansi = self._tigetstr('setaf')
233 if set_fg_ansi:
234 for (color, val) in self.__ansi_colors.items():
235 self.FG_COLOR[color] = curses.tparm(set_fg_ansi, val) or ''
236 set_bg = self._tigetstr('setb')
237 if set_bg:
238 for (color, val) in self.__colors.items():
239 self.BG_COLOR[color] = curses.tparm(set_bg, val) or ''
240 set_bg_ansi = self._tigetstr('setab')
241 if set_bg_ansi:
242 for (color, val) in self.__ansi_colors.items():
243 self.BG_COLOR[color] = curses.tparm(set_bg_ansi, val) or ''
244
245 def __init__(self, term_stream=None, color='auto'):
246 self.reinit(term_stream, color)
247
248 def _tigetstr(self, cap_name):
249 # String capabilities can include "delays" of the form "$<2>".
250 # For any modern terminal, we should be able to just ignore
251 # these, so strip them out.
252 cap = self._ctigetstr(cap_name) or ''
253 return re.sub(r'\$<\d+>[/*]?', '', cap)
254
255 def sub(self, haystack, beg, end, needles, escape=None, ignore_case=False):
256 if not self.__enabled:
257 return haystack
258
259 if not escape:
260 escape = re.escape
261
262 render = lambda match: beg + match.group() + end
263 for needle in needles:
264 pat = escape(needle)
265 if ignore_case:
266 pat = re.template(pat, re.I)
267 haystack = re.sub(pat, render, haystack)
268 return haystack
269 def sub_norm(self, haystack, beg, needles, **kwds):
270 return self.sub(haystack, beg, self.MODE['normal'], needles, **kwds)
271
272 def sub_mode(self, haystack, mode, needles, **kwds):
273 return self.sub_norm(haystack, self.MODE[mode], needles, **kwds)
274
275 def sub_bold(self, haystack, needles, **kwds):
276 return self.sub_mode(haystack, 'bold', needles, **kwds)
277
278 def sub_fg(self, haystack, color, needles, **kwds):
279 return self.sub_norm(haystack, self.FG_COLOR[color], needles, **kwds)
280
281 def sub_bg(self, haystack, color, needles, **kwds):
282 return self.sub_norm(haystack, self.BG_COLOR[color], needles, **kwds)
283
284
285
286class YumOutput:
287
288 """
289 Main output class for the yum command line.
290 """
291
292 def __init__(self):
293 self.logger = logging.getLogger("yum.cli")
294 self.verbose_logger = logging.getLogger("yum.verbose.cli")
295 if hasattr(rpm, "expandMacro"):
296 self.i18ndomains = rpm.expandMacro("%_i18ndomains").split(":")
297 else:
298 self.i18ndomains = ["redhat-dist"]
299
300 self.term = YumTerm()
301 self._last_interrupt = None
302
303
304 def printtime(self):
305 months = [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'),
306 _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec')]
307 now = time.localtime(time.time())
308 ret = months[int(time.strftime('%m', now)) - 1] + \
309 time.strftime(' %d %T ', now)
310 return ret
311
312 def failureReport(self, errobj):
313 """failure output for failovers from urlgrabber"""
314
315 self.logger.error('%s: %s', errobj.url, errobj.exception)
316 self.logger.error(_('Trying other mirror.'))
317 raise errobj.exception
318
319
320 def simpleProgressBar(self, current, total, name=None):
321 progressbar(current, total, name)
322
323 def _highlight(self, highlight):
324 hibeg = ''
325 hiend = ''
326 if not highlight:
327 pass
328 elif not isinstance(highlight, basestring) or highlight == 'bold':
329 hibeg = self.term.MODE['bold']
330 elif highlight == 'normal':
331 pass # Minor opt.
332 else:
333 # Turn a string into a specific output: colour, bold, etc.
334 for high in highlight.replace(',', ' ').split():
335 if False: pass
336 elif high == 'normal':
337 hibeg = ''
338 elif high in self.term.MODE:
339 hibeg += self.term.MODE[high]
340 elif high in self.term.FG_COLOR:
341 hibeg += self.term.FG_COLOR[high]
342 elif (high.startswith('fg:') and
343 high[3:] in self.term.FG_COLOR):
344 hibeg += self.term.FG_COLOR[high[3:]]
345 elif (high.startswith('bg:') and
346 high[3:] in self.term.BG_COLOR):
347 hibeg += self.term.BG_COLOR[high[3:]]
348
349 if hibeg:
350 hiend = self.term.MODE['normal']
351 return (hibeg, hiend)
352
353 def _sub_highlight(self, haystack, highlight, needles, **kwds):
354 hibeg, hiend = self._highlight(highlight)
355 return self.term.sub(haystack, hibeg, hiend, needles, **kwds)
356
357 @staticmethod
358 def _calc_columns_spaces_helps(current, data_tups, left):
359 """ Spaces left on the current field will help how many pkgs? """
360 ret = 0
361 for tup in data_tups:
362 if left < (tup[0] - current):
363 break
364 ret += tup[1]
365 return ret
366
367 def calcColumns(self, data, columns=None, remainder_column=0,
368 total_width=None, indent=''):
369 """ Dynamically calculate the width of the fields in the data, data is
370 of the format [column-number][field_length] = rows. """
371
372 if total_width is None:
373 total_width = self.term.columns
374
375 cols = len(data)
376 # Convert the data to ascending list of tuples, (field_length, pkgs)
377 pdata = data
378 data = [None] * cols # Don't modify the passed in data
379 for d in range(0, cols):
380 data[d] = sorted(pdata[d].items())
381
382 if columns is None:
383 columns = [1] * cols
384
385 total_width -= (sum(columns) + (cols - 1) + utf8_width(indent))
386 while total_width > 0:
387 # Find which field all the spaces left will help best
388 helps = 0
389 val = 0
390 for d in xrange(0, cols):
391 thelps = self._calc_columns_spaces_helps(columns[d], data[d],
392 total_width)
393 if not thelps:
394 continue
395 # We prefer to overflow: the last column, and then earlier
396 # columns. This is so that in the best case (just overflow the
397 # last) ... grep still "works", and then we make it prettier.
398 if helps and (d == (cols - 1)) and (thelps / 2) < helps:
399 continue
400 if thelps < helps:
401 continue
402 helps = thelps
403 val = d
404
405 # If we found a column to expand, move up to the next level with
406 # that column and start again with any remaining space.
407 if helps:
408 diff = data[val].pop(0)[0] - columns[val]
409 columns[val] += diff
410 total_width -= diff
411 continue
412
413 overflowed_columns = 0
414 for d in xrange(0, cols):
415 if not data[d]:
416 continue
417 overflowed_columns += 1
418 if overflowed_columns:
419 # Split the remaining spaces among each overflowed column
420 # equally
421 norm = total_width / overflowed_columns
422 for d in xrange(0, cols):
423 if not data[d]:
424 continue
425 columns[d] += norm
426 total_width -= norm
427
428 # Split the remaining spaces among each column equally, except the
429 # last one. And put the rest into the remainder column
430 cols -= 1
431 norm = total_width / cols
432 for d in xrange(0, cols):
433 columns[d] += norm
434 columns[remainder_column] += total_width - (cols * norm)
435 total_width = 0
436
437 return columns
438
439 @staticmethod
440 def _fmt_column_align_width(width):
441 if width < 0:
442 return (u"-", -width)
443 return (u"", width)
444
445 def _col_data(self, col_data):
446 assert len(col_data) == 2 or len(col_data) == 3
447 if len(col_data) == 2:
448 (val, width) = col_data
449 hibeg = hiend = ''
450 if len(col_data) == 3:
451 (val, width, highlight) = col_data
452 (hibeg, hiend) = self._highlight(highlight)
453 return (val, width, hibeg, hiend)
454
455 def fmtColumns(self, columns, msg=u'', end=u''):
456 """ Return a string for columns of data, which can overflow."""
457
458 total_width = len(msg)
459 data = []
460 for col_data in columns[:-1]:
461 (val, width, hibeg, hiend) = self._col_data(col_data)
462
463 if not width: # Don't count this column, invisible text
464 msg += u"%s"
465 data.append(val)
466 continue
467
468 (align, width) = self._fmt_column_align_width(width)
469 if utf8_width(val) <= width:
470 msg += u"%s "
471 val = utf8_width_fill(val, width, left=(align == u'-'),
472 prefix=hibeg, suffix=hiend)
473 data.append(val)
474 else:
475 msg += u"%s%s%s\n" + " " * (total_width + width + 1)
476 data.extend([hibeg, val, hiend])
477 total_width += width
478 total_width += 1
479 (val, width, hibeg, hiend) = self._col_data(columns[-1])
480 (align, width) = self._fmt_column_align_width(width)
481 val = utf8_width_fill(val, width, left=(align == u'-'),
482 prefix=hibeg, suffix=hiend)
483 msg += u"%%s%s" % end
484 data.append(val)
485 return msg % tuple(data)
486
487 def simpleList(self, pkg, ui_overflow=False, indent='', highlight=False,
488 columns=None):
489 """ Simple to use function to print a pkg as a line. """
490
491 if columns is None:
492 columns = (-40, -22, -16) # Old default
493 ver = pkg.printVer()
494 na = '%s%s.%s' % (indent, pkg.name, pkg.arch)
495 hi_cols = [highlight, 'normal', 'normal']
496 rid = pkg.ui_from_repo
497 columns = zip((na, ver, rid), columns, hi_cols)
498 print self.fmtColumns(columns)
499
500 def simpleEnvraList(self, pkg, ui_overflow=False,
501 indent='', highlight=False, columns=None):
502 """ Simple to use function to print a pkg as a line, with the pkg
503 itself in envra format so it can be pased to list/install/etc. """
504
505 if columns is None:
506 columns = (-63, -16) # Old default
507 envra = '%s%s' % (indent, str(pkg))
508 hi_cols = [highlight, 'normal', 'normal']
509 rid = pkg.ui_from_repo
510 columns = zip((envra, rid), columns, hi_cols)
511 print self.fmtColumns(columns)
512
513 def fmtKeyValFill(self, key, val):
514 """ Return a key value pair in the common two column output format. """
515 val = to_str(val)
516 keylen = utf8_width(key)
517 cols = self.term.columns
518 nxt = ' ' * (keylen - 2) + ': '
519 ret = utf8_text_fill(val, width=cols,
520 initial_indent=key, subsequent_indent=nxt)
521 if ret.count("\n") > 1 and keylen > (cols / 3):
522 # If it's big, redo it again with a smaller subsequent off
523 ret = utf8_text_fill(val, width=cols,
524 initial_indent=key,
525 subsequent_indent=' ...: ')
526 return ret
527
528 def fmtSection(self, name, fill='='):
529 name = to_str(name)
530 cols = self.term.columns - 2
531 name_len = utf8_width(name)
532 if name_len >= (cols - 4):
533 beg = end = fill * 2
534 else:
535 beg = fill * ((cols - name_len) / 2)
536 end = fill * (cols - name_len - len(beg))
537
538 return "%s %s %s" % (beg, name, end)
539
540 def _enc(self, s):
541 """Get the translated version from specspo and ensure that
542 it's actually encoded in UTF-8."""
543 s = to_utf8(s)
544 if len(s) > 0:
545 for d in self.i18ndomains:
546 t = gettext.dgettext(d, s)
547 if t != s:
548 s = t
549 break
550 return to_unicode(s)
551
552 def infoOutput(self, pkg, highlight=False):
553 (hibeg, hiend) = self._highlight(highlight)
554 print _("Name : %s%s%s") % (hibeg, to_unicode(pkg.name), hiend)
555 print _("Arch : %s") % to_unicode(pkg.arch)
556 if pkg.epoch != "0":
557 print _("Epoch : %s") % to_unicode(pkg.epoch)
558 print _("Version : %s") % to_unicode(pkg.version)
559 print _("Release : %s") % to_unicode(pkg.release)
560 print _("Size : %s") % self.format_number(float(pkg.size))
561 print _("Repo : %s") % to_unicode(pkg.repoid)
562 if pkg.repoid == 'installed' and 'from_repo' in pkg.yumdb_info:
563 print _("From repo : %s") % to_unicode(pkg.yumdb_info.from_repo)
564 if self.verbose_logger.isEnabledFor(logginglevels.DEBUG_3):
565 print _("Committer : %s") % to_unicode(pkg.committer)
566 print _("Committime : %s") % time.ctime(pkg.committime)
567 print _("Buildtime : %s") % time.ctime(pkg.buildtime)
568 if hasattr(pkg, 'installtime'):
569 print _("Installtime: %s") % time.ctime(pkg.installtime)
570 print self.fmtKeyValFill(_("Summary : "), self._enc(pkg.summary))
571 if pkg.url:
572 print _("URL : %s") % to_unicode(pkg.url)
573 print self.fmtKeyValFill(_("License : "), to_unicode(pkg.license))
574 print self.fmtKeyValFill(_("Description: "), self._enc(pkg.description))
575 print ""
576
577 def updatesObsoletesList(self, uotup, changetype, columns=None):
578 """takes an updates or obsoletes tuple of pkgobjects and
579 returns a simple printed string of the output and a string
580 explaining the relationship between the tuple members"""
581 (changePkg, instPkg) = uotup
582
583 if columns is not None:
584 # New style, output all info. for both old/new with old indented
585 chi = self.conf.color_update_remote
586 if changePkg.repo.id != 'installed' and changePkg.verifyLocalPkg():
587 chi = self.conf.color_update_local
588 self.simpleList(changePkg, columns=columns, highlight=chi)
589 self.simpleList(instPkg, columns=columns, indent=' ' * 4,
590 highlight=self.conf.color_update_installed)
591 return
592
593 # Old style
594 c_compact = changePkg.compactPrint()
595 i_compact = '%s.%s' % (instPkg.name, instPkg.arch)
596 c_repo = changePkg.repoid
597 print '%-35.35s [%.12s] %.10s %-20.20s' % (c_compact, c_repo, changetype, i_compact)
598
599 def listPkgs(self, lst, description, outputType, highlight_na={},
600 columns=None, highlight_modes={}):
601 """outputs based on whatever outputType is. Current options:
602 'list' - simple pkg list
603 'info' - similar to rpm -qi output
604 ...also highlight_na can be passed, and we'll highlight
605 pkgs with (names, arch) in that set."""
606
607 if outputType in ['list', 'info']:
608 thingslisted = 0
609 if len(lst) > 0:
610 thingslisted = 1
611 print '%s' % description
612 for pkg in sorted(lst):
613 key = (pkg.name, pkg.arch)
614 highlight = False
615 if False: pass
616 elif key not in highlight_na:
617 highlight = highlight_modes.get('not in', 'normal')
618 elif pkg.verEQ(highlight_na[key]):
619 highlight = highlight_modes.get('=', 'normal')
620 elif pkg.verLT(highlight_na[key]):
621 highlight = highlight_modes.get('>', 'bold')
622 else:
623 highlight = highlight_modes.get('<', 'normal')
624
625 if outputType == 'list':
626 self.simpleList(pkg, ui_overflow=True,
627 highlight=highlight, columns=columns)
628 elif outputType == 'info':
629 self.infoOutput(pkg, highlight=highlight)
630 else:
631 pass
632
633 if thingslisted == 0:
634 return 1, ['No Packages to list']
635 return 0, []
636
637
638
639 def userconfirm(self):
640 """gets a yes or no from the user, defaults to No"""
641
642 yui = (to_unicode(_('y')), to_unicode(_('yes')))
643 nui = (to_unicode(_('n')), to_unicode(_('no')))
644 aui = (yui[0], yui[1], nui[0], nui[1])
645 while True:
646 try:
647 choice = raw_input(_('Is this ok [y/N]: '))
648 except UnicodeEncodeError:
649 raise
650 except UnicodeDecodeError:
651 raise
652 except:
653 choice = ''
654 choice = to_unicode(choice)
655 choice = choice.lower()
656 if len(choice) == 0 or choice in aui:
657 break
658 # If the enlish one letter names don't mix, allow them too
659 if u'y' not in aui and u'y' == choice:
660 choice = yui[0]
661 break
662 if u'n' not in aui and u'n' == choice:
663 break
664
665 if len(choice) == 0 or choice not in yui:
666 return False
667 else:
668 return True
669
670 def _cli_confirm_gpg_key_import(self, keydict):
671 # FIXME what should we be printing here?
672 return self.userconfirm()
673
674 def _group_names2aipkgs(self, pkg_names):
675 """ Convert pkg_names to installed pkgs or available pkgs, return
676 value is a dict on pkg.name returning (apkg, ipkg). """
677 ipkgs = self.rpmdb.searchNames(pkg_names)
678 apkgs = self.pkgSack.searchNames(pkg_names)
679 apkgs = packagesNewestByNameArch(apkgs)
680
681 # This is somewhat similar to doPackageLists()
682 pkgs = {}
683 for pkg in ipkgs:
684 pkgs[(pkg.name, pkg.arch)] = (None, pkg)
685 for pkg in apkgs:
686 key = (pkg.name, pkg.arch)
687 if key not in pkgs:
688 pkgs[(pkg.name, pkg.arch)] = (pkg, None)
689 elif pkg.verGT(pkgs[key][1]):
690 pkgs[(pkg.name, pkg.arch)] = (pkg, pkgs[key][1])
691
692 # Convert (pkg.name, pkg.arch) to pkg.name dict
693 ret = {}
694 for (apkg, ipkg) in pkgs.itervalues():
695 pkg = apkg or ipkg
696 ret.setdefault(pkg.name, []).append((apkg, ipkg))
697 return ret
698
699 def _calcDataPkgColumns(self, data, pkg_names, pkg_names2pkgs,
700 indent=' '):
701 for item in pkg_names:
702 if item not in pkg_names2pkgs:
703 continue
704 for (apkg, ipkg) in pkg_names2pkgs[item]:
705 pkg = ipkg or apkg
706 envra = utf8_width(str(pkg)) + utf8_width(indent)
707 rid = len(pkg.ui_from_repo)
708 for (d, v) in (('envra', envra), ('rid', rid)):
709 data[d].setdefault(v, 0)
710 data[d][v] += 1
711
712 def _displayPkgsFromNames(self, pkg_names, verbose, pkg_names2pkgs,
713 indent=' ', columns=None):
714 if not verbose:
715 for item in sorted(pkg_names):
716 print '%s%s' % (indent, item)
717 else:
718 for item in sorted(pkg_names):
719 if item not in pkg_names2pkgs:
720 print '%s%s' % (indent, item)
721 continue
722 for (apkg, ipkg) in sorted(pkg_names2pkgs[item],
723 key=lambda x: x[1] or x[0]):
724 if ipkg and apkg:
725 highlight = self.conf.color_list_installed_older
726 elif apkg:
727 highlight = self.conf.color_list_available_install
728 else:
729 highlight = False
730 self.simpleEnvraList(ipkg or apkg, ui_overflow=True,
731 indent=indent, highlight=highlight,
732 columns=columns)
733
734 def displayPkgsInGroups(self, group):
735 print _('\nGroup: %s') % group.ui_name
736
737 verb = self.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)
738 if verb:
739 print _(' Group-Id: %s') % to_unicode(group.groupid)
740 pkg_names2pkgs = None
741 if verb:
742 pkg_names2pkgs = self._group_names2aipkgs(group.packages)
743 if group.ui_description:
744 print _(' Description: %s') % to_unicode(group.ui_description)
745
746 sections = ((_(' Mandatory Packages:'), group.mandatory_packages),
747 (_(' Default Packages:'), group.default_packages),
748 (_(' Optional Packages:'), group.optional_packages),
749 (_(' Conditional Packages:'), group.conditional_packages))
750 columns = None
751 if verb:
752 data = {'envra' : {}, 'rid' : {}}
753 for (section_name, pkg_names) in sections:
754 self._calcDataPkgColumns(data, pkg_names, pkg_names2pkgs)
755 data = [data['envra'], data['rid']]
756 columns = self.calcColumns(data)
757 columns = (-columns[0], -columns[1])
758
759 for (section_name, pkg_names) in sections:
760 if len(pkg_names) > 0:
761 print section_name
762 self._displayPkgsFromNames(pkg_names, verb, pkg_names2pkgs,
763 columns=columns)
764
765 def depListOutput(self, results):
766 """take a list of findDeps results and 'pretty print' the output"""
767
768 for pkg in results:
769 print _("package: %s") % pkg.compactPrint()
770 if len(results[pkg]) == 0:
771 print _(" No dependencies for this package")
772 continue
773
774 for req in results[pkg]:
775 reqlist = results[pkg][req]
776 print _(" dependency: %s") % prco_tuple_to_string(req)
777 if not reqlist:
778 print _(" Unsatisfied dependency")
779 continue
780
781 for po in reqlist:
782 print " provider: %s" % po.compactPrint()
783
784 def format_number(self, number, SI=0, space=' '):
785 """Turn numbers into human-readable metric-like numbers"""
786 symbols = [ ' ', # (none)
787 'k', # kilo
788 'M', # mega
789 'G', # giga
790 'T', # tera
791 'P', # peta
792 'E', # exa
793 'Z', # zetta
794 'Y'] # yotta
795
796 if SI: step = 1000.0
797 else: step = 1024.0
798
799 thresh = 999
800 depth = 0
801 max_depth = len(symbols) - 1
802
803 # we want numbers between 0 and thresh, but don't exceed the length
804 # of our list. In that event, the formatting will be screwed up,
805 # but it'll still show the right number.
806 while number > thresh and depth < max_depth:
807 depth = depth + 1
808 number = number / step
809
810 if type(number) == type(1) or type(number) == type(1L):
811 format = '%i%s%s'
812 elif number < 9.95:
813 # must use 9.95 for proper sizing. For example, 9.99 will be
814 # rounded to 10.0 with the .1f format string (which is too long)
815 format = '%.1f%s%s'
816 else:
817 format = '%.0f%s%s'
818
819 return(format % (float(number or 0), space, symbols[depth]))
820
821 @staticmethod
822 def format_time(seconds, use_hours=0):
823 return urlgrabber.progress.format_time(seconds, use_hours)
824
825 def matchcallback(self, po, values, matchfor=None, verbose=None,
826 highlight=None):
827 """ Output search/provides type callback matches. po is the pkg object,
828 values are the things in the po that we've matched.
829 If matchfor is passed, all the strings in that list will be
830 highlighted within the output.
831 verbose overrides logginglevel, if passed. """
832
833 if self.conf.showdupesfromrepos:
834 msg = '%s : ' % po
835 else:
836 msg = '%s.%s : ' % (po.name, po.arch)
837 msg = self.fmtKeyValFill(msg, self._enc(po.summary))
838 if matchfor:
839 if highlight is None:
840 highlight = self.conf.color_search_match
841 msg = self._sub_highlight(msg, highlight, matchfor,ignore_case=True)
842
843 print msg
844
845 if verbose is None:
846 verbose = self.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)
847 if not verbose:
848 return
849
850 print _("Repo : %s") % po.repoid
851 print _('Matched from:')
852 for item in yum.misc.unique(values):
853 item = to_utf8(item)
854 if to_utf8(po.name) == item or to_utf8(po.summary) == item:
855 continue # Skip double name/summary printing
856
857 can_overflow = True
858 if False: pass
859 elif to_utf8(po.description) == item:
860 key = _("Description : ")
861 item = self._enc(item)
862 elif to_utf8(po.url) == item:
863 key = _("URL : %s")
864 can_overflow = False
865 elif to_utf8(po.license) == item:
866 key = _("License : %s")
867 can_overflow = False
868 elif item.startswith("/"):
869 key = _("Filename : %s")
870 item = self._enc(item)
871 can_overflow = False
872 else:
873 key = _("Other : ")
874
875 if matchfor:
876 item = self._sub_highlight(item, highlight, matchfor,
877 ignore_case=True)
878 if can_overflow:
879 print self.fmtKeyValFill(key, to_unicode(item))
880 else:
881 print key % item
882 print '\n\n'
883
884 def matchcallback_verbose(self, po, values, matchfor=None):
885 return self.matchcallback(po, values, matchfor, verbose=True)
886
887 def reportDownloadSize(self, packages, installonly=False):
888 """Report the total download size for a set of packages"""
889 totsize = 0
890 locsize = 0
891 insize = 0
892 error = False
893 for pkg in packages:
894 # Just to be on the safe side, if for some reason getting
895 # the package size fails, log the error and don't report download
896 # size
897 try:
898 size = int(pkg.size)
899 totsize += size
900 try:
901 if pkg.verifyLocalPkg():
902 locsize += size
903 except:
904 pass
905
906 if not installonly:
907 continue
908
909 try:
910 size = int(pkg.installedsize)
911 except:
912 pass
913 insize += size
914 except:
915 error = True
916 self.logger.error(_('There was an error calculating total download size'))
917 break
918
919 if (not error):
920 if locsize:
921 self.verbose_logger.log(logginglevels.INFO_1, _("Total size: %s"),
922 self.format_number(totsize))
923 if locsize != totsize:
924 self.verbose_logger.log(logginglevels.INFO_1, _("Total download size: %s"),
925 self.format_number(totsize - locsize))
926 if installonly:
927 self.verbose_logger.log(logginglevels.INFO_1,
928 _("Installed size: %s"),
929 self.format_number(insize))
930
931 def listTransaction(self):
932 """returns a string rep of the transaction in an easy-to-read way."""
933
934 self.tsInfo.makelists(True, True)
935 pkglist_lines = []
936 data = {'n' : {}, 'v' : {}, 'r' : {}}
937 a_wid = 0 # Arch can't get "that big" ... so always use the max.
938
939 def _add_line(lines, data, a_wid, po, obsoletes=[]):
940 (n,a,e,v,r) = po.pkgtup
941 evr = po.printVer()
942 repoid = po.ui_from_repo
943 pkgsize = float(po.size)
944 size = self.format_number(pkgsize)
945
946 if a is None: # gpgkeys are weird
947 a = 'noarch'
948
949 # none, partial, full?
950 if po.repo.id == 'installed':
951 hi = self.conf.color_update_installed
952 elif po.verifyLocalPkg():
953 hi = self.conf.color_update_local
954 else:
955 hi = self.conf.color_update_remote
956 lines.append((n, a, evr, repoid, size, obsoletes, hi))
957 # Create a dict of field_length => number of packages, for
958 # each field.
959 for (d, v) in (("n",len(n)), ("v",len(evr)), ("r",len(repoid))):
960 data[d].setdefault(v, 0)
961 data[d][v] += 1
962 if a_wid < len(a): # max() is only in 2.5.z
963 a_wid = len(a)
964 return a_wid
965
966 for (action, pkglist) in [(_('Installing'), self.tsInfo.installed),
967 (_('Updating'), self.tsInfo.updated),
968 (_('Removing'), self.tsInfo.removed),
969 (_('Reinstalling'), self.tsInfo.reinstalled),
970 (_('Downgrading'), self.tsInfo.downgraded),
971 (_('Installing for dependencies'), self.tsInfo.depinstalled),
972 (_('Updating for dependencies'), self.tsInfo.depupdated),
973 (_('Removing for dependencies'), self.tsInfo.depremoved)]:
974 lines = []
975 for txmbr in pkglist:
976 a_wid = _add_line(lines, data, a_wid, txmbr.po, txmbr.obsoletes)
977
978 pkglist_lines.append((action, lines))
979
980 for (action, pkglist) in [(_('Skipped (dependency problems)'),
981 self.skipped_packages),]:
982 lines = []
983 for po in pkglist:
984 a_wid = _add_line(lines, data, a_wid, po)
985
986 pkglist_lines.append((action, lines))
987
988 if not data['n']:
989 return u''
990 else:
991 data = [data['n'], {}, data['v'], data['r'], {}]
992 columns = [1, a_wid, 1, 1, 5]
993 columns = self.calcColumns(data, indent=" ", columns=columns,
994 remainder_column=2)
995 (n_wid, a_wid, v_wid, r_wid, s_wid) = columns
996 assert s_wid == 5
997
998 out = [u"""
999%s
1000%s
1001%s
1002""" % ('=' * self.term.columns,
1003 self.fmtColumns(((_('Package'), -n_wid), (_('Arch'), -a_wid),
1004 (_('Version'), -v_wid), (_('Repository'), -r_wid),
1005 (_('Size'), s_wid)), u" "),
1006 '=' * self.term.columns)]
1007
1008 for (action, lines) in pkglist_lines:
1009 if lines:
1010 totalmsg = u"%s:\n" % action
1011 for (n, a, evr, repoid, size, obsoletes, hi) in lines:
1012 columns = ((n, -n_wid, hi), (a, -a_wid),
1013 (evr, -v_wid), (repoid, -r_wid), (size, s_wid))
1014 msg = self.fmtColumns(columns, u" ", u"\n")
1015 hibeg, hiend = self._highlight(self.conf.color_update_installed)
1016 for obspo in sorted(obsoletes):
1017 appended = _(' replacing %s%s%s.%s %s\n')
1018 appended %= (hibeg, obspo.name, hiend,
1019 obspo.arch, obspo.printVer())
1020 msg = msg+appended
1021 totalmsg = totalmsg + msg
1022
1023 if lines:
1024 out.append(totalmsg)
1025
1026 summary = _("""
1027Transaction Summary
1028%s
1029""") % ('=' * self.term.columns,)
1030 out.append(summary)
1031 num_in = len(self.tsInfo.installed + self.tsInfo.depinstalled)
1032 num_up = len(self.tsInfo.updated + self.tsInfo.depupdated)
1033 summary = _("""\
1034Install %5.5s Package(s)
1035Upgrade %5.5s Package(s)
1036""") % (num_in, num_up,)
1037 if num_in or num_up: # Always do this?
1038 out.append(summary)
1039 num_rm = len(self.tsInfo.removed + self.tsInfo.depremoved)
1040 num_re = len(self.tsInfo.reinstalled)
1041 num_dg = len(self.tsInfo.downgraded)
1042 summary = _("""\
1043Remove %5.5s Package(s)
1044Reinstall %5.5s Package(s)
1045Downgrade %5.5s Package(s)
1046""") % (num_rm, num_re, num_dg)
1047 if num_rm or num_re or num_dg:
1048 out.append(summary)
1049
1050 return ''.join(out)
1051
1052 def postTransactionOutput(self):
1053 out = ''
1054
1055 self.tsInfo.makelists()
1056
1057 # Works a bit like calcColumns, but we never overflow a column we just
1058 # have a dynamic number of columns.
1059 def _fits_in_cols(msgs, num):
1060 """ Work out how many columns we can use to display stuff, in
1061 the post trans output. """
1062 if len(msgs) < num:
1063 return []
1064
1065 left = self.term.columns - ((num - 1) + 2)
1066 if left <= 0:
1067 return []
1068
1069 col_lens = [0] * num
1070 col = 0
1071 for msg in msgs:
1072 if len(msg) > col_lens[col]:
1073 diff = (len(msg) - col_lens[col])
1074 if left <= diff:
1075 return []
1076 left -= diff
1077 col_lens[col] = len(msg)
1078 col += 1
1079 col %= len(col_lens)
1080
1081 for col in range(len(col_lens)):
1082 col_lens[col] += left / num
1083 col_lens[col] *= -1
1084 return col_lens
1085
1086 for (action, pkglist) in [(_('Removed'), self.tsInfo.removed),
1087 (_('Dependency Removed'), self.tsInfo.depremoved),
1088 (_('Installed'), self.tsInfo.installed),
1089 (_('Dependency Installed'), self.tsInfo.depinstalled),
1090 (_('Updated'), self.tsInfo.updated),
1091 (_('Dependency Updated'), self.tsInfo.depupdated),
1092 (_('Skipped (dependency problems)'), self.skipped_packages),
1093 (_('Replaced'), self.tsInfo.obsoleted),
1094 (_('Failed'), self.tsInfo.failed)]:
1095 msgs = []
1096 if len(pkglist) > 0:
1097 out += '\n%s:\n' % action
1098 for txmbr in pkglist:
1099 (n,a,e,v,r) = txmbr.pkgtup
1100 msg = "%s.%s %s:%s-%s" % (n,a,e,v,r)
1101 msgs.append(msg)
1102 for num in (8, 7, 6, 5, 4, 3, 2):
1103 cols = _fits_in_cols(msgs, num)
1104 if cols:
1105 break
1106 if not cols:
1107 cols = [-(self.term.columns - 2)]
1108 while msgs:
1109 current_msgs = msgs[:len(cols)]
1110 out += ' '
1111 out += self.fmtColumns(zip(current_msgs, cols), end=u'\n')
1112 msgs = msgs[len(cols):]
1113
1114 return out
1115
1116 def setupProgressCallbacks(self):
1117 """sets up the progress callbacks and various
1118 output bars based on debug level"""
1119
1120 # if we're below 2 on the debug level we don't need to be outputting
1121 # progress bars - this is hacky - I'm open to other options
1122 # One of these is a download
1123 if self.conf.debuglevel < 2 or not sys.stdout.isatty():
1124 self.repos.setProgressBar(None)
1125 self.repos.callback = None
1126 else:
1127 self.repos.setProgressBar(YumTextMeter(fo=sys.stdout))
1128 self.repos.callback = CacheProgressCallback()
1129
1130 # setup our failure report for failover
1131 freport = (self.failureReport,(),{})
1132 self.repos.setFailureCallback(freport)
1133
1134 # setup callback for CTRL-C's
1135 self.repos.setInterruptCallback(self.interrupt_callback)
1136
1137 # setup our depsolve progress callback
1138 dscb = DepSolveProgressCallBack(weakref(self))
1139 self.dsCallback = dscb
1140
1141 def setupProgessCallbacks(self):
1142 # api purposes only to protect the typo
1143 self.setupProgressCallbacks()
1144
1145 def setupKeyImportCallbacks(self):
1146 self.repos.confirm_func = self._cli_confirm_gpg_key_import
1147 self.repos.gpg_import_func = self.getKeyForRepo
1148
1149 def interrupt_callback(self, cbobj):
1150 '''Handle CTRL-C's during downloads
1151
1152 If a CTRL-C occurs a URLGrabError will be raised to push the download
1153 onto the next mirror.
1154
1155 If two CTRL-C's occur in quick succession then yum will exit.
1156
1157 @param cbobj: urlgrabber callback obj
1158 '''
1159 delta_exit_chk = 2.0 # Delta between C-c's so we treat as exit
1160 delta_exit_str = _("two") # Human readable version of above
1161
1162 now = time.time()
1163
1164 if not self._last_interrupt:
1165 hibeg = self.term.MODE['bold']
1166 hiend = self.term.MODE['normal']
1167 # For translators: This is output like:
1168# Current download cancelled, interrupt (ctrl-c) again within two seconds
1169# to exit.
1170 # Where "interupt (ctrl-c) again" and "two" are highlighted.
1171 msg = _("""
1172 Current download cancelled, %sinterrupt (ctrl-c) again%s within %s%s%s seconds
1173to exit.
1174""") % (hibeg, hiend, hibeg, delta_exit_str, hiend)
1175 self.verbose_logger.log(logginglevels.INFO_2, msg)
1176 elif now - self._last_interrupt < delta_exit_chk:
1177 # Two quick CTRL-C's, quit
1178 raise KeyboardInterrupt
1179
1180 # Go to next mirror
1181 self._last_interrupt = now
1182 raise URLGrabError(15, _('user interrupt'))
1183
1184 def download_callback_total_cb(self, remote_pkgs, remote_size,
1185 download_start_timestamp):
1186 if len(remote_pkgs) <= 1:
1187 return
1188 if not hasattr(urlgrabber.progress, 'TerminalLine'):
1189 return
1190
1191 tl = urlgrabber.progress.TerminalLine(8)
1192 self.verbose_logger.log(logginglevels.INFO_2, "-" * tl.rest())
1193 dl_time = time.time() - download_start_timestamp
1194 if dl_time <= 0: # This stops divide by zero, among other problems
1195 dl_time = 0.01
1196 ui_size = tl.add(' | %5sB' % self.format_number(remote_size))
1197 ui_time = tl.add(' %9s' % self.format_time(dl_time))
1198 ui_end = tl.add(' ' * 5)
1199 ui_bs = tl.add(' %5sB/s' % self.format_number(remote_size / dl_time))
1200 msg = "%s%s%s%s%s" % (utf8_width_fill(_("Total"), tl.rest(), tl.rest()),
1201 ui_bs, ui_size, ui_time, ui_end)
1202 self.verbose_logger.log(logginglevels.INFO_2, msg)
1203
1204 def _history_uiactions(self, hpkgs):
1205 actions = set()
1206 count = 0
1207 for hpkg in hpkgs:
1208 st = hpkg.state
1209 if st == 'True-Install':
1210 st = 'Install'
1211 if st == 'Dep-Install': # Mask these at the higher levels
1212 st = 'Install'
1213 if st == 'Obsoleted': # This is just a UI tweak, as we can't have
1214 # just one but we need to count them all.
1215 st = 'Obsoleting'
1216 if st in ('Install', 'Update', 'Erase', 'Reinstall', 'Downgrade',
1217 'Obsoleting'):
1218 actions.add(st)
1219 count += 1
1220 assert len(actions) <= 6
1221 if len(actions) > 1:
1222 large2small = {'Install' : _('I'),
1223 'Obsoleting' : _('O'),
1224 'Erase' : _('E'),
1225 'Reinstall' : _('R'),
1226 'Downgrade' : _('D'),
1227 'Update' : _('U'),
1228 }
1229 return count, ", ".join([large2small[x] for x in sorted(actions)])
1230
1231 # So empty transactions work, although that "shouldn't" really happen
1232 return count, "".join(list(actions))
1233
1234 def _pwd_ui_username(self, uid, limit=None):
1235 # loginuid is set to -1 on init.
1236 if uid is None or uid == 0xFFFFFFFF:
1237 loginid = _("<unset>")
1238 name = _("System") + " " + loginid
1239 if limit is not None and len(name) > limit:
1240 name = loginid
1241 return to_unicode(name)
1242
1243 try:
1244 user = pwd.getpwuid(uid)
1245 fullname = user.pw_gecos.split(';', 2)[0]
1246 name = "%s <%s>" % (fullname, user.pw_name)
1247 if limit is not None and len(name) > limit:
1248 name = "%s ... <%s>" % (fullname.split()[0], user.pw_name)
1249 if len(name) > limit:
1250 name = "<%s>" % user.pw_name
1251 return to_unicode(name)
1252 except KeyError:
1253 return to_unicode(str(uid))
1254
1255 def _history_list_transactions(self, extcmds):
1256 tids = set()
1257 pats = []
1258 usertids = extcmds[1:]
1259 printall = False
1260 if usertids:
1261 printall = True
1262 if usertids[0] == 'all':
1263 usertids.pop(0)
1264 for tid in usertids:
1265 try:
1266 int(tid)
1267 tids.add(tid)
1268 except ValueError:
1269 pats.append(tid)
1270 if pats:
1271 tids.update(self.history.search(pats))
1272
1273 if not tids and usertids:
1274 self.logger.critical(_('Bad transaction IDs, or package(s), given'))
1275 return None, None
1276 return tids, printall
1277
1278 def historyListCmd(self, extcmds):
1279 """ Shows the user a list of data about the history. """
1280
1281 tids, printall = self._history_list_transactions(extcmds)
1282 if tids is None:
1283 return 1, ['Failed history info']
1284
1285 fmt = "%s | %s | %s | %s | %s"
1286 print fmt % (utf8_width_fill(_("ID"), 6, 6),
1287 utf8_width_fill(_("Login user"), 22, 22),
1288 utf8_width_fill(_("Date and time"), 16, 16),
1289 utf8_width_fill(_("Action(s)"), 14, 14),
1290 utf8_width_fill(_("Altered"), 7, 7))
1291 print "-" * 79
1292 fmt = "%6u | %s | %-16.16s | %s | %4u"
1293 done = 0
1294 limit = 20
1295 if printall:
1296 limit = None
1297 for old in self.history.old(tids, limit=limit):
1298 if not printall and done >= limit:
1299 break
1300
1301 done += 1
1302 name = self._pwd_ui_username(old.loginuid, 22)
1303 tm = time.strftime("%Y-%m-%d %H:%M",
1304 time.localtime(old.beg_timestamp))
1305 num, uiacts = self._history_uiactions(old.trans_data)
1306 name = utf8_width_fill(name, 22, 22)
1307 uiacts = utf8_width_fill(uiacts, 14, 14)
1308 rmark = lmark = ' '
1309 if old.return_code is None:
1310 rmark = lmark = '*'
1311 elif old.return_code:
1312 rmark = lmark = '#'
1313 if old.altered_lt_rpmdb:
1314 rmark = '<'
1315 if old.altered_gt_rpmdb:
1316 lmark = '>'
1317 print fmt % (old.tid, name, tm, uiacts, num), "%s%s" % (lmark,rmark)
1318 lastdbv = self.history.last()
1319 if lastdbv is None:
1320 self._rpmdb_warn_checks(warn=False)
1321 else:
1322 # If this is the last transaction, is good and it doesn't
1323 # match the current rpmdb ... then mark it as bad.
1324 rpmdbv = self.rpmdb.simpleVersion(main_only=True)[0]
1325 if lastdbv.end_rpmdbversion != rpmdbv:
1326 self._rpmdb_warn_checks()
1327
1328 def _history_get_transactions(self, extcmds):
1329 if len(extcmds) < 2:
1330 self.logger.critical(_('No transaction ID given'))
1331 return None
1332
1333 tids = []
1334 last = None
1335 for extcmd in extcmds[1:]:
1336 try:
1337 if extcmd == 'last' or extcmd.startswith('last-'):
1338 if last is None:
1339 cto = False
1340 last = self.history.last(complete_transactions_only=cto)
1341 if last is None:
1342 int("z")
1343 tid = last.tid
1344 if extcmd.startswith('last-'):
1345 off = int(extcmd[len('last-'):])
1346 if off <= 0:
1347 int("z")
1348 tid -= off
1349 tids.append(str(tid))
1350 continue
1351
1352 if int(extcmd) <= 0:
1353 int("z")
1354 tids.append(extcmd)
1355 except ValueError:
1356 self.logger.critical(_('Bad transaction ID given'))
1357 return None
1358
1359 old = self.history.old(tids)
1360 if not old:
1361 self.logger.critical(_('Not found given transaction ID'))
1362 return None
1363 return old
1364 def _history_get_transaction(self, extcmds):
1365 old = self._history_get_transactions(extcmds)
1366 if old is None:
1367 return None
1368 if len(old) > 1:
1369 self.logger.critical(_('Found more than one transaction ID!'))
1370 return old[0]
1371
1372 def historyInfoCmd(self, extcmds):
1373 tids = set()
1374 pats = []
1375 for tid in extcmds[1:]:
1376 try:
1377 int(tid)
1378 tids.add(tid)
1379 except ValueError:
1380 pats.append(tid)
1381 if pats:
1382 tids.update(self.history.search(pats))
1383
1384 if not tids and len(extcmds) < 2:
1385 old = self.history.last(complete_transactions_only=False)
1386 if old is not None:
1387 tids.add(old.tid)
1388
1389 if not tids:
1390 self.logger.critical(_('No transaction ID, or package, given'))
1391 return 1, ['Failed history info']
1392
1393 lastdbv = self.history.last()
1394 if lastdbv is not None:
1395 lasttid = lastdbv.tid
1396 lastdbv = lastdbv.end_rpmdbversion
1397
1398 done = False
1399 for tid in self.history.old(tids):
1400 if lastdbv is not None and tid.tid == lasttid:
1401 # If this is the last transaction, is good and it doesn't
1402 # match the current rpmdb ... then mark it as bad.
1403 rpmdbv = self.rpmdb.simpleVersion(main_only=True)[0]
1404 if lastdbv != rpmdbv:
1405 tid.altered_gt_rpmdb = True
1406 lastdbv = None
1407
1408 if done:
1409 print "-" * 79
1410 done = True
1411 self._historyInfoCmd(tid, pats)
1412
1413 def _historyInfoCmd(self, old, pats=[]):
1414 name = self._pwd_ui_username(old.loginuid)
1415
1416 print _("Transaction ID :"), old.tid
1417 begtm = time.ctime(old.beg_timestamp)
1418 print _("Begin time :"), begtm
1419 if old.beg_rpmdbversion is not None:
1420 if old.altered_lt_rpmdb:
1421 print _("Begin rpmdb :"), old.beg_rpmdbversion, "**"
1422 else:
1423 print _("Begin rpmdb :"), old.beg_rpmdbversion
1424 if old.end_timestamp is not None:
1425 endtm = time.ctime(old.end_timestamp)
1426 endtms = endtm.split()
1427 if begtm.startswith(endtms[0]): # Chop uninteresting prefix
1428 begtms = begtm.split()
1429 sofar = 0
1430 for i in range(len(endtms)):
1431 if i > len(begtms):
1432 break
1433 if begtms[i] != endtms[i]:
1434 break
1435 sofar += len(begtms[i]) + 1
1436 endtm = (' ' * sofar) + endtm[sofar:]
1437 diff = _("(%s seconds)") % (old.end_timestamp - old.beg_timestamp)
1438 print _("End time :"), endtm, diff
1439 if old.end_rpmdbversion is not None:
1440 if old.altered_gt_rpmdb:
1441 print _("End rpmdb :"), old.end_rpmdbversion, "**"
1442 else:
1443 print _("End rpmdb :"), old.end_rpmdbversion
1444 print _("User :"), name
1445 if old.return_code is None:
1446 print _("Return-Code :"), "**", _("Aborted"), "**"
1447 elif old.return_code:
1448 print _("Return-Code :"), _("Failure:"), old.return_code
1449 else:
1450 print _("Return-Code :"), _("Success")
1451 print _("Transaction performed with:")
1452 for hpkg in old.trans_with:
1453 prefix = " " * 4
1454 state = _('Installed')
1455 ipkgs = self.rpmdb.searchNames([hpkg.name])
1456 ipkgs.sort()
1457 if not ipkgs:
1458 state = _('Erased')
1459 elif hpkg.pkgtup in (ipkg.pkgtup for ipkg in ipkgs):
1460 pass
1461 elif ipkgs[-1] > hpkg:
1462 state = _('Updated')
1463 elif ipkgs[0] < hpkg:
1464 state = _('Downgraded')
1465 else: # multiple versions installed, both older and newer
1466 state = _('Weird')
1467 print "%s%s %s" % (prefix, utf8_width_fill(state, 12), hpkg)
1468 print _("Packages Altered:")
1469 self.historyInfoCmdPkgsAltered(old, pats)
1470 if old.output:
1471 print _("Scriptlet output:")
1472 num = 0
1473 for line in old.output:
1474 num += 1
1475 print "%4d" % num, line
1476 if old.errors:
1477 print _("Errors:")
1478 num = 0
1479 for line in old.errors:
1480 num += 1
1481 print "%4d" % num, line
1482
1483 def historyInfoCmdPkgsAltered(self, old, pats=[]):
1484 last = None
1485 for hpkg in old.trans_data:
1486 prefix = " " * 4
1487 if not hpkg.done:
1488 prefix = " ** "
1489
1490 highlight = 'normal'
1491 if pats:
1492 x,m,u = yum.packages.parsePackages([hpkg], pats)
1493 if x or m:
1494 highlight = 'bold'
1495 (hibeg, hiend) = self._highlight(highlight)
1496
1497 # To chop the name off we need nevra strings, str(pkg) gives envra
1498 # so we have to do it by hand ... *sigh*.
1499 cn = hpkg.ui_nevra
1500
1501 uistate = {'True-Install' : _('Install'),
1502 'Install' : _('Install'),
1503 'Dep-Install' : _('Dep-Install'),
1504 'Obsoleted' : _('Obsoleted'),
1505 'Obsoleting' : _('Obsoleting'),
1506 'Erase' : _('Erase'),
1507 'Reinstall' : _('Reinstall'),
1508 'Downgrade' : _('Downgrade'),
1509 'Downgraded' : _('Downgraded'),
1510 'Update' : _('Update'),
1511 'Updated' : _('Updated'),
1512 }.get(hpkg.state, hpkg.state)
1513 uistate = utf8_width_fill(uistate, 12, 12)
1514 # Should probably use columns here...
1515 if False: pass
1516 elif (last is not None and
1517 last.state == 'Updated' and last.name == hpkg.name and
1518 hpkg.state == 'Update'):
1519 ln = len(hpkg.name) + 1
1520 cn = (" " * ln) + cn[ln:]
1521 print "%s%s%s%s %s" % (prefix, hibeg, uistate, hiend, cn)
1522 elif (last is not None and
1523 last.state == 'Downgrade' and last.name == hpkg.name and
1524 hpkg.state == 'Downgraded'):
1525 ln = len(hpkg.name) + 1
1526 cn = (" " * ln) + cn[ln:]
1527 print "%s%s%s%s %s" % (prefix, hibeg, uistate, hiend, cn)
1528 else:
1529 last = None
1530 if hpkg.state in ('Updated', 'Downgrade'):
1531 last = hpkg
1532 print "%s%s%s%s %s" % (prefix, hibeg, uistate, hiend, cn)
1533
1534 def historySummaryCmd(self, extcmds):
1535 tids, printall = self._history_list_transactions(extcmds)
1536 if tids is None:
1537 return 1, ['Failed history info']
1538
1539 fmt = "%s | %s | %s | %s"
1540 print fmt % (utf8_width_fill(_("Login user"), 26, 26),
1541 utf8_width_fill(_("Time"), 19, 19),
1542 utf8_width_fill(_("Action(s)"), 16, 16),
1543 utf8_width_fill(_("Altered"), 8, 8))
1544 print "-" * 79
1545 fmt = "%s | %s | %s | %8u"
1546 data = {'day' : {}, 'week' : {},
1547 'fortnight' : {}, 'quarter' : {}, 'half' : {},
1548 'year' : {}, 'all' : {}}
1549 for old in self.history.old(tids):
1550 name = self._pwd_ui_username(old.loginuid, 26)
1551 period = 'all'
1552 now = time.time()
1553 if False: pass
1554 elif old.beg_timestamp > (now - (24 * 60 * 60)):
1555 period = 'day'
1556 elif old.beg_timestamp > (now - (24 * 60 * 60 * 7)):
1557 period = 'week'
1558 elif old.beg_timestamp > (now - (24 * 60 * 60 * 14)):
1559 period = 'fortnight'
1560 elif old.beg_timestamp > (now - (24 * 60 * 60 * 7 * 13)):
1561 period = 'quarter'
1562 elif old.beg_timestamp > (now - (24 * 60 * 60 * 7 * 26)):
1563 period = 'half'
1564 elif old.beg_timestamp > (now - (24 * 60 * 60 * 365)):
1565 period = 'year'
1566 data[period].setdefault(name, []).append(old)
1567 _period2user = {'day' : _("Last day"),
1568 'week' : _("Last week"),
1569 'fortnight' : _("Last 2 weeks"), # US default :p
1570 'quarter' : _("Last 3 months"),
1571 'half' : _("Last 6 months"),
1572 'year' : _("Last year"),
1573 'all' : _("Over a year ago")}
1574 done = 0
1575 for period in ('day', 'week', 'fortnight', 'quarter', 'half', 'year',
1576 'all'):
1577 if not data[period]:
1578 continue
1579 for name in sorted(data[period]):
1580 if not printall and done > 19:
1581 break
1582 done += 1
1583
1584 hpkgs = []
1585 for old in data[period][name]:
1586 hpkgs.extend(old.trans_data)
1587 count, uiacts = self._history_uiactions(hpkgs)
1588 uperiod = _period2user[period]
1589 # Should probably use columns here, esp. for uiacts?
1590 print fmt % (utf8_width_fill(name, 22, 22),
1591 utf8_width_fill(uperiod, 19, 19),
1592 utf8_width_fill(uiacts, 16, 16), count)
1593
1594
1595class DepSolveProgressCallBack:
1596 """provides text output callback functions for Dependency Solver callback"""
1597
1598 def __init__(self, ayum=None):
1599 """requires yum-cli log and errorlog functions as arguments"""
1600 self.verbose_logger = logging.getLogger("yum.verbose.cli")
1601 self.loops = 0
1602 self.ayum = ayum
1603
1604 def pkgAdded(self, pkgtup, mode):
1605 modedict = { 'i': _('installed'),
1606 'u': _('updated'),
1607 'o': _('obsoleted'),
1608 'e': _('erased')}
1609 (n, a, e, v, r) = pkgtup
1610 modeterm = modedict[mode]
1611 self.verbose_logger.log(logginglevels.INFO_2,
1612 _('---> Package %s.%s %s:%s-%s set to be %s'), n, a, e, v, r,
1613 modeterm)
1614
1615 def start(self):
1616 self.loops += 1
1617
1618 def tscheck(self):
1619 self.verbose_logger.log(logginglevels.INFO_2, _('--> Running transaction check'))
1620
1621 def restartLoop(self):
1622 self.loops += 1
1623 self.verbose_logger.log(logginglevels.INFO_2,
1624 _('--> Restarting Dependency Resolution with new changes.'))
1625 self.verbose_logger.debug('---> Loop Number: %d', self.loops)
1626
1627 def end(self):
1628 self.verbose_logger.log(logginglevels.INFO_2,
1629 _('--> Finished Dependency Resolution'))
1630
1631
1632 def procReq(self, name, formatted_req):
1633 self.verbose_logger.log(logginglevels.INFO_2,
1634 _('--> Processing Dependency: %s for package: %s'), formatted_req,
1635 name)
1636
1637 def procReqPo(self, po, formatted_req):
1638 self.verbose_logger.log(logginglevels.INFO_2,
1639 _('--> Processing Dependency: %s for package: %s'), formatted_req,
1640 po)
1641
1642 def unresolved(self, msg):
1643 self.verbose_logger.log(logginglevels.INFO_2, _('--> Unresolved Dependency: %s'),
1644 msg)
1645
1646 def format_missing_requires(self, reqPo, reqTup):
1647 """ Create a message for errorlist, non-cli users could also store this
1648 data somewhere and not print errorlist. """
1649 needname, needflags, needversion = reqTup
1650
1651 yb = self.ayum
1652
1653 prob_pkg = "%s (%s)" % (reqPo, reqPo.ui_from_repo)
1654 msg = _('Package: %s') % (prob_pkg,)
1655 ui_req = formatRequire(needname, needversion, needflags)
1656 msg += _('\n Requires: %s') % (ui_req,)
1657
1658 # if DepSolveProgressCallback() is used instead of DepSolveProgressCallback(ayum=<YumBase Object>)
1659 # then ayum has no value and we can't continue to find details about the missing requirements
1660 if not yb:
1661 return msg
1662
1663 ipkgs = set()
1664 for pkg in sorted(yb.rpmdb.getProvides(needname)):
1665 ipkgs.add(pkg.pkgtup)
1666 action = _('Installed')
1667 if yb.tsInfo.getMembersWithState(pkg.pkgtup, TS_REMOVE_STATES):
1668 action = _('Removing')
1669 msg += _('\n %s: %s (%s)') % (action, pkg, pkg.ui_from_repo)
1670 last = None
1671 for pkg in sorted(yb.pkgSack.getProvides(needname)):
1672 # We don't want to see installed packages, or N packages of the
1673 # same version, from different repos.
1674 if pkg.pkgtup in ipkgs or pkg.verEQ(last):
1675 continue
1676 last = pkg
1677 action = _('Available')
1678 if yb.tsInfo.getMembersWithState(pkg.pkgtup, TS_INSTALL_STATES):
1679 action = _('Installing')
1680 msg += _('\n %s: %s (%s)') % (action, pkg, pkg.repoid)
1681 return msg
1682
1683 def procConflict(self, name, confname):
1684 self.verbose_logger.log(logginglevels.INFO_2,
1685 _('--> Processing Conflict: %s conflicts %s'),
1686 name, confname)
1687
1688 def procConflictPo(self, po, confname):
1689 self.verbose_logger.log(logginglevels.INFO_2,
1690 _('--> Processing Conflict: %s conflicts %s'),
1691 po, confname)
1692
1693 def transactionPopulation(self):
1694 self.verbose_logger.log(logginglevels.INFO_2, _('--> Populating transaction set '
1695 'with selected packages. Please wait.'))
1696
1697 def downloadHeader(self, name):
1698 self.verbose_logger.log(logginglevels.INFO_2, _('---> Downloading header for %s '
1699 'to pack into transaction set.'), name)
1700
1701
1702class CacheProgressCallback:
1703
1704 '''
1705 The class handles text output callbacks during metadata cache updates.
1706 '''
1707
1708 def __init__(self):
1709 self.logger = logging.getLogger("yum.cli")
1710 self.verbose_logger = logging.getLogger("yum.verbose.cli")
1711 self.file_logger = logging.getLogger("yum.filelogging.cli")
1712
1713 def log(self, level, message):
1714 self.verbose_logger.log(level, message)
1715
1716 def errorlog(self, level, message):
1717 self.logger.log(level, message)
1718
1719 def filelog(self, level, message):
1720 self.file_logger.log(level, message)
1721
1722 def progressbar(self, current, total, name=None):
1723 progressbar(current, total, name)
1724
1725def _pkgname_ui(ayum, pkgname, ts_states=None):
1726 """ Get more information on a simple pkgname, if we can. We need to search
1727 packages that we are dealing with atm. and installed packages (if the
1728 transaction isn't complete). """
1729 if ayum is None:
1730 return pkgname
1731
1732 if ts_states is None:
1733 # Note 'd' is a placeholder for downgrade, and
1734 # 'r' is a placeholder for reinstall. Neither exist atm.
1735 ts_states = ('d', 'e', 'i', 'r', 'u')
1736
1737 matches = []
1738 def _cond_add(po):
1739 if matches and matches[0].arch == po.arch and matches[0].verEQ(po):
1740 return
1741 matches.append(po)
1742
1743 for txmbr in ayum.tsInfo.matchNaevr(name=pkgname):
1744 if txmbr.ts_state not in ts_states:
1745 continue
1746 _cond_add(txmbr.po)
1747
1748 if not matches:
1749 return pkgname
1750 fmatch = matches.pop(0)
1751 if not matches:
1752 return str(fmatch)
1753
1754 show_ver = True
1755 show_arch = True
1756 for match in matches:
1757 if not fmatch.verEQ(match):
1758 show_ver = False
1759 if fmatch.arch != match.arch:
1760 show_arch = False
1761
1762 if show_ver: # Multilib. *sigh*
1763 if fmatch.epoch == '0':
1764 return '%s-%s-%s' % (fmatch.name, fmatch.version, fmatch.release)
1765 else:
1766 return '%s:%s-%s-%s' % (fmatch.epoch, fmatch.name,
1767 fmatch.version, fmatch.release)
1768
1769 if show_arch:
1770 return '%s.%s' % (fmatch.name, fmatch.arch)
1771
1772 return pkgname
1773
1774class YumCliRPMCallBack(RPMBaseCallback):
1775
1776 """
1777 Yum specific callback class for RPM operations.
1778 """
1779
1780 width = property(lambda x: _term_width())
1781
1782 def __init__(self, ayum=None):
1783 RPMBaseCallback.__init__(self)
1784 self.lastmsg = to_unicode("")
1785 self.lastpackage = None # name of last package we looked at
1786 self.output = logging.getLogger("yum.verbose.cli").isEnabledFor(logginglevels.INFO_2)
1787
1788 # for a progress bar
1789 self.mark = "#"
1790 self.marks = 22
1791 self.ayum = ayum
1792
1793 # Installing things have pkg objects passed to the events, so only need to
1794 # lookup for erased/obsoleted.
1795 def pkgname_ui(self, pkgname, ts_states=('e', None)):
1796 """ Get more information on a simple pkgname, if we can. """
1797 return _pkgname_ui(self.ayum, pkgname, ts_states)
1798
1799 def event(self, package, action, te_current, te_total, ts_current, ts_total):
1800 # this is where a progress bar would be called
1801 process = self.action[action]
1802
1803 if type(package) not in types.StringTypes:
1804 pkgname = str(package)
1805 else:
1806 pkgname = self.pkgname_ui(package)
1807
1808 self.lastpackage = package
1809 if te_total == 0:
1810 percent = 0
1811 else:
1812 percent = (te_current*100L)/te_total
1813
1814 if self.output and (sys.stdout.isatty() or te_current == te_total):
1815 (fmt, wid1, wid2) = self._makefmt(percent, ts_current, ts_total,
1816 pkgname=pkgname)
1817 msg = fmt % (utf8_width_fill(process, wid1, wid1),
1818 utf8_width_fill(pkgname, wid2, wid2))
1819 if msg != self.lastmsg:
1820 sys.stdout.write(to_unicode(msg))
1821 sys.stdout.flush()
1822 self.lastmsg = msg
1823 if te_current == te_total:
1824 print " "
1825
1826 def scriptout(self, package, msgs):
1827 if msgs:
1828 sys.stdout.write(to_unicode(msgs))
1829 sys.stdout.flush()
1830
1831 def _makefmt(self, percent, ts_current, ts_total, progress = True,
1832 pkgname=None):
1833 l = len(str(ts_total))
1834 size = "%s.%s" % (l, l)
1835 fmt_done = "%" + size + "s/%" + size + "s"
1836 done = fmt_done % (ts_current, ts_total)
1837
1838 # This should probably use TerminLine, but we don't want to dep. on
1839 # that. So we kind do an ok job by hand ... at least it's dynamic now.
1840 if pkgname is None:
1841 pnl = 22
1842 else:
1843 pnl = utf8_width(pkgname)
1844
1845 overhead = (2 * l) + 2 # Length of done, above
1846 overhead += 19 # Length of begining
1847 overhead += 1 # Space between pn and done
1848 overhead += 2 # Ends for progress
1849 overhead += 1 # Space for end
1850 width = self.width
1851 if width < overhead:
1852 width = overhead # Give up
1853 width -= overhead
1854 if pnl > width / 2:
1855 pnl = width / 2
1856
1857 marks = self.width - (overhead + pnl)
1858 width = "%s.%s" % (marks, marks)
1859 fmt_bar = "[%-" + width + "s]"
1860 # pnl = str(28 + marks + 1)
1861 full_pnl = pnl + marks + 1
1862
1863 if progress and percent == 100: # Don't chop pkg name on 100%
1864 fmt = "\r %s: %s " + done
1865 wid2 = full_pnl
1866 elif progress:
1867 bar = fmt_bar % (self.mark * int(marks * (percent / 100.0)), )
1868 fmt = "\r %s: %s " + bar + " " + done
1869 wid2 = pnl
1870 elif percent == 100:
1871 fmt = " %s: %s " + done
1872 wid2 = full_pnl
1873 else:
1874 bar = fmt_bar % (self.mark * marks, )
1875 fmt = " %s: %s " + bar + " " + done
1876 wid2 = pnl
1877 return fmt, 15, wid2
1878
1879
1880def progressbar(current, total, name=None):
1881 """simple progress bar 50 # marks"""
1882
1883 mark = '#'
1884 if not sys.stdout.isatty():
1885 return
1886
1887 if current == 0:
1888 percent = 0
1889 else:
1890 if total != 0:
1891 percent = float(current) / total
1892 else:
1893 percent = 0
1894
1895 width = _term_width()
1896
1897 if name is None and current == total:
1898 name = '-'
1899
1900 end = ' %d/%d' % (current, total)
1901 width -= len(end) + 1
1902 if width < 0:
1903 width = 0
1904 if name is None:
1905 width -= 2
1906 if width < 0:
1907 width = 0
1908 hashbar = mark * int(width * percent)
1909 output = '\r[%-*s]%s' % (width, hashbar, end)
1910 elif current == total: # Don't chop name on 100%
1911 output = '\r%s%s' % (utf8_width_fill(name, width, width), end)
1912 else:
1913 width -= 4
1914 if width < 0:
1915 width = 0
1916 nwid = width / 2
1917 if nwid > utf8_width(name):
1918 nwid = utf8_width(name)
1919 width -= nwid
1920 hashbar = mark * int(width * percent)
1921 output = '\r%s: [%-*s]%s' % (utf8_width_fill(name, nwid, nwid), width,
1922 hashbar, end)
1923
1924 if current <= total:
1925 sys.stdout.write(output)
1926
1927 if current == total:
1928 sys.stdout.write('\n')
1929
1930 sys.stdout.flush()
1931
1932
1933if __name__ == "__main__":
1934 if len(sys.argv) > 1 and sys.argv[1] == "format_number":
1935 print ""
1936 print " Doing format_number tests, right column should align"
1937 print ""
1938
1939 x = YumOutput()
1940 for i in (0, 0.0, 0.1, 1, 1.0, 1.1, 10, 11, 11.1, 100, 111.1,
1941 1000, 1111, 1024 * 2, 10000, 11111, 99999, 999999,
1942 10**19, 10**20, 10**35):
1943 out = x.format_number(i)
1944 print "%36s <%s> %s <%5s>" % (i, out, ' ' * (14 - len(out)), out)
1945
1946 if len(sys.argv) > 1 and sys.argv[1] == "progress":
1947 print ""
1948 print " Doing progress, small name"
1949 print ""
1950 for i in xrange(0, 101):
1951 progressbar(i, 100, "abcd")
1952 time.sleep(0.1)
1953 print ""
1954 print " Doing progress, big name"
1955 print ""
1956 for i in xrange(0, 101):
1957 progressbar(i, 100, "_%s_" % ("123456789 " * 5))
1958 time.sleep(0.1)
1959 print ""
1960 print " Doing progress, no name"
1961 print ""
1962 for i in xrange(0, 101):
1963 progressbar(i, 100)
1964 time.sleep(0.1)
1965
1966 if len(sys.argv) > 1 and sys.argv[1] in ("progress", "rpm-progress"):
1967 cb = YumCliRPMCallBack()
1968 cb.output = True
1969 cb.action["foo"] = "abcd"
1970 cb.action["bar"] = "_12345678_.end"
1971 print ""
1972 print " Doing CB, small proc / small pkg"
1973 print ""
1974 for i in xrange(0, 101):
1975 cb.event("spkg", "foo", i, 100, i, 100)
1976 time.sleep(0.1)
1977 print ""
1978 print " Doing CB, big proc / big pkg"
1979 print ""
1980 for i in xrange(0, 101):
1981 cb.event("lpkg" + "-=" * 15 + ".end", "bar", i, 100, i, 100)
1982 time.sleep(0.1)
1983
1984 if len(sys.argv) > 1 and sys.argv[1] in ("progress", "i18n-progress",
1985 "rpm-progress",
1986 'i18n-rpm-progress'):
1987 yum.misc.setup_locale()
1988 if len(sys.argv) > 1 and sys.argv[1] in ("progress", "i18n-progress"):
1989 print ""
1990 print " Doing progress, i18n: small name"
1991 print ""
1992 for i in xrange(0, 101):
1993 progressbar(i, 100, to_unicode('\xe6\xad\xa3\xe5\x9c\xa8\xe5\xae\x89\xe8\xa3\x85'))
1994 time.sleep(0.1)
1995 print ""
1996
1997 print ""
1998 print " Doing progress, i18n: big name"
1999 print ""
2000 for i in xrange(0, 101):
2001 progressbar(i, 100, to_unicode('\xe6\xad\xa3\xe5\x9c\xa8\xe5\xae\x89\xe8\xa3\x85' * 5 + ".end"))
2002 time.sleep(0.1)
2003 print ""
2004
2005
2006 if len(sys.argv) > 1 and sys.argv[1] in ("progress", "i18n-progress",
2007 "rpm-progress",
2008 'i18n-rpm-progress'):
2009 cb = YumCliRPMCallBack()
2010 cb.output = True
2011 cb.action["foo"] = to_unicode('\xe6\xad\xa3\xe5\x9c\xa8\xe5\xae\x89\xe8\xa3\x85')
2012 cb.action["bar"] = cb.action["foo"] * 5 + ".end"
2013 print ""
2014 print " Doing CB, i18n: small proc / small pkg"
2015 print ""
2016 for i in xrange(0, 101):
2017 cb.event("spkg", "foo", i, 100, i, 100)
2018 time.sleep(0.1)
2019 print ""
2020 print " Doing CB, i18n: big proc / big pkg"
2021 print ""
2022 for i in xrange(0, 101):
2023 cb.event("lpkg" + "-=" * 15 + ".end", "bar", i, 100, i, 100)
2024 time.sleep(0.1)
2025 print ""
2026
Note: See TracBrowser for help on using the repository browser.