From 9be4fd112dcf551841683ca85746bc9cefafb732 Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Thu, 16 Sep 2004 10:28:59 +0000 Subject: [PATCH] Finally got topological sorting working. --- src/Misc.py | 11 ++-- src/Owner.py | 20 +++----- src/callbacks.py | 131 ++++++++++++++++++++++------------------------- src/irclib.py | 74 +++++++++++++++++++------- 4 files changed, 131 insertions(+), 105 deletions(-) diff --git a/src/Misc.py b/src/Misc.py index e0ae15c8c..6d22433a2 100755 --- a/src/Misc.py +++ b/src/Misc.py @@ -79,13 +79,12 @@ class Misc(callbacks.Privmsg): super(Misc, self).__init__() self.invalidCommands = ircutils.FloodQueue(60) - priority = sys.maxint + 1 # This is for working with IrcCallbacks. - callAfter = utils.Everything() - callBefore = utils.Nothing() - def __lt__(self, other): - return False # We should always be the last plugin. - + def callPrecedence(self, irc): + return ([cb for cb in irc.callbacks if cb is not self], []) + 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) # First, we check for invalidCommand floods. This is rightfully done # here since this will be the last invalidCommand called, and thus it diff --git a/src/Owner.py b/src/Owner.py index 88aefea27..4402192d3 100644 --- a/src/Owner.py +++ b/src/Owner.py @@ -202,7 +202,6 @@ class LogProxy(object): def __getattr__(self, attr): return getattr(self.log, attr) - class Owner(privmsgs.CapabilityCheckingPrivmsg): # This plugin must be first; its priority must be lowest; otherwise odd # things will happen when adding callbacks. @@ -249,12 +248,9 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg): continue registerDefaultPlugin(name, s) - priority = ~sys.maxint - 1 # For working with IrcCallbacks. - callAfter = utils.Nothing() - callBefore = utils.Everything() - def __lt__(self, other): - return True # We should always be the first plugin. - + def callPrecedence(self, irc): + return ([], [cb for cb in irc.callbacks if cb is not self]) + def outFilter(self, irc, msg): if msg.command == 'PRIVMSG' and not world.testing: if ircutils.strEqual(msg.args[0], irc.nick): @@ -415,12 +411,14 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg): do422 = do377 = do376 def doPrivmsg(self, irc, msg): - callbacks.Privmsg.handled = False - callbacks.Privmsg.errored = False + assert self is irc.callbacks[0], 'Owner isn\'t first callback.' + msg.errored = False msg.repliedTo = False - ignored = ircdb.checkIgnored(msg.prefix) + if ircmsgs.isCtcp(msg): + return s = callbacks.addressed(irc.nick, msg) if s: + ignored = ircdb.checkIgnored(msg.prefix) if ignored: self.log.info('Ignoring command from %s.' % msg.prefix) return @@ -434,9 +432,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg): return self.processTokens(irc, msg, tokens) except SyntaxError, e: - callbacks.Privmsg.errored = True irc.queueMsg(callbacks.error(msg, str(e))) - return if conf.allowEval: _evalEnv = {'_': None, diff --git a/src/callbacks.py b/src/callbacks.py index 9497b4cbc..e58f1b364 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -194,6 +194,7 @@ def reply(msg, s, prefixName=None, private=None, def error(msg, s, **kwargs): """Makes an error reply to msg with the appropriate error payload.""" kwargs['error'] = True + msg.errored = True return reply(msg, s, **kwargs) def getHelp(method, name=None): @@ -504,7 +505,6 @@ class IrcObjectProxy(RichReplyMethods): # tokenized commands. self.args = copy.deepcopy(args) self.counter = 0 - self.finished = False # Used in _callInvalidCommands. self.commandMethod = None # Used in error. self._resetReplyAttributes() if not args: @@ -538,17 +538,34 @@ class IrcObjectProxy(RichReplyMethods): return 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): - if ircmsgs.isCtcp(self.msg): - log.debug('Skipping invalidCommand, msg is CTCP.') - return log.debug('Calling invalidCommands.') for cb in self.irc.callbacks: - log.debug('Trying to call %s.invalidCommand.' % cb.name()) if hasattr(cb, 'invalidCommand'): + log.debug('Trying to call %s.invalidCommand.' % cb.name()) self._callInvalidCommand(cb) - if self.finished: - log.debug('Finished calling invalidCommand: %s.',cb.name()) + if self.msg.repliedTo: + log.debug('Done calling invalidCommands: %s.',cb.name()) return def _callInvalidCommand(self, cb): @@ -557,7 +574,8 @@ class IrcObjectProxy(RichReplyMethods): except Error, e: self.error(str(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): try: @@ -581,31 +599,37 @@ class IrcObjectProxy(RichReplyMethods): name = self.args[0] cbs = findCallbackForCommand(self, name) 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: -# 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): -## 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) for (r, name) in cb.addressedRes: if r.search(payload): log.debug('Skipping invalidCommand: %s.%s', cb.name(), name) 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() else: # But we must canonicalName here, since we're comparing to a @@ -623,7 +647,6 @@ class IrcObjectProxy(RichReplyMethods): else: del self.args[0] cb = cbs[0] - Privmsg.handled = True if cb.threaded or conf.supybot.debug.threadAllCommands(): t = CommandThread(target=self._callCommand, args=(name, cb)) @@ -706,7 +729,6 @@ class IrcObjectProxy(RichReplyMethods): notice=self.notice, private=self.private, prefixName=self.prefixName)) - self.finished = True return msgs = ircutils.wrap(s, allowedLength-30) # -30 is for nick: msgs.reverse() @@ -742,7 +764,6 @@ class IrcObjectProxy(RichReplyMethods): notice=self.notice, private=self.private, prefixName=self.prefixName)) - self.finished = True finally: self._resetReplyAttributes() else: @@ -766,10 +787,9 @@ class IrcObjectProxy(RichReplyMethods): self.error(formatArgumentError(self.commandMethod), **kwargs) else: raise ArgumentError # We shouldn't get here, but just in case. - self.finished = True def noReply(self): - self.finished = True + self.msg.repliedTo = True def getRealIrc(self): """Returns the real irclib.Irc object underlying this proxy chain.""" @@ -868,7 +888,6 @@ class DisabledCommands(object): class Privmsg(irclib.IrcCallback): """Base class for all Privmsg handlers.""" - __metaclass__ = log.MetaFirewall # For awhile, a comment stood here to say, "Eventually callCommand." But # that's wrong, because we can't do generic error handling in this # callCommand -- plugins need to be able to override callCommand and do @@ -876,13 +895,9 @@ class Privmsg(irclib.IrcCallback): __firewalled__ = {'isCommand': None,} # 'invalidCommand': None} # Gotta raise callbacks.Error. public = True - handled = False - errored = False alwaysCall = () threaded = False noIgnore = False - callAfter = () - callBefore = () Proxy = IrcObjectProxy commandArgs = ['self', 'irc', 'msg', 'args'] # This must be class-scope, so all plugins use the same one. @@ -944,24 +959,6 @@ class Privmsg(irclib.IrcCallback): dispatcher.isDispatcher = True 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): if msg.command == 'PRIVMSG': if self.noIgnore or not ircdb.checkIgnored(msg.prefix,msg.args[0]): @@ -1151,9 +1148,8 @@ class PrivmsgRegexp(Privmsg): irc.replyError() def doPrivmsg(self, irc, msg): - if Privmsg.errored: - self.log.info('%s not running due to Privmsg.errored.', - self.name()) + if msg.errored: + self.log.info('%s not running due to msg.errored.', self.name()) return for (r, name) in self.res: spans = sets.Set() @@ -1218,24 +1214,21 @@ class PrivmsgCommandAndRegexp(Privmsg): raise def doPrivmsg(self, irc, msg): - if Privmsg.errored: - self.log.debug('%s not running due to Privmsg.errored.', - self.name()) + if msg.errored: + self.log.debug('%s not running due to msg.errored.', self.name()) return for (r, name) in self.res: for m in r.finditer(msg.args[1]): proxy = self.Proxy(irc, msg) self.callCommand(name, proxy, msg, m, catchErrors=True) - if not Privmsg.handled: - s = addressed(irc.nick, msg) - if s: - for (r, name) in self.addressedRes: - if Privmsg.handled and name not in self.alwaysCall: - continue - for m in r.finditer(s): - proxy = self.Proxy(irc, msg) - self.callCommand(name, proxy, msg, m, catchErrors=True) - Privmsg.handled = True + s = addressed(irc.nick, msg) + if s: + for (r, name) in self.addressedRes: + if msg.repliedTo and name not in self.alwaysCall: + continue + for m in r.finditer(s): + proxy = self.Proxy(irc, msg) + self.callCommand(name, proxy, msg, m, catchErrors=True) # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/src/irclib.py b/src/irclib.py index 6f6f27a76..90c4a0cf6 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -72,12 +72,8 @@ class IrcCallback(IrcCommandDispatcher): "doCommand" -- doPrivmsg, doNick, do433, etc. These will be called on matching messages. """ - # priority determines the order in which callbacks are called. Lower - # numbers mean *higher* priority (like nice values in *nix). Higher - # priority means the callback is called *earlier* on the inFilter chain, - # *earlier* on the __call__ chain, and *later* on the outFilter chain. - - priority = 99 + callAfter = () + callBefore = () __metaclass__ = log.MetaFirewall __firewalled__ = {'die': None, 'reset': None, @@ -87,19 +83,30 @@ class IrcCallback(IrcCommandDispatcher): 'outFilter': lambda self, irc, msg: msg, 'name': lambda self: self.__class__.__name__,} - def __lt__(self, other): - if isinstance(other, IrcCallback): - ret = self.priority < other.priority - if not ret: - ret = self.name() < other.name() - return ret - else: - return super(IrcCallback, self).__lt__(other) - + def __repr__(self): + return '<%s %s>' % (self.__class__.__name__, self.name()) + def name(self): """Returns the name of the callback.""" 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): """Used for filtering/modifying messages as they're entering. @@ -595,12 +602,43 @@ class Irc(IrcCommandDispatcher): def __repr__(self): return '' % self.network + # This *isn't* threadsafe! def addCallback(self, callback): """Adds a callback to the callbacks list.""" self.callbacks.append(callback) - self.callbacks.sort() - # We used to do this, then we implemented sorting in IrcCallback. - # utils.sortBy(operator.attrgetter('priority'), self.callbacks) + # This is the new list we're building, which will be tsorted. + cbs = [] + # 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): """Gets a given callback by name."""