Changed to use utils.abbrev to allow any unambiguous abbreviation.

This commit is contained in:
Jeremy Fincher 2003-11-11 14:14:34 +00:00
parent e0cfe722c7
commit 94041acb9b

View File

@ -37,8 +37,7 @@ Bugzilla bug retriever
import os import os
import re import re
import string import string
import urllib import urllib2
import base64
import xml.dom.minidom as minidom import xml.dom.minidom as minidom
from htmlentitydefs import entitydefs as entities from htmlentitydefs import entitydefs as entities
@ -47,36 +46,26 @@ import sqlite
import conf import conf
import utils import utils
import plugins import plugins
import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import ircutils import structures
dbfilename = os.path.join(conf.dataDir, 'Bugzilla.db') dbfilename = os.path.join(conf.dataDir, 'Bugzilla.db')
def makeDb(filename): def makeDb(filename):
if os.path.exists(filename): if os.path.exists(filename):
return sqlite.connect(filename) d = structures.PersistentDictionary(filename)
db = sqlite.connect(filename) else:
cursor = db.cursor() d = structures.PersistentDictionary(filename)
cursor.execute("""CREATE TABLE bugzillas ( d['gcc'] = ['http://gcc.gnu.org/bugzilla', 'GCC']
shorthand TEXT UNIQUE ON CONFLICT REPLACE, d['rh'] = ['http://bugzilla.redhat.com/bugzilla', 'Red Hat']
url TEXT, d['gnome'] = ['http://bugzilla.gnome.org/bugzilla', 'Gnome']
description TEXT d['mozilla'] = ['http://bugzilla.mozilla.org', 'Mozilla']
)""") d['ximian'] = ['http://bugzilla.ximian.com/bugzilla', 'Ximian Gnome']
cursor = db.cursor(); d.flush()
cursor.execute("""INSERT INTO bugzillas VALUES (%s, %s, %s)""", return d
'rh', 'http://bugzilla.redhat.com/bugzilla', 'Red Hat')
cursor.execute("""INSERT INTO bugzillas VALUES (%s, %s, %s)""",
'gnome', 'http://bugzilla.gnome.org', 'Gnome')
cursor.execute("""INSERT INTO bugzillas VALUES (%s, %s, %s)""",
'xim', 'http://bugzilla.ximian.com', 'Ximian')
cursor.execute("""INSERT INTO bugzillas VALUES (%s, %s, %s)""",
'moz', 'http://bugzilla.mozilla.org', 'Mozilla')
cursor.execute("""INSERT INTO bugzillas VALUES (%s, %s, %s)""",
'gcc', 'http://gcc.gnu.org/bugzilla', 'GCC')
db.commit()
return db
class BugError(Exception): class BugzillaError(Exception):
"""A bugzilla error""" """A bugzilla error"""
pass pass
@ -91,6 +80,8 @@ def configure(onStart, afterConnect, advanced):
if yn('Do you want the Bugzilla snarfer enabled by default?') == 'n': if yn('Do you want the Bugzilla snarfer enabled by default?') == 'n':
onStart.append('Bugzilla config bug-snarfer off') onStart.append('Bugzilla config bug-snarfer off')
replyNoBugzilla = 'I don\'t have a bugzilla %r'
class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
"""Show a link to a bug report with a brief description""" """Show a link to a bug report with a brief description"""
threaded = True threaded = True
@ -104,28 +95,28 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
def __init__(self): def __init__(self):
callbacks.PrivmsgCommandAndRegexp.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self)
self.entre = re.compile('&(\S*?);') self.entre = re.compile('&(\S*?);')
# Schema: {name, [url, description]}
self.db = makeDb(dbfilename) self.db = makeDb(dbfilename)
self.shorthand = utils.abbrev(self.db.keys())
def die(self): def die(self):
self.db.close() self.db.close()
del self.db del self.db
def add(self, irc, msg, args): def add(self, irc, msg, args):
"""<abbreviation> <url> <description> """<name> <url> <description>
Add a bugzilla <url> to the list of defined bugzillae. <abbreviation> Add a bugzilla <url> to the list of defined bugzillae. <name>
is the name that will be used to reference the zilla in all other is the name that will be used to reference the zilla in all
commands. <description> is the common name for the bugzilla and will commands. Unambiguous abbreviations of <name> will be accepted also.
<description> is the common name for the bugzilla and will
be listed with the bugzilla query. be listed with the bugzilla query.
""" """
(shorthand, url, description) = privmsgs.getArgs(args, required=3) (name, url, description) = privmsgs.getArgs(args, required=3)
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("""INSERT INTO bugzillas VALUES (%s, %s, %s)""", self.db[name] = [url, description]
shorthand, url, description) self.shorthand = utils.abbrev(self.db.keys())
self.db.commit() irc.reply(msg, conf.replySuccess)
irc.reply(msg, 'Added bugzilla entry for "%s" with shorthand "%s"' % (
description, shorthand))
return
def remove(self, irc, msg, args): def remove(self, irc, msg, args):
"""<abbreviation> """<abbreviation>
@ -133,19 +124,14 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
Remove the bugzilla associated with <abbreviation> from the list of Remove the bugzilla associated with <abbreviation> from the list of
defined bugzillae. defined bugzillae.
""" """
shorthand = privmsgs.getArgs(args) name = privmsgs.getArgs(args)
cursor = self.db.cursor() try:
cursor.execute("""SELECT * from bugzillas where shorthand = %s""", name = self.shorthand[name]
shorthand) del self.db[name]
if cursor.rowcount == 0: self.shorthand = utils.abbrev(self.db.keys())
irc.reply(msg, 'Bugzilla "%s" not defined. Try list.' % irc.reply(msg, conf.replySuccess)
shorthand) except KeyError:
return irc.error(msg, replyNoBugzilla % name)
cursor.execute("""DELETE FROM bugzillas where shorthand = %s""",
shorthand)
self.db.commit()
irc.reply(msg, 'Deleted bugzilla "%s"' % shorthand)
return
def list(self, irc, msg, args): def list(self, irc, msg, args):
"""[<abbreviation>] """[<abbreviation>]
@ -153,26 +139,21 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
List defined bugzillae. If <abbreviation> is specified, list the List defined bugzillae. If <abbreviation> is specified, list the
information for that bugzilla. information for that bugzilla.
""" """
shorthand = privmsgs.getArgs(args, required=0, optional=1) name = privmsgs.getArgs(args, required=0, optional=1)
if shorthand: if name:
cursor = self.db.cursor() try:
cursor.execute("""SELECT url,description from bugzillas where name = self.shorthand[name]
shorthand = %s""", shorthand) (url, description) = self.db[name]
if cursor.rowcount == 0: irc.reply(msg, '%s: %s, %s' % (name, description, url))
irc.reply(msg, 'No such bugzilla defined: "%s".' % shorthand) except KeyError:
return irc.error(msg, replyNoBugzilla % name)
url, description = cursor.fetchone()
irc.reply(msg, '%s: %s, %s' % (shorthand, description, url))
return
else: else:
cursor = self.db.cursor() if self.db:
cursor.execute("""SELECT shorthand from bugzillas""") L = self.db.keys()
if cursor.rowcount == 0: L.sort()
irc.reply(msg, 'No bugzillae defined. Add some with "add"!') irc.reply(msg, utils.commaAndify(L))
return else:
results = ['%s' % (item[0]) for item in cursor.fetchall()] irc.reply(msg, 'I have no defined bugzillae.')
irc.reply(msg, 'Defined bugzillae: %s' % ' '.join(results))
return
def bzSnarfer(self, irc, msg, match): def bzSnarfer(self, irc, msg, match):
r"(http://\S+)/show_bug.cgi\?id=([0-9]+)" r"(http://\S+)/show_bug.cgi\?id=([0-9]+)"
@ -182,7 +163,7 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
try: try:
summary = self._get_short_bug_summary(queryurl, 'Snarfed '\ summary = self._get_short_bug_summary(queryurl, 'Snarfed '\
'Bugzilla URL', match.group(2)) 'Bugzilla URL', match.group(2))
except BugError, e: except BugzillaError, e:
irc.reply(msg, str(e)) irc.reply(msg, str(e))
return return
except IOError, e: except IOError, e:
@ -205,34 +186,28 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
Look up bug <number> in the bugzilla associated with <abbreviation>. Look up bug <number> in the bugzilla associated with <abbreviation>.
""" """
(shorthand, num) = privmsgs.getArgs(args, required=2) (name, number) = privmsgs.getArgs(args, required=2)
cursor = self.db.cursor()
cursor.execute("""SELECT url,description from bugzillas where
shorthand = %s""", shorthand)
if cursor.rowcount == 0:
irc.reply(msg, 'Bugzilla "%s" is not defined.' % shorthand)
return
if not self._is_bug_number(num):
irc.reply(msg, '"%s" does not seem to be a number' % num)
return
url, desc = cursor.fetchone()
queryurl = '%s/xml.cgi?id=%s' % (url, num)
try: try:
summary = self._get_short_bug_summary(queryurl, desc, num) name = self.shorthand[name]
except BugError, e: (url, description) = self.db[name]
irc.reply(msg, str(e)) except KeyError:
irc.error(msg, replyNoBugzilla % name)
return
queryurl = '%s/xml.cgi?id=%s' % (url, number)
try:
summary = self._get_short_bug_summary(queryurl,description,number)
except BugzillaError, e:
irc.error(msg, str(e))
return return
except IOError, e: except IOError, e:
msgtouser = '%s. Try yourself: %s' % (e, queryurl) s = '%s. Try yourself: %s' % (e, queryurl)
irc.reply(msg, msgtouser) irc.error(msg, s)
return
report = {} report = {}
report['zilla'] = str(desc) report['zilla'] = description
report['id'] = str(num) report['id'] = number
report['url'] = str('%s/show_bug.cgi?id=%s' % (url, num)) report['url'] = '%s/show_bug.cgi?id=%s' % (url, number)
report['title'] = str(summary['title']) report['title'] = str(summary['title'])
report['summary'] = str(self._mk_summary_string(summary)) report['summary'] = self._mk_summary_string(summary)
s = '%(zilla)s bug #%(id)s: %(title)s %(summary)s %(url)s' % report s = '%(zilla)s bug #%(id)s: %(title)s %(summary)s %(url)s' % report
irc.reply(msg, s) irc.reply(msg, s)
@ -250,27 +225,20 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
L.append(ircutils.bold('Status: ') + summary['status']) L.append(ircutils.bold('Status: ') + summary['status'])
if 'resolution' in summary: if 'resolution' in summary:
L.append(ircutils.bold('Resolution: ') + summary['resolution']) L.append(ircutils.bold('Resolution: ') + summary['resolution'])
return ', '.join(L) return ', '.join(map(str, L))
def _is_bug_number(self, bug): def _get_short_bug_summary(self, url, desc, number):
try:
int(bug)
return True
except ValueError:
return False
def _get_short_bug_summary(self, url, desc, num):
bugxml = self._getbugxml(url, desc) bugxml = self._getbugxml(url, desc)
try: zilladom = minidom.parseString(bugxml) try:
zilladom = minidom.parseString(bugxml)
except Exception, e: except Exception, e:
msg = 'Could not parse XML returned by %s bugzilla: %s' s = 'Could not parse XML returned by %s bugzilla: %s' % (desc, e)
raise BugError, msg % (desc, e) raise BugzillaError, s
bug_n = zilladom.getElementsByTagName('bug')[0] bug_n = zilladom.getElementsByTagName('bug')[0]
if bug_n.hasAttribute('error'): if bug_n.hasAttribute('error'):
errtxt = bug_n.getAttribute('error') errtxt = bug_n.getAttribute('error')
zilladom.unlink() s = 'Error getting %s bug #%s: %s' % (desc, number, errtxt)
msg = 'Error getting %s bug #%s: %s' % (desc, num, errtxt) raise BugzillaError, s
raise BugError(str(msg))
summary = {} summary = {}
try: try:
node = bug_n.getElementsByTagName('short_desc')[0] node = bug_n.getElementsByTagName('short_desc')[0]
@ -291,17 +259,15 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
node = bug_n.getElementsByTagName('bug_severity')[0] node = bug_n.getElementsByTagName('bug_severity')[0]
summary['severity'] = self._getnodetxt(node) summary['severity'] = self._getnodetxt(node)
except Exception, e: except Exception, e:
zilladom.unlink() s = 'Could not parse XML returned by %s bugzilla: %s' % (desc, e)
msg = 'Could not parse XML returned by %s bugzilla: %s' raise BugzillaError, s
raise BugError(str(msg % (desc, e)))
zilladom.unlink()
return summary return summary
def _getbugxml(self, url, desc): def _getbugxml(self, url, desc):
try: try:
fh = urllib.urlopen(url) fh = urllib2.urlopen(url)
except: except urllib2.UrlError, e:
raise IOError('Connection to %s bugzilla failed' % desc) raise IOError, 'Connection to %s bugzilla failed' % desc
bugxml = fh.read() bugxml = fh.read()
fh.close() fh.close()
if not bugxml: if not bugxml:
@ -324,9 +290,9 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
while self.entre.search(val): while self.entre.search(val):
entity = self.entre.search(val).group(1) entity = self.entre.search(val).group(1)
if entity in entities: if entity in entities:
val = re.sub(self.entre, entities[entity], val) val = self.entre.sub(entities[entity], val)
else: else:
val = re.sub(self.entre, '_', val) val = self.entre.sub('?', val)
return val return val