Finally got topological sorting working.

This commit is contained in:
Jeremy Fincher 2004-09-16 10:28:59 +00:00
parent 727145afbe
commit 9be4fd112d
4 changed files with 131 additions and 105 deletions

View File

@ -79,13 +79,12 @@ class Misc(callbacks.Privmsg):
super(Misc, self).__init__() super(Misc, self).__init__()
self.invalidCommands = ircutils.FloodQueue(60) self.invalidCommands = ircutils.FloodQueue(60)
priority = sys.maxint + 1 # This is for working with IrcCallbacks. def callPrecedence(self, irc):
callAfter = utils.Everything() return ([cb for cb in irc.callbacks if cb is not self], [])
callBefore = utils.Nothing()
def __lt__(self, other):
return False # We should always be the last plugin.
def invalidCommand(self, irc, msg, tokens): def invalidCommand(self, irc, msg, tokens):
assert not msg.repliedTo, 'repliedTo msg in Misc.invalidCommand.'
assert self is irc.callbacks[-1], 'Misc isn\'t last callback.'
self.log.debug('Misc.invalidCommand called (tokens %s)', tokens) self.log.debug('Misc.invalidCommand called (tokens %s)', tokens)
# First, we check for invalidCommand floods. This is rightfully done # First, we check for invalidCommand floods. This is rightfully done
# here since this will be the last invalidCommand called, and thus it # here since this will be the last invalidCommand called, and thus it

View File

@ -202,7 +202,6 @@ class LogProxy(object):
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.log, attr) return getattr(self.log, attr)
class Owner(privmsgs.CapabilityCheckingPrivmsg): class Owner(privmsgs.CapabilityCheckingPrivmsg):
# This plugin must be first; its priority must be lowest; otherwise odd # This plugin must be first; its priority must be lowest; otherwise odd
# things will happen when adding callbacks. # things will happen when adding callbacks.
@ -249,11 +248,8 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
continue continue
registerDefaultPlugin(name, s) registerDefaultPlugin(name, s)
priority = ~sys.maxint - 1 # For working with IrcCallbacks. def callPrecedence(self, irc):
callAfter = utils.Nothing() return ([], [cb for cb in irc.callbacks if cb is not self])
callBefore = utils.Everything()
def __lt__(self, other):
return True # We should always be the first plugin.
def outFilter(self, irc, msg): def outFilter(self, irc, msg):
if msg.command == 'PRIVMSG' and not world.testing: if msg.command == 'PRIVMSG' and not world.testing:
@ -415,12 +411,14 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
do422 = do377 = do376 do422 = do377 = do376
def doPrivmsg(self, irc, msg): def doPrivmsg(self, irc, msg):
callbacks.Privmsg.handled = False assert self is irc.callbacks[0], 'Owner isn\'t first callback.'
callbacks.Privmsg.errored = False msg.errored = False
msg.repliedTo = False msg.repliedTo = False
ignored = ircdb.checkIgnored(msg.prefix) if ircmsgs.isCtcp(msg):
return
s = callbacks.addressed(irc.nick, msg) s = callbacks.addressed(irc.nick, msg)
if s: if s:
ignored = ircdb.checkIgnored(msg.prefix)
if ignored: if ignored:
self.log.info('Ignoring command from %s.' % msg.prefix) self.log.info('Ignoring command from %s.' % msg.prefix)
return return
@ -434,9 +432,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
return return
self.processTokens(irc, msg, tokens) self.processTokens(irc, msg, tokens)
except SyntaxError, e: except SyntaxError, e:
callbacks.Privmsg.errored = True
irc.queueMsg(callbacks.error(msg, str(e))) irc.queueMsg(callbacks.error(msg, str(e)))
return
if conf.allowEval: if conf.allowEval:
_evalEnv = {'_': None, _evalEnv = {'_': None,

View File

@ -194,6 +194,7 @@ def reply(msg, s, prefixName=None, private=None,
def error(msg, s, **kwargs): def error(msg, s, **kwargs):
"""Makes an error reply to msg with the appropriate error payload.""" """Makes an error reply to msg with the appropriate error payload."""
kwargs['error'] = True kwargs['error'] = True
msg.errored = True
return reply(msg, s, **kwargs) return reply(msg, s, **kwargs)
def getHelp(method, name=None): def getHelp(method, name=None):
@ -504,7 +505,6 @@ class IrcObjectProxy(RichReplyMethods):
# tokenized commands. # tokenized commands.
self.args = copy.deepcopy(args) self.args = copy.deepcopy(args)
self.counter = 0 self.counter = 0
self.finished = False # Used in _callInvalidCommands.
self.commandMethod = None # Used in error. self.commandMethod = None # Used in error.
self._resetReplyAttributes() self._resetReplyAttributes()
if not args: if not args:
@ -538,17 +538,34 @@ class IrcObjectProxy(RichReplyMethods):
return return
self.finalEval() self.finalEval()
def _callTokenizedCommands(self):
log.debug('Calling tokenizedCommands.')
for cb in self.irc.callbacks:
if hasattr(cb, 'tokenizedCommand'):
log.debug('Trying to call %s.tokenizedCommand.' % cb.name())
self._callTokenizedCommand(cb)
if self.msg.repliedTo:
log.debug('Done calling tokenizedCommands: %s.' % cb.name())
return
def _callTokenizedCommand(self, cb):
try:
cb.tokenizedCommand(self, self.msg, self.args)
except Error, e:
self.error(str(e))
except Exception, e:
log.exception('Uncaught exception in %s.tokenizedCommand.' %
cb.name())
def _callInvalidCommands(self): def _callInvalidCommands(self):
if ircmsgs.isCtcp(self.msg):
log.debug('Skipping invalidCommand, msg is CTCP.')
return
log.debug('Calling invalidCommands.') log.debug('Calling invalidCommands.')
for cb in self.irc.callbacks: for cb in self.irc.callbacks:
log.debug('Trying to call %s.invalidCommand.' % cb.name())
if hasattr(cb, 'invalidCommand'): if hasattr(cb, 'invalidCommand'):
log.debug('Trying to call %s.invalidCommand.' % cb.name())
self._callInvalidCommand(cb) self._callInvalidCommand(cb)
if self.finished: if self.msg.repliedTo:
log.debug('Finished calling invalidCommand: %s.',cb.name()) log.debug('Done calling invalidCommands: %s.',cb.name())
return return
def _callInvalidCommand(self, cb): def _callInvalidCommand(self, cb):
@ -557,7 +574,8 @@ class IrcObjectProxy(RichReplyMethods):
except Error, e: except Error, e:
self.error(str(e)) self.error(str(e))
except Exception, e: except Exception, e:
log.exception('Uncaught exception in %s.invalidCommand'% cb.name()) log.exception('Uncaught exception in %s.invalidCommand.'%
cb.name())
def _callCommand(self, name, cb): def _callCommand(self, name, cb):
try: try:
@ -581,31 +599,37 @@ class IrcObjectProxy(RichReplyMethods):
name = self.args[0] name = self.args[0]
cbs = findCallbackForCommand(self, name) cbs = findCallbackForCommand(self, name)
if len(cbs) == 0: if len(cbs) == 0:
# Normal command not found, let's go for the specialties now.
# First, check for addressedRegexps -- they take precedence over
# tokenizedCommands.
for cb in self.irc.callbacks: for cb in self.irc.callbacks:
# We used not to run invalidCommands if regexps matched, but now I'd say that
# invalidCommands are more essential than regexps, so it is regexps that should
# have to work around invalidCommands, not vice-versa.
## if isinstance(cb, PrivmsgRegexp):
## for (r, name) in cb.res:
## if r.search(self.msg.args[1]):
## log.debug('Skipping invalidCommand: %s.%s',
## cb.name(), name)
## return
if isinstance(cb, PrivmsgCommandAndRegexp): if isinstance(cb, PrivmsgCommandAndRegexp):
## for (r, name) in cb.res:
## if r.search(self.msg.args[1]):
## log.debug('Skipping invalidCommand: %s.%s',
## cb.name(), name)
## return
# Although we still consider addressedRegexps to be commands, so we don't call
# invalidCommnads if an addressedRegexp matches.
payload = addressed(self.irc.nick, self.msg) payload = addressed(self.irc.nick, self.msg)
for (r, name) in cb.addressedRes: for (r, name) in cb.addressedRes:
if r.search(payload): if r.search(payload):
log.debug('Skipping invalidCommand: %s.%s', log.debug('Skipping invalidCommand: %s.%s',
cb.name(), name) cb.name(), name)
return return
# Ok, no regexp-based things matched. # Now we call tokenizedCommands.
self._callTokenizedCommands()
# Return if we've replied.
if self.msg.repliedTo:
return
# Now we check for regexp commands, which override invalidCommand.
for cb in self.irc.callbacks:
if isinstance(cb, PrivmsgRegexp):
for (r, name) in cb.res:
if r.search(self.msg.args[1]):
log.debug('Skipping invalidCommand: %s.%s',
cb.name(), name)
return
elif isinstance(cb, PrivmsgCommandAndRegexp):
for (r, name) in cb.res:
if r.search(self.msg.args[1]):
log.debug('Skipping invalidCommand: %s.%s',
cb.name(), name)
return
# No matching regexp commands, now we do invalidCommands.
self._callInvalidCommands() self._callInvalidCommands()
else: else:
# But we must canonicalName here, since we're comparing to a # But we must canonicalName here, since we're comparing to a
@ -623,7 +647,6 @@ class IrcObjectProxy(RichReplyMethods):
else: else:
del self.args[0] del self.args[0]
cb = cbs[0] cb = cbs[0]
Privmsg.handled = True
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=(name, cb))
@ -706,7 +729,6 @@ class IrcObjectProxy(RichReplyMethods):
notice=self.notice, notice=self.notice,
private=self.private, private=self.private,
prefixName=self.prefixName)) prefixName=self.prefixName))
self.finished = True
return return
msgs = ircutils.wrap(s, allowedLength-30) # -30 is for nick: msgs = ircutils.wrap(s, allowedLength-30) # -30 is for nick:
msgs.reverse() msgs.reverse()
@ -742,7 +764,6 @@ class IrcObjectProxy(RichReplyMethods):
notice=self.notice, notice=self.notice,
private=self.private, private=self.private,
prefixName=self.prefixName)) prefixName=self.prefixName))
self.finished = True
finally: finally:
self._resetReplyAttributes() self._resetReplyAttributes()
else: else:
@ -766,10 +787,9 @@ class IrcObjectProxy(RichReplyMethods):
self.error(formatArgumentError(self.commandMethod), **kwargs) self.error(formatArgumentError(self.commandMethod), **kwargs)
else: else:
raise ArgumentError # We shouldn't get here, but just in case. raise ArgumentError # We shouldn't get here, but just in case.
self.finished = True
def noReply(self): def noReply(self):
self.finished = True self.msg.repliedTo = True
def getRealIrc(self): def getRealIrc(self):
"""Returns the real irclib.Irc object underlying this proxy chain.""" """Returns the real irclib.Irc object underlying this proxy chain."""
@ -868,7 +888,6 @@ class DisabledCommands(object):
class Privmsg(irclib.IrcCallback): class Privmsg(irclib.IrcCallback):
"""Base class for all Privmsg handlers.""" """Base class for all Privmsg handlers."""
__metaclass__ = log.MetaFirewall
# For awhile, a comment stood here to say, "Eventually callCommand." But # For awhile, a comment stood here to say, "Eventually callCommand." But
# that's wrong, because we can't do generic error handling in this # that's wrong, because we can't do generic error handling in this
# callCommand -- plugins need to be able to override callCommand and do # callCommand -- plugins need to be able to override callCommand and do
@ -876,13 +895,9 @@ class Privmsg(irclib.IrcCallback):
__firewalled__ = {'isCommand': None,} __firewalled__ = {'isCommand': None,}
# 'invalidCommand': None} # Gotta raise callbacks.Error. # 'invalidCommand': None} # Gotta raise callbacks.Error.
public = True public = True
handled = False
errored = False
alwaysCall = () alwaysCall = ()
threaded = False threaded = False
noIgnore = False noIgnore = False
callAfter = ()
callBefore = ()
Proxy = IrcObjectProxy Proxy = IrcObjectProxy
commandArgs = ['self', 'irc', 'msg', 'args'] commandArgs = ['self', 'irc', 'msg', 'args']
# This must be class-scope, so all plugins use the same one. # This must be class-scope, so all plugins use the same one.
@ -944,24 +959,6 @@ class Privmsg(irclib.IrcCallback):
dispatcher.isDispatcher = True dispatcher.isDispatcher = True
setattr(self.__class__, canonicalname, dispatcher) setattr(self.__class__, canonicalname, dispatcher)
# In addition to priority, plugins may specify callBefore and callAfter
# attributes which are lists of plugin names which the plugin should be
# called before and after, respectively. We may, at some future point,
# remove priority entirely.
def __lt__(self, other):
selfName = self.name()
otherName = other.name()
# We can't be certain of the order the callbacks list is in, so we
# can't be certain that our __lt__ is the most specific one, so
# we basically run the other callback's as well.
if isinstance(other, Privmsg):
if other.name() in self.callBefore or \
self.name() in other.callAfter:
return True
else:
return False
return self.__parent.__lt__(other)
def __call__(self, irc, msg): def __call__(self, irc, msg):
if msg.command == 'PRIVMSG': if msg.command == 'PRIVMSG':
if self.noIgnore or not ircdb.checkIgnored(msg.prefix,msg.args[0]): if self.noIgnore or not ircdb.checkIgnored(msg.prefix,msg.args[0]):
@ -1151,9 +1148,8 @@ class PrivmsgRegexp(Privmsg):
irc.replyError() irc.replyError()
def doPrivmsg(self, irc, msg): def doPrivmsg(self, irc, msg):
if Privmsg.errored: if msg.errored:
self.log.info('%s not running due to Privmsg.errored.', self.log.info('%s not running due to msg.errored.', self.name())
self.name())
return return
for (r, name) in self.res: for (r, name) in self.res:
spans = sets.Set() spans = sets.Set()
@ -1218,24 +1214,21 @@ class PrivmsgCommandAndRegexp(Privmsg):
raise raise
def doPrivmsg(self, irc, msg): def doPrivmsg(self, irc, msg):
if Privmsg.errored: if msg.errored:
self.log.debug('%s not running due to Privmsg.errored.', self.log.debug('%s not running due to msg.errored.', self.name())
self.name())
return return
for (r, name) in self.res: for (r, name) in self.res:
for m in r.finditer(msg.args[1]): for m in r.finditer(msg.args[1]):
proxy = self.Proxy(irc, msg) proxy = self.Proxy(irc, msg)
self.callCommand(name, proxy, msg, m, catchErrors=True) self.callCommand(name, proxy, msg, m, catchErrors=True)
if not Privmsg.handled: s = addressed(irc.nick, msg)
s = addressed(irc.nick, msg) if s:
if s: for (r, name) in self.addressedRes:
for (r, name) in self.addressedRes: if msg.repliedTo and name not in self.alwaysCall:
if Privmsg.handled and name not in self.alwaysCall: continue
continue for m in r.finditer(s):
for m in r.finditer(s): proxy = self.Proxy(irc, msg)
proxy = self.Proxy(irc, msg) self.callCommand(name, proxy, msg, m, catchErrors=True)
self.callCommand(name, proxy, msg, m, catchErrors=True)
Privmsg.handled = True
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -72,12 +72,8 @@ class IrcCallback(IrcCommandDispatcher):
"doCommand" -- doPrivmsg, doNick, do433, etc. These will be called "doCommand" -- doPrivmsg, doNick, do433, etc. These will be called
on matching messages. on matching messages.
""" """
# priority determines the order in which callbacks are called. Lower callAfter = ()
# numbers mean *higher* priority (like nice values in *nix). Higher callBefore = ()
# priority means the callback is called *earlier* on the inFilter chain,
# *earlier* on the __call__ chain, and *later* on the outFilter chain.
priority = 99
__metaclass__ = log.MetaFirewall __metaclass__ = log.MetaFirewall
__firewalled__ = {'die': None, __firewalled__ = {'die': None,
'reset': None, 'reset': None,
@ -87,19 +83,30 @@ class IrcCallback(IrcCommandDispatcher):
'outFilter': lambda self, irc, msg: msg, 'outFilter': lambda self, irc, msg: msg,
'name': lambda self: self.__class__.__name__,} 'name': lambda self: self.__class__.__name__,}
def __lt__(self, other): def __repr__(self):
if isinstance(other, IrcCallback): return '<%s %s>' % (self.__class__.__name__, self.name())
ret = self.priority < other.priority
if not ret:
ret = self.name() < other.name()
return ret
else:
return super(IrcCallback, self).__lt__(other)
def name(self): def name(self):
"""Returns the name of the callback.""" """Returns the name of the callback."""
return self.__class__.__name__ return self.__class__.__name__
def callPrecedence(self, irc):
"""Returns a pair of (callbacks to call before me,
callbacks to call after me)"""
after = []
before = []
for name in self.callAfter:
cb = irc.getCallback(name)
if cb is not None:
after.append(cb)
for name in self.callBefore:
cb = irc.getCallback(name)
if cb is not None:
before.append(cb)
assert self not in after, '%s was in its own after.' % self.name()
assert self not in before, '%s was in its own before.' % self.name()
return (before, after)
def inFilter(self, irc, msg): def inFilter(self, irc, msg):
"""Used for filtering/modifying messages as they're entering. """Used for filtering/modifying messages as they're entering.
@ -595,12 +602,43 @@ class Irc(IrcCommandDispatcher):
def __repr__(self): def __repr__(self):
return '<irclib.Irc object for %s>' % self.network return '<irclib.Irc object for %s>' % self.network
# This *isn't* threadsafe!
def addCallback(self, callback): def addCallback(self, callback):
"""Adds a callback to the callbacks list.""" """Adds a callback to the callbacks list."""
self.callbacks.append(callback) self.callbacks.append(callback)
self.callbacks.sort() # This is the new list we're building, which will be tsorted.
# We used to do this, then we implemented sorting in IrcCallback. cbs = []
# utils.sortBy(operator.attrgetter('priority'), self.callbacks) # The vertices are self.callbacks itself. Now we make the edges.
edges = sets.Set()
for cb in self.callbacks:
(before, after) = cb.callPrecedence(self)
assert cb not in after, 'cb was in its own after.'
assert cb not in before, 'cb was in its own before.'
for otherCb in before:
edges.add((otherCb, cb))
for otherCb in after:
edges.add((cb, otherCb))
def getFirsts():
firsts = sets.Set(self.callbacks) - sets.Set(cbs)
for (before, after) in edges:
firsts.discard(after)
return firsts
firsts = getFirsts()
while firsts:
# Then we add these to our list of cbs, and remove all edges that
# originate with these cbs.
for cb in firsts:
cbs.append(cb)
edgesToRemove = []
for edge in edges:
if edge[0] is cb:
edgesToRemove.append(edge)
for edge in edgesToRemove:
edges.remove(edge)
firsts = getFirsts()
assert len(cbs) == len(self.callbacks), \
'cbs: %s, self.callbacks: %s' % (cbs, self.callbacks)
self.callbacks[:] = cbs
def getCallback(self, name): def getCallback(self, name):
"""Gets a given callback by name.""" """Gets a given callback by name."""