Bugtracker: Various improvements.

* Add option to set aliases on trackers.
* Complete removal and renaming of trackers.
* Only load default trackers if none set yet.
* Improve handling of auto-discovered trackers.
* Add function to reset trackers to defaults.
* Minor fix to Encyclopedia along the way.
This commit is contained in:
Krytarik Raido 2019-07-08 02:45:04 +02:00
parent aec19ef265
commit 182cc59009
4 changed files with 197 additions and 90 deletions

View File

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

View File

@ -18,9 +18,6 @@ import supybot.conf as conf
import supybot.registry as registry
import supybot.ircutils as ircutils
class Bugtrackers(registry.SpaceSeparatedListOfStrings):
List = ircutils.IrcSet
def configure(advanced):
from supybot.questions import expect, something, yn, output
@ -63,6 +60,8 @@ def configure(advanced):
showassignee = yn("Show the assignee of a bug in the reply?", default=Bugtracker.showassignee._default)
extended = yn("Show tracker-specific extended infomation?", default=Bugtracker.extended._default)
saveDiscoveredTrackers = yn("Save automatically discovered trackers to configuration?", default=Bugtracker.saveDiscoveredTrackers._default)
Bugtracker.bugSnarfer.setValue(bugSnarfer)
Bugtracker.cveSnarfer.setValue(cveSnarfer)
Bugtracker.oopsSnarfer.setValue(oopsSnarfer)
@ -72,6 +71,7 @@ def configure(advanced):
Bugtracker.repeatdelay.setValue(repeatdelay)
Bugtracker.showassignee.setValue(showassignee)
Bugtracker.extended.setValue(extended)
Bugtracker.saveDiscoveredTrackers.setValue(saveDiscoveredTrackers)
Bugtracker = conf.registerPlugin('Bugtracker')
@ -96,15 +96,13 @@ conf.registerChannelValue(Bugtracker, 'replyNoBugtracker',
bugtracker site."""))
conf.registerChannelValue(Bugtracker, 'snarfTarget',
registry.String('launchpad', """Determines the bugtracker to query when the
snarf command is triggered"""))
registry.String('launchpad', """Determines the bugtracker to query when the snarf command is triggered"""))
conf.registerGlobalValue(Bugtracker, 'bugtrackers',
Bugtrackers([], """Determines what bugtrackers will be added to the bot when it starts."""))
conf.registerGroup(Bugtracker, 'bugtrackers',
help="""Determines what bugtrackers will be added to the bot when it starts.""")
conf.registerGlobalValue(Bugtracker, 'replyWhenNotFound',
registry.Boolean(False, """Whether to send a message when a bug could not be
found"""))
registry.Boolean(False, """Whether to send a message when a bug could not be found"""))
conf.registerChannelValue(Bugtracker, 'repeatdelay',
registry.Integer(60, """Number of seconds to wait between repeated bug calls"""))
@ -114,3 +112,6 @@ conf.registerChannelValue(Bugtracker, 'showassignee',
conf.registerChannelValue(Bugtracker, 'extended',
registry.Boolean(False, """Whether to show extended bug information, specific to trackers"""))
conf.registerGlobalValue(Bugtracker, 'saveDiscoveredTrackers',
registry.Boolean(False, """Whether to save automatically discovered trackers to configuration"""))

View File

@ -29,16 +29,18 @@ import xml.dom.minidom as minidom
from email.parser import FeedParser
from pysimplesoap.client import SoapClient
def registerBugtracker(name, url='', description='', trackertype=''):
conf.supybot.plugins.Bugtracker.bugtrackers().add(name)
def registerBugtracker(name, url='', description='', trackertype='', aliases=[]):
group = conf.registerGroup(conf.supybot.plugins.Bugtracker.bugtrackers, name)
URL = conf.registerGlobalValue(group, 'url', registry.String(url, ''))
DESC = conf.registerGlobalValue(group, 'description', registry.String(description, ''))
TRACKERTYPE = conf.registerGlobalValue(group, 'trackertype', registry.String(trackertype, ''))
ALIASES = conf.registerGlobalValue(group, 'aliases', registry.SpaceSeparatedSetOfStrings(aliases, ''))
if url:
URL.setValue(url)
if description:
DESC.setValue(description)
if aliases:
ALIASES.setValue(aliases)
if trackertype:
if trackertype in defined_bugtrackers:
TRACKERTYPE.setValue(trackertype)
@ -125,17 +127,26 @@ class Bugtracker(callbacks.PluginRegexp):
def __init__(self, irc):
self.__parent = super(Bugtracker, self)
self.__parent.__init__(irc)
self.db = ircutils.IrcDict()
for name in self.registryValue('bugtrackers'):
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(), group.trackertype())
else:
supylog.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name))
self.shorthand = utils.abbrev(list(self.db.keys()))
self.set_trackers()
self.shown = {}
def set_trackers(self):
self.db = ircutils.IrcDict()
self.aliases = {}
trackers = self.registryValue('bugtrackers', value=False)._children
if not trackers:
for (name, (url, description, trackertype, aliases)) in list(default_bugtrackers.items()):
registerBugtracker(name, url, description, trackertype, aliases)
for name in list(trackers.keys()):
if trackers[name].trackertype() in defined_bugtrackers:
self.db[name] = defined_bugtrackers[trackers[name].trackertype()](name, trackers[name].url(),
trackers[name].description(), trackers[name].trackertype(), trackers[name].aliases())
for a in trackers[name].aliases():
self.aliases[a] = name
else:
supylog.warning("Bugtracker: Unknown trackertype: %s (%s)" % (trackers[name].trackertype(), name))
self.shorthand = utils.abbrev(list(self.db.keys()))
def is_ok(self, channel, tracker, bug):
"""Flood/repeat protection"""
now = time.time()
@ -151,7 +162,7 @@ class Bugtracker(callbacks.PluginRegexp):
"""<name> <type> <url> [<description>]
Add a bugtracker to the list of defined bugtrackers. Currently supported types are
Launchpad, Debbugs, Bugzilla, SourceForge, Github, GitLab, Gitea, Mantis, and Trac.
Launchpad, Debbugs, Bugzilla, SourceForge, GitHub, GitLab, Gitea, 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
@ -181,8 +192,12 @@ class Bugtracker(callbacks.PluginRegexp):
"""
try:
name = self.shorthand[name.lower()]
for a in self.db[name].aliases:
del self.aliases[a]
del self.db[name]
self.registryValue('bugtrackers').remove(name)
group = self.registryValue('bugtrackers', value=False)
if name in group._children:
group.unregister(name)
self.shorthand = utils.abbrev(list(self.db.keys()))
irc.replySuccess()
except KeyError:
@ -191,52 +206,140 @@ class Bugtracker(callbacks.PluginRegexp):
remove = wrap(remove, [('checkCapability', 'admin'), 'text'])
def rename(self, irc, msg, args, oldname, newname, newdesc):
"""<oldname> <newname>
"""<oldname> <newname> [<newdescription>]
Rename the bugtracker associated with <oldname> to <newname>.
Rename the bugtracker associated with <oldname> to <newname>,
optionally with <newdescription>.
"""
try:
name = self.shorthand[oldname.lower()]
group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False)
d = group.description()
oldname = self.shorthand[oldname.lower()]
newname = newname.lower()
tracker = self.db[oldname]
d = tracker.description
if newdesc:
d = newdesc
self.db[newname] = defined_bugtrackers[group.trackertype()](name, group.url(), d)
registerBugtracker(newname, group.url(), d, group.trackertype())
del self.db[name]
self.registryValue('bugtrackers').remove(name)
self.db[newname] = defined_bugtrackers[tracker.trackertype](newname, tracker.url, d, tracker.aliases)
registerBugtracker(newname, tracker.url, d, tracker.trackertype, tracker.aliases)
del self.db[oldname]
group = self.registryValue('bugtrackers', value=False)
if oldname in group._children:
group.unregister(oldname)
self.shorthand = utils.abbrev(list(self.db.keys()))
for a in tracker.aliases:
self.aliases[a] = newname
irc.replySuccess()
except KeyError:
s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None)
irc.error(s % oldname)
rename = wrap(rename, [('checkCapability', 'admin'), 'something', 'something', additional('text')])
def alias(self, irc, msg, args, name, alias):
"""<name> [<alias>]
Add an alias to a defined bugtracker, or list any set ones.
"""
try:
name = self.shorthand[name.lower()]
if not alias:
a = self.db[name].aliases
irc.reply('%s: %s' % (name, utils.str.commaAndify(sorted(a)) if a else 'No aliases set'))
return
alias = alias.lower()
self.db[name].aliases.add(alias)
trackers = self.registryValue('bugtrackers', value=False)._children
if name not in trackers:
tracker = self.db[name]
registerBugtracker(tracker.name, tracker.url, tracker.description, tracker.trackertype, tracker.aliases)
trackers[name].aliases().add(alias)
self.aliases[alias] = name
irc.replySuccess()
except KeyError:
s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None)
irc.error(s % name)
rename = wrap(rename, [('checkCapability', 'admin'), 'something', 'something', additional('text')])
alias = wrap(alias, [('checkCapability', 'admin'), 'something', additional('text')])
def unalias(self, irc, msg, args, name, alias):
"""<name> <alias>
Remove an alias from a defined bugtracker.
"""
try:
name = self.shorthand[name.lower()]
c = 'bugtrackers.%s.aliases' % name.replace('.','\\.')
alias = alias.lower()
try:
self.db[name].aliases.remove(alias)
trackers = self.registryValue('bugtrackers', value=False)._children
if name in trackers:
trackers[name].aliases().remove(alias)
del self.aliases[alias]
irc.replySuccess()
except ValueError:
irc.error("Bugtracker '%s' has no alias '%s' set" % (name, alias))
return
except KeyError:
s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None)
irc.error(s % name)
unalias = wrap(unalias, [('checkCapability', 'admin'), 'something', 'something'])
def list(self, irc, msg, args, name):
"""[abbreviation]
"""[<abbreviation>]
List defined bugtrackers. If [abbreviation] is specified, list the
List defined bugtrackers. If <abbreviation> is specified, list the
information for that bugtracker.
"""
if name:
name = name.lower()
try:
name = self.shorthand[name]
(url, description, trackertype) = (self.db[name].url, self.db[name].description,
self.db[name].__class__.__name__)
irc.reply('%s: %s, %s [%s]' % (name, description, url, trackertype))
name = self.shorthand[name.lower()]
tracker = self.db[name]
irc.reply('%s%s: %s, %s [%s]' % (name, ' (%s)' % ', '.join(sorted(tracker.aliases)) if tracker.aliases else '',
tracker.description, tracker.url, tracker.__class__.__name__))
except KeyError:
s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None)
irc.error(s % name)
else:
if self.db:
L = list(self.db.keys())
L.sort()
irc.reply(utils.str.commaAndify(L))
irc.reply(utils.str.commaAndify(sorted(list(self.db.keys()))))
else:
irc.reply('I have no defined bugtrackers.')
list = wrap(list, [additional('text')])
def reset(self, irc, msg, args, name):
"""[<abbreviation>]
Reset defined bugtrackers to defaults. If <abbreviation> is specified,
reset only that bugtracker.
"""
group = self.registryValue('bugtrackers', value=False)
if name:
try:
name = self.shorthand[name.lower()]
for a in self.db[name].aliases:
del self.aliases[a]
del self.db[name]
if name in group._children:
group.unregister(name)
if name in default_bugtrackers:
(url, description, trackertype, aliases) = default_bugtrackers[name]
if trackertype in defined_bugtrackers:
self.db[name] = defined_bugtrackers[trackertype](name, url, description, trackertype, aliases)
for a in aliases:
self.aliases[a] = name
registerBugtracker(name, url, description, trackertype, aliases)
else:
supylog.warning("Bugtracker: Unknown trackertype: %s (%s)" % (trackers[name].trackertype(), name))
self.shorthand = utils.abbrev(list(self.db.keys()))
except KeyError:
s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None)
irc.error(s % name)
return
else:
for name in list(group._children.keys())[:]:
group.unregister(name)
self.set_trackers()
irc.replySuccess()
reset = wrap(reset, [('checkCapability', 'admin'), additional('text')])
def inFilter(self, irc, msg):
if not (msg.prefix and msg.args):
return msg
@ -283,12 +386,16 @@ class Bugtracker(callbacks.PluginRegexp):
if len(bt) == 1 and not sure_bug:
try:
name = bt[0]
if name in list(self.aliases.keys()):
name = self.aliases[name]
tracker = self.db[name]
except:
return
elif len(bt) == 2:
try:
name = bt[0]
if name in list(self.aliases.keys()):
name = self.aliases[name]
tracker = self.db[name]
except:
name = ''
@ -405,10 +512,7 @@ class Bugtracker(callbacks.PluginRegexp):
snarfurl = re.sub(r'pad\.lv/(bug=)?(?P<bug>[0-9]+)', r'launchpad.net/bugs/\g<bug>', snarfurl)
for t in list(self.db.keys()):
tracker = self.db.get(t, None)
if not tracker:
supylog.error("No tracker for key '%s'" % t)
continue
tracker = self.db[t]
url = tracker.url[tracker.url.rfind('://')+3:]
if url in snarfurl:
return tracker
@ -433,6 +537,8 @@ class Bugtracker(callbacks.PluginRegexp):
if tracker:
self.db[tracker.name] = tracker
if self.registryValue('saveDiscoveredTrackers'):
registerBugtracker(tracker.name, tracker.url, tracker.description, tracker.trackertype, tracker.aliases)
self.shorthand = utils.abbrev(list(self.db.keys()))
return tracker
@ -501,11 +607,12 @@ class Bugtracker(callbacks.PluginRegexp):
# Define all bugtrackers
class IBugtracker:
def __init__(self, name=None, url=None, description=None, trackertype=None):
def __init__(self, name=None, url=None, description=None, trackertype=None, aliases=[]):
self.name = name
self.url = url
self.description = description
self.trackertype = trackertype
self.aliases = set(aliases)
self.errget = 'Could not get data from %s: %s (%s)'
self.errparse = 'Could not parse data from %s: %s (%s)'
@ -530,10 +637,10 @@ class IBugtracker:
class Bugzilla(IBugtracker):
def get_tracker(self, url):
try:
match = re.match(r'(?P<url>(?P<name>[^\s/]+).*)/show_bug\.cgi', url)
name = desc = match.group('name')
match = re.match(r'(?P<url>(?P<desc>[^\s/]+).*)/show_bug\.cgi', url)
desc = match.group('desc')
name = desc.lower()
url = 'https://%s' % match.group('url')
# registerBugtracker(name, url, desc, 'bugzilla')
return Bugzilla(name, url, desc, 'bugzilla')
except:
pass
@ -797,9 +904,9 @@ 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')
desc = match.group(0)
name = desc.lower()
url = 'https://%s' % desc
return SourceForge(name, url, desc, 'sourceforge')
except:
pass
@ -826,11 +933,11 @@ 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
desc = match.group(0)
url = 'https://%s' % desc
# Pulls are inconsistent in main and single page URLs
name = desc = re.sub(r'/pull$', r'/pulls', name)
# registerBugtracker(name, url, desc, 'github')
desc = re.sub(r'/pull$', r'/pulls', desc)
name = desc.lower()
return GitHub(name, url, desc, 'github')
except:
pass
@ -862,12 +969,12 @@ class GitLab(IBugtracker):
def get_tracker(self, url, id):
try:
match = re.match(r'[^\s/]+/[^\s/]+/[^\s/]+/(issues|merge_requests)', url)
name = desc = match.group(0)
url = 'https://%s' % name
desc = match.group(0)
name = desc.lower()
url = 'https://%s' % desc
bugurl = '%s/%d.json' % (url, id)
bugjson = utils.web.getUrl(bugurl)
bug = json.loads(bugjson.decode('utf-8'))
# registerBugtracker(name, url, desc, 'gitlab')
return GitLab(name, url, desc, 'gitlab')
except:
pass
@ -894,12 +1001,12 @@ class Gitea(IBugtracker):
def get_tracker(self, url, id):
try:
match = re.match(r'[^\s/]+/[^\s/]+/[^\s/]+/(issues|pulls)', url)
name = desc = match.group(0)
url = 'https://%s' % name
desc = match.group(0)
name = desc.lower()
url = 'https://%s' % desc
bugurl = '%s/%d' % (re.sub(r'://[^\s/]+/', r'\g<0>api/v1/repos/', url), id)
bugjson = utils.web.getUrl(bugurl)
bug = json.loads(bugjson.decode('utf-8'))
# registerBugtracker(name, url, desc, 'gitea')
return Gitea(name, url, desc, 'gitea')
except:
pass
@ -933,10 +1040,10 @@ class Mantis(IBugtracker):
def get_tracker(self, url):
try:
match = re.match(r'(?P<url>(?P<name>[^\s/]+).*)/view\.php', url)
name = desc = match.group('name')
match = re.match(r'(?P<url>(?P<desc>[^\s/]+).*)/view\.php', url)
desc = match.group('desc')
name = desc.lower()
url = 'https://%s' % match.group('url')
# registerBugtracker(name, url, desc, 'mantis')
return Mantis(name, url, desc, 'mantis')
except:
pass
@ -982,10 +1089,10 @@ class Mantis(IBugtracker):
class Trac(IBugtracker):
def get_tracker(self, url):
try:
match = re.match(r'(?P<name>[^\s/]+).*/ticket', url)
name = desc = match.group('name')
match = re.match(r'(?P<desc>[^\s/]+).*/ticket', url)
desc = match.group('desc')
name = desc.lower()
url = 'https://%s' % match.group(0)
# registerBugtracker(name, url, desc, 'trac')
return Trac(name, url, desc, 'trac')
except:
pass
@ -1033,23 +1140,22 @@ for k in list(v.keys()):
if type(v[k]) == type(IBugtracker) and issubclass(v[k], IBugtracker) and not (v[k] == IBugtracker):
defined_bugtrackers[k.lower()] = v[k]
registerBugtracker('mozilla', 'https://bugzilla.mozilla.org', 'Mozilla', 'bugzilla')
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')
registerBugtracker('ubuntu', 'https://launchpad.net', 'Ubuntu', 'launchpad')
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('pidgin', 'https://developer.pidgin.im/ticket', 'Pidgin', 'trac')
default_bugtrackers = {
'mozilla': ('https://bugzilla.mozilla.org', 'Mozilla', 'bugzilla', []),
'gtk': ('https://gitlab.gnome.org/GNOME/gtk/issues', 'GTK', 'gitlab', []),
'kde': ('https://bugs.kde.org', 'KDE', 'bugzilla', []),
'xfce': ('https://bugzilla.xfce.org', 'Xfce', 'bugzilla', []),
'lxde': ('https://sourceforge.net/p/lxde/bugs', 'LXDE', 'sourceforge', []),
'freedesktop': ('https://bugzilla.freedesktop.org', 'Freedesktop', 'bugzilla', []),
'freedesktop2': ('https://bugs.freedesktop.org', 'Freedesktop', 'bugzilla', []),
'openoffice': ('https://bz.apache.org/ooo', 'OpenOffice', 'bugzilla', []),
'launchpad': ('https://launchpad.net', 'Launchpad', 'launchpad', ['lp', 'ubuntu', 'ubottu']),
'debian': ('https://bugs.debian.org', 'Debian', 'debbugs', []),
'supybot': ('https://sourceforge.net/p/supybot/bugs', 'Supybot', 'sourceforge', []),
'irssi': ('https://github.com/irssi/irssi/issues', 'Irssi', 'github', []),
'mantis': ('https://www.mantisbt.org/bugs', 'Mantis', 'mantis', []),
'trac': ('https://trac.edgewall.org/ticket', 'Trac', 'trac', []),
'pidgin': ('https://developer.pidgin.im/ticket', 'Pidgin', 'trac', [])
}
Class = Bugtracker

View File

@ -432,7 +432,7 @@ class Encyclopedia(callbacks.Plugin):
return
if not text:
return
if text.split()[0].lower() in tuple(self.registryValue('ignores', channel)):
if text.split()[0].lower() in self.registryValue('ignores', channel):
return
doChanMsg = True # Send message to channel