Permalink
Browse files

Reloading plugins on the fly.

  • Loading branch information...
1 parent 3821cb7 commit bf798e96179591daf1dc30ea4394010b38b2c6f8 @mythmon mythmon committed Jul 17, 2011
Showing with 57 additions and 12 deletions.
  1. +0 −1 bravo/plugin.py
  2. +5 −5 hamper/IHamper.py
  3. +25 −2 hamper/commander.py
  4. +1 −1 hamper/plugins/commands.py
  5. +26 −3 hamper/plugins/plugin_utils.py
View
@@ -231,7 +231,6 @@ def retrieve_plugins(interface, search, parameters=None):
"""
if not parameters and interface in __cache:
- print("Returning cached plugin.")
return __cache[interface]
log.msg("Discovering %s..." % interface)
View
@@ -1,20 +1,20 @@
from zope.interface import implements, Interface, Attribute
-class ICommand(Interface):
- """Interface for a command.."""
+class IPlugin(Interface):
+ """Interface for a plugin.."""
name = Attribute('Human readable name for the plugin.')
onlyDirected = Attribute('Only respond to messages directed at the bot.')
caseSensitive = Attribute('Compile the regex to be caseSensitive if True.')
- regex = Attribute('What messages the command will be called for.')
+ regex = Attribute('What messages the plugin will be called for.')
priority = Attribute('Higher numbers are called first.')
def __call__(commander, options):
"""
Called when a matching message comes in to the bot.
- Return `True` if the next command should be called, when there are
- multiple commands with the same priority. Returning `False` or not
+ Return `True` if the next plugin should be called, when there are
+ multiple plugins with the same priority. Returning `False` or not
returing a value will cause execution to stop.
"""
View
@@ -65,12 +65,25 @@ def privmsg(self, user, channel, msg):
self.factory.history[key] = deque(maxlen=100)
self.factory.history[key].append(comm)
+ # We can't remove/add plugins while we are in the loop, so do it here.
+ while self.factory.pluginsToRemove:
+ self.factory.plugins.remove(self.factory.pluginsToRemove.pop())
+
+ while self.factory.pluginsToAdd:
+ self.factory.registerPlugin(self.factory.pluginsToAdd.pop())
+
def connectionLost(self, reason):
reactor.stop()
def say(self, msg):
self.msg(self.factory.channel, msg)
+ def removePlugin(self, plugin):
+ self.factory.pluginsToRemove.add(plugin)
+
+ def addPlugin(self, plugin):
+ self.factory.pluginsToAdd.add(plugin)
+
class CommanderFactory(protocol.ClientFactory):
@@ -83,6 +96,12 @@ def __init__(self, channel, nickname):
self.history = {}
self.plugins = set()
+ # These are so plugins can be added/removed at run time. The
+ # addition/removal will happen at a time when the set isn't being
+ # iterated, so nothing breaks.
+ self.pluginsToAdd = set()
+ self.pluginsToRemove = set()
+
for _, plugin in retrieve_plugins(IPlugin, 'hamper.plugins').items():
self.registerPlugin(plugin)
@@ -93,9 +112,13 @@ def clientConnectionFailed(self, connector, reason):
print "Could not connect: %s" % (reason,)
def registerPlugin(self, plugin):
- """Register a plugin. To be used as a decorator."""
+ """
+ Registers a plugin.
+
+ Also sets up the regex and other options for the plugin.
+ """
options = re.I if not plugin.caseSensitive else None
plugin.regex = re.compile(plugin.regex, options)
- self.plugin.add(plugin)
+ self.plugins.add(plugin)
print 'registered', plugin.name
@@ -25,7 +25,7 @@ class Friendly(Plugin):
regex = 'hi'
def __call__(self, commander, options):
- commander.say('Hello {0[user]}'.format(options))
+ commander.say('Hello {0[user]}!'.format(options))
class QuitCommand(Plugin):
@@ -1,29 +1,52 @@
from zope.interface import implements, Interface, Attribute
+from bravo import plugin
+
from hamper.plugins.commands import Plugin
+from hamper.IHamper import IPlugin
class PluginUtils(Plugin):
- names = 'Plugin Utils'
+ name = 'Plugin Utils'
regex = '^plugins?(.*)$'
def __call__(self, commander, options):
args = options['groups'][0].split(' ')
args = [a.strip() for a in args]
args = [a for a in args if a]
- commander.say('args: ' + repr(args))
dispatch = {
'list': self.listPlugins,
+ 'reload': self.reloadPlugin,
}
dispatch[args[0]](commander, args[1:])
def listPlugins(self, commander, args):
"""Reply with a list of all currently loaded plugins."""
commander.say('Loaded Plugins: {0}.'.format(
- ', '.join([c.name for c in commander.factory.commands])))
+ ', '.join([c.name for c in commander.factory.plugins])))
+
+ def reloadPlugin(self, commander, args):
+ """Reload a named plugin."""
+ name = ' '.join(args)
+
+ ps = commander.factory.plugins
+
+ matched_plugins = [p for p in ps if p.name == name]
+ if len(matched_plugins) == 0:
+ commander.say("I can't find a plugin named %s!" % name)
+ return
+
+ target_plugin = matched_plugins[0]
+ # Fun fact: the fresh thing is just a dummy. It just can't be None
+ new_plugin = plugin.retrieve_named_plugins(IPlugin, [name],
+ 'hamper.plugins', {'fresh': True})[0]
+
+ commander.removePlugin(target_plugin)
+ commander.addPlugin(new_plugin)
+ commander.say('Request reload of {0}.'.format(new_plugin))
plugin_utils = PluginUtils()

0 comments on commit bf798e9

Please sign in to comment.