diff --git a/plugins/Alias.py b/plugins/Alias.py index 6ef3d858b..b31b9f902 100644 --- a/plugins/Alias.py +++ b/plugins/Alias.py @@ -198,8 +198,8 @@ class Alias(callbacks.Privmsg): if name != realName: raise AliasError,'That name isn\'t valid. Try %r instead'%realName name = realName - cb = callbacks.findCallbackForCommand(irc, name) - if cb is not None and cb != self: + cbs = callbacks.findCallbackForCommand(irc, name) + if [cb for cb in cbs if cb != self]: raise AliasError, 'A command with the name %r already exists.'%name if name in self.frozen: raise AliasError, 'Alias %r is frozen.' % name diff --git a/src/MiscCommands.py b/src/MiscCommands.py index 617658802..6f44c711e 100755 --- a/src/MiscCommands.py +++ b/src/MiscCommands.py @@ -124,79 +124,60 @@ class MiscCommands(callbacks.Privmsg): irc.error(msg, 'There is no plugin named %s, ' \ 'or that plugin has no commands.' % name) - def syntax(self, irc, msg, args): - """ - - Gives the syntax for a specific command. To find commands, - use the 'list' command to go see the commands offered by a plugin. - The 'list' command by itself will show you what plugins have commands. - """ - command = privmsgs.getArgs(args, needed=0, optional=1) - if not command: - command = 'help' - command = callbacks.canonicalName(command) - cb = callbacks.findCallbackForCommand(irc, command) - if cb: - method = getattr(cb, command) - if hasattr(method, '__doc__') and method.__doc__ is not None: - doclines = method.__doc__.strip().splitlines() - help = doclines.pop(0) - irc.reply(msg, '%s %s' % (command, help)) - else: - irc.reply(msg, 'That command exists, ' - 'but has no syntax description.') - else: - cb = irc.getCallback(command) - if cb: - s = '' - if hasattr(cb, '__doc__') and cb.__doc__ is not None: - s = cb.__doc__ - else: - module = sys.modules[cb.__module__] - if hasattr(module, '__doc__') and module.__doc__: - s = module.__doc__ - if s: - s = ' '.join(map(str.strip, s.splitlines())) - if not s.endswith('.'): - s += '.' - s += ' Use the list command to see what commands this ' \ - 'plugin supports.' - else: - s = 'That plugin has no help description.' - irc.reply(msg, s) - else: - irc.error(msg, 'There is no such command or plugin.') - def help(self, irc, msg, args): - """ + """[] This command gives a much more useful description than the simple - argument list given by the command 'syntax'. + argument list given by the command 'syntax'. is only + necessary if the command is in more than one plugin. """ + def helpFor(method): + doclines = method.__doc__.splitlines() + simplehelp = '(%s %s)' % (method.__name__, doclines.pop(0)) + if doclines: + doclines = filter(None, doclines) + doclines = map(str.strip, doclines) + help = ' '.join(doclines) + s = '%s -- %s' % (ircutils.bold(simplehelp), help) + return s + else: + return 'That command has no help. The syntax is: %s' % \ + simplehelp[1:-1] + if len(args) > 1: + cb = irc.getCallback(args[0]) + if cb is not None: + command = callbacks.canonicalName(privmsgs.getArgs(args[1:])) + if hasattr(cb, 'isCommand') and cb.isCommand(command): + method = getattr(cb, command) + if hasattr(method, '__doc__') and method.__doc__ != None: + irc.reply(msg, helpFor(method)) + else: + irc.error(msg, 'That command has no help.') + else: + irc.error(msg, 'There is no such command %s %s.' % + (args[0], command)) + else: + irc.error(msg, 'There is no such plugin %s' % args[0]) + return command = callbacks.canonicalName(privmsgs.getArgs(args)) # Users might expect "@help @list" to work. command = command.lstrip(conf.prefixChars) - cb = callbacks.findCallbackForCommand(irc, command) - if cb: + cbs = callbacks.findCallbackForCommand(irc, command) + if len(cbs) > 1: + irc.error(msg, 'That command exists in the %s %s. Please specify ' + 'exactly which plugin command you want help with.'%\ + (utils.commaAndify([cb.name() for cb in cbs]), + utils.nItems(len(cbs), 'plugin'))) + return + elif not cbs: + irc.error(msg, 'There is no such command %s.' % command) + else: + cb = cbs[0] method = getattr(cb, command) if hasattr(method, '__doc__') and method.__doc__ is not None: - doclines = method.__doc__.splitlines() - simplehelp = doclines.pop(0) - simplehelp = '(%s %s)' % (command, simplehelp) - if doclines: - doclines = filter(None, doclines) - doclines = map(str.strip, doclines) - help = ' '.join(doclines) - s = '%s %s' % (ircutils.bold(simplehelp),help) - irc.reply(msg, s) - else: - irc.reply(msg, 'That command has no help. '\ - 'The syntax is this: %s %s' % \ - (command, simplehelp)) + irc.reply(msg, helpFor(method)) else: irc.error(msg, '%s has no help or syntax description.'%command) - else: - irc.error(msg, 'There is no such command %s.' % command) def hostmask(self, irc, msg, args): """ @@ -257,9 +238,9 @@ class MiscCommands(callbacks.Privmsg): Returns the plugin is in. """ command = callbacks.canonicalName(privmsgs.getArgs(args)) - cb = callbacks.findCallbackForCommand(irc, command) - if cb is not None: - irc.reply(msg, cb.name()) + cbs = callbacks.findCallbackForCommand(irc, command) + if cbs: + irc.reply(msg, utils.commaAndify([cb.name() for cb in cbs])) else: irc.error(msg, 'There is no such command %s' % command) diff --git a/src/callbacks.py b/src/callbacks.py index dcaaf3679..ea835c3da 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -297,14 +297,15 @@ def getCommands(tokens): return L def findCallbackForCommand(irc, commandName): - """Given a command name and an Irc object, returns the callback that - command is in. Returns None if there is no callback with that command.""" + """Given a command name and an Irc object, returns a list of callbacks that + commandName is in.""" + L = [] for callback in irc.callbacks: if not isinstance(callback, PrivmsgRegexp): if hasattr(callback, 'isCommand'): if callback.isCommand(commandName): - return callback - return None + L.append(callback) + return L class IrcObjectProxy: "A proxy object to allow proper nested of commands (even threaded ones)." @@ -341,9 +342,22 @@ class IrcObjectProxy: self.finalEvaled = True originalName = self.args.pop(0) name = canonicalName(originalName) - cb = findCallbackForCommand(self, name) - try: - if cb is not None: + cbs = findCallbackForCommand(self, name) + if len(cbs) == 0: + self.args.insert(0, originalName) + if not isinstance(self.irc, irclib.Irc): + # If self.irc is an actual irclib.Irc, then this is the + # first command given, and should be ignored as usual. + self.reply(self.msg, '[%s]' % ' '.join(self.args)) + return + elif len(cbs) > 1: + s = 'The command %s is available in plugins %s. Please specify ' \ + 'the plugin whose command you wish to call.' % \ + (originalName, utils.commaAndify([cb.name() for cb in cbs])) + self.error(self.msg, s) + else: + try: + cb = cbs[0] anticap = ircdb.makeAntiCapability(name) #debug.printf('Checking for %s' % anticap) if ircdb.checkCapability(self.msg.prefix, anticap): @@ -371,26 +385,20 @@ class IrcObjectProxy: t.start() else: cb.callCommand(command, self, self.msg, self.args) - else: - self.args.insert(0, originalName) + except (getopt.GetoptError, ArgumentError): + if hasattr(command, '__doc__'): + s = '%s %s' % (name, command.__doc__.splitlines()[0]) + else: + s = 'Invalid arguments for %s.' % name + self.reply(self.msg, s) + except CannotNest, e: if not isinstance(self.irc, irclib.Irc): - # If self.irc is an actual irclib.Irc, then this is the - # first command given, and should be ignored as usual. - self.reply(self.msg, '[%s]' % ' '.join(self.args)) - except (getopt.GetoptError, ArgumentError): - if hasattr(command, '__doc__'): - s = '%s %s' % (name, command.__doc__.splitlines()[0]) - else: - s = 'Invalid arguments for %s.' % name - self.reply(self.msg, s) - except CannotNest, e: - if not isinstance(self.irc, irclib.Irc): - self.error(self.msg, 'Command %r cannot be nested.' % name) - except (SyntaxError, Error), e: - self.reply(self.msg, debug.exnToString(e)) - except Exception, e: - debug.recoverableException() - self.error(self.msg, debug.exnToString(e)) + self.error(self.msg, 'Command %r cannot be nested.' % name) + except (SyntaxError, Error), e: + self.reply(self.msg, debug.exnToString(e)) + except Exception, e: + debug.recoverableException() + self.error(self.msg, debug.exnToString(e)) def reply(self, msg, s, noLengthCheck=False, prefixName=True, action=False, private=False, notice=False): diff --git a/test/test_Alias.py b/test/test_Alias.py index d7fdaf35e..467162641 100644 --- a/test/test_Alias.py +++ b/test/test_Alias.py @@ -70,7 +70,6 @@ class AliasTestCase(ChannelPluginTestCase, PluginDocumentation): plugins = ('Alias', 'Fun', 'Utilities', 'MiscCommands') def testAliasHelp(self): self.assertNotError('alias slashdot foo') - self.assertNotRegexp('syntax slashdot', 'None') self.assertRegexp('help slashdot', "Alias for 'foo'") def testDollars(self): diff --git a/test/test_MiscCommands.py b/test/test_MiscCommands.py index c2ff72c2f..7a539f553 100644 --- a/test/test_MiscCommands.py +++ b/test/test_MiscCommands.py @@ -61,10 +61,6 @@ class MiscCommandsTestCase(ChannelPluginTestCase, PluginDocumentation): finally: conf.repylWhenNotCommand = False - def testSyntax(self): - self.assertNotError('syntax list') - self.assertNotError('syntax help') - def testHelp(self): self.assertNotError('help list') try: @@ -73,7 +69,7 @@ class MiscCommandsTestCase(ChannelPluginTestCase, PluginDocumentation): self.assertNotError('help @list') finally: conf.prefixChars = original - self.assertNotError('help syntax') + self.assertNotError('help list') self.assertRegexp('help help', r'^\x02\(help') self.assertError('help morehelp') diff --git a/test/test_callbacks.py b/test/test_callbacks.py index 7c8e1374d..0e3bed093 100644 --- a/test/test_callbacks.py +++ b/test/test_callbacks.py @@ -163,7 +163,7 @@ class FunctionsTestCase(unittest.TestCase): class PrivmsgTestCase(ChannelPluginTestCase): - plugins = ('Utilities', 'OwnerCommands') + plugins = ('Utilities', 'OwnerCommands', 'MiscCommands') conf.allowEval = True timeout = 2 def testEmptySquareBrackets(self): @@ -239,12 +239,15 @@ class PrivmsgTestCase(ChannelPluginTestCase): self.assertNotRegexp('firstcmd', '(foo.*baz|baz.*foo)') self.assertResponse('first firstcmd', 'foo') self.assertResponse('firstrepeat firstcmd', 'baz') + + def testHelpDispatching(self): + self.irc.addCallback(self.First()) + self.assertNotError('help firstcmd') + self.assertNotError('help first firstcmd') + self.irc.addCallback(self.FirstRepeat()) self.assertError('help firstcmd') self.assertRegexp('help first firstcmd', 'First', 0) # no re.I flag. self.assertRegexp('help firstrepeat firstcmd', 'FirstRepeat', 0) - self.assertResponse('syntax first firstcmd', 'firstcmd First') - self.assertResponse('syntax firstrepeat firstcmd', - 'firstcmd FirstRepeat') def testDefaultCommand(self): self.irc.addCallback(self.First())