Bugtracker: Update trackers.

* Add Bugzilla JSON, SourceForge, GitHub.
* Make Mantis and Trac getable.
* Deprecate Bugzilla XML.
This commit is contained in:
Krytarik Raido
2018-01-30 07:45:04 +01:00
parent 1e4fc7f3d0
commit 11b6996639
2 changed files with 163 additions and 17 deletions

View File

@ -24,7 +24,7 @@ import supybot.world as world
from imp import reload from imp import reload
__version__ = "2.8.0" __version__ = "2.9.0"
__author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com") __author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com")
__contributors__ = { __contributors__ = {
supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Author'], supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Author'],

View File

@ -24,7 +24,7 @@ import supybot.conf as conf
import supybot.registry as registry import supybot.registry as registry
import supybot.log as supylog import supybot.log as supylog
import re, os, sys, time import re, os, sys, time, json
import xml.dom.minidom as minidom import xml.dom.minidom as minidom
from email.parser import FeedParser from email.parser import FeedParser
if sys.version_info < (3,0): if sys.version_info < (3,0):
@ -131,7 +131,7 @@ class Bugtracker(callbacks.PluginRegexp):
registerBugtracker(name) registerBugtracker(name)
group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False) group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False)
if group.trackertype() in defined_bugtrackers: if group.trackertype() in defined_bugtrackers:
self.db[name] = defined_bugtrackers[group.trackertype()](name, group.url(), group.description()) self.db[name] = defined_bugtrackers[group.trackertype()](name, group.url(), group.description(), group.trackertype())
else: else:
supylog.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name)) supylog.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name))
self.shorthand = utils.abbrev(list(self.db.keys())) self.shorthand = utils.abbrev(list(self.db.keys()))
@ -151,8 +151,8 @@ class Bugtracker(callbacks.PluginRegexp):
def add(self, irc, msg, args, name, trackertype, url, description): def add(self, irc, msg, args, name, trackertype, url, description):
"""<name> <type> <url> [<description>] """<name> <type> <url> [<description>]
Add a bugtracker to the list of defined bugtrackers. Currently Add a bugtracker to the list of defined bugtrackers. Currently supported
supported types are Launchpad, Debbugs, Bugzilla, Mantis, and Trac. types are Launchpad, Debbugs, Bugzilla, SourceForge, Github, Mantis, and Trac.
<name> will be used to reference the bugtracker in all commands. <name> will be used to reference the bugtracker in all commands.
Unambiguous abbreviations of it will also be accepted. Unambiguous abbreviations of it will also be accepted.
<description> will be used to reference the bugtracker in the <description> will be used to reference the bugtracker in the
@ -165,7 +165,7 @@ class Bugtracker(callbacks.PluginRegexp):
url = url[:-1] url = url[:-1]
trackertype = trackertype.lower() trackertype = trackertype.lower()
if trackertype in defined_bugtrackers: if trackertype in defined_bugtrackers:
self.db[name] = defined_bugtrackers[trackertype](name, url, description) self.db[name] = defined_bugtrackers[trackertype](name, url, description, trackertype)
else: else:
irc.error("Bugtrackers of type '%s' are not understood" % trackertype) irc.error("Bugtrackers of type '%s' are not understood" % trackertype)
return return
@ -323,7 +323,7 @@ class Bugtracker(callbacks.PluginRegexp):
irc.reply(r) irc.reply(r)
def turlSnarfer(self, irc, msg, match): def turlSnarfer(self, irc, msg, match):
r"(https?://)?((bugs\.debian\.org|pad\.lv)/|\S+/(show_bug\.cgi\?id=|bugreport\.cgi\?bug=|view\.php\?id=|bug=|bugs/|\+bug/|ticket/))(?P<bug>\d+)" r"(https?://)?((bugs\.debian\.org|pad\.lv)/|\S+/(show_bug\.cgi\?id=|bugreport\.cgi\?bug=|view\.php\?id=|bug=|bugs/|\+bug/|ticket/|feature-requests/|patches/|todo/|issues/|pulls?/))(?P<bug>\d+)/?"
channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None
if checkAddressed(msg.args[1].strip(), channel): if checkAddressed(msg.args[1].strip(), channel):
return return
@ -396,6 +396,9 @@ class Bugtracker(callbacks.PluginRegexp):
#TODO: As we will depend on launchpadlib, we should consider using lazr.uri.URI to do URL parsing #TODO: As we will depend on launchpadlib, we should consider using lazr.uri.URI to do URL parsing
def get_tracker(self, snarfurl): def get_tracker(self, snarfurl):
# SourceForge short domain
snarfurl = snarfurl.replace('sf.net', 'sourceforge.net', 1)
# Launchpad URL shortening # Launchpad URL shortening
snarfurl = re.sub(r'pad\.lv/(bug=)?(?P<bug>[0-9]+)', r'launchpad.net/bugs/\g<bug>', snarfurl) snarfurl = re.sub(r'pad\.lv/(bug=)?(?P<bug>[0-9]+)', r'launchpad.net/bugs/\g<bug>', snarfurl)
@ -411,10 +414,21 @@ class Bugtracker(callbacks.PluginRegexp):
# No tracker found, bummer. Let's try and get one # No tracker found, bummer. Let's try and get one
if 'show_bug.cgi' in snarfurl: if 'show_bug.cgi' in snarfurl:
tracker = Bugzilla().get_tracker(snarfurl) tracker = Bugzilla().get_tracker(snarfurl)
if tracker: elif 'sourceforge.net' in snarfurl:
self.db[tracker.name] = tracker tracker = SourceForge().get_tracker(snarfurl)
self.shorthand = utils.abbrev(list(self.db.keys())) elif 'github.com' in snarfurl:
return tracker tracker = GitHub().get_tracker(snarfurl)
elif 'view.php' in snarfurl:
tracker = Mantis().get_tracker(snarfurl)
elif '/ticket/' in snarfurl:
tracker = Trac().get_tracker(snarfurl)
else:
return None
if tracker:
self.db[tracker.name] = tracker
self.shorthand = utils.abbrev(list(self.db.keys()))
return tracker
return None return None
def get_bug(self, channel, tracker, id, do_assignee, do_extinfo, do_url=True, do_tracker=True): def get_bug(self, channel, tracker, id, do_assignee, do_extinfo, do_url=True, do_tracker=True):
@ -430,10 +444,20 @@ class Bugtracker(callbacks.PluginRegexp):
if duplicate and not self.is_ok(channel, tracker, bid): if duplicate and not self.is_ok(channel, tracker, bid):
continue continue
if do_tracker: bugtype = re.match(r'.*/(feature-)?(?P<type>request|patch|todo|issue|pull|ticket)(e?s)?/[0-9]+/?$', url)
report = '%s bug %d' % (tracker.description, bid) if do_tracker and tracker.trackertype != 'github':
if re.match(r'.*/(bugs|feature-requests|patches|todo|issues|pulls?|ticket)/?$', tracker.description):
report = '%s %d' % (tracker.description, bid)
else:
if bugtype:
report = '%s %s %d' % (tracker.description, bugtype.group('type'), bid)
else:
report = '%s bug %d' % (tracker.description, bid)
else: else:
report = 'Bug %d' % bid if bugtype:
report = '%s %d' % (bugtype.group('type').title(), bid)
else:
report = 'Bug %d' % bid
if product: if product:
report += ' in %s' % product report += ' in %s' % product
@ -472,10 +496,11 @@ class Bugtracker(callbacks.PluginRegexp):
# Define all bugtrackers # Define all bugtrackers
class IBugtracker: class IBugtracker:
def __init__(self, name=None, url=None, description=None): def __init__(self, name=None, url=None, description=None, trackertype=None):
self.name = name self.name = name
self.url = url self.url = url
self.description = description self.description = description
self.trackertype = trackertype
self.errget = 'Could not get data from %s: %s (%s)' self.errget = 'Could not get data from %s: %s (%s)'
self.errparse = 'Could not parse data from %s: %s (%s)' self.errparse = 'Could not parse data from %s: %s (%s)'
@ -504,11 +529,36 @@ class Bugzilla(IBugtracker):
name = desc = match.group('name') name = desc = match.group('name')
url = 'https://%s' % match.group('url') url = 'https://%s' % match.group('url')
# registerBugtracker(name, url, desc, 'bugzilla') # registerBugtracker(name, url, desc, 'bugzilla')
return Bugzilla(name, url, desc) return Bugzilla(name, url, desc, 'bugzilla')
except: except:
return None return None
def get_bug(self, id): def get_bug(self, id):
url = "%s/rest/bug/%d" % (self.url, id)
try:
bugjson = utils.web.getUrl(url)
bug = json.loads(bugjson.decode('utf-8'))['bugs'][0]
except Exception as e:
# For old-stable Bugzilla
if 'HTTP Error 404' in str(e):
return self.get_bug_old(id)
raise BugtrackerError(self.errget % (self.description, e, url))
try:
status = bug['status']
if bug['resolution']:
status += ': %s' % bug['resolution']
if bug['assigned_to_detail']:
assignee = bug['assigned_to_detail']['real_name']
if not assignee:
assignee = bug['assigned_to_detail']['name']
else:
assignee = ''
return [(id, bug['product'], bug['summary'], bug['severity'], status, assignee,
"%s/show_bug.cgi?id=%d" % (self.url, id), [], [])]
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
def get_bug_old(self, id): # Deprecated
url = "%s/show_bug.cgi?id=%d&ctype=xml" % (self.url, id) url = "%s/show_bug.cgi?id=%d&ctype=xml" % (self.url, id)
try: try:
bugxml = utils.web.getUrl(url) bugxml = utils.web.getUrl(url)
@ -741,6 +791,71 @@ class Debbugs(IBugtracker):
except Exception as e: except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url)) raise BugtrackerError(self.errparse % (self.description, e, url))
class SourceForge(IBugtracker):
def get_tracker(self, url):
try:
match = re.match(r'sourceforge\.net/p/[^\s/]+/(bugs|feature-requests|patches|todo)', url)
name = desc = match.group(0)
url = 'https://%s' % name
# registerBugtracker(name, url, desc, 'sourceforge')
return SourceForge(name, url, desc, 'sourceforge')
except:
return None
def get_bug(self, id):
url = "%s/%d/" % (self.url.replace('sourceforge.net', 'sourceforge.net/rest'), id)
try:
bugjson = utils.web.getUrl(url)
bug = json.loads(bugjson.decode('utf-8'))['ticket']
except Exception as e:
raise BugtrackerError(self.errget % (self.description, e, url))
try:
product = severity = ''
if bug['labels']:
product = bug['labels'][0]
if '_priority' in bug['custom_fields']:
severity = 'Pri: %s' % bug['custom_fields']['_priority']
return [(id, product, bug['summary'], severity, ': '.join(bug['status'].split('-')),
bug['assigned_to'], "%s/%d/" % (self.url, id), [], [])]
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
class GitHub(IBugtracker):
def get_tracker(self, url):
try:
match = re.match(r'github\.com/[^\s/]+/[^\s/]+/(issues|pulls?)', url)
name = desc = match.group(0)
url = 'https://%s' % name
# Pulls are inconsistent in main and single page URLs
name = desc = re.sub(r'/pull$', r'/pulls', name)
# registerBugtracker(name, url, desc, 'github')
return GitHub(name, url, desc, 'github')
except:
return None
def get_bug(self, id):
url = "%s/%d" % (self.url.replace('github.com', 'api.github.com/repos'), id)
# Pulls are inconsistent in web and API URLs
url = url.replace('/pull/', '/pulls/')
try:
bugjson = utils.web.getUrl(url)
bug = json.loads(bugjson.decode('utf-8'))
except Exception as e:
raise BugtrackerError(self.errget % (self.description, e, url))
try:
product = '/'.join(url.split('/')[-4:-2])
if 'merged' in bug and bug['merged']:
status = 'Merged'
else:
status = bug['state']
if bug['assignee']:
assignee = bug['assignee']['login']
else:
assignee = ''
return [(id, product, bug['title'], '', status, assignee, bug['html_url'], [], [])]
except Exception as e:
raise BugtrackerError(self.errparse % (self.description, e, url))
class Mantis(IBugtracker): class Mantis(IBugtracker):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if not sys.version_info < (3,0): if not sys.version_info < (3,0):
@ -749,11 +864,25 @@ class Mantis(IBugtracker):
IBugtracker.__init__(self, *args, **kwargs) IBugtracker.__init__(self, *args, **kwargs)
self.soap_proxy = SOAPProxy("%s/api/soap/mantisconnect.php" % self.url, namespace="http://futureware.biz/mantisconnect") self.soap_proxy = SOAPProxy("%s/api/soap/mantisconnect.php" % self.url, namespace="http://futureware.biz/mantisconnect")
def get_tracker(self, url):
try:
match = re.match(r'(?P<url>(?P<name>[^\s/]+).*)/view\.php', url)
name = desc = match.group('name')
url = 'https://%s' % match.group('url')
# registerBugtracker(name, url, desc, 'mantis')
return Mantis(name, url, desc, 'mantis')
except:
return None
def get_bug(self, id): def get_bug(self, id):
url = "%s/view.php?id=%d" % (self.url, id) url = "%s/view.php?id=%d" % (self.url, id)
try: try:
raw = self.soap_proxy.mc_issue_get('', '', id) raw = self.soap_proxy.mc_issue_get('', '', id)
except Exception as e: except Exception as e:
# Often SOAP is not enabled
if '.' in self.name:
supylog.exception(self.errget % (self.description, e, url))
return
raise BugtrackerError(self.errget % (self.description, e, url)) raise BugtrackerError(self.errget % (self.description, e, url))
if not raw: if not raw:
raise BugNotFoundError raise BugNotFoundError
@ -767,11 +896,25 @@ class Mantis(IBugtracker):
# has commas, things get tricky. # has commas, things get tricky.
# This should be more robust than the screen scraping done previously. # This should be more robust than the screen scraping done previously.
class Trac(IBugtracker): class Trac(IBugtracker):
def get_tracker(self, url):
try:
match = re.match(r'(?P<name>[^\s/]+).*/ticket', url)
name = desc = match.group('name')
url = 'https://%s' % match.group(0)
# registerBugtracker(name, url, desc, 'trac')
return Trac(name, url, desc, 'trac')
except:
return None
def get_bug(self, id): # This is still a little rough, but it works :) def get_bug(self, id): # This is still a little rough, but it works :)
url = "%s/%d" % (self.url, id) url = "%s/%d" % (self.url, id)
try: try:
raw = utils.web.getUrl("%s?format=tab" % url).decode('utf-8') raw = utils.web.getUrl("%s?format=tab" % url).decode('utf-8')
except Exception as e: except Exception as e:
# Due to unreliable matching
if '.' in self.name:
supylog.exception(self.errget % (self.description, e, url))
return
if 'HTTP Error 500' in str(e): if 'HTTP Error 500' in str(e):
raise BugNotFoundError raise BugNotFoundError
raise BugtrackerError(self.errget % (self.description, e, url)) raise BugtrackerError(self.errget % (self.description, e, url))
@ -807,6 +950,7 @@ registerBugtracker('gnome', 'https://bugzilla.gnome.org', 'Gnome', 'bugzilla')
registerBugtracker('gnome2', 'https://bugs.gnome.org', 'Gnome', 'bugzilla') registerBugtracker('gnome2', 'https://bugs.gnome.org', 'Gnome', 'bugzilla')
registerBugtracker('kde', 'https://bugs.kde.org', 'KDE', 'bugzilla') registerBugtracker('kde', 'https://bugs.kde.org', 'KDE', 'bugzilla')
registerBugtracker('xfce', 'https://bugzilla.xfce.org', 'Xfce', 'bugzilla') registerBugtracker('xfce', 'https://bugzilla.xfce.org', 'Xfce', 'bugzilla')
registerBugtracker('lxde', 'https://sourceforge.net/p/lxde/bugs', 'LXDE', 'sourceforge')
registerBugtracker('freedesktop', 'https://bugzilla.freedesktop.org', 'Freedesktop', 'bugzilla') registerBugtracker('freedesktop', 'https://bugzilla.freedesktop.org', 'Freedesktop', 'bugzilla')
registerBugtracker('freedesktop2', 'https://bugs.freedesktop.org', 'Freedesktop', 'bugzilla') registerBugtracker('freedesktop2', 'https://bugs.freedesktop.org', 'Freedesktop', 'bugzilla')
registerBugtracker('openoffice', 'https://bz.apache.org/ooo', 'OpenOffice', 'bugzilla') registerBugtracker('openoffice', 'https://bz.apache.org/ooo', 'OpenOffice', 'bugzilla')
@ -815,7 +959,9 @@ registerBugtracker('ubottu', 'https://launchpad.net', 'Ubottu', 'launchpad')
registerBugtracker('launchpad', 'https://launchpad.net', 'Launchpad', 'launchpad') registerBugtracker('launchpad', 'https://launchpad.net', 'Launchpad', 'launchpad')
registerBugtracker('lp', 'https://launchpad.net', 'Launchpad', 'launchpad') registerBugtracker('lp', 'https://launchpad.net', 'Launchpad', 'launchpad')
registerBugtracker('debian', 'https://bugs.debian.org', 'Debian', 'debbugs') registerBugtracker('debian', 'https://bugs.debian.org', 'Debian', 'debbugs')
registerBugtracker('supybot', 'https://sourceforge.net/p/supybot/bugs', 'Supybot', 'sourceforge')
registerBugtracker('irssi', 'https://github.com/irssi/irssi/issues', 'irssi/irssi', 'github')
registerBugtracker('mantis', 'https://www.mantisbt.org/bugs', 'Mantis', 'mantis') registerBugtracker('mantis', 'https://www.mantisbt.org/bugs', 'Mantis', 'mantis')
registerBugtracker('trac', 'https://trac.edgewall.org/ticket', 'Trac', 'trac') registerBugtracker('trac', 'https://trac.edgewall.org/ticket', 'Trac', 'trac')
registerBugtracker('django', 'https://code.djangoproject.com/ticket', 'Django', 'trac') registerBugtracker('pidgin', 'https://developer.pidgin.im/ticket', 'Pidgin', 'trac')
Class = Bugtracker Class = Bugtracker