From 182cc5900931bbab77e2358eefbb529aa0b2c19d Mon Sep 17 00:00:00 2001 From: Krytarik Raido Date: Mon, 8 Jul 2019 02:45:04 +0200 Subject: [PATCH] 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. --- Bugtracker/__init__.py | 2 +- Bugtracker/config.py | 19 +-- Bugtracker/plugin.py | 264 +++++++++++++++++++++++++++++------------ Encyclopedia/plugin.py | 2 +- 4 files changed, 197 insertions(+), 90 deletions(-) diff --git a/Bugtracker/__init__.py b/Bugtracker/__init__.py index 87d8544..d6bdf37 100644 --- a/Bugtracker/__init__.py +++ b/Bugtracker/__init__.py @@ -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'], diff --git a/Bugtracker/config.py b/Bugtracker/config.py index 83067e9..b62a666 100644 --- a/Bugtracker/config.py +++ b/Bugtracker/config.py @@ -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""")) diff --git a/Bugtracker/plugin.py b/Bugtracker/plugin.py index 5f62589..f0c0e3e 100644 --- a/Bugtracker/plugin.py +++ b/Bugtracker/plugin.py @@ -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): """ [] 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. will be used to reference the bugtracker in all commands. Unambiguous abbreviations of it will also be accepted. 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): - """ + """ [] - Rename the bugtracker associated with to . + Rename the bugtracker associated with to , + optionally with . """ 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): + """ [] + + 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): + """ + + 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] + """[] - List defined bugtrackers. If [abbreviation] is specified, list the + List defined bugtrackers. If 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): + """[] + + Reset defined bugtrackers to defaults. If 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[0-9]+)', r'launchpad.net/bugs/\g', 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(?P[^\s/]+).*)/show_bug\.cgi', url) - name = desc = match.group('name') + match = re.match(r'(?P(?P[^\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(?P[^\s/]+).*)/view\.php', url) - name = desc = match.group('name') + match = re.match(r'(?P(?P[^\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[^\s/]+).*/ticket', url) - name = desc = match.group('name') + match = re.match(r'(?P[^\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 diff --git a/Encyclopedia/plugin.py b/Encyclopedia/plugin.py index da8b7ec..2b5c7dc 100644 --- a/Encyclopedia/plugin.py +++ b/Encyclopedia/plugin.py @@ -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