Bugtracker: Update trackers.
* Add Bugzilla JSON, SourceForge, GitHub. * Make Mantis and Trac getable. * Deprecate Bugzilla XML.
This commit is contained in:
@ -24,7 +24,7 @@ import supybot.world as world
|
||||
|
||||
from imp import reload
|
||||
|
||||
__version__ = "2.8.0"
|
||||
__version__ = "2.9.0"
|
||||
__author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com")
|
||||
__contributors__ = {
|
||||
supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Author'],
|
||||
|
@ -24,7 +24,7 @@ import supybot.conf as conf
|
||||
import supybot.registry as registry
|
||||
import supybot.log as supylog
|
||||
|
||||
import re, os, sys, time
|
||||
import re, os, sys, time, json
|
||||
import xml.dom.minidom as minidom
|
||||
from email.parser import FeedParser
|
||||
if sys.version_info < (3,0):
|
||||
@ -131,7 +131,7 @@ class Bugtracker(callbacks.PluginRegexp):
|
||||
registerBugtracker(name)
|
||||
group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False)
|
||||
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:
|
||||
supylog.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name))
|
||||
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):
|
||||
"""<name> <type> <url> [<description>]
|
||||
|
||||
Add a bugtracker to the list of defined bugtrackers. Currently
|
||||
supported types are Launchpad, Debbugs, Bugzilla, Mantis, and Trac.
|
||||
Add a bugtracker to the list of defined bugtrackers. Currently supported
|
||||
types are Launchpad, Debbugs, Bugzilla, SourceForge, Github, Mantis, and Trac.
|
||||
<name> will be used to reference the bugtracker in all commands.
|
||||
Unambiguous abbreviations of it will also be accepted.
|
||||
<description> will be used to reference the bugtracker in the
|
||||
@ -165,7 +165,7 @@ class Bugtracker(callbacks.PluginRegexp):
|
||||
url = url[:-1]
|
||||
trackertype = trackertype.lower()
|
||||
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:
|
||||
irc.error("Bugtrackers of type '%s' are not understood" % trackertype)
|
||||
return
|
||||
@ -323,7 +323,7 @@ class Bugtracker(callbacks.PluginRegexp):
|
||||
irc.reply(r)
|
||||
|
||||
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
|
||||
if checkAddressed(msg.args[1].strip(), channel):
|
||||
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
|
||||
def get_tracker(self, snarfurl):
|
||||
# SourceForge short domain
|
||||
snarfurl = snarfurl.replace('sf.net', 'sourceforge.net', 1)
|
||||
|
||||
# Launchpad URL shortening
|
||||
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
|
||||
if 'show_bug.cgi' in snarfurl:
|
||||
tracker = Bugzilla().get_tracker(snarfurl)
|
||||
if tracker:
|
||||
self.db[tracker.name] = tracker
|
||||
self.shorthand = utils.abbrev(list(self.db.keys()))
|
||||
return tracker
|
||||
elif 'sourceforge.net' in snarfurl:
|
||||
tracker = SourceForge().get_tracker(snarfurl)
|
||||
elif 'github.com' in snarfurl:
|
||||
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
|
||||
|
||||
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):
|
||||
continue
|
||||
|
||||
if do_tracker:
|
||||
report = '%s bug %d' % (tracker.description, bid)
|
||||
bugtype = re.match(r'.*/(feature-)?(?P<type>request|patch|todo|issue|pull|ticket)(e?s)?/[0-9]+/?$', url)
|
||||
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:
|
||||
report = 'Bug %d' % bid
|
||||
if bugtype:
|
||||
report = '%s %d' % (bugtype.group('type').title(), bid)
|
||||
else:
|
||||
report = 'Bug %d' % bid
|
||||
|
||||
if product:
|
||||
report += ' in %s' % product
|
||||
@ -472,10 +496,11 @@ class Bugtracker(callbacks.PluginRegexp):
|
||||
|
||||
# Define all bugtrackers
|
||||
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.url = url
|
||||
self.description = description
|
||||
self.trackertype = trackertype
|
||||
self.errget = 'Could not get 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')
|
||||
url = 'https://%s' % match.group('url')
|
||||
# registerBugtracker(name, url, desc, 'bugzilla')
|
||||
return Bugzilla(name, url, desc)
|
||||
return Bugzilla(name, url, desc, 'bugzilla')
|
||||
except:
|
||||
return None
|
||||
|
||||
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)
|
||||
try:
|
||||
bugxml = utils.web.getUrl(url)
|
||||
@ -741,6 +791,71 @@ class Debbugs(IBugtracker):
|
||||
except Exception as e:
|
||||
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):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not sys.version_info < (3,0):
|
||||
@ -749,11 +864,25 @@ class Mantis(IBugtracker):
|
||||
IBugtracker.__init__(self, *args, **kwargs)
|
||||
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):
|
||||
url = "%s/view.php?id=%d" % (self.url, id)
|
||||
try:
|
||||
raw = self.soap_proxy.mc_issue_get('', '', id)
|
||||
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))
|
||||
if not raw:
|
||||
raise BugNotFoundError
|
||||
@ -767,11 +896,25 @@ class Mantis(IBugtracker):
|
||||
# has commas, things get tricky.
|
||||
# This should be more robust than the screen scraping done previously.
|
||||
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 :)
|
||||
url = "%s/%d" % (self.url, id)
|
||||
try:
|
||||
raw = utils.web.getUrl("%s?format=tab" % url).decode('utf-8')
|
||||
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):
|
||||
raise BugNotFoundError
|
||||
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('kde', 'https://bugs.kde.org', 'KDE', '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('freedesktop2', 'https://bugs.freedesktop.org', 'Freedesktop', '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('lp', 'https://launchpad.net', 'Launchpad', 'launchpad')
|
||||
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('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
|
||||
|
Reference in New Issue
Block a user