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