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

View File

@ -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