When yum-cron can use bits from output.py, we don't have to duplicate about 450 lines of code. --- yum-cron/yum-cron.py | 449 +-------------------------------------------------- 1 file changed, 8 insertions(+), 441 deletions(-)
diff --git a/yum-cron/yum-cron.py b/yum-cron/yum-cron.py index f53fbfb..5661fbb 100755 --- a/yum-cron/yum-cron.py +++ b/yum-cron/yum-cron.py @@ -21,6 +21,7 @@ from yum.misc import setup_locale # FIXME: is it really sane to use this from here? sys.path.append('/usr/share/yum-cli') +from output import YumOutput import callback default_config_file = '/etc/yum/yum-cron.conf' @@ -155,447 +156,9 @@ class UpdateEmitter(object): """ pass - def _format_number(self, number, SI=0, space=' '): - """Return a human-readable metric-like string representation - of a number. - - :param number: the number to be converted to a human-readable form - :param SI: If is 0, this function will use the convention - that 1 kilobyte = 1024 bytes, otherwise, the convention - that 1 kilobyte = 1000 bytes will be used - :param space: string that will be placed between the number - and the SI prefix - :return: a human-readable metric-like string representation of - *number* - """ - symbols = [ ' ', # (none) - 'k', # kilo - 'M', # mega - 'G', # giga - 'T', # tera - 'P', # peta - 'E', # exa - 'Z', # zetta - 'Y'] # yotta - - if SI: step = 1000.0 - else: step = 1024.0 - - thresh = 999 - depth = 0 - max_depth = len(symbols) - 1 - - # we want numbers between 0 and thresh, but don't exceed the length - # of our list. In that event, the formatting will be screwed up, - # but it'll still show the right number. - while number > thresh and depth < max_depth: - depth = depth + 1 - number = number / step - - if type(number) == type(1) or type(number) == type(1L): - format = '%i%s%s' - elif number < 9.95: - # must use 9.95 for proper sizing. For example, 9.99 will be - # rounded to 10.0 with the .1f format string (which is too long) - format = '%.1f%s%s' - else: - format = '%.0f%s%s' - - return(format % (float(number or 0), space, symbols[depth])) - - def _fmtColumns(self, columns, msg=u'', end=u'', text_width=utf8_width): - """Return a row of data formatted into a string for output. - Items can overflow their columns. - - :param columns: a list of tuples containing the data to - output. Each tuple contains first the item to be output, - then the amount of space allocated for the column, and then - optionally a type of highlighting for the item - :param msg: a string to begin the line of output with - :param end: a string to end the line of output with - :param text_width: a function to find the width of the items - in the columns. This defaults to utf8 but can be changed - to len() if you know it'll be fine - :return: a row of data formatted into a string for output - """ - total_width = len(msg) - data = [] - for col_data in columns[:-1]: - (val, width) = col_data - - if not width: # Don't count this column, invisible text - msg += u"%s" - data.append(val) - continue - - (align, width) = self._fmt_column_align_width(width) - val_width = text_width(val) - if val_width <= width: - # Don't use utf8_width_fill() because it sucks performance - # wise for 1,000s of rows. Also allows us to use len(), when - # we can. - msg += u"%s%s " - if (align == u'-'): - data.extend([val, " " * (width - val_width)]) - else: - data.extend([" " * (width - val_width), val]) - else: - msg += u"%s\n" + " " * (total_width + width + 1) - data.append(val) - total_width += width - total_width += 1 - (val, width) = columns[-1] - (align, width) = self._fmt_column_align_width(width) - val = utf8_width_fill(val, width, left=(align == u'-')) - msg += u"%%s%s" % end - data.append(val) - return msg % tuple(data) - - def _calcColumns(self, data, total_width, columns=None, remainder_column=0, indent=''): - """Dynamically calculate the widths of the columns that the - fields in data should be placed into for output. - - :param data: a list of dictionaries that represent the data to - be output. Each dictionary in the list corresponds to annn - column of output. The keys of the dictionary are the - lengths of the items to be output, and the value associated - with a key is the number of items of that length. - :param total_width: the total width of the output. - :param columns: a list containing the minimum amount of space - that must be allocated for each row. This can be used to - ensure that there is space available in a column if, for - example, the actual lengths of the items being output - cannot be given in *data* - :param remainder_column: number of the column to receive a few - extra spaces that may remain after other allocation has - taken place - :param indent: string that will be prefixed to a line of - output to create e.g. an indent - :return: a list of the widths of the columns that the fields - in data should be placed into for output - """ - if total_width is None: - total_width = self.term.columns - - cols = len(data) - # Convert the data to ascending list of tuples, (field_length, pkgs) - pdata = data - data = [None] * cols # Don't modify the passed in data - for d in range(0, cols): - data[d] = sorted(pdata[d].items()) - - # We start allocating 1 char to everything but the last column, and a - # space between each (again, except for the last column). Because - # at worst we are better with: - # |one two three| - # | four | - # ...than: - # |one two three| - # | f| - # |our | - # ...the later being what we get if we pre-allocate the last column, and - # thus. the space, due to "three" overflowing it's column by 2 chars. - if columns is None: - columns = [1] * (cols - 1) - columns.append(0) - - total_width -= (sum(columns) + (cols - 1) + - utf8_width(indent)) - if not columns[-1]: - total_width += 1 - while total_width > 0: - # Find which field all the spaces left will help best - helps = 0 - val = 0 - for d in xrange(0, cols): - thelps = self._calc_columns_spaces_helps(columns[d], data[d], - total_width) - if not thelps: - continue - # We prefer to overflow: the last column, and then earlier - # columns. This is so that in the best case (just overflow the - # last) ... grep still "works", and then we make it prettier. - if helps and (d == (cols - 1)) and (thelps / 2) < helps: - continue - if thelps < helps: - continue - helps = thelps - val = d - - # If we found a column to expand, move up to the next level with - # that column and start again with any remaining space. - if helps: - diff = data[val].pop(0)[0] - columns[val] - if not columns[val] and (val == (cols - 1)): - # If we are going from 0 => N on the last column, take 1 - # for the space before the column. - total_width -= 1 - columns[val] += diff - total_width -= diff - continue - - overflowed_columns = 0 - for d in xrange(0, cols): - if not data[d]: - continue - overflowed_columns += 1 - if overflowed_columns: - # Split the remaining spaces among each overflowed column - # equally - norm = total_width / overflowed_columns - for d in xrange(0, cols): - if not data[d]: - continue - columns[d] += norm - total_width -= norm - - # Split the remaining spaces among each column equally, except the - # last one. And put the rest into the remainder column - cols -= 1 - norm = total_width / cols - for d in xrange(0, cols): - columns[d] += norm - columns[remainder_column] += total_width - (cols * norm) - total_width = 0 - - return columns - - @staticmethod - def _fmt_column_align_width(width): - if width < 0: - return (u"-", -width) - return (u"", width) - - @staticmethod - def _calc_columns_spaces_helps(current, data_tups, left): - """ Spaces left on the current field will help how many pkgs? """ - ret = 0 - for tup in data_tups: - if left < (tup[0] - current): - break - ret += tup[1] - return ret - def _formatTransaction(self, tsInfo): - """Return a string containing a human-readable formatted - summary of the transaction. - - :param tsInfo: :class:`yum.transactioninfo.TransactionData` - instance that contains information about the transaction - :return: a string that contains a formatted summary of the - transaction - """ - # Sort the packages in the transaction into different lists, - # e.g. installed, updated etc - tsInfo.makelists(True, True) - - # For each package list, pkglist_lines will contain a tuple - # that contains the name of the list, and a list of tuples - # with information about each package in the list - pkglist_lines = [] - data = {'n' : {}, 'v' : {}, 'r' : {}} - a_wid = 0 # Arch can't get "that big" ... so always use the max. - - - def _add_line(lines, data, a_wid, po, obsoletes=[]): - # Create a tuple of strings that contain the name, arch, - # version, repository, size, and obsoletes of the package - # given in po. Then, append this tuple to lines. The - # strings are formatted so that the tuple can be easily - # joined together for output. - - - (n,a,e,v,r) = po.pkgtup - - # Retrieve the version, repo id, and size of the package - # in human-readable form - evr = po.printVer() - repoid = po.ui_from_repo - size = self._format_number(float(po.size)) - - if a is None: # gpgkeys are weird - a = 'noarch' - - lines.append((n, a, evr, repoid, size, obsoletes)) - # Create a dict of field_length => number of packages, for - # each field. - for (d, v) in (("n",len(n)), ("v",len(evr)), ("r",len(repoid))): - data[d].setdefault(v, 0) - data[d][v] += 1 - a_wid = max(a_wid, len(a)) - - return a_wid - - ninstalled = self.tsInfo.installed - ginstalled = {} - if self.conf.group_command == 'objects' and ninstalled: - # Show new pkgs. that are installed via. a group. - ninstalled = [] - for txmbr in self.tsInfo.installed: - if not hasattr(txmbr, '_ugroup_member'): - ninstalled.append(txmbr) - continue - if txmbr._ugroup_member not in ginstalled: - ginstalled[txmbr._ugroup_member] = [] - ginstalled[txmbr._ugroup_member].append(txmbr) - - for grp in sorted(ginstalled, key=lambda x: x.ui_name): - action = _('Installing for group upgrade "%s"') % grp.ui_name - pkglist = ginstalled[grp] - - lines = [] - for txmbr in pkglist: - a_wid = _add_line(lines, data, a_wid, txmbr.po, txmbr.obsoletes) - - pkglist_lines.append((action, lines)) - - # Iterate through the different groups of packages - for (action, pkglist) in [(_('Installing'), ninstalled), - (_('Updating'), tsInfo.updated), - (_('Removing'), tsInfo.removed), - (_('Reinstalling'), tsInfo.reinstalled), - (_('Downgrading'), tsInfo.downgraded), - (_('Installing for dependencies'), tsInfo.depinstalled), - (_('Updating for dependencies'), tsInfo.depupdated), - (_('Removing for dependencies'), tsInfo.depremoved)]: - # Create a list to hold the tuples of strings for each package - lines = [] - - # Append the tuple for each package to lines, and update a_wid - for txmbr in pkglist: - a_wid = _add_line(lines, data, a_wid, txmbr.po, txmbr.obsoletes) - - # Append the lines instance for this package list to pkglist_lines - pkglist_lines.append((action, lines)) - - # # Iterate through other package lists - # for (action, pkglist) in [(_('Skipped (dependency problems)'), - # self.skipped_packages), - # (_('Not installed'), self._not_found_i.values()), - # (_('Not available'), self._not_found_a.values())]: - # lines = [] - # for po in pkglist: - # a_wid = _add_line(lines, data, a_wid, po) - - # pkglist_lines.append((action, lines)) - - if not data['n']: - return u'' - else: - # Change data to a list with the correct number of - # columns, in the correct order - data = [data['n'], {}, data['v'], data['r'], {}] - - - - # Calculate the space needed for each column - columns = [1, a_wid, 1, 1, 5] - - columns = self._calcColumns(data, self.opts.output_width, - columns, remainder_column = 2, indent=" ") - - (n_wid, a_wid, v_wid, r_wid, s_wid) = columns - assert s_wid == 5 - - # out will contain the output as a list of strings, that - # can be later joined together - out = [u""" -%s -%s -%s -""" % ('=' * self.opts.output_width, - self._fmtColumns(((_('Package'), -n_wid), (_('Arch'), -a_wid), - (_('Version'), -v_wid), (_('Repository'), -r_wid), - (_('Size'), s_wid)), u" "), - '=' * self.opts.output_width)] - - # Add output for each package list in pkglist_lines - for (action, lines) in pkglist_lines: - #If the package list is empty, skip it - if not lines: - continue - - # Add the name of the package list - totalmsg = u"%s:\n" % action - # Add a line of output about an individual package - for (n, a, evr, repoid, size, obsoletes) in lines: - columns = ((n, -n_wid), (a, -a_wid), - (evr, -v_wid), (repoid, -r_wid), (size, s_wid)) - msg = self._fmtColumns(columns, u" ", u"\n") - for obspo in sorted(obsoletes): - appended = _(' replacing %s.%s %s\n') - appended %= (obspo.name, - obspo.arch, obspo.printVer()) - msg = msg+appended - totalmsg = totalmsg + msg - - # Append the line about the individual package to out - out.append(totalmsg) - - # Add a summary of the transaction - out.append(_(""" -Transaction Summary -%s -""") % ('=' * self.opts.output_width)) - summary_data = ( - (_('Install'), len(tsInfo.installed), - len(tsInfo.depinstalled)), - (_('Upgrade'), len(tsInfo.updated), - len(tsInfo.depupdated)), - (_('Remove'), len(tsInfo.removed), - len(tsInfo.depremoved)), - (_('Reinstall'), len(tsInfo.reinstalled), 0), - (_('Downgrade'), len(tsInfo.downgraded), 0), - # (_('Skipped (dependency problems)'), len(self.skipped_packages), 0), - # (_('Not installed'), len(self._not_found_i.values()), 0), - # (_('Not available'), len(self._not_found_a.values()), 0), - ) - max_msg_action = 0 - max_msg_count = 0 - max_msg_pkgs = 0 - max_msg_depcount = 0 - for action, count, depcount in summary_data: - if not count and not depcount: - continue - - msg_pkgs = P_('Package', 'Packages', count) - len_msg_action = utf8_width(action) - len_msg_count = utf8_width(str(count)) - len_msg_pkgs = utf8_width(msg_pkgs) - - if depcount: - len_msg_depcount = utf8_width(str(depcount)) - else: - len_msg_depcount = 0 - - max_msg_action = max(len_msg_action, max_msg_action) - max_msg_count = max(len_msg_count, max_msg_count) - max_msg_pkgs = max(len_msg_pkgs, max_msg_pkgs) - max_msg_depcount = max(len_msg_depcount, max_msg_depcount) - - for action, count, depcount in summary_data: - msg_pkgs = P_('Package', 'Packages', count) - if depcount: - msg_deppkgs = P_('Dependent package', 'Dependent packages', - depcount) - if count: - msg = '%s %*d %s (+%*d %s)\n' - out.append(msg % (utf8_width_fill(action, max_msg_action), - max_msg_count, count, - utf8_width_fill(msg_pkgs, max_msg_pkgs), - max_msg_depcount, depcount, msg_deppkgs)) - else: - msg = '%s %*s %s ( %*d %s)\n' - out.append(msg % (utf8_width_fill(action, max_msg_action), - max_msg_count, '', - utf8_width_fill('', max_msg_pkgs), - max_msg_depcount, depcount, msg_deppkgs)) - elif count: - msg = '%s %*d %s\n' - out.append(msg % (utf8_width_fill(action, max_msg_action), - max_msg_count, count, msg_pkgs)) - - return ''.join(out) + assert self.opts._base.tsInfo == tsInfo + return self.opts._base.listTransaction() class EmailEmitter(UpdateEmitter): @@ -738,7 +301,7 @@ class YumCronConfig(BaseConfig): group_package_types = ListOption(['mandatory', 'default']) -class YumCronBase(yum.YumBase): +class YumCronBase(yum.YumBase, YumOutput): """Main class to check for and apply the updates.""" def __init__(self, config_file_name = None): @@ -748,9 +311,13 @@ class YumCronBase(yum.YumBase): config file to use. """ yum.YumBase.__init__(self) + YumOutput.__init__(self) # Read the config file self.readConfigFile(config_file_name) + self.term.reinit(color='never') + self.term.columns = self.opts.output_width + self.opts._base = self # Create the emitters, and add them to the list -- 1.7.11.7 _______________________________________________ Yum-devel mailing list Yum-devel@lists.baseurl.org http://lists.baseurl.org/mailman/listinfo/yum-devel