mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-23 11:09:23 +01:00
Moved disambiguation stuff to callbacks.IOP. Also fixed rename-persistence.
This commit is contained in:
parent
3aa8bdcf18
commit
998f61cce8
@ -143,11 +143,7 @@ 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)
|
self.Proxy(irc.irc, msg, tokens)
|
||||||
if d:
|
|
||||||
Owner.ambiguousError(irc, msg, d)
|
|
||||||
else:
|
|
||||||
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)
|
||||||
f = utils.changeFunctionName(f, name, doc)
|
f = utils.changeFunctionName(f, name, doc)
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
29
src/Misc.py
29
src/Misc.py
@ -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]
|
|
||||||
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:
|
|
||||||
irc.error('There is no such command %s.' % command)
|
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:
|
else:
|
||||||
cb = cbs[0]
|
cb = cbs[0]
|
||||||
method = getattr(cb, command)
|
method = getattr(cb, command)
|
||||||
|
105
src/Owner.py
105
src/Owner.py
@ -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:
|
||||||
newName = v()
|
for command in renames:
|
||||||
renameCommand(cb, name, newName)
|
v = registerRename(plugin, command)
|
||||||
|
newName = v()
|
||||||
|
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
|
||||||
v.setValue(newName) # In case it was already registered.
|
in this plugin are to be renamed."""))
|
||||||
return v
|
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):
|
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,64 +341,8 @@ 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)
|
callbacks.IrcObjectProxy(irc, msg, tokens)
|
||||||
if ambiguousCommands:
|
|
||||||
self.ambiguousError(irc, msg, ambiguousCommands)
|
|
||||||
else:
|
|
||||||
callbacks.IrcObjectProxy(irc, msg, tokens)
|
|
||||||
|
|
||||||
def do376(self, irc, msg):
|
def do376(self, irc, msg):
|
||||||
channels = ircutils.IrcSet(conf.supybot.channels())
|
channels = ircutils.IrcSet(conf.supybot.channels())
|
||||||
@ -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)))
|
||||||
|
@ -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
|
cb = cbs[0]
|
||||||
# canonicalName.
|
del self.args[0] # Remove the command.
|
||||||
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]
|
|
||||||
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):
|
||||||
|
13
src/conf.py
13
src/conf.py
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user