From 998f61cce8232e0b944140c070adf8d6bc1e3f22 Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Wed, 22 Sep 2004 21:38:20 +0000 Subject: [PATCH] Moved disambiguation stuff to callbacks.IOP. Also fixed rename-persistence. --- plugins/Alias.py | 6 +-- plugins/Network.py | 2 - plugins/Scheduler.py | 6 +-- plugins/Utilities.py | 5 +- src/Misc.py | 29 +++--------- src/Owner.py | 105 ++++++++++------------------------------- src/callbacks.py | 74 ++++++++++++++++++----------- src/conf.py | 13 +++++ test/test_callbacks.py | 1 - 9 files changed, 97 insertions(+), 144 deletions(-) diff --git a/plugins/Alias.py b/plugins/Alias.py index 798591bac..5f056b119 100644 --- a/plugins/Alias.py +++ b/plugins/Alias.py @@ -143,11 +143,7 @@ def makeNewAlias(name, alias): return False everythingReplace(tokens) Owner = irc.getCallback('Owner') - d = Owner.disambiguate(irc, tokens) - if d: - Owner.ambiguousError(irc, msg, d) - else: - self.Proxy(irc.irc, msg, tokens) + self.Proxy(irc.irc, msg, tokens) doc ='\n\nAlias for %r' % \ (utils.nItems('argument', biggestDollar), alias) f = utils.changeFunctionName(f, name, doc) diff --git a/plugins/Network.py b/plugins/Network.py index 92ae4d5ac..971a64305 100644 --- a/plugins/Network.py +++ b/plugins/Network.py @@ -152,8 +152,6 @@ class Network(callbacks.Privmsg): raise callbacks.ArgumentError network = args.pop(0) otherIrc = self._getIrc(network) - Owner = irc.getCallback('Owner') - Owner.disambiguate(irc, args) self.Proxy(otherIrc, msg, args) command = privmsgs.checkCapability(command, 'admin') diff --git a/plugins/Scheduler.py b/plugins/Scheduler.py index b5b07440f..3b3118bd3 100644 --- a/plugins/Scheduler.py +++ b/plugins/Scheduler.py @@ -57,10 +57,6 @@ class Scheduler(callbacks.Privmsg): def _makeCommandFunction(self, irc, msg, command, remove=True): """Makes a function suitable for scheduling from command.""" tokens = callbacks.tokenize(command) - Owner = irc.getCallback('Owner') - ambiguous = Owner.disambiguate(irc, tokens) - if ambiguous: - raise callbacks.Error, callbacks.ambiguousReply(ambiguous) def f(): if remove: del self.events[str(f.eventId)] @@ -74,7 +70,7 @@ class Scheduler(callbacks.Privmsg): 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). + echo). Do pay attention to the quotes in that example. """ (seconds, command) = privmsgs.getArgs(args, required=2) try: diff --git a/plugins/Utilities.py b/plugins/Utilities.py index 05445dad5..dac9eb114 100644 --- a/plugins/Utilities.py +++ b/plugins/Utilities.py @@ -160,10 +160,9 @@ class Utilities(callbacks.Privmsg): commands = map(callbacks.canonicalName, commands) tokens = callbacks.tokenize(text) allTokens = commands + tokens - Owner = irc.getCallback('Owner') - Owner.processTokens(irc, msg, allTokens) - + self.Proxy(irc, msg, allTokens) Class = Utilities + # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/src/Misc.py b/src/Misc.py index debb59343..f2fceaf6e 100755 --- a/src/Misc.py +++ b/src/Misc.py @@ -243,29 +243,14 @@ class Misc(callbacks.Privmsg): command = callbacks.canonicalName(name) # Users might expect "@help @list" to work. # command = command.lstrip(conf.supybot.reply.whenAddressedBy.chars()) - cbs = callbacks.findCallbackForCommand(irc, command) - if len(cbs) > 1: - tokens = [command] - ambiguous = {} - Owner = irc.getCallback('Owner') - Owner.disambiguate(irc, tokens, ambiguous) - if ambiguous: - names = [cb.name() for cb in cbs] - names.sort() - irc.error('That command exists in the %s plugins. ' - 'Please specify exactly which plugin command ' - 'you want help with.'% utils.commaAndify(names)) - return - else: - if len(tokens) == 1: - # It's a src plugin that wasn't disambiguated. - tokens.append(tokens[0]) - assert len(tokens) == 2, tokens - cb = irc.getCallback(tokens[0]) - method = getattr(cb, tokens[1]) - getHelp(method) - elif not cbs: + cbs = irc.findCallbackForCommand(command) + if not cbs: irc.error('There is no such command %s.' % command) + elif len(cbs) > 1: + names = sorted([cb.name() for cb in cbs]) + irc.error('That command exists in the %s plugins. ' + 'Please specify exactly which plugin command ' + 'you want help with.'% utils.commaAndify(names)) else: cb = cbs[0] method = getattr(cb, command) diff --git a/src/Owner.py b/src/Owner.py index 4b422b955..c15cb8525 100644 --- a/src/Owner.py +++ b/src/Owner.py @@ -113,18 +113,23 @@ def loadPluginClass(irc, module, register=None): 'instantiated. If you didn\'t write this ' \ 'plugin, but received it with Supybot, file ' \ 'a bug with us about this error. %s.' % e - name = cb.name() + plugin = cb.name() public = True if hasattr(cb, 'public'): public = cb.public - conf.registerPlugin(name, register, public) - assert not irc.getCallback(name) + conf.registerPlugin(plugin, register, public) + assert not irc.getCallback(plugin) try: # XXX We should first register the rename plugin. - renames = conf.supybot.commands.renames.get(name) - for (name, v) in renames.getValues(fullNames=False): - newName = v() - renameCommand(cb, name, newName) + renames = registerRename(plugin)() + if renames: + for command in renames: + v = registerRename(plugin, command) + newName = v() + assert newName + renameCommand(cb, command, newName) + else: + conf.supybot.commands.renames.unregister(plugin) except registry.NonExistentRegistryEntry, e: pass # The plugin isn't there. irc.addCallback(cb) @@ -138,10 +143,6 @@ conf.supybot.plugins.Owner.register('public', registry.Boolean(True, # supybot.commands. ### -conf.registerGroup(conf.supybot.commands, 'defaultPlugins', - orderAlphabetically=True, help=utils.normalizeWhitespace("""Determines - what commands have default plugins set, and which plugins are set to - be the default for each of those commands.""")) conf.registerGroup(conf.supybot.commands, 'renames', orderAlphabetically=True) def registerDefaultPlugin(command, plugin): @@ -151,13 +152,20 @@ def registerDefaultPlugin(command, plugin): # This must be set, or the quotes won't be removed. conf.supybot.commands.defaultPlugins.get(command).set(plugin) -def registerRename(plugin, command, newName): +def registerRename(plugin, command=None, newName=None): # XXX renames.plugin should have a list of the renames, so they can be # registered from that value. - g = conf.registerGroup(conf.supybot.commands.renames, plugin) - v = conf.registerGlobalValue(g, command, registry.String(newName, '')) - v.setValue(newName) # In case it was already registered. - return v + g = conf.registerGlobalValue(conf.supybot.commands.renames, plugin, + registry.SpaceSeparatedSetOfStrings([], """Determines what commands + in this plugin are to be renamed.""")) + if command is not None: + g().add(command) + v = conf.registerGlobalValue(g, command, registry.String('', '')) + if newName is not None: + v.setValue(newName) # In case it was already registered. + return v + else: + return g def renameCommand(cb, name, newName): assert not hasattr(cb, newName), 'Cannot rename over existing attributes.' @@ -333,64 +341,8 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg): name, e) world.starting = False - def disambiguate(self, irc, tokens, ambiguousCommands=None): - """Disambiguates the given tokens based on the plugins loaded and - commands available in the given irc. Returns a dictionary of - ambiguous commands, mapping the command to the plugins it's - available in.""" - if ambiguousCommands is None: - ambiguousCommands = {} - if tokens: - command = callbacks.canonicalName(tokens[0]) - try: - plugin = conf.supybot.commands.defaultPlugins.get(command)() - if plugin and plugin != '(Unused)': - tokens.insert(0, plugin) - else: - raise registry.NonExistentRegistryEntry - except registry.NonExistentRegistryEntry: - cbs = callbacks.findCallbackForCommand(irc, command) - if len(cbs) > 1: - names = [cb.name() for cb in cbs] - srcs = [name for name in names if name in self._srcPlugins] - if len(srcs) == 1: - if callbacks.canonicalName(name) != command: - # We don't insert the dispatcher name here because - # it's handled later. Man, this stuff is a mess. - tokens.insert(0, srcs[0]) - elif command not in map(callbacks.canonicalName, names): - ambiguousCommands[command] = names - for elt in tokens: - if isinstance(elt, list): - self.disambiguate(irc, elt, ambiguousCommands) - return ambiguousCommands - - def ambiguousError(self, irc, msg, ambiguousCommands): - if len(ambiguousCommands) == 1: # Common case. - (command, names) = ambiguousCommands.popitem() - names.sort() - s = 'The command %r is available in the %s plugins. ' \ - 'Please specify the plugin whose command you ' \ - 'wish to call by using its name as a command ' \ - 'before calling it.' % \ - (command, utils.commaAndify(names)) - else: - L = [] - for (command, names) in ambiguousCommands.iteritems(): - names.sort() - L.append('The command %r is available in the %s ' - 'plugins' % - (command, utils.commaAndify(names))) - s = '%s; please specify from which plugins to ' \ - 'call these commands.' % '; '.join(L) - irc.queueMsg(callbacks.error(msg, s)) - def processTokens(self, irc, msg, tokens): - ambiguousCommands = self.disambiguate(irc, tokens) - if ambiguousCommands: - self.ambiguousError(irc, msg, ambiguousCommands) - else: - callbacks.IrcObjectProxy(irc, msg, tokens) + callbacks.IrcObjectProxy(irc, msg, tokens) def do376(self, irc, msg): channels = ircutils.IrcSet(conf.supybot.channels()) @@ -421,14 +373,9 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg): if ignored: self.log.info('Ignoring command from %s.' % msg.prefix) return - brackets = conf.supybot.reply.brackets.get(msg.args[0])() + brackets = conf.get(conf.supybot.reply.brackets, msg.args[0]) try: tokens = callbacks.tokenize(s, brackets=brackets) - if tokens and isinstance(tokens[0], list): - s = 'The command called may not be the result ' \ - 'of a nested command.' - irc.queueMsg(callbacks.error(msg, s)) - return self.processTokens(irc, msg, tokens) except SyntaxError, e: irc.queueMsg(callbacks.error(msg, str(e))) diff --git a/src/callbacks.py b/src/callbacks.py index 7c81a3c74..9314e9017 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -246,7 +246,6 @@ class Tokenizer(object): def _insideBrackets(self, lexer): ret = [] - firstToken = True while True: token = lexer.get_token() if not token: @@ -258,11 +257,6 @@ class Tokenizer(object): elif token == self.right: return ret elif token == self.left: - if firstToken: - s = 'The command called may not be the result ' \ - 'of a nested command.' - raise SyntaxError, 'The command called may not be the ' \ - 'result or a nested command.' ret.append(self._insideBrackets(lexer)) else: ret.append(self._handleToken(token)) @@ -595,13 +589,47 @@ class IrcObjectProxy(RichReplyMethods): finally: self.commandMethod = None + def findCallbackForCommand(self, command): + cbs = findCallbackForCommand(self, command) + if len(cbs) > 1: + command = canonicalName(command) + # Check for whether it's the name of a callback; their dispatchers + # need to get precedence. + for cb in cbs: + if canonicalName(cb.name()) == command: + return [cb] + try: + # Check if there's a configured defaultPlugin -- the user gets + # precedence in that case. + defaultPlugins = conf.supybot.commands.defaultPlugins + plugin = defaultPlugins.get(command)() + if plugin and plugin != '(Unused)': + cb = self.irc.getCallback(plugin) + if cb is None: + log.warning('%s is set as a default plugin ' + 'for %s, but it isn\'t loaded.', + plugin, command) + raise registry.NonExistentRegistryEntry + else: + return [cb] + except registry.NonExistentRegistryEntry, e: + # Check for whether it's a src/ plugin; they get precedence. + for cb in cbs: + important = [] + importantPlugins = defaultPlugins.importantPlugins() + if cb.name() in importantPlugins: + # We do this to handle multiple importants matching. + important.append(cb) + if len(important) == 1: + return important + return cbs + def finalEval(self): assert not self.finalEvaled, 'finalEval called twice.' self.finalEvaled = True - # We can't canonicalName here because it might be a regexp method. - name = self.args[0] - cbs = findCallbackForCommand(self, name) - if len(cbs) == 0: + command = self.args[0] + cbs = self.findCallbackForCommand(command) + if not cbs: # Normal command not found, let's go for the specialties now. # First, check for addressedRegexps -- they take precedence over # tokenizedCommands. @@ -634,28 +662,20 @@ class IrcObjectProxy(RichReplyMethods): return # No matching regexp commands, now we do invalidCommands. self._callInvalidCommands() + elif len(cbs) > 1: + self.error('The command %s is available in the %s plugins. ' + 'Please specify the plugin whose command you wish ' + 'to call by using its name as a command before %s.' % + (command, sorted([cb.name() for cb in cbs]), command)) else: - # But we must canonicalName here, since we're comparing to a - # canonicalName. - name = canonicalName(name) - if len(cbs) > 1: - for cb in cbs: - if canonicalName(cb.name()) == name: - del self.args[0] - break - else: - # This should've been caught earlier, that's why we - # assert instead of raising a ValueError or something. - assert False,'Non-disambiguated command: args=%r'%self.args - else: - del self.args[0] - cb = cbs[0] + cb = cbs[0] + del self.args[0] # Remove the command. if cb.threaded or conf.supybot.debug.threadAllCommands(): t = CommandThread(target=self._callCommand, - args=(name, cb)) + args=(command, cb)) t.start() else: - self._callCommand(name, cb) + self._callCommand(command, cb) def reply(self, s, noLengthCheck=False, prefixName=None, action=None, private=None, notice=None, to=None, msg=None): diff --git a/src/conf.py b/src/conf.py index 83085afe1..6c64207e3 100644 --- a/src/conf.py +++ b/src/conf.py @@ -513,6 +513,19 @@ registerGlobalValue(supybot, 'flush', # supybot.commands. For stuff relating to commands. ### registerGroup(supybot, 'commands') +registerGroup(supybot.commands, 'defaultPlugins', + orderAlphabetically=True, help=utils.normalizeWhitespace("""Determines + what commands have default plugins set, and which plugins are set to + be the default for each of those commands.""")) +registerGlobalValue(supybot.commands.defaultPlugins, 'importantPlugins', + registry.SpaceSeparatedSetOfStrings(['Admin', 'Channel', 'Config', 'Misc', + 'Owner', 'User'], """Determines what + plugins automatically get precedence over all other plugins when selecting + a default plugin for a command. By default, this includes the standard + loaded plugins. You probably shouldn't change this if you don't know what + you're doing; if you do know what you're doing, then also know that this + set is case-sensitive.""")) + # supybot.commands.disabled moved to callbacks for canonicalName. diff --git a/test/test_callbacks.py b/test/test_callbacks.py index 35f7a4d44..a33f8857f 100644 --- a/test/test_callbacks.py +++ b/test/test_callbacks.py @@ -92,7 +92,6 @@ class TokenizerTestCase(SupyTestCase): def testError(self): self.assertRaises(SyntaxError, tokenize, '[foo') #] self.assertRaises(SyntaxError, tokenize, '"foo') #" - self.assertRaises(SyntaxError, tokenize, '[[foo]]') def testPipe(self): try: