### # Copyright (c) 2003-2005, James Vega # Copyright (c) 2011, Valentin Lorentz # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import os import re import time import urllib import fnmatch import bs4 as BeautifulSoup import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.utils.iter import all class Debian(callbacks.Plugin): threaded = True _debreflags = re.DOTALL | re.MULTILINE _deblistreFileExact = re.compile(r'([^<]+)', _debreflags) def file(self, irc, msg, args, optlist, filename): """[--exact] \ [--mode {path,filename,exactfilename}] \ [--branch {oldstable,stable,testing,unstable,experimental}] \ [--arch ] \ [--section {main,contrib,non-free}] Returns the package(s) containing the . --mode defaults to path, and defines how to search. --branch defaults to stable, and defines in what branch to search.""" url = 'http://packages.debian.org/search?searchon=contents' + \ '&keywords=%(keywords)s&mode=%(mode)s&suite=%(suite)s' + \ '&arch=%(arch)s' def reg(name): return self.registryValue('defaults.file.%s' % name, msg.args[0]) args = {'keywords': None, 'mode': reg('mode'), 'suite': reg('branch'), 'section': reg('section'), 'arch': reg('arch')} exact = ('exact', True) in optlist for (key, value) in optlist: if key == 'branch': args['suite'] = value elif key == 'section': args['section'] = value elif key == 'arch': args['arch'] = value elif key == 'mode': args['mode'] = value responses = [] if '*' in filename: irc.error('Wildcard characters can not be specified.', Raise=True) args['keywords'] = utils.web.urlquote(filename, '') url %= args try: html = utils.web.getUrl(url).decode() except utils.web.Error as e: irc.error(format('I couldn\'t reach the search page (%s).', e), Raise=True) if 'is down at the moment' in html: irc.error('Packages.debian.org is down at the moment. ' 'Please try again later.', Raise=True) step = 0 pkgs = [] for line in html.split('\n'): if '' in line: step += 1 elif step == 1 or (step >= 1 and not exact): pkgs.extend(self._deblistreFileExact.findall(line)) if pkgs == []: irc.reply(format('No filename found for %s (%s)', utils.web.urlunquote(filename), args['suite'])) else: # Filter duplicated pkgs = dict(map(lambda x:(x, None), pkgs)).keys() irc.reply(format('%i matches found: %s (%s)', len(pkgs), '; '.join(pkgs), args['suite'])) file = wrap(file, [getopts({'exact': '', 'branch': ('literal', ('oldstable', 'stable', 'testing', 'unstable', 'experimental')), 'mode': ('literal', ('path', 'exactfilename', 'filename')), 'section': ('literal', ('main', 'contrib', 'non-free')), 'arch': 'somethingWithoutSpaces'}), 'text']) _debreflags = re.DOTALL | re.IGNORECASE _deblistreVersion = re.compile(r'

Package ([^<]+)

(.*?)', _debreflags) def version(self, irc, msg, args, optlist, package): """[--exact] \ [--searchon {names,all,sourcenames}] \ [--branch {oldstable,stable,testing,unstable,experimental}] \ [--section {main,contrib,non-free}] Returns the current version(s) of the Debian package . --exact, if given, means you want only the , and not package names containing this name. --searchon defaults to names, and defines where to search. --branch defaults to all, and defines in what branch to search. --section defaults to all, and defines in what section to search.""" url = 'http://packages.debian.org/search?keywords=%(keywords)s' + \ '&searchon=%(searchon)s&suite=%(suite)s§ion=%(section)s' def reg(name): return self.registryValue('defaults.version.%s' % name, msg.args[0]) args = {'keywords': None, 'searchon': reg('searchon'), 'suite': reg('branch'), 'section': reg('section')} for (key, value) in optlist: if key == 'exact': url += '&exact=1' elif key == 'branch': args['suite'] = value elif key == 'section': args['section'] = value elif key == 'searchon': args['searchon'] = value responses = [] if '*' in package: irc.error('Wildcard characters can not be specified.', Raise=True) args['keywords'] = utils.web.urlquote(package) url %= args try: html = utils.web.getUrl(url).decode() except utils.web.Error as e: irc.error(format('I couldn\'t reach the search page (%s).', e), Raise=True) if 'is down at the moment' in html: irc.error('Packages.debian.org is down at the moment. ' 'Please try again later.', Raise=True) pkgs = self._deblistreVersion.findall(html) if not pkgs: irc.reply(format('No package found for %s (%s)', utils.web.urlunquote(package), args['suite'])) else: for pkg in pkgs: pkgMatch = pkg[0] soup = BeautifulSoup.BeautifulSoup(pkg[1]) liBranches = soup.find_all('li') branches = [] versions = [] def branchVers(br): vers = [b.next.string.strip() for b in br] return [utils.str.rsplit(v, ':', 1)[0] for v in vers] for li in liBranches: branches.append(li.a.string) versions.append(branchVers(li.find_all('br'))) if branches and versions: for pairs in zip(branches, versions): branch = pairs[0] ver = ', '.join(pairs[1]) s = format('%s (%s)', pkgMatch, ': '.join([branch, ver])) responses.append(s) resp = format('%i matches found: %s', len(responses), '; '.join(responses)) irc.reply(resp) version = wrap(version, [getopts({'exact': '', 'searchon': ('literal', ('names', 'all', 'sourcenames')), 'branch': ('literal', ('oldstable', 'stable', 'testing', 'unstable', 'experimental')), 'arch': ('literal', ('main', 'contrib', 'non-free'))}), 'text']) _incomingRe = re.compile(r'', re.I) def incoming(self, irc, msg, args, optlist, globs): """[--{regexp,arch} ] [ ...] Checks debian incoming for a matching package name. The arch parameter defaults to i386; --regexp returns only those package names that match a given regexp, and normal matches use standard *nix globbing. """ predicates = [] archPredicate = lambda s: ('_i386.' in s) for (option, arg) in optlist: if option == 'regexp': predicates.append(r.search) elif option == 'arch': arg = '_%s.' % arg archPredicate = lambda s, arg=arg: (arg in s) predicates.append(archPredicate) for glob in globs: glob = fnmatch.translate(glob) predicates.append(re.compile(glob).search) packages = [] try: fd = utils.web.getUrlFd('http://incoming.debian.org/') except utils.web.Error as e: irc.error(str(e), Raise=True) for line in fd: m = self._incomingRe.search(line.decode()) if m: name = m.group(1) if all(None, map(lambda p: p(name), predicates)): realname = utils.str.rsplit(name, '_', 1)[0] packages.append(realname) if len(packages) == 0: irc.error('No packages matched that search.') else: irc.reply(format('%L', packages)) incoming = thread(wrap(incoming, [getopts({'regexp': 'regexpMatcher', 'arch': 'something'}), any('glob')])) def bold(self, s): if self.registryValue('bold', dynamic.channel): return ircutils.bold(s) return s _update = re.compile(r' : ([^<]+)') _latestVersion = re.compile(r'(.+)') _maintainer = re.compile(r'.*' '' '(?P[^<]+)', re.S) def stats(self, irc, msg, args, pkg): """ Reports various statistics (from http://packages.qa.debian.org/) about . """ pkg = pkg.lower() try: text = utils.web.getUrl('http://packages.qa.debian.org/%s/%s.html' % (pkg[0], pkg)).decode('utf8') except utils.web.Error: irc.errorInvalid('source package name') for line in text.split('\n'): match = self._latestVersion.search(text) if match is not None: break assert match is not None version = '%s: %s' % (self.bold('Last version'), match.group(1)) updated = None m = self._update.search(text) if m: updated = m.group(1) soup = BeautifulSoup.BeautifulSoup(text) pairs = zip(soup.find_all('dt'), soup.find_all('dd')) for (label, content) in pairs: try: title = self._bugsCategoryTitle.search(str(label)).group(1) except AttributeError: # Didn't match if str(label).startswith('
'): title = 'All bugs' elif str(label) == '
' + \ 'maint
': title = 'Maintainer and Uploaders' else: continue if title == 'Maintainer and Uploaders': match = self._maintainer.search(str(content)) name, email = match.group('name'), match.group('email') maintainer = format('%s: %s %u', self.bold('Maintainer'), name, utils.web.mungeEmail(email)) elif title == 'All bugs': bugsAll = format('%i Total', content.span.string) elif title == 'Release Critical': bugsRC = format('%i RC', content.span.string) elif title == 'Important and Normal': bugs = format('%i Important/Normal', content.span.string) elif title == 'Minor and Wishlist': bugsMinor = format('%i Minor/Wishlist', content.span.string) elif title == 'Fixed and Pending': bugsFixed = format('%i Fixed/Pending', content.span.string) bugL = (bugsAll, bugsRC, bugs, bugsMinor, bugsFixed) s = '. '.join((version, maintainer, '%s: %s' % (self.bold('Bugs'), '; '.join(bugL)))) if updated: s = 'As of %s, %s' % (updated, s) irc.reply(s) stats = wrap(stats, ['somethingWithoutSpaces']) _newpkgre = re.compile(r'
  • /]+>([^<]+)') def new(self, irc, msg, args, section, version, glob): """[{main,contrib,non-free}] [] [] Checks for packages that have been added to Debian's unstable branch in the past week. If no glob is specified, returns a list of all packages. If no section is specified, defaults to main. """ if version is None: version = 'unstable' try: fd = utils.web.getUrlFd('http://packages.debian.org/%s/%s/newpkg' % (version, section)) except utils.web.Error as e: irc.error(str(e), Raise=True) packages = [] for line in fd: m = self._newpkgre.search(line.decode()) if m: m = m.group(1) if fnmatch.fnmatch(m, glob): packages.append(m) fd.close() if packages: irc.reply(format('%L', packages)) else: irc.error('No packages matched that search.') new = wrap(new, [optional(('literal', ('main', 'contrib', 'non-free')), 'main'), optional('something'), additional('glob', '*')]) _severity = re.compile(r'

    Severity: ([^<]+)

    ', re.I) _package = re.compile(r'
    Package: ([^<\n]+)\n',
                              re.I | re.S)
        _reporter = re.compile(r'Reported by: <[^>]+>([^<]+)<', re.I | re.S)
        _subject = re.compile(r'Subject: [^:]+: ([^<]+)', re.I | re.S)
        _date = re.compile(r'Date: ([^\n]+)\n', re.I | re.S)
        _tags = re.compile(r'

    Tags: ([^<]+)

    ', re.I) _searches = (_package, _subject, _reporter, _date) def bug(self, irc, msg, args, bug): """ Returns a description of the bug with bug id . """ url = 'http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%s' % bug try: text = utils.web.getUrl(url).decode() except utils.web.Error as e: irc.error(str(e), Raise=True) if "There is no record of Bug" in text: irc.error('I could not find a bug report matching that number.', Raise=True) searches = list(map(lambda p: p.search(text), self._searches)) sev = self._severity.search(text) tags = self._tags.search(text) # This section should be cleaned up to ease future modifications if all(None, searches): L = map(self.bold, ('Package', 'Subject', 'Reported')) resp = format('%s: %%s; %s: %%s; %s: by %%s on %%s', *L) L = map(utils.web.htmlToText, map(lambda p: p.group(1), searches)) resp = format(resp, *L) if sev: sev = list(filter(None, sev.groups())) if sev: sev = utils.web.htmlToText(sev[0]) resp += format('; %s: %s', self.bold('Severity'), sev) if tags: resp += format('; %s: %s', self.bold('Tags'), tags.group(1)) resp += format('; %u', url) irc.reply(resp) else: irc.error('I was unable to properly parse the BTS page.') bug = wrap(bug, [('id', 'bug')]) Class = Debian # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: