From 3e58419338623ec87d09775dd7b93582306a3644 Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Thu, 23 Sep 2004 23:15:27 +0000 Subject: [PATCH] Various minor refactorings, moved supybot.reply.{brackets,pipeSyntax} to supybot.commands.nested. --- plugins/Observer.py | 3 +-- src/Misc.py | 3 ++- src/Owner.py | 8 ++----- src/callbacks.py | 53 ++++++++++++++++++++++++++++-------------- src/conf.py | 42 ++++++++++++++++++++------------- test/test_callbacks.py | 10 ++++---- 6 files changed, 72 insertions(+), 47 deletions(-) diff --git a/plugins/Observer.py b/plugins/Observer.py index 53014036f..d85474171 100644 --- a/plugins/Observer.py +++ b/plugins/Observer.py @@ -120,7 +120,6 @@ class Observer(callbacks.Privmsg): self.commandCalled = False return channel = msg.args[0] - Owner = irc.getCallback('Owner') observers = self.registryValue('observers') active = self.registryValue('observers.active', channel) for name in active: @@ -141,7 +140,7 @@ class Observer(callbacks.Privmsg): for (i, group) in enumerate(groups): command = command.replace('$%s' % i, group) tokens = callbacks.tokenize(command, channel=channel) - Owner.processTokens(irc, msg, tokens) + self.Proxy(irc, msg, tokens) def list(self, irc, msg, args): """[] diff --git a/src/Misc.py b/src/Misc.py index d3497bb25..4e5c5f5b0 100755 --- a/src/Misc.py +++ b/src/Misc.py @@ -115,7 +115,8 @@ class Misc(callbacks.Privmsg): # to log this in that case anyway, it being a nested command. self.log.info('Not replying to %s, not a command.' % tokens[0]) if not isinstance(irc.irc, irclib.Irc): - brackets = conf.get(conf.supybot.reply.brackets, channel) + bracketConfig = conf.supybot.commands.nested.brackets + brackets = conf.get(bracketConfig, channel) if brackets: (left, right) = brackets irc.reply(left + ' '.join(tokens) + right) diff --git a/src/Owner.py b/src/Owner.py index f1a68504f..a1c5b938f 100644 --- a/src/Owner.py +++ b/src/Owner.py @@ -338,9 +338,6 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg): name, e) world.starting = False - def processTokens(self, irc, msg, tokens): - callbacks.IrcObjectProxy(irc, msg, tokens) - def do376(self, irc, msg): channels = ircutils.IrcSet(conf.supybot.channels()) channels |= conf.supybot.networks.get(irc.network).channels() @@ -370,10 +367,9 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg): if ignored: self.log.info('Ignoring command from %s.' % msg.prefix) return - brackets = conf.get(conf.supybot.reply.brackets, msg.args[0]) try: - tokens = callbacks.tokenize(s, brackets=brackets) - self.processTokens(irc, msg, tokens) + tokens = callbacks.tokenize(s, channel=msg.args[0]) + self.Proxy(irc, msg, tokens) except SyntaxError, e: irc.queueMsg(callbacks.error(msg, str(e))) diff --git a/src/callbacks.py b/src/callbacks.py index 90200e18b..bedaa88ba 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -230,15 +230,19 @@ class Tokenizer(object): # double-quote, left-bracket, and right-bracket. validChars = string.ascii.translate(string.ascii, '\x00\r\n \t"') quotes = '"' - def __init__(self, tokens=''): - # Add a '|' to tokens to have the pipe syntax. - self.validChars = self.validChars.translate(string.ascii, tokens) - if len(tokens) >= 2: - self.left = tokens[0] - self.right = tokens[1] + def __init__(self, brackets='', pipe=False): + if brackets: + self.validChars = self.validChars.translate(string.ascii, brackets) + self.left = brackets[0] + self.right = brackets[1] else: self.left = '' self.right = '' + self.pipe = pipe + if self.pipe: + self.validChars = self.validChars.translate(string.ascii, '|') + else: + assert '|' in self.validChars def _handleToken(self, token): if token[0] == token[-1] and token[0] in self.quotes: @@ -276,7 +280,10 @@ class Tokenizer(object): token = lexer.get_token() if not token: break - elif token == '|' and conf.supybot.reply.pipeSyntax(): + elif token == '|' and self.pipe: + # The "and self.pipe" might seem redundant here, but it's there + # for strings like 'foo | bar', where a pipe stands alone as a + # token, but shouldn't be treated specially. if not args: raise SyntaxError, '"|" with nothing preceding. I ' \ 'obviously can\'t do a pipe with ' \ @@ -303,18 +310,20 @@ class Tokenizer(object): args[-1].append(ends.pop()) return args -def tokenize(s, brackets=None, channel=None): +def tokenize(s, channel=None): """A utility function to create a Tokenizer and tokenize a string.""" + pipe = False + brackets = '' + nested = conf.supybot.commands.nested + if nested(): + brackets = conf.get(nested.brackets, channel) + if conf.get(nested.pipeSyntax, channel): # No nesting, no pipe. + pipe = True start = time.time() try: - if brackets is None: - tokens = conf.get(conf.supybot.reply.brackets, channel) - else: - tokens = brackets - if conf.get(conf.supybot.reply.pipeSyntax, channel): - tokens = '%s|' % tokens + ret = Tokenizer(brackets=brackets, pipe=pipe).tokenize(s) log.stat('tokenize took %s seconds.' % (time.time() - start)) - return Tokenizer(tokens).tokenize(s) + return ret except ValueError, e: raise SyntaxError, str(e) @@ -496,11 +505,21 @@ _repr = repr class IrcObjectProxy(RichReplyMethods): "A proxy object to allow proper nested of commands (even threaded ones)." - def __init__(self, irc, msg, args, nested=False): + def __init__(self, irc, msg, args, nested=0): log.debug('IrcObjectProxy.__init__: %s' % args) self.irc = irc self.msg = msg self.nested = nested + if not self.nested and isinstance(irc, self.__class__): + # This is for plugins that indirectly spawn a Proxy, like Alias. + self.nested += irc.nested + maxNesting = conf.supybot.commands.nested.maximum() + if maxNesting and self.nested > maxNesting: + log.warning('%s attempted more than %s levels of nesting.', + 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. self.args = copy.deepcopy(args) @@ -539,7 +558,7 @@ class IrcObjectProxy(RichReplyMethods): self.counter += 1 else: self.__class__(self, self.msg, - self.args[self.counter], nested=True) + self.args[self.counter], nested=self.nested+1) return self.finalEval() diff --git a/src/conf.py b/src/conf.py index b9ef46a0e..00e38ba8e 100644 --- a/src/conf.py +++ b/src/conf.py @@ -288,22 +288,6 @@ registerGlobalValue(supybot.reply, 'oneToOne', safety purposes (so the bot is less likely to flood) it will normally send everything in a single message, using mores if necessary.""")) -class ValidBrackets(registry.OnlySomeStrings): - validStrings = ('', '[]', '<>', '{}', '()') - -registerChannelValue(supybot.reply, 'brackets', - ValidBrackets('[]', """Supybot allows you to specify what brackets are used - for your nested commands. Valid sets of brackets include [], <>, and {} - (). [] has strong historical motivation, as well as being the brackets - that don't require shift. <> or () might be slightly superior because they - cannot occur in a nick. If this value is set to the empty string, no - nesting will be allowed.""")) - -registerChannelValue(supybot.reply, 'pipeSyntax', - registry.Boolean(False, """Supybot allows nested commands. Enabling this - option will allow nested commands with a syntax similar to UNIX pipes, for - example: 'bot: foo | bar'.""")) - registerChannelValue(supybot.reply, 'whenNotCommand', registry.Boolean(True, """Determines whether the bot will reply with an error message when it is addressed but not given a valid command. If this @@ -518,6 +502,32 @@ registerGlobalValue(supybot, 'flush', # supybot.commands. For stuff relating to commands. ### registerGroup(supybot, 'commands') +# This is a GlobalValue because bot owners should be able to say, "There will +# be no nesting at all on this bot." Individual channels can just set their +# brackets to the empty string. +registerGlobalValue(supybot.commands, 'nested', + registry.Boolean(True, """Determines whether the bot will allow nested + commands, which rule. You definitely should keep this on.""")) +registerGlobalValue(supybot.commands.nested, 'maximum', + registry.PositiveInteger(10, """Determines what the maximum number of + nested commands will be; users will receive an error if they attempt + commands more nested than this.""")) + +class ValidBrackets(registry.OnlySomeStrings): + validStrings = ('', '[]', '<>', '{}', '()') + +registerChannelValue(supybot.commands.nested, 'brackets', + ValidBrackets('[]', """Supybot allows you to specify what brackets are used + for your nested commands. Valid sets of brackets include [], <>, and {} + (). [] has strong historical motivation, as well as being the brackets + that don't require shift. <> or () might be slightly superior because they + cannot occur in a nick. If this string is empty, nested commands will + not be allowed in this channel.""")) +registerChannelValue(supybot.commands.nested, 'pipeSyntax', + registry.Boolean(False, """Supybot allows nested commands. Enabling this + option will allow nested commands with a syntax similar to UNIX pipes, for + example: 'bot: foo | bar'.""")) + registerGroup(supybot.commands, 'defaultPlugins', orderAlphabetically=True, help=utils.normalizeWhitespace("""Determines what commands have default plugins set, and which plugins are set to diff --git a/test/test_callbacks.py b/test/test_callbacks.py index a33f8857f..20cf30c35 100644 --- a/test/test_callbacks.py +++ b/test/test_callbacks.py @@ -79,15 +79,15 @@ class TokenizerTestCase(SupyTestCase): self.assertEqual(tokenize('foo bar [baz quux]'), ['foo', 'bar', ['baz', 'quux']]) try: - orig = conf.supybot.reply.brackets() - conf.supybot.reply.brackets.setValue('') + orig = conf.supybot.commands.nested() + conf.supybot.commands.nested.setValue(False) self.assertEqual(tokenize('[]'), ['[]']) self.assertEqual(tokenize('[foo]'), ['[foo]']) self.assertEqual(tokenize('foo [bar]'), ['foo', '[bar]']) self.assertEqual(tokenize('foo bar [baz quux]'), ['foo', 'bar', '[baz', 'quux]']) finally: - conf.supybot.reply.brackets.setValue(orig) + conf.supybot.commands.nested.setValue(orig) def testError(self): self.assertRaises(SyntaxError, tokenize, '[foo') #] @@ -95,7 +95,7 @@ class TokenizerTestCase(SupyTestCase): def testPipe(self): try: - conf.supybot.reply.pipeSyntax.set('True') + conf.supybot.commands.nested.pipeSyntax.setValue(True) self.assertRaises(SyntaxError, tokenize, '| foo') self.assertRaises(SyntaxError, tokenize, 'foo ||bar') self.assertRaises(SyntaxError, tokenize, 'bar |') @@ -110,7 +110,7 @@ class TokenizerTestCase(SupyTestCase): self.assertEqual(tokenize('foo bar | baz quux'), ['baz', 'quux', ['foo', 'bar']]) finally: - conf.supybot.reply.pipeSyntax.set('False') + conf.supybot.commands.nested.pipeSyntax.setValue(False) self.assertEqual(tokenize('foo|bar'), ['foo|bar']) self.assertEqual(tokenize('foo | bar'), ['foo', '|', 'bar']) self.assertEqual(tokenize('foo | bar | baz'),