1 # vim:fileencoding=utf-8
2 # Copyright 2010 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
7 from portage.output import colorize
9 __all__ = ['keywords_content']
11 from display_pretty import colorize_string
12 from display_pretty import align_string
14 class keywords_content:
15 class RedundancyChecker:
16 def __listRedundant(self, masks, keywords, ignoreslots, slots):
17 """List all redundant packages."""
19 return self.__listRedundantAll(masks, keywords)
21 return self.__listRedundantSlots(masks, keywords, slots)
23 def __listRedundantSlots(self, masks, keywords, slots):
24 """Search for redundant packages walking per keywords for specified slot."""
26 for slot in self.__uniq(slots):
29 for m, k, s in zip(masks, keywords, slots):
33 output.append(self.__compareSelected(ms, ks))
34 # this is required because the list itself is not just one level depth
35 return list(''.join(output))
37 def __uniq(self, seq):
38 """Remove all duplicate elements from list."""
48 def __cleanKeyword(self, keyword):
49 """Remove masked arches and hardmasks from keywords since we don't care about that."""
50 return ["%s" % x for x in keyword.split()
51 if x != '-*' and not x.startswith('-')]
53 def __listRedundantAll(self, masks, keywords):
54 """Search for redundant packages using all versions ignoring its slotting."""
55 return list(self.__compareSelected(list(masks), list(keywords)))
57 def __compareSelected(self, masks, kws):
59 Rotate over list of keywords and compare each element with others.
60 Selectively remove each already compared list from the remaining keywords.
65 for i in range(len(kws)):
68 if self.__compareKeywordWithRest(kw, kws, masks):
74 return ''.join(result)
76 def __compareKeywordWithRest(self, keyword, keywords, masks):
77 """Compare keywords with list of keywords."""
78 kw = self.__cleanKeyword(keyword)
79 for kwi, mask in zip(keywords, masks):
80 kwi = self.__cleanKeyword(kwi)
82 kw = self.__checkShadow(kw, kwi)
87 def __checkShadow(self, old, new):
88 """Check if package version is overshadowed by other package version."""
90 tmp.update("~%s" % x for x in new
91 if not x.startswith("~"))
92 return list(set(old).difference(tmp))
94 def __init__(self, masks, keywords, slots, ignore_slots = False):
95 """Query all relevant data for redundancy package checking"""
96 self.redundant = self.__listRedundant(masks, keywords, ignore_slots, slots)
99 def __getVersions(self, packages):
100 """Obtain properly aligned version strings without colors."""
101 return map(lambda x: self.__separateVersion(x), packages)
103 def __separateVersion(self, cpv):
104 """Get version string for specfied cpv"""
105 #pv = port.versions.cpv_getversion(cpv)
106 return self.__prependVersionInfo(cpv, self.cpv_getversion(cpv))
108 # remove me when portage 2.1.9 is stable
109 def cpv_getversion(self, mycpv):
110 """Returns the v (including revision) from an cpv."""
111 cp = port.versions.cpv_getkey(mycpv)
114 return mycpv[len(cp+"-"):]
116 def __prependVersionInfo(self, cpv, pv):
117 """Prefix version with string based on whether version is installed or masked."""
118 mask = self.__getMaskStatus(cpv)
119 install = self.__getInstallStatus(cpv)
129 def __getMaskStatus(self, cpv):
130 """Figure out if package is pmasked."""
132 mysettings = port.config(local_config=False)
133 if port.getmaskingstatus(cpv, settings=mysettings) == ['package.mask']:
136 # occurs when package is not known by portdb
137 # so we consider it unmasked
141 def __getInstallStatus(self, cpv):
142 """Check if package version we test is installed."""
143 vartree = port.db[port.settings['ROOT']]['vartree'].dbapi
144 return vartree.cpv_exists(cpv)
146 def __init__(self, packages):
147 """Query all relevant data for version data formatting"""
148 self.versions = self.__getVersions(packages)
149 self.masks = map(lambda x: self.__getMaskStatus(x), packages)
151 def __packages_sort(self, package_content):
153 Sort packages queried based on version and slot
154 %% pn , repo, slot, keywords
156 from operator import itemgetter
158 if len(package_content) > 1:
160 for cpv in package_content:
161 ver_map[cpv[0]] = '-'.join(port.versions.catpkgsplit(cpv[0])[2:])
162 def cmp_cpv(cpv1, cpv2):
163 return port.versions.vercmp(ver_map[cpv1[0]], ver_map[cpv2[0]])
165 package_content.sort(key=port.util.cmp_sort_key(cmp_cpv))
166 package_content.sort(key=itemgetter(2))
168 def __xmatch(self, pdb, package):
169 """xmatch function that searches for all packages over all repos"""
171 mycp = port.dep_expand(package, mydb=pdb, settings=pdb.settings).cp
172 except port.exception.AmbiguousPackageName as Arg:
173 msg_err = 'Ambiguous package name "%s".\n' % package
174 found = 'Possibilities: %s' % Arg
175 raise SystemExit('%s%s' % (msg_err, found))
176 except port.exception.InvalidAtom:
177 msg_err = 'No such package "%s"' % package
178 raise SystemExit(msg_err)
180 mysplit = mycp.split('/')
182 for oroot in pdb.porttrees:
184 file_list = os.listdir(os.path.join(oroot, mycp))
188 pf = x[:-7] if x[-7:] == '.ebuild' else []
190 ps = port.pkgsplit(pf)
191 if not ps or ps[0] != mysplit[1]:
192 # we got garbage or ebuild with wrong name in the dir
194 ver_match = port.versions.ver_regexp.match("-".join(ps[1:]))
195 if ver_match is None or not ver_match.groups():
196 # version is not allowed by portage or unset
198 # obtain related data from metadata and append to the pkg list
199 keywords, slot = self.__getMetadata(pdb, mysplit[0]+'/'+pf, oroot)
200 mypkgs.append([mysplit[0]+'/'+pf, oroot, slot, keywords])
202 self.__packages_sort(mypkgs)
205 def __checkExist(self, pdb, package):
206 """Check if specified package even exists."""
207 matches = self.__xmatch(pdb, package)
208 if len(matches) <= 0:
209 msg_err = 'No such package "%s"' % package
210 raise SystemExit(msg_err)
211 return list(zip(*matches))
213 def __getMetadata(self, pdb, package, repo):
214 """Obtain all required metadata from portage auxdb"""
216 metadata = pdb.aux_get(package, ['KEYWORDS', 'SLOT'], repo)
218 # portage prints out more verbose error for us if we were lucky
219 raise SystemExit('Failed to obtain metadata')
222 def __formatKeywords(self, keywords, keywords_list, usebold = False, toplist = 'archlist'):
223 """Loop over all keywords and replace them with nice visual identifier"""
224 # the % is fancy separator, we use it to split keywords for rotation
225 # so we wont loose the empty spaces
226 return ['% %'.join([self.__prepareKeywordChar(arch, i, version.split(), usebold, toplist)
227 for i, arch in enumerate(keywords_list)])
228 for version in keywords]
230 def __prepareKeywordChar(self, arch, field, keywords, usebold = False, toplist = 'archlist'):
232 Convert specified keywords for package into their visual replacements.
239 keys = [ '~%s' % arch, '-%s' % arch, '%s' % arch, '-*' ]
241 colorize('darkyellow', '~'),
242 colorize('darkred', '-'),
243 colorize('darkgreen', '+'),
244 colorize('darkred', '*')
246 # check what keyword we have
247 # here we cant just append space because it would get stripped later
248 char = colorize('darkgray','o')
249 for k, v in zip(keys, values):
253 if toplist == 'archlist' and usebold and (field)%2 == 0 and char != ' ':
254 char = colorize('bold', char)
257 def __formatVersions(self, versions, align, length):
258 """Append colors and align keywords properly"""
259 # % are used as separators for further split so we wont loose spaces and coloring
262 pv = align_string(pv, align, length)
263 pv = '%'.join(list(pv))
264 if pv.find('[%M%][%I%]') != -1:
265 tmp.append(colorize_string('darkyellow', pv))
266 elif pv.find('[%M%]') != -1:
267 tmp.append(colorize_string('darkred', pv))
268 elif pv.find('[%I%]') != -1:
269 tmp.append(colorize_string('bold', pv))
274 def __formatAdditional(self, additional, color, length):
275 """Align additional items properly"""
276 # % are used as separators for further split so we wont loose spaces and coloring
280 x = align_string(x, 'left', length)
281 x = '%'.join(list(x))
283 # the value is unset so the color is gray
285 x = colorize_string(tmpc, x)
289 def __prepareContentResult(self, versions, keywords, redundant, slots, slot_length, repos, linesep):
290 """Parse version fields into one list with proper separators"""
295 for v, k, r, s, t in zip(versions, keywords, redundant, slots, repos):
298 content.append(linesep)
300 s = '%'.join(list(''.rjust(slot_length)))
301 content.append('%s%s%s%s%s%s%s%s%s' % (v, fieldsep, k, fieldsep, r, normsep, s, fieldsep, t))
304 def __init__(self, package, keywords_list, porttree, ignoreslots = False, content_align = 'bottom', usebold = False, toplist = 'archlist'):
305 """Query all relevant data from portage databases."""
306 packages, self.repositories, self.slots, self.keywords = self.__checkExist(porttree, package)
307 # convert repositories from path to name
308 self.repositories = [porttree.getRepositoryName(x) for x in self.repositories]
309 self.slot_length = max([len(x) for x in self.slots])
310 repositories_length = max([len(x) for x in self.repositories])
311 self.keyword_length = len(keywords_list)
312 self.versions = self.VersionChecker(packages).versions
313 masks = self.VersionChecker(packages).masks
314 self.version_length = max([len(x) for x in self.versions])
315 self.version_count = len(self.versions)
316 self.redundant = self.RedundancyChecker(masks, self.keywords, self.slots, ignoreslots).redundant
317 redundant_length = max([len(x) for x in self.redundant])
319 ver = self.__formatVersions(self.versions, content_align, self.version_length)
320 kws = self.__formatKeywords(self.keywords, keywords_list, usebold, toplist)
321 red = self.__formatAdditional(self.redundant, 'purple', redundant_length)
322 slt = self.__formatAdditional(self.slots, 'bold', self.slot_length)
323 rep = self.__formatAdditional(self.repositories, 'yellow', repositories_length)
324 # those + nubers are spaces in printout. keywords are multiplied also because of that
325 linesep = '%s+%s+%s+%s' % (''.ljust(self.version_length+1, '-'),
326 ''.ljust(self.keyword_length*2+1, '-'),
327 ''.ljust(redundant_length+self.slot_length+3, '-'),
328 ''.ljust(repositories_length+1, '-')
331 self.content = self.__prepareContentResult(ver, kws, red, slt, self.slot_length, rep, linesep)
332 self.content_length = len(linesep)
333 self.cp = port.cpv_getkey(packages[0])