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 gentoolkit.eshowkw.display_pretty import colorize_string
12 from gentoolkit.eshowkw.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 zipped = list(zip(masks, keywords, slots))
27 for slot in self.__uniq(slots):
30 for m, k, s in zipped:
34 output.append(self.__compareSelected(ms, ks))
35 # this is required because the list itself is not just one level depth
36 return list(''.join(output))
40 """Remove all duplicate elements from list."""
51 def __cleanKeyword(keyword):
52 """Remove masked arches and hardmasks from keywords since we don't care about that."""
53 return ["%s" % x for x in keyword.split()
54 if x != '-*' and not x.startswith('-')]
56 def __listRedundantAll(self, masks, keywords):
57 """Search for redundant packages using all versions ignoring its slotting."""
58 return list(self.__compareSelected(list(masks), list(keywords)))
60 def __compareSelected(self, masks, kws):
62 Rotate over list of keywords and compare each element with others.
63 Selectively remove each already compared list from the remaining keywords.
68 for i in range(len(kws)):
71 if self.__compareKeywordWithRest(kw, kws, masks):
77 return ''.join(result)
79 def __compareKeywordWithRest(self, keyword, keywords, masks):
80 """Compare keywords with list of keywords."""
81 kw = self.__cleanKeyword(keyword)
82 for kwi, mask in zip(keywords, masks):
83 kwi = self.__cleanKeyword(kwi)
85 kw = self.__checkShadow(kw, kwi)
90 def __checkShadow(self, old, new):
91 """Check if package version is overshadowed by other package version."""
93 tmp.update("~%s" % x for x in new
94 if not x.startswith("~"))
95 return list(set(old).difference(tmp))
97 def __init__(self, masks, keywords, slots, ignore_slots = False):
98 """Query all relevant data for redundancy package checking"""
99 self.redundant = self.__listRedundant(masks, keywords, ignore_slots, slots)
101 class VersionChecker:
102 def __getVersions(self, packages):
103 """Obtain properly aligned version strings without colors."""
104 revlength = max([len(self.__getRevision(x)) for x in packages])
105 return [self.__separateVersion(x, revlength) for x in packages]
107 def __getRevision(self, cpv):
108 """Get revision informations for each package for nice further alignment"""
109 rev = port.catpkgsplit(cpv)[3]
110 return rev if rev != 'r0' else ''
112 def __separateVersion(self, cpv, revlength):
113 return self.__modifyVersionInfo(cpv, port.versions.cpv_getversion(cpv), revlength)
115 def __modifyVersionInfo(self, cpv, pv, revlength):
116 """Prefix and suffix version with string based on whether version is installed or masked and its revision."""
117 mask = self.__getMaskStatus(cpv)
118 install = self.__getInstallStatus(cpv)
120 # calculate suffix length
121 currevlen = len(self.__getRevision(cpv))
122 suffixlen = revlength - currevlen
123 # +1 required for the dash in revision
124 if suffixlen != 0 and currevlen == 0:
125 suffixlen = suffixlen + 1
127 for x in range(suffixlen):
128 suffix = '%s ' % suffix
131 pv = '[M][I]%s%s' % (pv, suffix)
133 pv = '[M]%s%s' % (pv, suffix)
135 pv = '[I]%s%s' % (pv, suffix)
137 pv = '%s%s' % (pv, suffix)
140 def __getMaskStatus(self, cpv):
141 """Figure out if package is pmasked."""
143 if "package.mask" in port.getmaskingstatus(cpv, settings=self.mysettings):
146 # occurs when package is not known by portdb
147 # so we consider it unmasked
152 def __getInstallStatus(self, cpv):
153 """Check if package version we test is installed."""
154 return self.vartree.cpv_exists(cpv)
156 def __init__(self, packages):
157 """Query all relevant data for version data formatting"""
158 self.vartree = port.db[port.root]['vartree'].dbapi
159 self.mysettings = port.config(local_config=False)
160 self.versions = self.__getVersions(packages)
161 self.masks = list(map(lambda x: self.__getMaskStatus(x), packages))
164 def __packages_sort(package_content):
166 Sort packages queried based on version and slot
167 %% pn , repo, slot, keywords
169 from operator import itemgetter
171 if len(package_content) > 1:
173 for cpv in package_content:
174 ver_map[cpv[0]] = '-'.join(port.versions.catpkgsplit(cpv[0])[2:])
175 def cmp_cpv(cpv1, cpv2):
176 return port.versions.vercmp(ver_map[cpv1[0]], ver_map[cpv2[0]])
178 package_content.sort(key=port.util.cmp_sort_key(cmp_cpv))
179 package_content.sort(key=itemgetter(2))
181 def __xmatch(self, pdb, package):
182 """xmatch function that searches for all packages over all repos"""
184 mycp = port.dep_expand(package, mydb=pdb, settings=pdb.settings).cp
185 except port.exception.AmbiguousPackageName as Arg:
186 msg_err = 'Ambiguous package name "%s".\n' % package
187 found = 'Possibilities: %s' % Arg
188 raise SystemExit('%s%s' % (msg_err, found))
189 except port.exception.InvalidAtom:
190 msg_err = 'No such package "%s"' % package
191 raise SystemExit(msg_err)
193 mysplit = mycp.split('/')
195 for oroot in pdb.porttrees:
197 file_list = os.listdir(os.path.join(oroot, mycp))
201 pf = x[:-7] if x[-7:] == '.ebuild' else []
203 ps = port.pkgsplit(pf)
204 if not ps or ps[0] != mysplit[1]:
205 # we got garbage or ebuild with wrong name in the dir
207 ver_match = port.versions.ver_regexp.match("-".join(ps[1:]))
208 if ver_match is None or not ver_match.groups():
209 # version is not allowed by portage or unset
211 # obtain related data from metadata and append to the pkg list
212 keywords, slot = self.__getMetadata(pdb, mysplit[0]+'/'+pf, oroot)
213 mypkgs.append([mysplit[0]+'/'+pf, oroot, slot, keywords])
215 self.__packages_sort(mypkgs)
218 def __checkExist(self, pdb, package):
219 """Check if specified package even exists."""
220 matches = self.__xmatch(pdb, package)
221 if len(matches) <= 0:
222 msg_err = 'No such package "%s"' % package
223 raise SystemExit(msg_err)
224 return list(zip(*matches))
227 def __getMetadata(pdb, package, repo):
228 """Obtain all required metadata from portage auxdb"""
230 metadata = pdb.aux_get(package, ['KEYWORDS', 'SLOT'], repo)
232 # portage prints out more verbose error for us if we were lucky
233 raise SystemExit('Failed to obtain metadata')
236 def __formatKeywords(self, keywords, keywords_list, usebold = False, toplist = 'archlist'):
237 """Loop over all keywords and replace them with nice visual identifier"""
238 # the % is fancy separator, we use it to split keywords for rotation
239 # so we wont loose the empty spaces
240 return ['% %'.join([self.__prepareKeywordChar(arch, i, version.split(), usebold, toplist)
241 for i, arch in enumerate(keywords_list)])
242 for version in keywords]
245 def __prepareKeywordChar(arch, field, keywords, usebold = False, toplist = 'archlist'):
247 Convert specified keywords for package into their visual replacements.
254 keys = [ '~%s' % arch, '-%s' % arch, '%s' % arch, '-*' ]
256 colorize('darkyellow', '~'),
257 colorize('darkred', '-'),
258 colorize('darkgreen', '+'),
259 colorize('darkred', '*')
261 # check what keyword we have
262 # here we cant just append space because it would get stripped later
263 char = colorize('darkgray','o')
264 for k, v in zip(keys, values):
268 if toplist == 'archlist' and usebold and (field)%2 == 0 and char != ' ':
269 char = colorize('bold', char)
273 def __formatVersions(versions, align, length):
274 """Append colors and align keywords properly"""
275 # % are used as separators for further split so we wont loose spaces and coloring
278 pv = align_string(pv, align, length)
279 pv = '%'.join(list(pv))
280 if pv.find('[%M%][%I%]') != -1:
281 tmp.append(colorize_string('darkyellow', pv))
282 elif pv.find('[%M%]') != -1:
283 tmp.append(colorize_string('darkred', pv))
284 elif pv.find('[%I%]') != -1:
285 tmp.append(colorize_string('bold', pv))
291 def __formatAdditional(additional, color, length):
292 """Align additional items properly"""
293 # % are used as separators for further split so we wont loose spaces and coloring
297 x = align_string(x, 'left', length)
298 x = '%'.join(list(x))
300 # the value is unset so the color is gray
302 x = colorize_string(tmpc, x)
307 def __prepareContentResult(versions, keywords, redundant, slots, slot_length, repos, linesep):
308 """Parse version fields into one list with proper separators"""
313 for v, k, r, s, t in zip(versions, keywords, redundant, slots, repos):
316 content.append(linesep)
318 s = '%'.join(list(''.rjust(slot_length)))
319 content.append('%s%s%s%s%s%s%s%s%s' % (v, fieldsep, k, fieldsep, r, normsep, s, fieldsep, t))
322 def __init__(self, package, keywords_list, porttree, ignoreslots = False, content_align = 'bottom', usebold = False, toplist = 'archlist'):
323 """Query all relevant data from portage databases."""
324 packages, self.repositories, self.slots, self.keywords = self.__checkExist(porttree, package)
325 # convert repositories from path to name
326 self.repositories = [porttree.getRepositoryName(x) for x in self.repositories]
327 self.slot_length = max([len(x) for x in self.slots])
328 repositories_length = max([len(x) for x in self.repositories])
329 self.keyword_length = len(keywords_list)
330 vers =self.VersionChecker(packages)
331 self.versions = vers.versions
333 self.version_length = max([len(x) for x in self.versions])
334 self.version_count = len(self.versions)
335 self.redundant = self.RedundancyChecker(masks, self.keywords, self.slots, ignoreslots).redundant
336 redundant_length = max([len(x) for x in self.redundant])
338 ver = self.__formatVersions(self.versions, content_align, self.version_length)
339 kws = self.__formatKeywords(self.keywords, keywords_list, usebold, toplist)
340 red = self.__formatAdditional(self.redundant, 'purple', redundant_length)
341 slt = self.__formatAdditional(self.slots, 'bold', self.slot_length)
342 rep = self.__formatAdditional(self.repositories, 'yellow', repositories_length)
343 # those + nubers are spaces in printout. keywords are multiplied also because of that
344 linesep = '%s+%s+%s+%s' % (''.ljust(self.version_length+1, '-'),
345 ''.ljust(self.keyword_length*2+1, '-'),
346 ''.ljust(redundant_length+self.slot_length+3, '-'),
347 ''.ljust(repositories_length+1, '-')
350 self.content = self.__prepareContentResult(ver, kws, red, slt, self.slot_length, rep, linesep)
351 self.content_length = len(linesep)
352 self.cp = port.cpv_getkey(packages[0])