From cb79d91cd5e45bc8ad1ed0418fbd1d00b3954989 Mon Sep 17 00:00:00 2001 From: Fabio Erculiani Date: Sat, 8 Sep 2012 21:34:43 +0200 Subject: [PATCH] [Solo] implement "solo search" command --- client/solo/commands/search.py | 208 ++++++++++++++++++++++ client/solo/utils.py | 311 ++++++++++++++++++++++++++++++++- 2 files changed, 517 insertions(+), 2 deletions(-) create mode 100644 client/solo/commands/search.py diff --git a/client/solo/commands/search.py b/client/solo/commands/search.py new file mode 100644 index 000000000..d9e6efc53 --- /dev/null +++ b/client/solo/commands/search.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +""" + + @author: Fabio Erculiani + @contact: lxnay@sabayon.org + @copyright: Fabio Erculiani + @license: GPL-2 + + B{Entropy Command Line Client}. + +""" +import sys +import argparse + +from entropy.i18n import _, ngettext +from entropy.output import darkred, blue, brown, darkgreen, purple + +from solo.commands.descriptor import SoloCommandDescriptor +from solo.commands.command import SoloCommand +from solo.utils import print_table, print_package_info + +import entropy.dep + +class SoloSearch(SoloCommand): + """ + Main Solo Search command. + """ + + NAME = "search" + ALIASES = ["s"] + ALLOW_UNPRIVILEGED = True + + INTRODUCTION = """\ +Search for packages. +""" + SEE_ALSO = "" + + def __init__(self, args): + SoloCommand.__init__(self, args) + self._quiet = False + self._verbose = False + self._installed = False + self._available = False + self._packages = [] + + def man(self): + """ + Overridden from SoloCommand. + """ + return self._man() + + def _get_parser(self): + """ + Overridden from SoloCommand. + """ + self._real_command = sys.argv[0] + descriptor = SoloCommandDescriptor.obtain_descriptor( + SoloSearch.NAME) + parser = argparse.ArgumentParser( + description=descriptor.get_description(), + formatter_class=argparse.RawDescriptionHelpFormatter, + prog="%s %s" % (sys.argv[0], SoloSearch.NAME)) + + parser.add_argument("string", nargs='+', + metavar="", help=_("search keyword")) + + parser.add_argument("--quiet", "-q", action="store_true", + default=self._quiet, + help=_('quiet output, for scripting purposes')) + + parser.add_argument("--verbose", "-v", action="store_true", + default=self._verbose, + help=_('verbose output')) + + group = parser.add_mutually_exclusive_group() + group.add_argument("--installed", action="store_true", + default=self._installed, + help=_('search among installed packages only')) + + group.add_argument("--available", action="store_true", + default=self._available, + help=_('search among available packages only')) + + return parser + + def parse(self): + """ + Parse command + """ + parser = self._get_parser() + try: + nsargs = parser.parse_args(self._args) + except IOError as err: + sys.stderr.write("%s\n" % (err,)) + return parser.print_help, [] + + self._quiet = nsargs.quiet + self._verbose = nsargs.verbose + self._installed = nsargs.installed + self._available = nsargs.available + self._packages = nsargs.string + return self._call_unlocked, [self.search] + + def bashcomp(self, last_arg): + """ + Overridden from SoloCommand. + """ + return self._bashcomp(sys.stdout, last_arg, []) + + def _search_string(self, entropy_client, string): + """ + Search method, returns search results. + """ + search_data = set() + found = False + inst_repo = entropy_client.installed_repository() + + def _adv_search(dbconn, package): + slot = entropy.dep.dep_getslot(package) + tag = entropy.dep.dep_gettag(package) + package = entropy.dep.remove_slot(package) + package = entropy.dep.remove_tag(package) + pkg_ids = set(dbconn.searchPackages( + package, slot = slot, + tag = tag, just_id = True, order_by = "atom")) + if not pkg_ids: # look for something else? + pkg_id, rc = dbconn.atomMatch( + package, matchSlot = slot) + if pkg_id != -1: + pkg_ids.add(pkg_id) + return pkg_ids + + if not self._installed: + for repo in entropy_client.repositories(): + dbconn = entropy_client.open_repository(repo) + pkg_ids = _adv_search(dbconn, string) + if pkg_ids: + found = True + search_data.update(((x, repo) for x in pkg_ids)) + + # try to actually match something in installed packages db + if not found and (inst_repo is not None) \ + and not self._available: + pkg_ids = _adv_search(inst_repo, string) + if pkg_ids: + found = True + search_data.update( + ((x, inst_repo.repository_id()) for x in pkg_ids)) + + key_sorter = lambda x: \ + entropy_client.open_repository(x[1]).retrieveAtom(x[0]) + return sorted(search_data, key=key_sorter) + + def search(self, entropy_client): + """ + Solo Search command. + """ + if not self._quiet: + entropy_client.output( + "%s..." % (darkgreen(_("Searching")),), + header=darkred(" @@ ")) + + matches_found = 0 + for string in self._packages: + results = self._search( + entropy_client, string) + matches_found += len(results) + + if not self._quiet: + toc = [] + toc.append(("%s:" % (blue(_("Keywords")),), + purple(', '.join(self._packages)))) + toc.append(("%s:" % (blue(_("Found")),), "%s %s" % ( + matches_found, + brown(ngettext("entry", "entries", matches_found)),))) + print_table(toc) + + if not matches_found: + return 1 + return 0 + + def _search(self, entropy_client, string): + """ + Solo Search string command. + """ + results = self._search_string( + entropy_client, string) + + inst_repo = entropy_client.installed_repository() + inst_repo_class = inst_repo.__class__ + for pkg_id, pkg_repo in results: + dbconn = entropy_client.open_repository(pkg_repo) + from_client = isinstance(dbconn, inst_repo_class) + print_package_info( + pkg_id, entropy_client, dbconn, + extended = self._verbose, + installed_search = from_client, + quiet = self._quiet) + + return results + + +SoloCommandDescriptor.register( + SoloCommandDescriptor( + SoloSearch, + SoloSearch.NAME, + _("search packages in repositories")) + ) diff --git a/client/solo/utils.py b/client/solo/utils.py index d11601ffb..89c1b8fd1 100644 --- a/client/solo/utils.py +++ b/client/solo/utils.py @@ -12,8 +12,12 @@ import os import codecs -from entropy.const import etpConst -from entropy.output import print_generic, darkgreen, decolorize +from entropy.const import etpConst, const_convert_to_unicode +from entropy.i18n import _ +from entropy.output import print_generic, darkgreen, purple, blue, \ + brown, bold, red, darkred, teal, decolorize + +import entropy.dep def read_client_release(): @@ -91,3 +95,306 @@ def print_table(lines_data, cell_spacing = 2, cell_padding = 0, col_n += 1 else: print_generic(cols) + +def show_dependencies_legend(entropy_client, indent = '', + get_data = False): + data = [] + dep_type_ids = etpConst['dependency_type_ids'] + for dep_id, dep_val in sorted(dep_type_ids.items(), + key = lambda x: x[0], reverse = True): + + dep_desc = etpConst['dependency_type_ids_desc'].get( + dep_id, _("N/A")) + txt = '%s%s%s%s %s' % ( + indent, teal("{"), dep_val, teal("}"), dep_desc,) + if get_data: + data.append(txt) + else: + entropy_client.output(txt) + if get_data: + return data + +def _formatted_print(entropy_client, data, header, reset_columns, + min_chars = 25, color = None, get_data = False): + + out_data = [] + + if isinstance(data, (frozenset, set)): + mydata = sorted(data) + elif not isinstance(data, (list, tuple)): + mydata = data.split() + else: + mydata = data + + fcount = 0 + desc_text = header + for item in mydata: + fcount += len(item) + if color: + desc_text += color(item)+" " + else: + desc_text += item+" " + if fcount > min_chars: + fcount = 0 + if get_data: + out_data.append(desc_text) + else: + entropy_client.output(desc_text) + desc_text = reset_columns + + if fcount > 0: + if get_data: + out_data.append(desc_text) + else: + entropy_client.output(desc_text) + + if get_data: + return out_data + +def print_package_info(package_id, entropy_client, entropy_repository, + installed_search = False, strict_output = False, extended = False, + quiet = False, show_repo_if_quiet = False, show_desc_if_quiet = False): + """ + Print Entropy Package Metadata in a pretty and uniform way. + """ + corrupted_str = _("corrupted") + pkgatom = entropy_repository.retrieveAtom(package_id) or corrupted_str + if quiet: + repoinfo = '' + desc = '' + if show_repo_if_quiet: + repoinfo = "[%s] " % (entropy_repository.repository_id(),) + if show_desc_if_quiet: + desc = ' %s' % ( + entropy_repository.retrieveDescription(package_id),) + if not extended: + pkgatom = entropy.dep.dep_getkey(pkgatom) + entropy_client.output( + "%s%s%s" % (repoinfo, pkgatom, desc,), + level="generic") + return + + pkghome = entropy_repository.retrieveHomepage(package_id) + if pkghome is None: + pkghome = corrupted_str + pkgslot = entropy_repository.retrieveSlot(package_id) \ + or corrupted_str + pkgver = entropy_repository.retrieveVersion(package_id) \ + or corrupted_str + pkgtag = entropy_repository.retrieveTag(package_id) + if pkgtag is None: + pkgtag = corrupted_str + pkgrev = entropy_repository.retrieveRevision(package_id) + if pkgrev is None: + pkgrev = 0 + pkgdesc = entropy_repository.retrieveDescription(package_id) + if pkgdesc is None: + pkgdesc = corrupted_str + pkgbranch = entropy_repository.retrieveBranch(package_id) \ + or corrupted_str + if not pkgtag: + pkgtag = "NoTag" + + installed_ver = _("Not installed") + installed_tag = _("N/A") + installed_rev = _("N/A") + if not installed_search: + + # client info + pkginstalled = entropy_client.installed_repository().atomMatch( + entropy.dep.dep_getkey(pkgatom), matchSlot = pkgslot) + if pkginstalled[1] == 0: + idx = pkginstalled[0] + # found + installed_ver = entropy_client.installed_repository( + ).retrieveVersion(idx) or corrupted_str + installed_tag = entropy_client.installed_repository( + ).retrieveTag(idx) + if not installed_tag: + installed_tag = "NoTag" + installed_rev = entropy_client.installed_repository( + ).retrieveRevision(idx) + if installed_rev is None: + installed_rev = const_convert_to_unicode("0") + else: + installed_rev = const_convert_to_unicode(installed_rev) + + toc = [] + + entropy_client.output(red(" @@ %s: " % (_("Package"),) ) + \ + bold(pkgatom) + \ + " "+ blue("%s: " % (_("branch"),)) + bold(pkgbranch) + \ + ", [" + purple(str(entropy_repository.repository_id())) + "] ") + if not strict_output and extended: + pkgname = entropy_repository.retrieveName(package_id) \ + or corrupted_str + pkgcat = entropy_repository.retrieveCategory(package_id) \ + or corrupted_str + toc.append((darkgreen(" %s:" % (_("Category"),)), + blue(pkgcat))) + toc.append((darkgreen(" %s:" % (_("Name"),)), + blue(pkgname))) + + if extended: + + pkgmasked = False + masking_reason = '' + # check if it's masked + package_id_masked, idmasking_reason = \ + entropy_repository.maskFilter(package_id) + if package_id_masked == -1: + pkgmasked = True + masking_reason = ", %s" % ( + entropy_client.Settings()['pkg_masking_reasons'].get( + idmasking_reason),) + + toc.append((darkgreen(" %s:" % (_("Masked"),)), + blue(str(pkgmasked)) + masking_reason,)) + + avail_str = _("Available") + if installed_search: + avail_str = _("Installed") + toc.append(( + darkgreen(" %s:" % (avail_str,)), + blue("%s: " % (_("version"),) ) + bold(pkgver) + blue(" ~ tag: ") + \ + bold(pkgtag) + blue(" ~ %s: " % (_("revision"),) ) + bold(str(pkgrev)),) + ) + + if not installed_search: + toc.append((darkgreen(" %s:" % (_("Installed"),) ), + blue("%s: " % (_("version"),) ) + bold(installed_ver) + \ + blue(" ~ tag: ") + bold(installed_tag) + \ + blue(" ~ %s: " % (_("revision"),) ) + bold(installed_rev),)) + + if not strict_output: + toc.append((darkgreen(" %s:" % (_("Slot"),) ), + blue(str(pkgslot)),)) + + if extended: + pkgsize = entropy_repository.retrieveSize(package_id) + pkgsize = entropy.tools.bytes_into_human(pkgsize) + pkgbin = entropy_repository.retrieveDownloadURL(package_id) + if pkgbin is None: + pkgbin = corrupted_str + pkgdigest = entropy_repository.retrieveDigest(package_id) or \ + corrupted_str + pkgdeps = entropy_repository.retrieveDependencies(package_id, + extended = True, resolve_conditional_deps = False) + pkgconflicts = entropy_repository.retrieveConflicts(package_id) + depsorter = lambda x: entropy.dep.dep_getcpv(x[0]) + + toc.append((darkgreen(" %s:" % (_("Size"),) ), + blue(str(pkgsize)),)) + toc.append((darkgreen(" %s:" % (_("Download"),) ), + brown(str(pkgbin)),)) + toc.append((darkgreen(" %s:" % (_("Checksum"),) ), + brown(str(pkgdigest)),)) + + if pkgdeps: + toc.append(darkred(" ##") + " " + \ + darkgreen("%s:" % (_("Dependencies"),) )) + + for pdep, p_id in sorted(pkgdeps, key = depsorter): + toc.append((" %s " % (brown("##"),), + "%s%s%s %s" % (blue("["), p_id, blue("]"), + brown(pdep),))) + + # show legend + len_txt = " %s" % (brown("##"),) + toc.append((len_txt, "%s:" % (blue(_("Legend")),),)) + dep_leg = show_dependencies_legend(entropy_client, + indent = "", get_data = True) + toc.extend([(len_txt, x) for x in dep_leg]) + + + if pkgconflicts: + toc.append(darkred(" ##") + " " + \ + darkgreen("%s:" % (_("Conflicts"),) )) + for conflict in sorted(pkgconflicts, key = depsorter): + toc.append((" %s" % (darkred("##"),), + brown(conflict),)) + + home_txt = " %s:" % (_("Homepage"),) + home_lines = _formatted_print( + entropy_client, pkghome, "", "", color = brown, + min_chars = 15, get_data = True) + for home_line in home_lines: + toc.append((darkgreen(home_txt), home_line,)) + home_txt = " " + + if not strict_output: + + desc_txt = " %s:" % (_("Description"),) + desc_lines = _formatted_print( + entropy_client, pkgdesc, "", "", get_data = True) + for desc_line in desc_lines: + toc.append((darkgreen(desc_txt), purple(desc_line))) + desc_txt = " " + + if extended: + pkguseflags = entropy_repository.retrieveUseflags(package_id) + use_txt = " %s:" % (_("USE flags"),) + use_lines = _formatted_print( + entropy_client, + pkguseflags, "", "", color = teal, + get_data = True) + for use_line in use_lines: + toc.append((darkgreen(use_txt), use_line)) + use_txt = " " + + if not strict_output: + + if extended: + + chost, cflags, cxxflags = \ + entropy_repository.retrieveCompileFlags(package_id) + sources = entropy_repository.retrieveSources(package_id) + etpapi = entropy_repository.retrieveApi(package_id) + if etpapi is None: + etpapi = corrupted_str + + toc.append((darkgreen(" %s:" % (_("CHOST"),)), + blue(chost))) + toc.append((darkgreen(" %s:" % (_("CFLAGS"),)), + blue(cflags))) + toc.append((darkgreen(" %s:" % (_("CXXFLAGS"),)), + blue(cxxflags))) + + if sources: + sources_txt = " %s:" % (_("Sources"),) + toc.append(darkgreen(sources_txt)) + for source in sources: + toc.append((" ", source,)) + + toc.append((darkgreen(" %s:" % (_("Entry API"),)), + purple(str(etpapi)))) + toc.append((darkgreen(" %s:" % (_("Compiled with"),)), + blue(cflags))) + + pkgkeywords = ' '.join( + sorted(entropy_repository.retrieveKeywords(package_id))) + keyword_txt = " %s:" % (_("Keywords"),) + keyword_lines = _formatted_print( + entropy_client, pkgkeywords, "", "", + color = brown, get_data = True) + for keyword_line in keyword_lines: + toc.append((darkgreen(keyword_txt), brown(keyword_line))) + keyword_txt = " " + + mydate = entropy_repository.retrieveCreationDate(package_id) + pkgcreatedate = "N/A" + if mydate: + pkgcreatedate = \ + entropy.tools.convert_unix_time_to_human_time( + float(mydate)) + + toc.append((darkgreen(" %s:" % (_("Created"),)), + purple(pkgcreatedate))) + + pkglic = entropy_repository.retrieveLicense(package_id) + if pkglic is None: + pkglic = corrupted_str + toc.append((darkgreen(" %s:" % (_("License"),)), + teal(pkglic))) + + print_table(toc, cell_spacing = 3)