PackageInfo: Add option to look up source packages too.

* Improve error handling.
* Improve output formatting.
This commit is contained in:
Krytarik Raido 2019-05-30 14:04:04 +02:00
parent 9bf38fa948
commit b27c23725b
3 changed files with 89 additions and 56 deletions

View File

@ -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'],

View File

@ -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__":

View File

@ -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):
"""<package> [<release>]
"""<[src:]package> [<release>]
Look up information for <package>, optionally in <release>
"""
@ -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):
"""<package> [<release>]
"""<[src:]package> [<release>]
Look up dependencies for <package>, optionally in <release>
"""
@ -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)