search.py: use _aux_cache_keys more
[portage.git] / pym / _emerge / search.py
1 # Copyright 1999-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 from __future__ import print_function
5
6 import re
7 import portage
8 from portage import os
9 from portage.dbapi.porttree import _parse_uri_map
10 from portage.output import  bold, bold as white, darkgreen, green, red
11 from portage.util import writemsg_stdout
12
13 from _emerge.Package import Package
14
15 class search(object):
16
17         #
18         # class constants
19         #
20         VERSION_SHORT=1
21         VERSION_RELEASE=2
22
23         #
24         # public interface
25         #
26         def __init__(self, root_config, spinner, searchdesc,
27                 verbose, usepkg, usepkgonly):
28                 """Searches the available and installed packages for the supplied search key.
29                 The list of available and installed packages is created at object instantiation.
30                 This makes successive searches faster."""
31                 self.settings = root_config.settings
32                 self.vartree = root_config.trees["vartree"]
33                 self.spinner = spinner
34                 self.verbose = verbose
35                 self.searchdesc = searchdesc
36                 self.root_config = root_config
37                 self.setconfig = root_config.setconfig
38                 self.matches = {"pkg" : []}
39                 self.mlen = 0
40
41                 self._dbs = []
42
43                 portdb = root_config.trees["porttree"].dbapi
44                 bindb = root_config.trees["bintree"].dbapi
45                 vardb = root_config.trees["vartree"].dbapi
46
47                 if not usepkgonly and portdb._have_root_eclass_dir:
48                         self._dbs.append(portdb)
49
50                 if (usepkg or usepkgonly) and bindb.cp_all():
51                         self._dbs.append(bindb)
52
53                 self._dbs.append(vardb)
54                 self._portdb = portdb
55
56         def _spinner_update(self):
57                 if self.spinner:
58                         self.spinner.update()
59
60         def _cp_all(self):
61                 cp_all = set()
62                 for db in self._dbs:
63                         cp_all.update(db.cp_all())
64                 return list(sorted(cp_all))
65
66         def _aux_get(self, *args, **kwargs):
67                 for db in self._dbs:
68                         try:
69                                 return db.aux_get(*args, **kwargs)
70                         except KeyError:
71                                 pass
72                 raise
73
74         def _findname(self, *args, **kwargs):
75                 for db in self._dbs:
76                         if db is not self._portdb:
77                                 # We don't want findname to return anything
78                                 # unless it's an ebuild in a portage tree.
79                                 # Otherwise, it's already built and we don't
80                                 # care about it.
81                                 continue
82                         func = getattr(db, "findname", None)
83                         if func:
84                                 value = func(*args, **kwargs)
85                                 if value:
86                                         return value
87                 return None
88
89         def _getFetchMap(self, *args, **kwargs):
90                 for db in self._dbs:
91                         func = getattr(db, "getFetchMap", None)
92                         if func:
93                                 value = func(*args, **kwargs)
94                                 if value:
95                                         return value
96                 return {}
97
98         def _visible(self, db, cpv, metadata):
99                 installed = db is self.vartree.dbapi
100                 built = installed or db is not self._portdb
101                 pkg_type = "ebuild"
102                 if installed:
103                         pkg_type = "installed"
104                 elif built:
105                         pkg_type = "binary"
106                 return Package(type_name=pkg_type,
107                         root_config=self.root_config,
108                         cpv=cpv, built=built, installed=installed,
109                         metadata=metadata).visible
110
111         def _xmatch(self, level, atom):
112                 """
113                 This method does not expand old-style virtuals because it
114                 is restricted to returning matches for a single ${CATEGORY}/${PN}
115                 and old-style virual matches unreliable for that when querying
116                 multiple package databases. If necessary, old-style virtuals
117                 can be performed on atoms prior to calling this method.
118                 """
119                 cp = portage.dep_getkey(atom)
120                 if level == "match-all":
121                         matches = set()
122                         for db in self._dbs:
123                                 if hasattr(db, "xmatch"):
124                                         matches.update(db.xmatch(level, atom))
125                                 else:
126                                         matches.update(db.match(atom))
127                         result = list(x for x in matches if portage.cpv_getkey(x) == cp)
128                         db._cpv_sort_ascending(result)
129                 elif level == "match-visible":
130                         matches = set()
131                         for db in self._dbs:
132                                 if hasattr(db, "xmatch"):
133                                         matches.update(db.xmatch(level, atom))
134                                 else:
135                                         db_keys = list(db._aux_cache_keys)
136                                         for cpv in db.match(atom):
137                                                 metadata = zip(db_keys,
138                                                         db.aux_get(cpv, db_keys))
139                                                 if not self._visible(db, cpv, metadata):
140                                                         continue
141                                                 matches.add(cpv)
142                         result = list(x for x in matches if portage.cpv_getkey(x) == cp)
143                         db._cpv_sort_ascending(result)
144                 elif level == "bestmatch-visible":
145                         result = None
146                         for db in self._dbs:
147                                 if hasattr(db, "xmatch"):
148                                         cpv = db.xmatch("bestmatch-visible", atom)
149                                         if not cpv or portage.cpv_getkey(cpv) != cp:
150                                                 continue
151                                         if not result or cpv == portage.best([cpv, result]):
152                                                 result = cpv
153                                 else:
154                                         db_keys = list(db._aux_cache_keys)
155                                         # break out of this loop with highest visible
156                                         # match, checked in descending order
157                                         for cpv in reversed(db.match(atom)):
158                                                 if portage.cpv_getkey(cpv) != cp:
159                                                         continue
160                                                 metadata = zip(db_keys,
161                                                         db.aux_get(cpv, db_keys))
162                                                 if not self._visible(db, cpv, metadata):
163                                                         continue
164                                                 if not result or cpv == portage.best([cpv, result]):
165                                                         result = cpv
166                                                 break
167                 else:
168                         raise NotImplementedError(level)
169                 return result
170
171         def execute(self,searchkey):
172                 """Performs the search for the supplied search key"""
173                 match_category = 0
174                 self.searchkey=searchkey
175                 self.packagematches = []
176                 if self.searchdesc:
177                         self.searchdesc=1
178                         self.matches = {"pkg":[], "desc":[], "set":[]}
179                 else:
180                         self.searchdesc=0
181                         self.matches = {"pkg":[], "set":[]}
182                 print("Searching...   ", end=' ')
183
184                 regexsearch = False
185                 if self.searchkey.startswith('%'):
186                         regexsearch = True
187                         self.searchkey = self.searchkey[1:]
188                 if self.searchkey.startswith('@'):
189                         match_category = 1
190                         self.searchkey = self.searchkey[1:]
191                 if regexsearch:
192                         self.searchre=re.compile(self.searchkey,re.I)
193                 else:
194                         self.searchre=re.compile(re.escape(self.searchkey), re.I)
195
196                 for package in self._cp_all():
197                         self._spinner_update()
198
199                         if match_category:
200                                 match_string  = package[:]
201                         else:
202                                 match_string  = package.split("/")[-1]
203
204                         masked=0
205                         if self.searchre.search(match_string):
206                                 if not self._xmatch("match-visible", package):
207                                         masked=1
208                                 self.matches["pkg"].append([package,masked])
209                         elif self.searchdesc: # DESCRIPTION searching
210                                 full_package = self._xmatch("bestmatch-visible", package)
211                                 if not full_package:
212                                         #no match found; we don't want to query description
213                                         full_package = portage.best(
214                                                 self._xmatch("match-all", package))
215                                         if not full_package:
216                                                 continue
217                                         else:
218                                                 masked=1
219                                 try:
220                                         full_desc = self._aux_get(
221                                                 full_package, ["DESCRIPTION"])[0]
222                                 except KeyError:
223                                         print("emerge: search: aux_get() failed, skipping")
224                                         continue
225                                 if self.searchre.search(full_desc):
226                                         self.matches["desc"].append([full_package,masked])
227
228                 self.sdict = self.setconfig.getSets()
229                 for setname in self.sdict:
230                         self._spinner_update()
231                         if match_category:
232                                 match_string = setname
233                         else:
234                                 match_string = setname.split("/")[-1]
235                         
236                         if self.searchre.search(match_string):
237                                 self.matches["set"].append([setname, False])
238                         elif self.searchdesc:
239                                 if self.searchre.search(
240                                         self.sdict[setname].getMetadata("DESCRIPTION")):
241                                         self.matches["set"].append([setname, False])
242                         
243                 self.mlen=0
244                 for mtype in self.matches:
245                         self.matches[mtype].sort()
246                         self.mlen += len(self.matches[mtype])
247
248         def addCP(self, cp):
249                 if not self._xmatch("match-all", cp):
250                         return
251                 masked = 0
252                 if not self._xmatch("bestmatch-visible", cp):
253                         masked = 1
254                 self.matches["pkg"].append([cp, masked])
255                 self.mlen += 1
256
257         def output(self):
258                 """Outputs the results of the search."""
259                 msg = []
260                 msg.append("\b\b  \n[ Results for search key : " + \
261                         bold(self.searchkey) + " ]\n")
262                 msg.append("[ Applications found : " + \
263                         bold(str(self.mlen)) + " ]\n\n")
264                 vardb = self.vartree.dbapi
265                 metadata_keys = set(Package.metadata_keys)
266                 metadata_keys.update(["DESCRIPTION", "HOMEPAGE", "LICENSE", "SRC_URI"])
267                 metadata_keys = tuple(metadata_keys)
268                 for mtype in self.matches:
269                         for match,masked in self.matches[mtype]:
270                                 full_package = None
271                                 if mtype == "pkg":
272                                         full_package = self._xmatch(
273                                                 "bestmatch-visible", match)
274                                         if not full_package:
275                                                 #no match found; we don't want to query description
276                                                 masked=1
277                                                 full_package = portage.best(
278                                                         self._xmatch("match-all",match))
279                                 elif mtype == "desc":
280                                         full_package = match
281                                         match        = portage.cpv_getkey(match)
282                                 elif mtype == "set":
283                                         msg.append(green("*") + "  " + bold(match) + "\n")
284                                         if self.verbose:
285                                                 msg.append("      " + darkgreen("Description:") + \
286                                                         "   " + \
287                                                         self.sdict[match].getMetadata("DESCRIPTION") \
288                                                         + "\n\n")
289                                 if full_package:
290                                         try:
291                                                 metadata = dict(zip(metadata_keys,
292                                                         self._aux_get(full_package, metadata_keys)))
293                                         except KeyError:
294                                                 msg.append("emerge: search: aux_get() failed, skipping\n")
295                                                 continue
296
297                                         desc = metadata["DESCRIPTION"]
298                                         homepage = metadata["HOMEPAGE"]
299                                         license = metadata["LICENSE"]
300
301                                         if masked:
302                                                 msg.append(green("*") + "  " + \
303                                                         white(match) + " " + red("[ Masked ]") + "\n")
304                                         else:
305                                                 msg.append(green("*") + "  " + bold(match) + "\n")
306                                         myversion = self.getVersion(full_package, search.VERSION_RELEASE)
307
308                                         mysum = [0,0]
309                                         file_size_str = None
310                                         mycat = match.split("/")[0]
311                                         mypkg = match.split("/")[1]
312                                         mycpv = match + "-" + myversion
313                                         myebuild = self._findname(mycpv)
314                                         if myebuild:
315                                                 pkg = Package(built=False, cpv=mycpv,
316                                                         installed=False, metadata=metadata,
317                                                         root_config=self.root_config, type_name="ebuild")
318                                                 pkgdir = os.path.dirname(myebuild)
319                                                 mf = self.settings.repositories.get_repo_for_location(
320                                                         os.path.dirname(os.path.dirname(pkgdir)))
321                                                 mf = mf.load_manifest(
322                                                         pkgdir, self.settings["DISTDIR"])
323                                                 try:
324                                                         uri_map = _parse_uri_map(mycpv, metadata,
325                                                                 use=pkg.use.enabled)
326                                                 except portage.exception.InvalidDependString as e:
327                                                         file_size_str = "Unknown (%s)" % (e,)
328                                                         del e
329                                                 else:
330                                                         try:
331                                                                 mysum[0] = mf.getDistfilesSize(uri_map)
332                                                         except KeyError as e:
333                                                                 file_size_str = "Unknown (missing " + \
334                                                                         "digest for %s)" % (e,)
335                                                                 del e
336
337                                         available = False
338                                         for db in self._dbs:
339                                                 if db is not vardb and \
340                                                         db.cpv_exists(mycpv):
341                                                         available = True
342                                                         if not myebuild and hasattr(db, "bintree"):
343                                                                 myebuild = db.bintree.getname(mycpv)
344                                                                 try:
345                                                                         mysum[0] = os.stat(myebuild).st_size
346                                                                 except OSError:
347                                                                         myebuild = None
348                                                         break
349
350                                         if myebuild and file_size_str is None:
351                                                 mystr = str(mysum[0] // 1024)
352                                                 mycount = len(mystr)
353                                                 while (mycount > 3):
354                                                         mycount -= 3
355                                                         mystr = mystr[:mycount] + "," + mystr[mycount:]
356                                                 file_size_str = mystr + " kB"
357
358                                         if self.verbose:
359                                                 if available:
360                                                         msg.append("      %s %s\n" % \
361                                                                 (darkgreen("Latest version available:"),
362                                                                 myversion))
363                                                 msg.append("      %s\n" % \
364                                                         self.getInstallationStatus(mycat+'/'+mypkg))
365                                                 if myebuild:
366                                                         msg.append("      %s %s\n" % \
367                                                                 (darkgreen("Size of files:"), file_size_str))
368                                                 msg.append("      " + darkgreen("Homepage:") + \
369                                                         "      " + homepage + "\n")
370                                                 msg.append("      " + darkgreen("Description:") \
371                                                         + "   " + desc + "\n")
372                                                 msg.append("      " + darkgreen("License:") + \
373                                                         "       " + license + "\n\n")
374                 writemsg_stdout(''.join(msg), noiselevel=-1)
375         #
376         # private interface
377         #
378         def getInstallationStatus(self,package):
379                 installed_package = self.vartree.dep_bestmatch(package)
380                 result = ""
381                 version = self.getVersion(installed_package,search.VERSION_RELEASE)
382                 if len(version) > 0:
383                         result = darkgreen("Latest version installed:")+" "+version
384                 else:
385                         result = darkgreen("Latest version installed:")+" [ Not Installed ]"
386                 return result
387
388         def getVersion(self,full_package,detail):
389                 if len(full_package) > 1:
390                         package_parts = portage.catpkgsplit(full_package)
391                         if detail == search.VERSION_RELEASE and package_parts[3] != 'r0':
392                                 result = package_parts[2]+ "-" + package_parts[3]
393                         else:
394                                 result = package_parts[2]
395                 else:
396                         result = ""
397                 return result
398