diff --git a/.gitignore b/.gitignore index a9d752345..a6d8e0758 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,7 @@ test-logs/ src/version.py INSTALL README.txt + +# Intellij PyCharm / IDEA related files +*.iml +.idea diff --git a/plugins/Alias/plugin.py b/plugins/Alias/plugin.py index d5d3084f3..467f320b7 100644 --- a/plugins/Alias/plugin.py +++ b/plugins/Alias/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2009-2010, James McCoy +# Copyright (c) 2014, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -81,9 +81,6 @@ def getArgs(args, required=1, optional=0, wildcard=0): class AliasError(Exception): pass -class RecursiveAlias(AliasError): - pass - dollarRe = re.compile(r'\$(\d+)') def findBiggestDollar(alias): dollars = dollarRe.findall(alias) @@ -214,11 +211,11 @@ def makeNewAlias(name, alias): return True return False everythingReplace(tokens) - maxNesting = conf.supybot.commands.nested.maximum() - if maxNesting and irc.nested+1 > maxNesting: - irc.error(_('You\'ve attempted more nesting than is ' - 'currently allowed on this bot.'), Raise=True) - self.Proxy(irc, msg, tokens, nested=irc.nested+1) + # Limit memory use by constraining the size of the message being passed + # in to the alias. Also tracking nesting to avoid endless recursion. + maxLength = conf.supybot.reply.maximumLength() + tokens = [t[:maxLength] for t in tokens] + self.Proxy(irc, msg, tokens, nested=irc.nested + 1) flexargs = '' if biggestDollar and (wildcard or biggestAt): flexargs = _(' at least') @@ -343,11 +340,8 @@ class Alias(callbacks.Plugin): (currentAlias, locked, _) = self.aliases[name] if locked and currentAlias != alias: raise AliasError(format('Alias %q is locked.', name)) - try: - f = makeNewAlias(name, alias) - f = types.MethodType(f, self) - except RecursiveAlias: - raise AliasError('You can\'t define a recursive alias.') + f = makeNewAlias(name, alias) + f = new.instancemethod(f, self, Alias) if '.' in name or '|' in name: aliasGroup = self.registryValue('escapedaliases', value=False) confname = escapeAlias(name) diff --git a/plugins/Anonymous/plugin.py b/plugins/Anonymous/plugin.py index 4d6226a11..1e3f17183 100644 --- a/plugins/Anonymous/plugin.py +++ b/plugins/Anonymous/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2005, Daniel DiPaolo -# Copyright (c) 2010, James McCoy +# Copyright (c) 2014, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -78,17 +78,30 @@ class Anonymous(callbacks.Plugin): @internationalizeDocstring def say(self, irc, msg, args, target, text): - """ + """ - Sends to . Can only send to if + Sends to . Can only send to if supybot.plugins.Anonymous.allowPrivateTarget is True. """ self._preCheck(irc, msg, target, 'say') - self.log.info('Saying %q to %s due to %s.', + self.log.info('Saying %q in %s due to %s.', text, target, msg.prefix) - irc.reply(text, to=target, prefixNick=False, - private=not ircutils.isChannel(target)) - say = wrap(say, [first('nick', 'inChannel'), 'text']) + irc.queueMsg(ircmsgs.privmsg(target, text)) + irc.noReply() + say = wrap(say, ['inChannel', 'text']) + + def tell(self, irc, msg, args, target, text): + """ + + Sends to . Can only be used if + supybot.plugins.Anonymous.allowPrivateTarget is True. + """ + self._preCheck(irc, msg, target, 'tell') + self.log.info('Telling %q to %s due to %s.', + text, target, msg.prefix) + irc.queueMsg(ircmsgs.privmsg(target, text)) + irc.noReply() + tell = wrap(tell, ['nick', 'text']) @internationalizeDocstring def do(self, irc, msg, args, channel, text): diff --git a/plugins/Anonymous/test.py b/plugins/Anonymous/test.py index b4ea46911..4f9713496 100644 --- a/plugins/Anonymous/test.py +++ b/plugins/Anonymous/test.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2005, Daniel DiPaolo -# Copyright (c) 2010, James McCoy +# Copyright (c) 2014, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -34,16 +34,24 @@ class AnonymousTestCase(ChannelPluginTestCase): plugins = ('Anonymous',) def testSay(self): self.assertError('anonymous say %s I love you!' % self.channel) - self.assertError('anonymous say %s I love you!' % self.nick) + origreg = conf.supybot.plugins.Anonymous.requireRegistration() + try: + conf.supybot.plugins.Anonymous.requireRegistration.setValue(False) + m = self.assertNotError('anonymous say %s foo!' % self.channel) + self.failUnless(m.args[1] == 'foo!') + finally: + conf.supybot.plugins.Anonymous.requireRegistration.setValue(origreg) + + def testTell(self): + self.assertError('anonymous tell %s I love you!' % self.nick) origreg = conf.supybot.plugins.Anonymous.requireRegistration() origpriv = conf.supybot.plugins.Anonymous.allowPrivateTarget() try: conf.supybot.plugins.Anonymous.requireRegistration.setValue(False) - m = self.assertNotError('anonymous say %s foo!' % self.channel) - self.assertEqual(m.args[1], 'foo!') + self.assertError('anonymous tell %s foo!' % self.channel) conf.supybot.plugins.Anonymous.allowPrivateTarget.setValue(True) - m = self.assertNotError('anonymous say %s foo!' % self.nick) - self.assertEqual(m.args[1], 'foo!') + m = self.assertNotError('anonymous tell %s foo!' % self.nick) + self.failUnless(m.args[1] == 'foo!') finally: conf.supybot.plugins.Anonymous.requireRegistration.setValue(origreg) conf.supybot.plugins.Anonymous.allowPrivateTarget.setValue(origpriv) diff --git a/sandbox/release.py b/sandbox/release.py index bc90db6e5..e6d1316c0 100644 --- a/sandbox/release.py +++ b/sandbox/release.py @@ -186,6 +186,3 @@ if __name__ == '__main__': # 'test-data', 'test-logs', 'tmp') # for fn in configFiles: # os.remove(fn) - -# This is the part where we do our release on Freshmeat using XMLRPC and -# ESR's software to do it: http://freshmeat.net/p/freshmeat-submit/ diff --git a/setup.py b/setup.py index 68df4f877..06702744a 100644 --- a/setup.py +++ b/setup.py @@ -274,7 +274,17 @@ setup( ('share/man/man1', ['docs/man/supybot-adduser.1']), ('share/man/man1', ['docs/man/supybot-plugin-doc.1']), ('share/man/man1', ['docs/man/supybot-plugin-create.1']), - ] + ], + + install_requires=[ + # Time plugin + 'python-dateutil <2.0,>=1.3', + 'feedparser', + ], + + tests_require=[ + 'mock', + ] ) diff --git a/src/callbacks.py b/src/callbacks.py index 97331f9ae..5cdad5d04 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -1,7 +1,7 @@ # -*- coding: utf8 -*- ### # Copyright (c) 2002-2005, Jeremiah Fincher -# Copyright (c) 2008-2010, James McCoy +# Copyright (c) 2014, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -642,6 +642,7 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): self.msg.prefix, maxNesting) self.error(_('You\'ve attempted more nesting than is ' 'currently allowed on this bot.')) + return # The deepcopy here is necessary for Scheduler; it re-runs already # tokenized commands. There's a possibility a simple copy[:] would # work, but we're being careful. diff --git a/test/test_yn.py b/test/test_yn.py new file mode 100644 index 000000000..92340c340 --- /dev/null +++ b/test/test_yn.py @@ -0,0 +1,99 @@ +### +# Copyright (c) 2014, Artur Krysiak +# 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 import questions +from supybot.test import SupyTestCase + +import mock + +# so complicated construction because I want to +# gain the string 'y' instead of the character 'y' +# the reason of usage this construction is to prove +# that comparing strings by 'is' is wrong +# better solution is usage of '==' operator ;) +_yes_answer = ''.join(['', 'y']) + +class TestYn(SupyTestCase): + def test_default_yes_selected(self): + questions.expect = mock.Mock(return_value=_yes_answer) + + answer = questions.yn('up', default='y') + + self.assertTrue(answer) + + def test_default_no_selected(self): + questions.expect = mock.Mock(return_value='n') + + answer = questions.yn('up', default='n') + + self.assertFalse(answer) + + def test_yes_selected_without_defaults(self): + questions.expect = mock.Mock(return_value=_yes_answer) + + answer = questions.yn('up') + + self.assertTrue(answer) + + def test_no_selected_without_defaults(self): + questions.expect = mock.Mock(return_value='n') + + answer = questions.yn('up') + + self.assertFalse(answer) + + def test_no_selected_with_default_yes(self): + questions.expect = mock.Mock(return_value='n') + + answer = questions.yn('up', default='y') + + self.assertFalse(answer) + + def test_yes_selected_with_default_yes(self): + questions.expect = mock.Mock(return_value=_yes_answer) + + answer = questions.yn('up', default='y') + + self.assertTrue(answer) + + def test_yes_selected_with_default_no(self): + questions.expect = mock.Mock(return_value=_yes_answer) + + answer = questions.yn('up', default='n') + + self.assertTrue(answer) + + def test_no_selected_with_default_no(self): + questions.expect = mock.Mock(return_value='n') + + answer = questions.yn('up', default='n') + + self.assertFalse(answer) + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: