1 # vim:fileencoding=utf-8
2 # Copyright 2010 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
6 from portage.output import colorize
8 __all__ = ['keywords_content']
10 from display_pretty import colorize_string
11 from display_pretty import align_string
13 class keywords_content:
14 class RedundancyChecker:
15 def __listRedundant(self, keywords, ignoreslots, slots):
16 """List all redundant packages."""
18 return self.__listRedundantAll(keywords)
20 return self.__listRedundantSlots(keywords, slots)
22 def __listRedundantSlots(self, keywords, slots):
23 """Search for redundant packages walking per keywords for specified slot."""
24 result = [self.__compareSelected([k for k, s in zip(keywords, slots)
26 for slot in self.__uniq(slots)]
27 # this is required because the list itself is not just one level depth
28 return list(''.join(result))
30 def __uniq(self, seq):
31 """Remove all duplicate elements from list."""
41 def __listRedundantAll(self, keywords):
42 """Search for redundant packages using all versions ignoring its slotting."""
43 return list(self.__compareSelected(list(keywords)))
45 def __compareSelected(self, kws):
47 Rotate over list of keywords and compare each element with others.
48 Selectively remove each already compared list from the remaining keywords.
52 for i in range(len(kws)):
54 if self.__compareKeywordWithRest(kw, kws):
60 return ''.join(result)
62 def __compareKeywordWithRest(self, keyword, keywords):
63 """Compare keywords with list of keywords."""
65 if self.__checkShadow(keyword, key):
69 def __checkShadow(self, old, new):
70 """Check if package version is overshadowed by other package version."""
71 # remove -* and -arch since they are useless for us
72 newclean = ["%s" % x for x in new.split()
73 if x != '-*' and not x.startswith('-')]
74 oldclean = ["%s" % x for x in old.split()
75 if x != '-*' and not x.startswith('-')]
78 tmp.update("~%s" % x for x in newclean
79 if not x.startswith("~"))
80 if not set(oldclean).difference(tmp):
85 def __init__(self, keywords, slots, ignore_slots = False):
86 """Query all relevant data for redundancy package checking"""
87 self.redundant = self.__listRedundant(keywords, ignore_slots, slots)
90 def __getVersions(self, packages, vartree):
91 """Obtain properly aligned version strings without colors."""
92 return self.__stripStartingSpaces(map(lambda x: self.__separateVersion(x, vartree), packages))
94 def __stripStartingSpaces(self, pvs):
95 """Strip starting whitespace if there is no real reason for it."""
96 if not self.__require_prepend:
97 return map(lambda x: x.lstrip(), pvs)
101 def __separateVersion(self, cpv, vartree):
102 """Get version string for specfied cpv"""
103 #pv = port.versions.cpv_getversion(cpv)
104 return self.__prependVersionInfo(cpv, self.cpv_getversion(cpv), vartree)
106 # remove me when portage 2.1.9 is stable
107 def cpv_getversion(self, mycpv):
108 """Returns the v (including revision) from an cpv."""
109 cp = port.versions.cpv_getkey(mycpv)
112 return mycpv[len(cp+"-"):]
114 def __prependVersionInfo(self, cpv, pv, vartree):
115 """Prefix version with string based on whether version is installed or masked."""
116 mask = self.__getMaskStatus(cpv)
117 install = self.__getInstallStatus(cpv, vartree)
121 self.__require_longprepend = True
124 self.__require_prepend = True
127 self.__require_prepend = True
130 def __getMaskStatus(self, cpv):
132 Figure out if package is pmasked.
133 This also uses user settings in /etc/ so local changes are important.
137 if port.getmaskingstatus(cpv) == ['package.mask']:
140 # occurs when package is not known by portdb
141 # so we consider it unmasked
145 def __getInstallStatus(self, cpv, vartree):
146 """Check if package version we test is installed."""
147 return vartree.cpv_exists(cpv)
149 def __init__(self, packages, vartree):
150 """Query all relevant data for version data formatting"""
151 self.__require_longprepend = False
152 self.__require_prepend = False
153 self.versions = self.__getVersions(packages, vartree)
155 def __checkExist(self, pdb, package):
156 """Check if specified package even exists."""
158 matches = pdb.xmatch('match-all', package)
159 except port.exception.AmbiguousPackageName as Arg:
160 msg_err = 'Ambiguous package name "%s".\n' % package
161 found = 'Possibilities: %s' % Arg
162 raise SystemExit('%s%s' % (msg_err, found))
163 except port.exception.InvalidAtom:
164 msg_err = 'No such package "%s"' % package
165 raise SystemExit(msg_err)
166 if len(matches) <= 0:
167 msg_err = 'No such package "%s"' % package
168 raise SystemExit(msg_err)
171 def __getMetadata(self, pdb, packages):
172 """Obtain all KEYWORDS and SLOT from metadata"""
174 metadata = map(lambda x: pdb.aux_get(x, ['KEYWORDS', 'SLOT', 'repository']), packages)
176 # portage prints out more verbose error for us if we were lucky
177 raise SystemExit('Failed to obtain metadata')
178 return list(zip(*metadata))
180 def __formatKeywords(self, keywords, keywords_list, usebold = False, toplist = 'archlist'):
181 """Loop over all keywords and replace them with nice visual identifier"""
182 # the % is fancy separator, we use it to split keywords for rotation
183 # so we wont loose the empty spaces
184 return ['% %'.join([self.__prepareKeywordChar(arch, i, version.split(), usebold, toplist)
185 for i, arch in enumerate(keywords_list)])
186 for version in keywords]
188 def __prepareKeywordChar(self, arch, field, keywords, usebold = False, toplist = 'archlist'):
190 Convert specified keywords for package into their visual replacements.
197 keys = [ '~%s' % arch, '-%s' % arch, '%s' % arch, '-*' ]
198 nocolor_values = [ '~', '-', '+', '*' ]
200 colorize('darkyellow', '~'),
201 colorize('darkred', '-'),
202 colorize('darkgreen', '+'),
203 colorize('darkred', '*')
205 # check what keyword we have
206 # here we cant just append space because it would get stripped later
207 char = colorize('darkgray','o')
208 for k, v, n in zip(keys, values, nocolor_values):
212 if toplist == 'archlist' and usebold and (field)%2 == 0 and char != ' ':
213 char = colorize('bold', char)
216 def __formatVersions(self, versions, align, length):
217 """Append colors and align keywords properly"""
218 # % are used as separators for further split so we wont loose spaces and coloring
221 pv = align_string(pv, align, length)
222 pv = '%'.join(list(pv))
223 if pv.find('[%M%][%I%]') != -1:
224 tmp.append(colorize_string('darkyellow', pv))
225 elif pv.find('[%M%]') != -1:
226 tmp.append(colorize_string('darkred', pv))
227 elif pv.find('[%I%]') != -1:
228 tmp.append(colorize_string('bold', pv))
233 def __formatAdditional(self, additional, color, length):
234 """Align additional items properly"""
235 # % are used as separators for further split so we wont loose spaces and coloring
239 x = align_string(x, 'left', length)
240 x = '%'.join(list(x))
242 # the value is unset so the color is gray
244 x = colorize_string(tmpc, x)
248 def __prepareContentResult(self, versions, keywords, redundant, slots, slot_length, repos, linesep):
249 """Parse version fields into one list with proper separators"""
254 for v, k, r, s, t in zip(versions, keywords, redundant, slots, repos):
257 content.append(linesep)
259 s = '%'.join(list(''.rjust(slot_length)))
260 content.append('%s%s%s%s%s%s%s%s%s' % (v, fieldsep, k, fieldsep, r, normsep, s, fieldsep, t))
263 def __init__(self, package, keywords_list, porttree, ignoreslots = False, content_align = 'bottom', usebold = False, toplist = 'archlist'):
264 """Query all relevant data from portage databases."""
265 vartree = port.db[port.settings['ROOT']]['vartree'].dbapi
266 packages = self.__checkExist(porttree, package)
267 self.keywords, self.slots, self.repositories = self.__getMetadata(porttree, packages)
268 self.slot_length = max([len(x) for x in self.slots])
269 repositories_length = max([len(x) for x in self.repositories])
270 self.keyword_length = len(keywords_list)
271 self.versions = self.VersionChecker(packages, vartree).versions
272 self.version_length = max([len(x) for x in self.versions])
273 self.version_count = len(self.versions)
274 self.redundant = self.RedundancyChecker(self.keywords, self.slots, ignoreslots).redundant
275 redundant_length = max([len(x) for x in self.redundant])
277 ver = self.__formatVersions(self.versions, content_align, self.version_length)
278 kws = self.__formatKeywords(self.keywords, keywords_list, usebold, toplist)
279 red = self.__formatAdditional(self.redundant, 'purple', redundant_length)
280 slt = self.__formatAdditional(self.slots, 'bold', self.slot_length)
281 rep = self.__formatAdditional(self.repositories, 'yellow', repositories_length)
282 # those + nubers are spaces in printout. keywords are multiplied also because of that
283 linesep = '%s+%s+%s+%s' % (''.ljust(self.version_length+1, '-'),
284 ''.ljust(self.keyword_length*2+1, '-'),
285 ''.ljust(redundant_length+self.slot_length+3, '-'),
286 ''.ljust(repositories_length+1, '-')
289 self.content = self.__prepareContentResult(ver, kws, red, slt, self.slot_length, rep, linesep)
290 self.content_length = len(linesep)
291 self.cp = port.cpv_getkey(packages[0])