From 39e323f4b1f74353550b6b172735bba4f167b8b2 Mon Sep 17 00:00:00 2001 From: James Vega Date: Wed, 2 Feb 2005 05:02:08 +0000 Subject: [PATCH] Split off commands from Fun and Utilities to the new String plugin. --- plugins/String/README.txt | 1 + plugins/String/__init__.py | 60 +++++++++++++ plugins/String/config.py | 54 +++++++++++ plugins/String/plugin.py | 178 +++++++++++++++++++++++++++++++++++++ plugins/String/test.py | 138 ++++++++++++++++++++++++++++ 5 files changed, 431 insertions(+) create mode 100644 plugins/String/README.txt create mode 100644 plugins/String/__init__.py create mode 100644 plugins/String/config.py create mode 100644 plugins/String/plugin.py create mode 100644 plugins/String/test.py diff --git a/plugins/String/README.txt b/plugins/String/README.txt new file mode 100644 index 000000000..d60b47a97 --- /dev/null +++ b/plugins/String/README.txt @@ -0,0 +1 @@ +Insert a description of your plugin here, with any notes, etc. about using it. diff --git a/plugins/String/__init__.py b/plugins/String/__init__.py new file mode 100644 index 000000000..eb65a20a4 --- /dev/null +++ b/plugins/String/__init__.py @@ -0,0 +1,60 @@ +### +# Copyright (c) 2003-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. +### + +""" +Provides various string-related commands. +""" + +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/String/config.py b/plugins/String/config.py new file mode 100644 index 000000000..7ca6a0244 --- /dev/null +++ b/plugins/String/config.py @@ -0,0 +1,54 @@ +### +# 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('String', True) + + +String = conf.registerPlugin('String') +conf.registerGroup(String, 'levenshtein') +conf.registerGlobalValue(String.levenshtein, 'max', + registry.PositiveInteger(256, """Determines the maximum size of a string + given to the levenshtein command. The levenshtein command uses an O(n**3) + algorithm, which means that with strings of length 256, it can take 1.5 + seconds to finish; with strings of length 384, though, it can take 4 + seconds to finish, and with strings of much larger lengths, it takes more + and more time. Using nested commands, strings can get quite large, hence + this variable, to limit the size of arguments passed to the levenshtein + command.""")) + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78 diff --git a/plugins/String/plugin.py b/plugins/String/plugin.py new file mode 100644 index 000000000..290ea931c --- /dev/null +++ b/plugins/String/plugin.py @@ -0,0 +1,178 @@ +### +# 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 md5 +import sha +import types + +import supybot.utils as utils +from supybot.commands import * +import supybot.plugins as plugins +import supybot.ircutils as ircutils +import supybot.callbacks as callbacks + + +class String(callbacks.Privmsg): + def ord(self, irc, msg, args, letter): + """ + + Returns the 8-bit value of . + """ + irc.reply(str(ord(letter))) + ord = wrap(ord, ['letter']) + + def chr(self, irc, msg, args, i): + """ + + Returns the character associated with the 8-bit value + """ + try: + irc.reply(chr(i)) + except ValueError: + irc.error('That number doesn\'t map to an 8-bit character.') + chr = wrap(chr, ['int']) + + def encode(self, irc, msg, args, encoding, text): + """ + + Returns an encoded form of the given text; the valid encodings are + available in the documentation of the Python codecs module: + . + """ + try: + irc.reply(text.encode(encoding)) + except LookupError: + irc.errorInvalid('encoding', encoding) + encode = wrap(encode, ['something', 'text']) + + def decode(self, irc, msg, args, encoding, text): + """ + + Returns an un-encoded form of the given text; the valid encodings are + available in the documentation of the Python codecs module: + . + """ + try: + irc.reply(text.decode(encoding).encode('utf-8')) + except LookupError: + irc.errorInvalid('encoding', encoding) + decode = wrap(decode, ['something', 'text']) + + def levenshtein(self, irc, msg, args, s1, s2): + """ + + Returns the levenshtein distance (also known as the "edit distance" + between and ) + """ + max = self.registryValue('levenshtein.max') + if len(s1) > max or len(s2) > max: + irc.error('Levenshtein distance is a complicated algorithm, try ' + 'it with some smaller inputs.') + else: + irc.reply(str(utils.str.distance(s1, s2))) + levenshtein = wrap(levenshtein, ['something', 'text']) + + def soundex(self, irc, msg, args, text, length): + """ [] + + Returns the Soundex hash to a given length. The length defaults to + 4, since that's the standard length for a soundex hash. For unlimited + length, use 0. + """ + irc.reply(utils.str.soundex(text, length)) + soundex = wrap(soundex, ['somethingWithoutSpaces', additional('int', 4)]) + + def strlen(self, irc, msg, args): + """ + + Returns the length of . + """ + total = 0 + for arg in args: + total += len(arg) + total += len(args)-1 # spaces between the arguments. + irc.reply(str(total)) + + def re(self, irc, msg, args, ff, text): + """ + + If is of the form m/regexp/flags, returns the portion of + that matches the regexp. If is of the form + s/regexp/replacement/flags, returns the result of applying such a + regexp to + """ + if isinstance(ff, (types.FunctionType, types.MethodType)): + f = ff + else: + f = lambda s: ff.search(s) and ff.search(s).group(0) or '' + if f('') and len(f(' ')) > len(f(''))+1: # Matches the empty string. + s = 'You probably don\'t want to match the empty string.' + irc.error(s) + else: + irc.reply(f(text)) + re = wrap(re, [('checkCapability', 'trusted'), + first('regexpMatcher', 'regexpReplacer'), + 'text']) + + def xor(self, irc, msg, args, password, text): + """ + + Returns XOR-encrypted with . See + http://www.yoe.org/developer/xor.html for information about XOR + encryption. + """ + chars = utils.iter.cycle(password) + ret = [chr(ord(c) ^ ord(chars.next())) for c in text] + irc.reply(''.join(ret)) + xor = wrap(xor, ['something', 'text']) + + def md5(self, irc, msg, args, text): + """ + + Returns the md5 hash of a given string. Read + http://www.rsasecurity.com/rsalabs/faq/3-6-6.html for more information + about md5. + """ + irc.reply(md5.md5(text).hexdigest()) + md5 = wrap(md5, ['text']) + + def sha(self, irc, msg, args, text): + """ + + Returns the SHA hash of a given string. Read + http://www.secure-hash-algorithm-md5-sha-1.co.uk/ for more information + about SHA. + """ + irc.reply(sha.sha(text).hexdigest()) + sha = wrap(sha, ['text']) + +Class = String + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/String/test.py b/plugins/String/test.py new file mode 100644 index 000000000..dc2dec51f --- /dev/null +++ b/plugins/String/test.py @@ -0,0 +1,138 @@ +### +# 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 re + +from supybot.test import * +import supybot.utils as utils + +nicks = ['fatjim','scn','moshez','LordVan','MetaCosm','pythong','fishfart', + 'alb','d0rt','jemfinch','StyxAlso','fors','deltab','gd', + 'hellz_hunter','are_j|pub_comp','jason_','dreid','sayke_','winjer', + 'TenOfTen','GoNoVas','queuetue','the|zzz','Hellfried','Therion', + 'shro','DaCa','rexec','polin8','r0ky','aaron_','ironfroggy','eugene', + 'faassen','tirloni','mackstann','Yhg1s','ElBarono','vegai','shang', + 'typo_','kikoforgetme','asqui','TazyTiggy','fab','nixman','liiwi', + 'AdamV','paolo','red_one','_AleX_','lament','jamessan','supybot', + 'macr0_zzz','plaisthos','redghost','disco','mphardy','gt3','mathie', + 'jonez','r0ky-office','tic','d33p','ES3merge','talin','af','flippo', + 'sholden','ameoba','shepherg','j2','Acapnotic','dash','merlin262', + 'Taaus','_moshez','rik','jafo__','blk-majik','JT__','itamar', + 'kermit-','davidmccabe','glyph','jojo','dave_p','goo','hyjinx', + 'SamB','exarkun','drewp','Ragica','skylan','redgore','k3','Ra1stlin', + 'StevenK','carball','h3x','carljm','_jacob','teratorn','frangen', + 'phed','datazone','Yaggo','acct_','nowhere','pyn','ThomasWaldmann', + 'dunker','pilotLight','brainless','LoganH_','jmpnz','steinn', + 'EliasREC','lowks__','OldSmrf','Mad77','snibril','delta','psy', + 'skimpIzu','Kengur','MoonFallen','kotkis','Hyperi'] + +def group(seq, groupSize, noneFill=True): + """Groups a given sequence into sublists of length groupSize.""" + ret = [] + L = [] + i = groupSize + for elt in seq: + if i > 0: + L.append(elt) + else: + ret.append(L) + i = groupSize + L = [] + L.append(elt) + i -= 1 + if L: + if noneFill: + while len(L) < groupSize: + L.append(None) + ret.append(L) + return ret + +class StringTestCase(PluginTestCase): + plugins = ('String', 'Format', 'Status') + def testNoErrors(self): + self.assertNotError('levenshtein Python Perl') + + def testSoundex(self): + self.assertNotError('soundex jemfinch') + self.assertNotRegexp('soundex foobar 3:30', 'ValueError') + + def testChr(self): + for i in range(256): + c = chr(i) + regexp = r'%s|%s' % (re.escape(c), re.escape(repr(c))) + self.assertRegexp('chr %s' % i, regexp) + + def testOrd(self): + for c in map(chr, range(256)): + i = ord(c) + self.assertResponse('ord %s' % utils.str.dqrepr(c), str(i)) + + def testEncodeDecode(self): + # This no longer works correctly. It almost seems like were throwing + # in a repr() somewhere. + s = 'the recalcitrant jamessan tests his scramble function' + self.assertNotRegexp('encode aldkfja foobar', 'LookupError') + self.assertNotRegexp('decode asdflkj foobar', 'LookupError') + self.assertResponse('decode zlib [encode zlib %s]' % s, s) + + def testRe(self): + self.assertResponse('re "m/system time/" [status cpu]', 'system time') + self.assertResponse('re s/user/luser/g user user', 'luser luser') + self.assertResponse('re s/user/luser/ user user', 'luser user') + self.assertNotRegexp('re m/foo/ bar', 'has no attribute') + self.assertResponse('re m/a\S+y/ "the bot angryman is hairy"','angry') + + def testReNotEmptyString(self): + self.assertError('re s//foo/g blah') + + def testReWorksWithJustCaret(self): + self.assertResponse('re s/^/foo/ bar', 'foobar') + + def testReNoEscapingUnpackListOfWrongSize(self): + self.assertNotRegexp('re foo bar baz', 'unpack list of wrong size') + + def testReBug850931(self): + self.assertResponse('re s/\b(\w+)\b/\1./g foo bar baz', + 'foo. bar. baz.') + + def testNotOverlongRe(self): + self.assertError('re [strjoin "" s/./ [eval \'xxx\'*400]] blah blah') + + def testXor(self): + # This no longer works correctly. It almost seems like were throwing + # in a repr() somewhere. + L = [nick for nick in nicks if '|' not in nick and + '[' not in nick and + ']' not in nick] + for s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 in group(L, 10): + data = '%s%s%s%s%s%s%s%s%s' % (s0, s1, s2, s3, s4, s5, s6, s7, s8) + self.assertResponse('xor %s [xor %s %s]' % (s9, s9, data), data) + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: