Moved disambiguation stuff to callbacks.IOP. Also fixed rename-persistence.

This commit is contained in:
Jeremy Fincher 2004-09-22 21:38:20 +00:00
parent 3aa8bdcf18
commit 998f61cce8
9 changed files with 97 additions and 144 deletions

View File

@ -143,10 +143,6 @@ def makeNewAlias(name, alias):
return False return False
everythingReplace(tokens) everythingReplace(tokens)
Owner = irc.getCallback('Owner') 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 ='<an alias, %s>\n\nAlias for %r' % \ doc ='<an alias, %s>\n\nAlias for %r' % \
(utils.nItems('argument', biggestDollar), alias) (utils.nItems('argument', biggestDollar), alias)

View File

@ -152,8 +152,6 @@ class Network(callbacks.Privmsg):
raise callbacks.ArgumentError raise callbacks.ArgumentError
network = args.pop(0) network = args.pop(0)
otherIrc = self._getIrc(network) otherIrc = self._getIrc(network)
Owner = irc.getCallback('Owner')
Owner.disambiguate(irc, args)
self.Proxy(otherIrc, msg, args) self.Proxy(otherIrc, msg, args)
command = privmsgs.checkCapability(command, 'admin') command = privmsgs.checkCapability(command, 'admin')

View File

@ -57,10 +57,6 @@ class Scheduler(callbacks.Privmsg):
def _makeCommandFunction(self, irc, msg, command, remove=True): def _makeCommandFunction(self, irc, msg, command, remove=True):
"""Makes a function suitable for scheduling from command.""" """Makes a function suitable for scheduling from command."""
tokens = callbacks.tokenize(command) tokens = callbacks.tokenize(command)
Owner = irc.getCallback('Owner')
ambiguous = Owner.disambiguate(irc, tokens)
if ambiguous:
raise callbacks.Error, callbacks.ambiguousReply(ambiguous)
def f(): def f():
if remove: if remove:
del self.events[str(f.eventId)] del self.events[str(f.eventId)]
@ -74,7 +70,7 @@ class Scheduler(callbacks.Privmsg):
future. For example, 'scheduler add [seconds 30m] "echo [cpu]"' will future. For example, 'scheduler add [seconds 30m] "echo [cpu]"' will
schedule the command "cpu" to be sent to the channel the schedule add 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 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) (seconds, command) = privmsgs.getArgs(args, required=2)
try: try:

View File

@ -160,10 +160,9 @@ class Utilities(callbacks.Privmsg):
commands = map(callbacks.canonicalName, commands) commands = map(callbacks.canonicalName, commands)
tokens = callbacks.tokenize(text) tokens = callbacks.tokenize(text)
allTokens = commands + tokens allTokens = commands + tokens
Owner = irc.getCallback('Owner') self.Proxy(irc, msg, allTokens)
Owner.processTokens(irc, msg, allTokens)
Class = Utilities Class = Utilities
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -243,29 +243,14 @@ class Misc(callbacks.Privmsg):
command = callbacks.canonicalName(name) command = callbacks.canonicalName(name)
# Users might expect "@help @list" to work. # Users might expect "@help @list" to work.
# command = command.lstrip(conf.supybot.reply.whenAddressedBy.chars()) # command = command.lstrip(conf.supybot.reply.whenAddressedBy.chars())
cbs = callbacks.findCallbackForCommand(irc, command) cbs = irc.findCallbackForCommand(command)
if len(cbs) > 1: if not cbs:
tokens = [command] irc.error('There is no such command %s.' % command)
ambiguous = {} elif len(cbs) > 1:
Owner = irc.getCallback('Owner') names = sorted([cb.name() for cb in cbs])
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. ' irc.error('That command exists in the %s plugins. '
'Please specify exactly which plugin command ' 'Please specify exactly which plugin command '
'you want help with.'% utils.commaAndify(names)) '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:
irc.error('There is no such command %s.' % command)
else: else:
cb = cbs[0] cb = cbs[0]
method = getattr(cb, command) method = getattr(cb, command)

View File

@ -113,18 +113,23 @@ def loadPluginClass(irc, module, register=None):
'instantiated. If you didn\'t write this ' \ 'instantiated. If you didn\'t write this ' \
'plugin, but received it with Supybot, file ' \ 'plugin, but received it with Supybot, file ' \
'a bug with us about this error. %s.' % e 'a bug with us about this error. %s.' % e
name = cb.name() plugin = cb.name()
public = True public = True
if hasattr(cb, 'public'): if hasattr(cb, 'public'):
public = cb.public public = cb.public
conf.registerPlugin(name, register, public) conf.registerPlugin(plugin, register, public)
assert not irc.getCallback(name) assert not irc.getCallback(plugin)
try: try:
# XXX We should first register the rename plugin. # XXX We should first register the rename plugin.
renames = conf.supybot.commands.renames.get(name) renames = registerRename(plugin)()
for (name, v) in renames.getValues(fullNames=False): if renames:
for command in renames:
v = registerRename(plugin, command)
newName = v() newName = v()
renameCommand(cb, name, newName) assert newName
renameCommand(cb, command, newName)
else:
conf.supybot.commands.renames.unregister(plugin)
except registry.NonExistentRegistryEntry, e: except registry.NonExistentRegistryEntry, e:
pass # The plugin isn't there. pass # The plugin isn't there.
irc.addCallback(cb) irc.addCallback(cb)
@ -138,10 +143,6 @@ conf.supybot.plugins.Owner.register('public', registry.Boolean(True,
# supybot.commands. # 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) conf.registerGroup(conf.supybot.commands, 'renames', orderAlphabetically=True)
def registerDefaultPlugin(command, plugin): def registerDefaultPlugin(command, plugin):
@ -151,13 +152,20 @@ def registerDefaultPlugin(command, plugin):
# This must be set, or the quotes won't be removed. # This must be set, or the quotes won't be removed.
conf.supybot.commands.defaultPlugins.get(command).set(plugin) 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 # XXX renames.plugin should have a list of the renames, so they can be
# registered from that value. # registered from that value.
g = conf.registerGroup(conf.supybot.commands.renames, plugin) g = conf.registerGlobalValue(conf.supybot.commands.renames, plugin,
v = conf.registerGlobalValue(g, command, registry.String(newName, '')) 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. v.setValue(newName) # In case it was already registered.
return v return v
else:
return g
def renameCommand(cb, name, newName): def renameCommand(cb, name, newName):
assert not hasattr(cb, newName), 'Cannot rename over existing attributes.' assert not hasattr(cb, newName), 'Cannot rename over existing attributes.'
@ -333,63 +341,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
name, e) name, e)
world.starting = False 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): 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): def do376(self, irc, msg):
@ -421,14 +373,9 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
if ignored: if ignored:
self.log.info('Ignoring command from %s.' % msg.prefix) self.log.info('Ignoring command from %s.' % msg.prefix)
return return
brackets = conf.supybot.reply.brackets.get(msg.args[0])() brackets = conf.get(conf.supybot.reply.brackets, msg.args[0])
try: try:
tokens = callbacks.tokenize(s, brackets=brackets) 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) self.processTokens(irc, msg, tokens)
except SyntaxError, e: except SyntaxError, e:
irc.queueMsg(callbacks.error(msg, str(e))) irc.queueMsg(callbacks.error(msg, str(e)))

View File

@ -246,7 +246,6 @@ class Tokenizer(object):
def _insideBrackets(self, lexer): def _insideBrackets(self, lexer):
ret = [] ret = []
firstToken = True
while True: while True:
token = lexer.get_token() token = lexer.get_token()
if not token: if not token:
@ -258,11 +257,6 @@ class Tokenizer(object):
elif token == self.right: elif token == self.right:
return ret return ret
elif token == self.left: 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)) ret.append(self._insideBrackets(lexer))
else: else:
ret.append(self._handleToken(token)) ret.append(self._handleToken(token))
@ -595,13 +589,47 @@ class IrcObjectProxy(RichReplyMethods):
finally: finally:
self.commandMethod = None 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): def finalEval(self):
assert not self.finalEvaled, 'finalEval called twice.' assert not self.finalEvaled, 'finalEval called twice.'
self.finalEvaled = True self.finalEvaled = True
# We can't canonicalName here because it might be a regexp method. command = self.args[0]
name = self.args[0] cbs = self.findCallbackForCommand(command)
cbs = findCallbackForCommand(self, name) if not cbs:
if len(cbs) == 0:
# Normal command not found, let's go for the specialties now. # Normal command not found, let's go for the specialties now.
# First, check for addressedRegexps -- they take precedence over # First, check for addressedRegexps -- they take precedence over
# tokenizedCommands. # tokenizedCommands.
@ -634,28 +662,20 @@ class IrcObjectProxy(RichReplyMethods):
return return
# No matching regexp commands, now we do invalidCommands. # No matching regexp commands, now we do invalidCommands.
self._callInvalidCommands() 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: 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(): if cb.threaded or conf.supybot.debug.threadAllCommands():
t = CommandThread(target=self._callCommand, t = CommandThread(target=self._callCommand,
args=(name, cb)) args=(command, cb))
t.start() t.start()
else: else:
self._callCommand(name, cb) self._callCommand(command, cb)
def reply(self, s, noLengthCheck=False, prefixName=None, def reply(self, s, noLengthCheck=False, prefixName=None,
action=None, private=None, notice=None, to=None, msg=None): action=None, private=None, notice=None, to=None, msg=None):

View File

@ -513,6 +513,19 @@ registerGlobalValue(supybot, 'flush',
# supybot.commands. For stuff relating to commands. # supybot.commands. For stuff relating to commands.
### ###
registerGroup(supybot, '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. # supybot.commands.disabled moved to callbacks for canonicalName.

View File

@ -92,7 +92,6 @@ class TokenizerTestCase(SupyTestCase):
def testError(self): def testError(self):
self.assertRaises(SyntaxError, tokenize, '[foo') #] self.assertRaises(SyntaxError, tokenize, '[foo') #]
self.assertRaises(SyntaxError, tokenize, '"foo') #" self.assertRaises(SyntaxError, tokenize, '"foo') #"
self.assertRaises(SyntaxError, tokenize, '[[foo]]')
def testPipe(self): def testPipe(self):
try: try: