#! /usr/bin/python -tt # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # # Copyright Red Hat Inc. 2007, 2008 # # Author: James Antill # # Examples: # # yum list-vendors # yum list-packagers yum* # yum list-groups updates import types from yum.plugins import TYPE_INTERACTIVE import logging # for commands from yum import logginglevels # For baseurl import urlparse # Decent (UK/US English only) number formatting. import locale locale.setlocale(locale.LC_ALL, '') def loc_num(x): """ Return a string of a number in the readable "locale" format. """ return locale.format("%d", int(x), True) def to_str(obj): """ Convert something to a string, if it isn't one. """ # NOTE: unicode counts as a string just fine. We just want objects to call # their __str__ methods. if not isinstance(obj, basestring): obj = str(obj) return obj requires_api_version = '2.5' plugin_type = (TYPE_INTERACTIVE,) class ListDataCommands: unknown = "-- Unknown --" def __init__(self, name, attr, help=None): self.name = name self.attr = attr self.__help = help def cmd_beg(self): pass def cmd_end(self): pass def getNames(self): return ['list-' + self.name] def getUsage(self): return "[PACKAGE|all|installed|updates|extras|obsoletes|recent]" def _getSummary(self): if self.__help is not None: return self.__help return """\ Display aggregate data on the %s attribute of a group of packages""" % self.attr def getSummary(self): return self._getSummary() def doCheck(self, base, basecmd, extcmds): pass def show_pkgs(self, msg, gval, pkgs): pass def show_data(self, msg, pkgs, name): if not pkgs: return msg("%s %s %s" % ('=' * 20, name, '=' * 20)) pkgs.sort(key=lambda x: x.name) calc = {} for pkg in pkgs: (data, rdata) = self.get_data(pkg) if type(data) != type([]): calc.setdefault(data, []).append((pkg, rdata)) else: for sdata in data: calc.setdefault(sdata, []).append((pkg, rdata)) maxlen = 0 totlen = 0 for data in calc: val = len(data) totlen += len(calc[data]) if val > maxlen: maxlen = val fmt = "%%-%ds %%6s (%%3d%%%%)" % maxlen for data in sorted(calc): val = len(calc[data]) msg(fmt % (to_str(data), loc_num(val), (100 * val) / totlen)) self.show_pkgs(msg, data, calc[data]) # pkg.vendor has weird values, for instance def get_data(self, data, strip=True): if not hasattr(data, self.attr): return (self.unknown, self.unknown) val = getattr(data, self.attr) if val is None: return (self.unknown, self.unknown) if type(val) == type([]): return (self.unknown, self.unknown) tval = to_str(val).strip() if tval == "": return (self.unknown, self.unknown) if strip: return (tval, tval) return (val, val) def doCommand(self, base, basecmd, extcmds): self.base = base logger = logging.getLogger("yum.verbose.main") def msg(x): logger.log(logginglevels.INFO_2, x) def msg_warn(x): logger.warn(x) self.cmd_beg() ypl = base.returnPkgLists(extcmds) self.show_data(msg, ypl.installed, 'Installed Packages') self.show_data(msg, ypl.available, 'Available Packages') self.show_data(msg, ypl.extras, 'Extra Packages') self.show_data(msg, ypl.updates, 'Updated Packages') self.show_data(msg, ypl.obsoletes, 'Obsoleting Packages') self.show_data(msg, ypl.recent, 'Recent Packages') self.cmd_end() return 0, [basecmd + ' done'] def needTs(self, base, basecmd, extcmds): if len(extcmds) and extcmds[0] == 'installed': return False return True class InfoDataCommands(ListDataCommands): def getNames(self): return ['info-' + self.name] def getSummary(self): return self._getSummary() + "\nAnd list all the packages under each" def show_pkgs(self, msg, gval, pkgs): for (pkg, val) in pkgs: if gval == val: msg(" %s" % pkg) continue if type(val) == type([]): val = ", ".join(sorted(val)) linelen = len(val) + len(to_str(pkg)) if linelen < 77: msg(" %s %*s%s" % (pkg, 77 - linelen, '', val)) else: msg(" %s" % pkg) msg(" => %s" % val) msg('') if pkgs: msg('') def buildhost_get_data(self, data): # Just show the dnsname val = self.oget_data(data)[0] if val == self.unknown: return (val, val) dns_names = val.split('.') if len(dns_names) > 2: dns_names[0] = '*' return (".".join(dns_names), val) def url_get_data(self, data): # Just show the hostname val = self.oget_data(data)[0] if val == self.unknown: return (val, val) (scheme, netloc, path, query, fragid) = urlparse.urlsplit(val) return ("%s://%s/" % (scheme, netloc), val) class SizeRangeData: def __init__(self, beg, msg): self._beg = beg self._msg = msg def __cmp__(self, o): if not hasattr(o, '_beg'): return 1 return cmp(self._beg, o._beg) def __str__(self): return self._msg def __len__(self): return len(self._msg) def __hash__(self): return hash(self._msg) def _format_size(base, num, max): return "%s (%*s)" %(base.format_number(num),len(loc_num(max)),loc_num(num)) def size_get_data(self, data): val = self.oget_data(data, strip=False)[0] if val == self.unknown: return (val, val) # Even if it was sane, don't put 1GB and up here, or the alpha sorting # won't be correct. nums = (( 10 * 1024, " 10KB"), ( 25 * 1024, " 25KB"), ( 50 * 1024, " 50KB"), ( 75 * 1024, " 75KB"), (100 * 1024, "100KB"), (250 * 1024, "250KB"), (500 * 1024, "500KB"), (750 * 1024, "750KB"), ( 1 * 1024 * 1024, " 1MB"), ( 5 * 1024 * 1024, " 5MB"), ( 10 * 1024 * 1024, " 10MB"), ( 50 * 1024 * 1024, " 50MB"), (100 * 1024 * 1024, "100MB"), (500 * 1024 * 1024, "500MB"), ) pnum = (0, " 0B") if val <= pnum[0]: msg = "[ %s - %s ] " % (" " * len(pnum[1]), pnum[1]) return (SizeRangeData(pnum[0], msg), _format_size(self.base, val, pnum[0])) pnum = (1, " 1B") for num in nums: if val >= pnum[0] and val <= num[0]: msg = "[ %s - %s ] " % (pnum[1], num[1]) return (SizeRangeData(pnum[0], msg), _format_size(self.base, val, num[0])) pnum = num msg = "[ %s - %s ] " % (pnum[1], " " * len(pnum[1])) return (SizeRangeData(pnum[0], msg), _format_size(self.base, val, pnum[0] * 20)) all_yum_grp_mbrs = {} def yum_group_make_data(self): global all_yum_grp_mbrs base = self.attr installed, available = base.doGroupLists(uservisible=0) for group in installed + available: # Should use translated_name/nameByLang() for pkgname in group.mandatory_packages: all_yum_grp_mbrs.setdefault(pkgname, []).append(group.name) for pkgname in group.default_packages: all_yum_grp_mbrs.setdefault(pkgname, []).append(group.name) for pkgname in group.optional_packages: all_yum_grp_mbrs.setdefault(pkgname, []).append(group.name) for pkgname, cond in group.conditional_packages.iteritems(): all_yum_grp_mbrs.setdefault(pkgname, []).append(group.name) def yum_group_free_data(self): global all_yum_grp_mbrs all_yum_grp_mbrs = {} def yum_group_get_data(self, pkg): if pkg.name not in all_yum_grp_mbrs: return (self.unknown, self.unknown) return (all_yum_grp_mbrs[pkg.name], all_yum_grp_mbrs[pkg.name]) def _list_data_custom(conduit, data, func, beg=None, end=None, help=None): if help is not None: cmd = ListDataCommands(data[0], data[1], help=help) else: cmd = ListDataCommands(*data) cmd.oget_data = cmd.get_data cmd.get_data = types.MethodType(func, cmd) if beg: cmd.cmd_beg = types.MethodType(beg, cmd) if end: cmd.cmd_end = types.MethodType(end, cmd) conduit.registerCommand(cmd) if help is not None: cmd = InfoDataCommands(data[0], data[1], help=help) else: cmd = InfoDataCommands(*data) cmd.oget_data = cmd.get_data cmd.get_data = types.MethodType(func, cmd) if beg: cmd.cmd_beg = types.MethodType(beg, cmd) if end: cmd.cmd_end = types.MethodType(end, cmd) conduit.registerCommand(cmd) def config_hook(conduit): ''' Yum Plugin Config Hook: Add the 'list-vendors', 'list-baseurls', 'list-packagers', 'list-buildhosts' commands and the info varients. ''' for data in [('vendors', 'vendor'), ('rpm-groups', 'group'), ('packagers', 'packager'), ('licenses', 'license'), ('arches', 'arch'), ('committers', 'committer')]: conduit.registerCommand(ListDataCommands(*data)) conduit.registerCommand(InfoDataCommands(*data)) _list_data_custom(conduit, ('buildhosts', 'buildhost'), buildhost_get_data) _list_data_custom(conduit, ('baseurls', 'url'), url_get_data) _list_data_custom(conduit, ('package-sizes', 'packagesize'), size_get_data) _list_data_custom(conduit, ('archive-sizes', 'archivesize'), size_get_data) _list_data_custom(conduit, ('installed-sizes', 'installedsize'), size_get_data) _list_data_custom(conduit, ('groups', conduit._base), yum_group_get_data, beg=yum_group_make_data, end=yum_group_free_data, help='Display aggregate group data, for matching members') # Buildtime/installtime/committime?