From 01c8f3445a1a295ce283e4670d6d6cfa8f169611 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 8 Apr 2010 20:02:39 -0400 Subject: [PATCH] create conditional plugin with associated tests. includes string and numeric comparisons, simple string matching. --- plugins/Conditional/README.txt | 1 + plugins/Conditional/__init__.py | 66 ++++++ plugins/Conditional/config.py | 49 +++++ plugins/Conditional/local/__init__.py | 1 + plugins/Conditional/plugin.py | 276 ++++++++++++++++++++++++++ plugins/Conditional/test.py | 143 +++++++++++++ 6 files changed, 536 insertions(+) create mode 100644 plugins/Conditional/README.txt create mode 100644 plugins/Conditional/__init__.py create mode 100644 plugins/Conditional/config.py create mode 100644 plugins/Conditional/local/__init__.py create mode 100644 plugins/Conditional/plugin.py create mode 100644 plugins/Conditional/test.py diff --git a/plugins/Conditional/README.txt b/plugins/Conditional/README.txt new file mode 100644 index 000000000..d60b47a97 --- /dev/null +++ b/plugins/Conditional/README.txt @@ -0,0 +1 @@ +Insert a description of your plugin here, with any notes, etc. about using it. diff --git a/plugins/Conditional/__init__.py b/plugins/Conditional/__init__.py new file mode 100644 index 000000000..8a48b27be --- /dev/null +++ b/plugins/Conditional/__init__.py @@ -0,0 +1,66 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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. + +### + +""" +Add a description of the plugin (to be presented to the user inside the wizard) +here. This should describe *what* the plugin does. +""" + +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__ = "" + +# XXX Replace this with an appropriate author or supybot.Author instance. +__author__ = supybot.authors.unknown + +# This is a dictionary mapping supybot.Author instances to lists of +# contributions. +__contributors__ = {} + +# This is a url where the most recent plugin package can be downloaded. +__url__ = '' # 'http://supybot.com/Members/yourname/Conditional/download' + +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=4 expandtab textwidth=79: diff --git a/plugins/Conditional/config.py b/plugins/Conditional/config.py new file mode 100644 index 000000000..ab69968bd --- /dev/null +++ b/plugins/Conditional/config.py @@ -0,0 +1,49 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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('Conditional', True) + + +Conditional = conf.registerPlugin('Conditional') +# This is where your configuration variables (if any) should go. For example: +# conf.registerGlobalValue(Conditional, 'someConfigVariableName', +# registry.Boolean(False, """Help for someConfigVariableName.""")) + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/Conditional/local/__init__.py b/plugins/Conditional/local/__init__.py new file mode 100644 index 000000000..e86e97b86 --- /dev/null +++ b/plugins/Conditional/local/__init__.py @@ -0,0 +1 @@ +# Stub so local is a module, used for third-party modules diff --git a/plugins/Conditional/plugin.py b/plugins/Conditional/plugin.py new file mode 100644 index 000000000..347b50f7a --- /dev/null +++ b/plugins/Conditional/plugin.py @@ -0,0 +1,276 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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.utils as utils +from supybot.commands import * +import supybot.plugins as plugins +import supybot.ircutils as ircutils +import supybot.callbacks as callbacks + +import re + +# builtin any is overwritten by callbacks... and python2.4 doesn't have it +def _any(iterable): + for element in iterable: + if element: + return True + return False +# for consistency with above, and for python2.4 +def _all(iterable): + for element in iterable: + if not element: + return False + return True + + +class Conditional(callbacks.Plugin): + """Add the help for "@plugin help Conditional" here + This should describe *how* to use this plugin.""" + threaded = True + def __init__(self, irc): + callbacks.Plugin.__init__(self, irc) + + def _runCommandFunction(self, irc, msg, command): + """Run a command from message, as if command was sent over IRC.""" + tokens = callbacks.tokenize(command) + try: + self.Proxy(irc.irc, msg, tokens) + except Exception, e: + log.exception('Uncaught exception in requested function:') + + def cif(self, irc, msg, args, condition, ifcommand, elsecommand): + """ + + Runs if evaluates to true, runs + if it evaluates to false. + + Use other logical operators defined in this plugin and command nesting + to your advantage here. + """ + if condition: + self._runCommandFunction(irc, msg, ifcommand) + else: + self._runCommandFunction(irc, msg, elsecommand) + irc.noReply() + cif = wrap(cif, ['boolean', 'something', 'something']) + + def cand(self, irc, msg, args, conds): + """ [ ... ] + + Returns true if all conditions supplied evaluate to true. + """ + if _all(conds): + irc.reply("true") + else: + irc.reply("false") + cand = wrap(cand, [many('boolean'),]) + + def cor(self, irc, msg, args, conds): + """ [ ... ] + + Returns true if any one of conditions supplied evaluates to true. + """ + if _any(conds): + irc.reply("true") + else: + irc.reply("false") + cor = wrap(cor, [many('boolean'),]) + + def cxor(self, irc, msg, args, conds): + """ [ ... ] + + Returns true if only one of conditions supplied evaluates to true. + """ + if sum(conds) == 1: + irc.reply("true") + else: + irc.reply("false") + cxor = wrap(cxor, [many('boolean'),]) + + def ceq(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if they are equal. + """ + if item1 == item2: + irc.reply('true') + else: + irc.reply('false') + ceq = wrap(ceq, ['something', 'something']) + + def ne(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if they are not equal. + """ + if item1 != item2: + irc.reply('true') + else: + irc.reply('false') + ne = wrap(ne, ['something', 'something']) + + def gt(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if they is greater than . + """ + if item1 > item2: + irc.reply('true') + else: + irc.reply('false') + gt = wrap(gt, ['something', 'something']) + + def ge(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if is greater than or equal to . + """ + if item1 >= item2: + irc.reply('true') + else: + irc.reply('false') + ge = wrap(ge, ['something', 'something']) + + def lt(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if is less than . + """ + if item1 < item2: + irc.reply('true') + else: + irc.reply('false') + lt = wrap(lt, ['something', 'something']) + + def le(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if is less than or equal to . + """ + if item1 <= item2: + irc.reply('true') + else: + irc.reply('false') + le = wrap(le, ['something', 'something']) + + def match(self, irc, msg, args, item1, item2): + """ + + Determines if is a substring of . + Returns true if is contained in . + """ + if item2.find(item1) != -1: + irc.reply('true') + else: + irc.reply('false') + match = wrap(match, ['something', 'something']) + + def nceq(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if they are equal. + """ + if item1 == item2: + irc.reply('true') + else: + irc.reply('false') + nceq = wrap(nceq, ['float', 'float']) + + def nne(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if they are not equal. + """ + if item1 != item2: + irc.reply('true') + else: + irc.reply('false') + nne = wrap(nne, ['float', 'float']) + + def ngt(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if they is greater than . + """ + if item1 > item2: + irc.reply('true') + else: + irc.reply('false') + ngt = wrap(ngt, ['float', 'float']) + + def nge(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if is greater than or equal to . + """ + if item1 >= item2: + irc.reply('true') + else: + irc.reply('false') + nge = wrap(nge, ['float', 'float']) + + def nlt(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if is less than . + """ + if item1 < item2: + irc.reply('true') + else: + irc.reply('false') + nlt = wrap(nlt, ['float', 'float']) + + def nle(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if is less than or equal to . + """ + if item1 <= item2: + irc.reply('true') + else: + irc.reply('false') + nle = wrap(nle, ['float', 'float']) + +Class = Conditional + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Conditional/test.py b/plugins/Conditional/test.py new file mode 100644 index 000000000..9dc856b94 --- /dev/null +++ b/plugins/Conditional/test.py @@ -0,0 +1,143 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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 * + +class ConditionalTestCase(PluginTestCase): + plugins = ('Conditional','Utilities',) + + def testCif(self): + self.assertError('cif stuff') + self.assertRegexp('cif [ceq bla bla] "echo moo" "echo foo"', 'moo') + self.assertRegexp('cif [ceq bla bar] "echo moo" "echo foo"', 'foo') + self.assertRegexp('cif [cand [ceq bla bla] [ne soo boo]] "echo moo" "echo foo"', 'moo') + self.assertRegexp('cif [ceq [echo $nick] "test"] "echo yay" "echo nay"', 'yay') + + def testCand(self): + self.assertRegexp('cand true true', 'true') + self.assertRegexp('cand false true', 'false') + self.assertRegexp('cand true false', 'false') + self.assertRegexp('cand false false', 'false') + self.assertRegexp('cand true true true', 'true') + + def testCor(self): + self.assertRegexp('cor true true', 'true') + self.assertRegexp('cor false true', 'true') + self.assertRegexp('cor true false', 'true') + self.assertRegexp('cor false false', 'false') + self.assertRegexp('cor true true true', 'true') + + def testCxor(self): + self.assertRegexp('cxor true true', 'false') + self.assertRegexp('cxor false true', 'true') + self.assertRegexp('cxor true false', 'true') + self.assertRegexp('cxor false false', 'false') + self.assertRegexp('cxor true true true', 'false') + + def testCeq(self): + self.assertRegexp('ceq bla bla', 'true') + self.assertRegexp('ceq bla moo', 'false') + self.assertError('ceq bla bla bla') + + def testNe(self): + self.assertRegexp('ne bla bla', 'false') + self.assertRegexp('ne bla moo', 'true') + self.assertError('ne bla bla bla') + + def testGt(self): + self.assertRegexp('gt bla bla', 'false') + self.assertRegexp('gt bla moo', 'false') + self.assertRegexp('gt moo bla', 'true') + self.assertError('gt bla bla bla') + + def testGe(self): + self.assertRegexp('ge bla bla', 'true') + self.assertRegexp('ge bla moo', 'false') + self.assertRegexp('ge moo bla', 'true') + self.assertError('ge bla bla bla') + + def testLt(self): + self.assertRegexp('lt bla bla', 'false') + self.assertRegexp('lt bla moo', 'true') + self.assertRegexp('lt moo bla', 'false') + self.assertError('lt bla bla bla') + + def testLe(self): + self.assertRegexp('le bla bla', 'true') + self.assertRegexp('le bla moo', 'true') + self.assertRegexp('le moo bla', 'false') + self.assertError('le bla bla bla') + + def testMatch(self): + self.assertRegexp('match bla mooblafoo', 'true') + self.assertRegexp('match bla mooblfoo', 'false') + self.assertError('match bla bla stuff') + + def testNceq(self): + self.assertRegexp('nceq 10.0 10', 'true') + self.assertRegexp('nceq 4 5', 'false') + self.assertError('nceq 1 2 3') + self.assertError('nceq bla 1') + + def testNne(self): + self.assertRegexp('nne 1 1', 'false') + self.assertRegexp('nne 2.2 3', 'true') + self.assertError('nne 1 2 3') + self.assertError('nne bla 3') + + def testNgt(self): + self.assertRegexp('ngt 3 3', 'false') + self.assertRegexp('ngt 2 3', 'false') + self.assertRegexp('ngt 4 3', 'true') + self.assertError('ngt 1 2 3') + self.assertError('ngt 3 bla') + + def testNge(self): + self.assertRegexp('nge 3 3', 'true') + self.assertRegexp('nge 3 4', 'false') + self.assertRegexp('nge 5 4.3', 'true') + self.assertError('nge 3 4.5 4') + self.assertError('nge 45 bla') + + def testNlt(self): + self.assertRegexp('nlt 3 3', 'false') + self.assertRegexp('nlt 3 4.5', 'true') + self.assertRegexp('nlt 5 3', 'false') + self.assertError('nlt 2 3 4') + self.assertError('nlt bla bla') + + def testNle(self): + self.assertRegexp('nle 2 2', 'true') + self.assertRegexp('nle 2 3.5', 'true') + self.assertRegexp('nle 4 3', 'false') + self.assertError('nle 3 4 5') + self.assertError('nle 1 bla') + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: