From b27c23725bece5c6b354d2e1091ea0e79a01902a Mon Sep 17 00:00:00 2001 From: Krytarik Raido Date: Thu, 30 May 2019 14:04:04 +0200 Subject: [PATCH] PackageInfo: Add option to look up source packages too. * Improve error handling. * Improve output formatting. --- PackageInfo/__init__.py | 2 +- PackageInfo/packages.py | 127 ++++++++++++++++++++++++---------------- PackageInfo/plugin.py | 16 +++-- 3 files changed, 89 insertions(+), 56 deletions(-) diff --git a/PackageInfo/__init__.py b/PackageInfo/__init__.py index 0e5dfa1..ca6382a 100644 --- a/PackageInfo/__init__.py +++ b/PackageInfo/__init__.py @@ -22,7 +22,7 @@ import supybot import supybot.world as world from imp import reload -__version__ = "1.5.0" +__version__ = "1.6.0" __author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com") __contributors__ = { supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Concept'], diff --git a/PackageInfo/packages.py b/PackageInfo/packages.py index ec2481d..4f76c83 100644 --- a/PackageInfo/packages.py +++ b/PackageInfo/packages.py @@ -33,30 +33,6 @@ def description(pkg): return pkg['Description'].split('\n')[0] return "Description not available" -def apt_cache(aptdir, distro, cmd, pkg): - return subprocess.check_output(['apt-cache', - '-oAPT::Architecture=amd64', - '-oAPT::Architectures::=i386', - '-oAPT::Architectures::=amd64', - '-oDir::State::Lists=%s/%s' % (aptdir, distro), - '-oDir::State::Status=%s/%s.status' % (aptdir, distro), - '-oDir::Etc::SourceList=%s/%s.list' % (aptdir, distro), - '-oDir::Etc::SourceParts=""', - '-oDir::Cache=%s/cache' % aptdir] + - cmd + [pkg.lower()]).decode('utf8') - -def apt_file(aptdir, distro, pkg): - return subprocess.check_output(['apt-file', - '-oAPT::Architecture=amd64', - '-oAPT::Architectures::=i386', - '-oAPT::Architectures::=amd64', - '-oDir::State::Lists=%s/%s' % (aptdir, distro), - '-oDir::State::Status=%s/%s.status' % (aptdir, distro), - '-oDir::Etc::SourceList=%s/%s.list' % (aptdir, distro), - '-oDir::Etc::SourceParts=""', - '-oDir::Cache=%s/cache' % aptdir, - '-l', '-i', 'search', pkg]).decode('utf8') - class Apt: def __init__(self, plugin): self.aptdir = plugin.registryValue('aptdir') @@ -67,6 +43,30 @@ class Apt: if self.aptdir: self.distros = sorted([x[:-5] for x in os.listdir(self.aptdir) if x.endswith('.list')]) + def apt_cache(self, distro, cmd, pkg): + return subprocess.check_output(['apt-cache', + '-oAPT::Architecture=amd64', + '-oAPT::Architectures::=i386', + '-oAPT::Architectures::=amd64', + '-oDir::State::Lists=%s/%s' % (self.aptdir, distro), + '-oDir::State::Status=%s/%s.status' % (self.aptdir, distro), + '-oDir::Etc::SourceList=%s/%s.list' % (self.aptdir, distro), + '-oDir::Etc::SourceParts=""', + '-oDir::Cache=%s/cache' % self.aptdir] + + cmd + [pkg.lower()]).decode('utf-8') + + def apt_file(self, distro, pkg): + return subprocess.check_output(['apt-file', + '-oAPT::Architecture=amd64', + '-oAPT::Architectures::=i386', + '-oAPT::Architectures::=amd64', + '-oDir::State::Lists=%s/%s' % (self.aptdir, distro), + '-oDir::State::Status=%s/%s.status' % (self.aptdir, distro), + '-oDir::Etc::SourceList=%s/%s.list' % (self.aptdir, distro), + '-oDir::Etc::SourceParts=""', + '-oDir::Cache=%s/cache' % self.aptdir, + '-l', '-i', 'search', pkg]).decode('utf-8') + def _parse(self, pkg): parser = FeedParser() parser.feed(pkg) @@ -82,22 +82,20 @@ class Apt: pkgTracURL = "https://packages.ubuntu.com" try: - data = apt_cache(self.aptdir, distro, ['search', '-n'], pkg) + data = self.apt_cache(distro, ['search', '-n'], pkg) except subprocess.CalledProcessError as e: data = e.output if not data: if filelookup: try: - data = apt_file(self.aptdir, distro, pkg).split() - except subprocess.CalledProcessError as e: - data = e.output + data = self.apt_file(distro, pkg).split() + except subprocess.CalledProcessError: + self.log.error("PackageInfo/packages: Please update the cache for %s" % distro) + return "Cache out of date, please contact the administrator" + except OSError: + self.log.error("PackageInfo/packages: apt-file is not installed") + return "Please use %s/ to search for files" % pkgTracURL if data: - if data[0] == 'sh:': # apt-file isn't installed - self.log.error("PackageInfo/packages: apt-file is not installed") - return "Please use %s/ to search for files" % pkgTracURL - if data[0] == 'E:': # No files in the cache dir - self.log.error("PackageInfo/packages: Please update the cache for %s" % distro) - return "Cache out of date, please contact the administrator" if len(data) > 10: return "File %s found in %s and %d others <%s/search?searchon=contents&keywords=%s&mode=exactfilename&suite=%s&arch=any>" % (pkg, ', '.join(data[:10]), len(data)-10, pkgTracURL, utils.web.urlquote(pkg), distro) return "File %s found in %s" % (pkg, ', '.join(data)) @@ -109,15 +107,15 @@ class Apt: else: return "Found: %s" % ', '.join(pkgs) - def raw_info(self, pkg, distro, archlookup=True): + def raw_info(self, pkg, distro, isSource, archlookup=True): if distro not in self.distros: return "%r is not a valid release: %s" % (distro, ", ".join(self.distros)) try: - data = apt_cache(self.aptdir, distro, ['show'], pkg) - except subprocess.CalledProcessError as e: - data = e.output - if not data or 'E: No packages found' in data: + data = self.apt_cache(distro, ['show'] if not isSource else ['showsrc', '--only-source'], pkg) + except subprocess.CalledProcessError: + data = '' + if not data: return 'Package %s does not exist in %s' % (pkg, distro) maxp = {'Version': '0~'} @@ -126,12 +124,31 @@ class Apt: if apt.apt_pkg.version_compare(maxp['Version'], p['Version']) <= 0: maxp = p + if isSource: + bdeps = maxp.get('Build-Depends') + vcs = maxp.get('Vcs-Browser') + for (key, value) in list(maxp.items()): + if key.startswith('Build-Depends-'): + bdeps = "%s, %s" % (bdeps, value) if bdeps else value + elif key.startswith('Vcs-') and not vcs: + vcs = "%s (%s)" % (value, key[4:]) + maxp['Builddeps'] = bdeps + maxp['Vcs'] = vcs + return maxp + + if not maxp.get('Source'): + maxp['Sourcepkg'] = maxp['Package'] + else: + maxp['Sourcepkg'] = maxp['Source'].split()[0] + if not archlookup: return maxp try: - data2 = apt_cache(self.aptdir, distro, ['showsrc'], pkg) + data2 = self.apt_cache(distro, ['showsrc', '--only-source'], maxp['Sourcepkg']) except subprocess.CalledProcessError: + data2 = '' + if not data2: return maxp maxp2 = {'Version': '0~'} @@ -149,22 +166,30 @@ class Apt: return maxp - def info(self, pkg, distro): - maxp = self.raw_info(pkg, distro) + def info(self, pkg, distro, isSource): + maxp = self.raw_info(pkg, distro, isSource) if isinstance(maxp, str): return maxp - return("%s (source: %s): %s. In component %s, is %s. Version %s (%s), package size %s kB, installed size %s kB%s" % - (maxp['Package'], maxp.get('Source', None) or maxp['Package'], description(maxp), component(maxp['Section']), - maxp['Priority'], maxp['Version'], distro, int((int(maxp['Size'])/102.4)+0.5)/10, maxp['Installed-Size'], - ". (Only available for %s)" % maxp['Architectures'] if maxp.get('Architectures', None) else "")) + if isSource: + return "%s (%s, %s): Packages %s. Maintained by %s%s" % ( + maxp['Package'], maxp['Version'], distro, maxp['Binary'], + re.sub(r' <\S+>$', '', maxp.get('Original-Maintainer', maxp['Maintainer'])), + " @ %s" % maxp['Vcs'] if maxp['Vcs'] else "") + return "{} ({}, {}): {}. In component {}, is {}. Built by {}. Size {:,} kB / {:,} kB{}".format( + maxp['Package'], maxp['Version'], distro, description(maxp), component(maxp['Section']), + maxp['Priority'], maxp['Sourcepkg'], int((int(maxp['Size'])/1024)+1), int(maxp['Installed-Size']), + ". (Only available for %s.)" % maxp['Architectures'] if maxp.get('Architectures') else "") - def depends(self, pkg, distro): - maxp = self.raw_info(pkg, distro, archlookup=False) + def depends(self, pkg, distro, isSource): + maxp = self.raw_info(pkg, distro, isSource, archlookup=False) if isinstance(maxp, str): return maxp - return("%s (version: %s, %s): Depends on %s%s" % - (maxp['Package'], maxp['Version'], distro, maxp.get('Depends', None) or "nothing", - ". Recommends %s" % maxp['Recommends'] if maxp.get('Recommends', None) else "")) + if isSource: + return "%s (%s, %s): Build depends on %s" % ( + maxp['Package'], maxp['Version'], distro, maxp.get('Builddeps', "nothing")) + return "%s (%s, %s): Depends on %s%s" % ( + maxp['Package'], maxp['Version'], distro, maxp.get('Depends', "nothing"), + ". Recommends %s" % maxp['Recommends'] if maxp.get('Recommends') else "") # Simple test if __name__ == "__main__": diff --git a/PackageInfo/plugin.py b/PackageInfo/plugin.py index 8c1d0ea..bc46c5b 100644 --- a/PackageInfo/plugin.py +++ b/PackageInfo/plugin.py @@ -102,6 +102,12 @@ class PackageInfo(callbacks.Plugin): return (defaultRelease, "%s %s" % (release, rest)) return (release, rest) + def __getPackage(self, package): + if package.startswith('src:'): + package = package[4:] + return (package, True) + return (package, False) + def __handleRest(self, msg, target, reply, rest): targeto = target prefix = '' @@ -131,7 +137,7 @@ class PackageInfo(callbacks.Plugin): return (target, prefix + reply) def real_info(self, irc, msg, args, package, release=None): - """ [] + """<[src:]package> [] Look up information for , optionally in """ @@ -141,15 +147,16 @@ class PackageInfo(callbacks.Plugin): (release, rest) = self.__getRelease(irc, release, channel) if not release: return + (package, isSource) = self.__getPackage(package) target = ircutils.replyTo(msg) - reply = self.Apt.info(package, release) + reply = self.Apt.info(package, release, isSource) if rest: (target, reply) = self.__handleRest(msg, target, reply, rest) queue(irc, target, reply) info = wrap(real_info, ['anything', optional('text')]) def real_depends(self, irc, msg, args, package, release=None): - """ [] + """<[src:]package> [] Look up dependencies for , optionally in """ @@ -159,8 +166,9 @@ class PackageInfo(callbacks.Plugin): (release, rest) = self.__getRelease(irc, release, channel) if not release: return + (package, isSource) = self.__getPackage(package) target = ircutils.replyTo(msg) - reply = self.Apt.depends(package, release) + reply = self.Apt.depends(package, release, isSource) if rest: (target, reply) = self.__handleRest(msg, target, reply, rest) queue(irc, target, reply)