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
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)
doc ='<an alias, %s>\n\nAlias for %r' % \
(utils.nItems('argument', biggestDollar), alias)

View File

@ -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')

View File

@ -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:

View File

@ -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:

View File

@ -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()
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))
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:
cb = cbs[0]
method = getattr(cb, command)

View File

@ -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):
renames = registerRename(plugin)()
if renames:
for command in renames:
v = registerRename(plugin, command)
newName = v()
renameCommand(cb, name, newName)
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, ''))
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,63 +341,7 @@ 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)
def do376(self, irc, msg):
@ -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)))

View File

@ -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]
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):

View File

@ -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.

View File

@ -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: