From e075c72270bd1ff0ec07008dd967324ba19b3b17 Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Tue, 1 Feb 2005 06:08:46 +0000 Subject: [PATCH] Added the Scheduler plugin in the new plugin format. --- plugins/Scheduler/README.txt | 1 + plugins/Scheduler/__init__.py | 61 ++++++++++++++++ plugins/Scheduler/config.py | 48 +++++++++++++ plugins/Scheduler/plugin.py | 130 ++++++++++++++++++++++++++++++++++ plugins/Scheduler/test.py | 85 ++++++++++++++++++++++ setup.py | 1 + 6 files changed, 326 insertions(+) create mode 100644 plugins/Scheduler/README.txt create mode 100644 plugins/Scheduler/__init__.py create mode 100644 plugins/Scheduler/config.py create mode 100644 plugins/Scheduler/plugin.py create mode 100644 plugins/Scheduler/test.py diff --git a/plugins/Scheduler/README.txt b/plugins/Scheduler/README.txt new file mode 100644 index 000000000..d60b47a97 --- /dev/null +++ b/plugins/Scheduler/README.txt @@ -0,0 +1 @@ +Insert a description of your plugin here, with any notes, etc. about using it. diff --git a/plugins/Scheduler/__init__.py b/plugins/Scheduler/__init__.py new file mode 100644 index 000000000..95f863ec2 --- /dev/null +++ b/plugins/Scheduler/__init__.py @@ -0,0 +1,61 @@ +### +# Copyright (c) 2005, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +""" +Gives the user the ability to schedule commands to run at a particular time, +or repeatedly run at a particular interval. +""" + +import supybot +import supybot.world as world + +# Use this for the version of this plugin. You may wish to put a CVS keyword +# in here if you\'re keeping the plugin in CVS or some similar system. +__version__ = "%%VERSION%%" + +__author__ = supybot.authors.jemfinch + +# This is a dictionary mapping supybot.Author instances to lists of +# contributions. +__contributors__ = {} + +import config +import plugin +reload(plugin) # In case we're being reloaded. +# Add more reloads here if you add third-party modules and want them to be +# reloaded when this plugin is reloaded. Don't forget to import them as well! + +if world.testing: + import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Scheduler/config.py b/plugins/Scheduler/config.py new file mode 100644 index 000000000..4fb964c72 --- /dev/null +++ b/plugins/Scheduler/config.py @@ -0,0 +1,48 @@ +### +# Copyright (c) 2005, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +import supybot.conf as conf +import supybot.registry as registry + +def configure(advanced): + # This will be called by supybot to configure this module. advanced is + # a bool that specifies whether the user identified himself as an advanced + # user or not. You should effect your configuration by manipulating the + # registry as appropriate. + from supybot.questions import expect, anything, something, yn + conf.registerPlugin('Scheduler', True) + + +Scheduler = conf.registerPlugin('Scheduler') +# This is where your configuration variables (if any) should go. For example: +# conf.registerGlobalValue(Scheduler, 'someConfigVariableName', +# registry.Boolean(False, """Help for someConfigVariableName.""")) + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78 diff --git a/plugins/Scheduler/plugin.py b/plugins/Scheduler/plugin.py new file mode 100644 index 000000000..8fe99c1f6 --- /dev/null +++ b/plugins/Scheduler/plugin.py @@ -0,0 +1,130 @@ +### +# Copyright (c) 2003-2004, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +import sets +import time + +import supybot.conf as conf +import supybot.utils as utils +from supybot.commands import * +import supybot.privmsgs as privmsgs +import supybot.schedule as schedule +import supybot.callbacks as callbacks + +class Scheduler(callbacks.Privmsg): + def __init__(self, irc): + self.__parent = super(Scheduler, self) + self.__parent.__init__(irc) + self.events = {} + + def _makeCommandFunction(self, irc, msg, command, remove=True): + """Makes a function suitable for scheduling from command.""" + tokens = callbacks.tokenize(command) + def f(): + if remove: + del self.events[str(f.eventId)] + self.Proxy(irc.irc, msg, tokens) + return f + + def add(self, irc, msg, args, seconds, command): + """ + + Schedules the command string to run seconds in the + future. For example, 'scheduler add [seconds 30m] "echo [cpu]"' will + schedule the command "cpu" to be sent to the channel the schedule add + command was given in (with no prefixed nick, a consequence of using + echo). Do pay attention to the quotes in that example. + """ + f = self._makeCommandFunction(irc, msg, command) + id = schedule.addEvent(f, time.time() + seconds) + f.eventId = id + self.events[str(id)] = command + irc.replySuccess(format('Event #%i added.', id)) + add = wrap(add, ['positiveInt', 'text']) + + def remove(self, irc, msg, args, id): + """ + + Removes the event scheduled with id from the schedule. + """ + if id in self.events: + del self.events[id] + try: + id = int(id) + except ValueError: + pass + try: + schedule.removeEvent(id) + irc.replySuccess() + except KeyError: + irc.error('Invalid event id.') + else: + irc.error('Invalid event id.') + remove = wrap(remove, ['lowered']) + + def repeat(self, irc, msg, args, name, seconds, command): + """ + + Schedules the command to run every seconds, + starting now (i.e., the command runs now, and every seconds + thereafter). is a name by which the command can be + unscheduled. + """ + name = name.lower() + if name in self.events: + irc.error('There is already an event with that name, please ' + 'choose another name.', Raise=True) + self.events[name] = command + f = self._makeCommandFunction(irc, msg, command, remove=False) + id = schedule.addPeriodicEvent(f, seconds, name) + assert id == name + # We don't reply because the command runs immediately. + # But should we? What if the command doesn't have visible output? + # irc.replySuccess() + repeat = wrap(repeat, ['nonInt', 'positiveInt', 'text']) + + def list(self, irc, msg, args): + """takes no arguments + + Lists the currently scheduled events. + """ + L = self.events.items() + if L: + L.sort() + for (i, (name, command)) in enumerate(L): + L[i] = format('%s: %q', name, command) + irc.reply(format('%L', L)) + else: + irc.reply('There are currently no scheduled commands.') + list = wrap(list) + + +Class = Scheduler + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Scheduler/test.py b/plugins/Scheduler/test.py new file mode 100644 index 000000000..f02a19e0a --- /dev/null +++ b/plugins/Scheduler/test.py @@ -0,0 +1,85 @@ +### +# Copyright (c) 2002-2004, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +from supybot.test import * + +import supybot.schedule as schedule + +class SchedulerTestCase(ChannelPluginTestCase): + plugins = ('Scheduler', 'Utilities') + def tearDown(self): + schedule.schedule.reset() + ChannelPluginTestCase.tearDown(self) + + def testAddRemove(self): + self.assertRegexp('scheduler list', 'no.*commands') + m = self.assertNotError('scheduler add 5 echo testAddRemove') + self.assertNotRegexp('scheduler list', 'no.*commands') + self.assertNoResponse(' ', 3) + self.assertResponse(' ', 'testAddRemove') + m = self.assertNotError('scheduler add 5 echo testAddRemove2') + # Get id. + id = None + for s in m.args[1].split(): + s = s.lstrip('#') + if s.isdigit(): + id = s + break + self.failUnless(id, 'Couldn\'t find id in reply.') + self.assertNotError('scheduler remove %s' % id) + self.assertNoResponse(' ', 5) + + def testRepeat(self): + self.assertNotError('scheduler repeat repeater 5 echo testRepeat') + self.assertResponse(' ', 'testRepeat') + self.assertResponse('scheduler list', 'repeater: "echo testRepeat"') + self.assertNoResponse(' ', 3) + self.assertResponse(' ', 'testRepeat') + self.assertNotError('scheduler remove repeater') + self.assertNotRegexp('scheduler list', 'repeater') + self.assertNoResponse(' ', 5) + + def testRepeatWorksWithNestedCommands(self): + self.assertNotError('scheduler repeat foo 5 "echo foo [echo nested]"') + self.assertResponse(' ', 'foo nested') + self.assertNoResponse(' ', 3) + self.assertResponse(' ', 'foo nested') + self.assertNotError('scheduler remove foo') + self.assertNoResponse(' ', 5) + + def testRepeatDisallowsIntegerNames(self): + self.assertError('scheduler repeat 1234 1234 "echo NoIntegerNames"') + + def testRepeatDisallowsDuplicateNames(self): + self.assertNotError('scheduler repeat foo 5 "echo foo"') + self.assertError('scheduler repeat foo 5 "echo another foo fails"') + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: + diff --git a/setup.py b/setup.py index c0ec0c556..d1eaa5ff6 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ plugins = [ 'Misc', 'Owner', 'QuoteGrabs', + 'Scheduler', 'Status', 'User', 'Utilities',