From 4dafdcdd57a2cb45abe88e96b1fd157459ec9e01 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sat, 23 Oct 2004 22:07:50 +0000 Subject: [PATCH] Massive updates. %r -> %s, some commands.wrap updates. Factoids is broke until we get some stuff working in commands.py --- plugins/Alias.py | 12 +- plugins/Anonymous.py | 7 +- plugins/Bayes.py | 3 +- plugins/Bugzilla.py | 8 +- plugins/Ctcp.py | 5 +- plugins/Dict.py | 7 +- plugins/Dunno.py | 8 +- plugins/Factoids.py | 430 +++++++++++++++++++++----------------- plugins/FunDB.py | 8 +- plugins/Google.py | 4 +- plugins/Infobot.py | 18 +- plugins/Karma.py | 12 +- plugins/LogToIrc.py | 2 +- plugins/Lookup.py | 25 ++- plugins/Math.py | 9 +- plugins/MoobotFactoids.py | 11 +- plugins/Note.py | 3 +- plugins/Poll.py | 15 +- plugins/QuoteGrabs.py | 2 +- plugins/Quotes.py | 11 +- plugins/RSS.py | 5 +- plugins/Services.py | 3 +- plugins/ShrinkUrl.py | 6 +- plugins/Success.py | 8 +- plugins/Todo.py | 14 +- plugins/Topic.py | 2 +- plugins/URL.py | 7 +- plugins/Unix.py | 4 +- plugins/WordStats.py | 31 +-- plugins/Words.py | 16 +- src/callbacks.py | 5 + src/commands.py | 9 +- test/test_Alias.py | 2 +- test/test_Todo.py | 5 +- test/test_commands.py | 4 + 35 files changed, 407 insertions(+), 314 deletions(-) diff --git a/plugins/Alias.py b/plugins/Alias.py index d480f1c43..1b56b8356 100644 --- a/plugins/Alias.py +++ b/plugins/Alias.py @@ -225,7 +225,8 @@ class Alias(callbacks.Privmsg): raise AliasError, 'Names cannot coincide with names of plugins.' realName = callbacks.canonicalName(name) if name != realName: - s = 'That name isn\'t valid. Try %r instead.' % realName + s = 'That name isn\'t valid. Try %s instead.' % \ + utils.quoted(realName) raise AliasError, s name = realName cbs = callbacks.findCallbackForCommand(irc, name) @@ -236,7 +237,7 @@ class Alias(callbacks.Privmsg): if name in self.aliases: (currentAlias, locked) = self.aliases[name] if locked and currentAlias != alias: - raise AliasError, 'Alias %r is locked.' % name + raise AliasError, 'Alias %s is locked.' % utils.quoted(name) try: f = makeNewAlias(name, alias) except RecursiveAlias: @@ -280,8 +281,8 @@ class Alias(callbacks.Privmsg): alias += ' $*' try: self.addAlias(irc, name, alias) - self.log.info('Adding alias %r for %r (from %s)' % - (name, alias, msg.prefix)) + self.log.info('Adding alias %s for %s (from %s)' % + (utils.quoted(name), utils.quoted(alias),msg.prefix)) irc.replySuccess() except AliasError, e: irc.error(str(e)) @@ -294,7 +295,8 @@ class Alias(callbacks.Privmsg): name = privmsgs.getArgs(args) try: self.removeAlias(name) - self.log.info('Removing alias %r (from %s)' % (name, msg.prefix)) + self.log.info('Removing alias %s (from %s)' % (utils.quoted(name), + msg.prefix)) irc.replySuccess() except AliasError, e: irc.error(str(e)) diff --git a/plugins/Anonymous.py b/plugins/Anonymous.py index 0a29bdfd7..90219b4b3 100644 --- a/plugins/Anonymous.py +++ b/plugins/Anonymous.py @@ -103,7 +103,8 @@ class Anonymous(callbacks.Privmsg): """ (channel, text) = privmsgs.getArgs(args, required=2) self._preCheck(irc, msg, channel) - self.log.info('Saying %r in %s due to %s.', text, channel, msg.prefix) + self.log.info('Saying %s in %s due to %s.', + utils.quoted(text), channel, msg.prefix) irc.queueMsg(ircmsgs.privmsg(channel, text)) def do(self, irc, msg, args): @@ -113,8 +114,8 @@ class Anonymous(callbacks.Privmsg): """ (channel, action) = privmsgs.getArgs(args, required=2) self._preCheck(irc, msg, channel) - self.log.info('Performing %r in %s due to %s.', - action, channel, msg.prefix) + self.log.info('Performing %s in %s due to %s.', + utils.quoted(action), channel, msg.prefix) irc.queueMsg(ircmsgs.action(channel, action)) diff --git a/plugins/Bayes.py b/plugins/Bayes.py index b94519227..d3ad1cca0 100644 --- a/plugins/Bayes.py +++ b/plugins/Bayes.py @@ -135,7 +135,8 @@ class Bayes(callbacks.Privmsg): (kind, prob) = kind prob *= 100 text = utils.ellipsisify(text, 30) - self.log.debug('Classified %r as %s. (%.2f%%)', text, kind, prob) + self.log.debug('Classified %s as %s. (%.2f%%)', + utils.quoted(text), kind, prob) self.db.trainNick(channel, msg.nick, text) def guess(self, irc, msg, args, channel, text): diff --git a/plugins/Bugzilla.py b/plugins/Bugzilla.py index 258a810d1..ee82671b7 100644 --- a/plugins/Bugzilla.py +++ b/plugins/Bugzilla.py @@ -89,7 +89,7 @@ conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'bugSnarfer', conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'bold', registry.Boolean(True, """Determines whether results are bolded.""")) conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'replyNoBugzilla', - registry.String('I don\'t have a bugzilla %r.', """Determines the phrase + registry.String('I don\'t have a bugzilla %s.', """Determines the phrase to use when notifying the user that there is no information about that bugzilla site.""")) conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'snarfTarget', @@ -333,8 +333,8 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp): if not bugs: irc.error('I could not find any bugs.') return - s = '%s match %r (%s): %s.' % \ - (utils.nItems('bug', len(bugs)), searchstr, + s = '%s match %s (%s): %s.' % \ + (utils.nItems('bug', len(bugs)), utils.quoted(searchstr), ' AND '.join(keywords), utils.commaAndify(map(str, bugids))) irc.reply(s) search = wrap(search, [getopts({'keywords':'text'}), 'text', 'text']) @@ -349,7 +349,7 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp): (url, description) = self.db[name] except KeyError: s = self.registryValue('replyNoBugzilla', msg.args[0]) - irc.error(s % name) + irc.error(s % utils.quoted(name)) return queryurl = '%s/xml.cgi?id=%s' % (url, number) try: diff --git a/plugins/Ctcp.py b/plugins/Ctcp.py index e95d8533f..cd5dc1b35 100644 --- a/plugins/Ctcp.py +++ b/plugins/Ctcp.py @@ -164,8 +164,9 @@ class Ctcp(callbacks.PrivmsgCommandAndRegexp): L = [] for (reply, nicks) in self.versions.iteritems(): if nicks: - L.append('%s responded with %r' % - (utils.commaAndify(nicks), reply)) + L.append('%s responded with %s' % + (utils.commaAndify(nicks), + utils.quoted(reply))) else: L.append(reply) irc.reply(utils.commaAndify(L)) diff --git a/plugins/Dict.py b/plugins/Dict.py index 5c47d985f..d48922140 100644 --- a/plugins/Dict.py +++ b/plugins/Dict.py @@ -130,10 +130,11 @@ class Dict(callbacks.Privmsg): dbs = sets.Set() if not definitions: if dictionary == '*': - irc.reply('No definition for %r could be found.' % word) + irc.reply('No definition for %s could be found.' % + utils.quoted(word)) else: - irc.reply('No definition for %r could be found in %s' % - (word, ircutils.bold(dictionary))) + irc.reply('No definition for %s could be found in %s' % + (utils.quoted(word), ircutils.bold(dictionary))) return L = [] for d in definitions: diff --git a/plugins/Dunno.py b/plugins/Dunno.py index 2bcaf5567..91f11ff3d 100644 --- a/plugins/Dunno.py +++ b/plugins/Dunno.py @@ -162,8 +162,8 @@ class Dunno(callbacks.Privmsg): return text.lower() in dunno.text.lower() ids = [str(dunno.id) for dunno in self.db.select(channel, p)] if ids: - s = 'Dunno search for %r (%s found): %s.' % \ - (text, len(ids), utils.commaAndify(ids)) + s = 'Dunno search for %s (%s found): %s.' % \ + (utils.quoted(text), len(ids), utils.commaAndify(ids)) irc.reply(s) else: irc.reply('No dunnos found matching that search criteria.') @@ -180,8 +180,8 @@ class Dunno(callbacks.Privmsg): name = ircdb.users.getUser(dunno.by).name at = time.localtime(dunno.at) timeStr = time.strftime(conf.supybot.humanTimestampFormat(), at) - irc.reply("Dunno #%s: %r (added by %s at %s)" % \ - (id, dunno.text, name, timeStr)) + irc.reply("Dunno #%s: %s (added by %s at %s)" % \ + (id, utils.quoted(dunno.text), name, timeStr)) except KeyError: irc.error('No dunno found with that id.') get = wrap(get, ['channeldb', ('id', 'dunno')]) diff --git a/plugins/Factoids.py b/plugins/Factoids.py index dde5d997b..137fee11b 100644 --- a/plugins/Factoids.py +++ b/plugins/Factoids.py @@ -34,8 +34,6 @@ available on demand via several commands. __revision__ = "$Id$" -import supybot.plugins as plugins - import time import getopt import string @@ -45,6 +43,8 @@ from itertools import imap import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb +from supybot.commands import * +import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.privmsgs as privmsgs import supybot.registry as registry @@ -79,19 +79,36 @@ conf.registerChannelValue(conf.supybot.plugins.Factoids, 'factoidPrefix', registry.StringWithSpaceOnRight('could be ', """Determines the string that factoids will be introduced by.""")) -class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): - def __init__(self): - callbacks.Privmsg.__init__(self) - plugins.ChannelDBHandler.__init__(self) +class MultiKeyError(KeyError): + pass - def die(self): - callbacks.Privmsg.die(self) - plugins.ChannelDBHandler.die(self) +class LockError(Exception): + pass - def makeDb(self, filename): +class SqliteFactoidsDB(object): + def __init__(self, filename): + self.dbs = ircutils.IrcDict() + self.filename = filename + + def close(self): + for db in self.dbs.itervalues(): + db.close() + + def _getDb(self, channel): + try: + import sqlite + except ImportError: + raise callbacks.Error, 'You need to have PySQLite installed to ' \ + 'use this plugin. Download it at ' \ + '' + filename = plugins.makeChannelFilename(self.filename, channel) + if filename in self.dbs: + return self.dbs[filename] if os.path.exists(filename): - return sqlite.connect(filename) + self.dbs[filename] = sqlite.connect(filename) + return self.dbs[filename] db = sqlite.connect(filename) + self.dbs[filename] = db cursor = db.cursor() cursor.execute("""CREATE TABLE keys ( id INTEGER PRIMARY KEY, @@ -114,25 +131,8 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): db.commit() return db - def learn(self, irc, msg, args): - """[] as - - Associates with . is only necessary if the - message isn't sent on the channel itself. The word 'as' is necessary - to separate the key from the value. It can be changed to another - word via the learnSeparator registry value. - """ - channel = privmsgs.getChannel(msg, args) - try: - separator = conf.supybot.plugins.Factoids. \ - learnSeparator.get(channel)() - i = args.index(separator) - except ValueError: - raise callbacks.ArgumentError - args.pop(i) - key = ' '.join(args[:i]) - factoid = ' '.join(args[i:]) - db = self.getDb(channel) + def add(self, channel, key, factoid, name): + db = self._getDb(channel) cursor = db.cursor() cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) if cursor.rowcount == 0: @@ -140,21 +140,15 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): db.commit() cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s",key) (id, locked) = imap(int, cursor.fetchone()) - capability = ircdb.makeChannelCapability(channel, 'factoids') if not locked: - if ircdb.users.hasUser(msg.prefix): - name = ircdb.users.getUser(msg.prefix).name - else: - name = msg.nick cursor.execute("""INSERT INTO factoids VALUES (NULL, %s, %s, %s, %s)""", id, name, int(time.time()), factoid) db.commit() - irc.replySuccess() else: - irc.error('That factoid is locked.') + raise LockError - def _lookupFactoid(self, channel, key): + def get(self, channel, key): db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT factoids.fact FROM factoids, keys @@ -163,17 +157,158 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): LIMIT 20""", key) return [t[0] for t in cursor.fetchall()] + def lock(self, channel, key): + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("UPDATE keys SET locked=1 WHERE key LIKE %s", key) + db.commit() + + def unlock(self, channel, key): + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("UPDATE keys SET locked=0 WHERE key LIKE %s", key) + db.commit() + + def remove(self, channel, key, number): + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT keys.id, factoids.id + FROM keys, factoids + WHERE key LIKE %s AND + factoids.key_id=keys.id""", key) + if cursor.rowcount == 0: + raise dbi.NoRecordError + elif cursor.rowcount == 1 or number is True: + (id, _) = cursor.fetchone() + cursor.execute("""DELETE FROM factoids WHERE key_id=%s""", id) + cursor.execute("""DELETE FROM keys WHERE key LIKE %s""", key) + db.commit() + else: + if number is not None: + results = cursor.fetchall() + try: + (_, id) = results[number] + except IndexError: + raise dbi.NoRecordError + cursor.execute("DELETE FROM factoids WHERE id=%s", id) + db.commit() + else: + raise MultiKeyError, cursor.rowcount + + def random(self, channel): + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT fact, key_id FROM factoids + ORDER BY random() + LIMIT 3""") + if cursor.rowcount == 0: + raise dbi.NoRecordError + L = [] + for (factoid, id) in cursor.fetchall(): + cursor.execute("""SELECT key FROM keys WHERE id=%s""", id) + (key,) = cursor.fetchone() + L.append((key, factoid)) + return L + + def info(self, channel, key): + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) + if cursor.rowcount == 0: + raise dbi.NoRecordError + (id, locked) = imap(int, cursor.fetchone()) + cursor.execute("""SELECT added_by, added_at FROM factoids + WHERE key_id=%s + ORDER BY id""", id) + return (locked, cursor.fetchall()) + + _sqlTrans = string.maketrans('*?', '%_') + def select(self, channel, values, predicates, globs): + db = self.getDb(channel) + cursor = db.cursor() + tables = ['keys'] + criteria = [] + predicateName = 'p' + formats = [] + if not values: + target = 'keys.key' + else: + target = 'factoids.fact' + if 'factoids' not in tables: + tables.append('factoids') + criteria.append('factoids.key_id=keys.id') + for glob in globs: + criteria.append('TARGET LIKE %s') + formats.append(glob.translate(self._sqlTrans)) + for predicate in predicates: + criteria.append('%s(TARGET)' % predicateName) + def p(s, r=arg): + return int(bool(predicate(s))) + db.create_function(predicateName, 1, p) + predicateName += 'p' + sql = """SELECT keys.key FROM %s WHERE %s""" % \ + (', '.join(tables), ' AND '.join(criteria)) + sql = sql.replace('TARGET', target) + cursor.execute(sql, formats) + if cursor.rowcount == 0: + raise dbi.NoRecordError + elif cursor.rowcount == 1 and \ + conf.supybot.plugins.Factoids.showFactoidIfOnlyOneMatch.get(channel)(): + return cursor.fetchone()[0] + elif cursor.rowcount > 100: + return None + else: + return cursor.fetchall() + +class Factoids(callbacks.Privmsg): + def __init__(self): + self.__parent = super(Factoids, self) + self.__parent.__init__() + self.db = FactoidsDB() + + def die(self): + self.__parent.die() + self.db.close() + + def learn(self, irc, msg, args, channel, text): + """[] as + + Associates with . is only necessary if the + message isn't sent on the channel itself. The word 'as' is necessary + to separate the key from the value. It can be changed to another + word via the learnSeparator registry value. + """ + try: + separator = conf.supybot.plugins.Factoids. \ + learnSeparator.get(channel)() + i = text.index(separator) + except ValueError: + raise callbacks.ArgumentError + text.pop(i) + key = ' '.join(text[:i]) + factoid = ' '.join(text[i:]) + try: + name = ircdb.users.getUser(msg.prefix).name + except KeyError: + name = msg.nick + try: + self.db.add(channel, key, factoid, nick) + irc.replySuccess() + except LockError: + irc.error('That factoid is locked.') + learn = wrap(learn, ['channeldb', many('text')]) + def _replyFactoids(self, irc, channel, key, factoids, number=0, error=True): if factoids: if number: try: - irc.reply(factoids[number-1]) + irc.reply(factoids[number]) except IndexError: irc.error('That\'s not a valid number for that key.') return else: intro = self.registryValue('factoidPrefix', channel) - prefix = '%r %s' % (key, intro) + prefix = '%s %s' % (utils.quoted(key), intro) if len(factoids) == 1: irc.reply(prefix + factoids[0]) else: @@ -192,64 +327,44 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): channel = msg.args[0] if self.registryValue('replyWhenInvalidCommand', channel): key = ' '.join(tokens) - factoids = self._lookupFactoid(channel, key) + factoids = self.db.get(channel, key) self._replyFactoids(irc, channel, key, factoids, error=False) - def whatis(self, irc, msg, args): + def whatis(self, irc, msg, args, channel, number, key): """[] [] Looks up the value of in the factoid database. If given a number, will return only that exact factoid. is only necessary if the message isn't sent in the channel itself. """ - channel = privmsgs.getChannel(msg, args) - if len(args) > 1 and args[-1].isdigit(): - number = args.pop() - else: - number = '' - key = privmsgs.getArgs(args) - if number: - try: - number = int(number) - except ValueError: - irc.error('%s is not a valid number.' % number) - return - else: - number = 0 - factoids = self._lookupFactoid(channel, key) + factoids = self.db.get(channel, key) self._replyFactoids(irc, channel, key, factoids, number) + whatis = wrap(whatis, ['channeldb', reversed(optional('positiveInt', 0)), 'text'], + allowExtra=True) - def lock(self, irc, msg, args): + def lock(self, irc, msg, args, channel, key): """[] Locks the factoid(s) associated with so that they cannot be removed or added to. is only necessary if the message isn't sent in the channel itself. """ - channel = privmsgs.getChannel(msg, args) - key = privmsgs.getArgs(args) - db = self.getDb(channel) - cursor = db.cursor() - cursor.execute("UPDATE keys SET locked=1 WHERE key LIKE %s", key) - db.commit() + self.db.lock(channel, key) irc.replySuccess() + lock = wrap(lock, ['channeldb', 'text'], allowExtra=True) - def unlock(self, irc, msg, args): + def unlock(self, irc, msg, args, channel, key): """[] Unlocks the factoid(s) associated with so that they can be removed or added to. is only necessary if the message isn't sent in the channel itself. """ - channel = privmsgs.getChannel(msg, args) - key = privmsgs.getArgs(args) - db = self.getDb(channel) - cursor = db.cursor() - cursor.execute("UPDATE keys SET locked=0 WHERE key LIKE %s", key) - db.commit() + self.db.unlock(channel, key) irc.replySuccess() + unlock = wrap(unlock, ['channeldb', 'text'], allowExtra=True) - def forget(self, irc, msg, args): + def forget(self, irc, msg, args, channel, number, key): """[] [|*] Removes the factoid from the factoids database. If there are @@ -258,92 +373,49 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): factoids associated with a key. is only necessary if the message isn't sent in the channel itself. """ - channel = privmsgs.getChannel(msg, args) - if args[-1].isdigit(): - number = int(args.pop()) - number -= 1 - if number < 0: - irc.error('Negative numbers aren\'t valid.') - return - elif args[-1] == '*': - del args[-1] + if number == '*': number = True else: - number = None - key = privmsgs.getArgs(args) - db = self.getDb(channel) - cursor = db.cursor() - cursor.execute("""SELECT keys.id, factoids.id - FROM keys, factoids - WHERE key LIKE %s AND - factoids.key_id=keys.id""", key) - if cursor.rowcount == 0: - irc.error('There is no such factoid.') - elif cursor.rowcount == 1 or number is True: - (id, _) = cursor.fetchone() - cursor.execute("""DELETE FROM factoids WHERE key_id=%s""", id) - cursor.execute("""DELETE FROM keys WHERE key LIKE %s""", key) - db.commit() + number -= 1 + key = ' '.join(key) + try: + self.db.remove(channel, key, number) irc.replySuccess() - else: - if number is not None: - results = cursor.fetchall() - try: - (_, id) = results[number] - except IndexError: - irc.error('Invalid factoid number.') - return - cursor.execute("DELETE FROM factoids WHERE id=%s", id) - db.commit() - irc.replySuccess() - else: - irc.error('%s factoids have that key. ' - 'Please specify which one to remove, ' - 'or use * to designate all of them.' % - cursor.rowcount) + except dbi.NoRecordError: + irc.error('There is no such factoid.') + except MultiKeyError, e: + irc.error('%s factoids have that key. ' + 'Please specify which one to remove, ' + 'or use * to designate all of them.' % str(e)) + forget = wrap(forget, ['channeldb', + reversed(first('positiveInt', ('literal', '*'))), + 'text'], allowExtra=True) - def random(self, irc, msg, args): + def random(self, irc, msg, args, channel): """[] Returns a random factoid from the database for . is only necessary if the message isn't sent in the channel itself. """ - channel = privmsgs.getChannel(msg, args) - db = self.getDb(channel) - cursor = db.cursor() - cursor.execute("""SELECT fact, key_id FROM factoids - ORDER BY random() - LIMIT 3""") - if cursor.rowcount != 0: - L = [] - for (factoid, id) in cursor.fetchall(): - cursor.execute("""SELECT key FROM keys WHERE id=%s""", id) - (key,) = cursor.fetchone() - L.append('"%s": %s' % (ircutils.bold(key), factoid)) + try: + L = ['"%s": %s' % (ircutils.bold(k), v) + for (k, v) in self.db.random(channel)] irc.reply('; '.join(L)) - else: + except dbi.NoRecordError: irc.error('I couldn\'t find a factoid.') + random = wrap(random, ['channeldb']) - def info(self, irc, msg, args): + def info(self, irc, msg, args, channel, key): """[] Gives information about the factoid(s) associated with . is only necessary if the message isn't sent in the channel itself. """ - channel = privmsgs.getChannel(msg, args) - key = privmsgs.getArgs(args) - db = self.getDb(channel) - cursor = db.cursor() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) - if cursor.rowcount == 0: - irc.error('No factoid matches that key.') - return - (id, locked) = imap(int, cursor.fetchone()) - cursor.execute("""SELECT added_by, added_at FROM factoids - WHERE key_id=%s - ORDER BY id""", id) - factoids = cursor.fetchall() + try: + (locked, factoids) = self.db.info(channel, key) + except dbi.NoRecordError: + irc.error('No factoid matches that key.', Raise=True) L = [] counter = 0 for (added_by, added_at) in factoids: @@ -352,10 +424,11 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): time.localtime(int(added_at))) L.append('#%s was added by %s at %s' % (counter,added_by,added_at)) factoids = '; '.join(L) - s = 'Key %r is %s and has %s associated with it: %s' % \ - (key, locked and 'locked' or 'not locked', + s = 'Key %s is %s and has %s associated with it: %s' % \ + (utils.quoted(key), locked and 'locked' or 'not locked', utils.nItems('factoid', counter), factoids) irc.reply(s) + info = wrap(info, ['channeldb', 'text'], allowExtra=True) def change(self, irc, msg, args): """[] @@ -384,7 +457,7 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): WHERE keys.key LIKE %s AND keys.id=factoids.key_id""", key) if cursor.rowcount == 0: - irc.error('I couldn\'t find any key %r' % key) + irc.error('I couldn\'t find any key %s' % utils.quoted(key)) return elif cursor.rowcount < number: irc.error('That\'s not a valid key id.') @@ -395,63 +468,42 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): db.commit() irc.replySuccess() - _sqlTrans = string.maketrans('*?', '%_') - def search(self, irc, msg, args): + def search(self, irc, msg, args, channel, optlist, globs): """[] [--values] [--{regexp}=] [] Searches the keyspace for keys matching . If --regexp is given, it associated value is taken as a regexp and matched against the keys. If --values is given, search the value space instead of the keyspace. """ - channel = privmsgs.getChannel(msg, args) - (optlist, rest) = getopt.getopt(args, '', ['values', 'regexp=']) - if not optlist and not rest: + if not optlist and not globs: raise callbacks.ArgumentError - tables = ['keys'] - formats = [] - criteria = [] - target = 'keys.key' - predicateName = 'p' - db = self.getDb(channel) + values = False for (option, arg) in optlist: - if option == '--values': - target = 'factoids.fact' - if 'factoids' not in tables: - tables.append('factoids') - criteria.append('factoids.key_id=keys.id') - elif option == '--regexp': - criteria.append('%s(TARGET)' % predicateName) - try: - r = utils.perlReToPythonRe(arg) - except ValueError, e: - irc.error('Invalid regexp: %s' % e) - return - def p(s, r=r): - return int(bool(r.search(s))) - db.create_function(predicateName, 1, p) - predicateName += 'p' - for glob in rest: + if option == 'values': + values = True + elif option == 'regexp': + predicates.append(r.search) + L = [] + for glob in globs: if '*' not in glob and '?' not in glob: glob = '*%s*' % glob - criteria.append('TARGET LIKE %s') - formats.append(glob.translate(self._sqlTrans)) - cursor = db.cursor() - sql = """SELECT keys.key FROM %s WHERE %s""" % \ - (', '.join(tables), ' AND '.join(criteria)) - sql = sql.replace('TARGET', target) - cursor.execute(sql, formats) - if cursor.rowcount == 0: + L.append(glob) + try: + factoids = self.db.select(channel, values, predicates, L) + if isinstance(factoids, basestring): + self.whatis(irc, msg, factoids) + elif factoids is None: + irc.reply('More than 100 keys matched that query; ' + 'please narrow your query.') + else: + keys = [repr(t[0]) for t in factoids] + s = utils.commaAndify(keys) + irc.reply(s) + except dbi.NoRecordError: irc.reply('No keys matched that query.') - elif cursor.rowcount == 1 and \ - conf.supybot.plugins.Factoids.showFactoidIfOnlyOneMatch.get(channel)(): - self.whatis(irc, msg, [cursor.fetchone()[0]]) - elif cursor.rowcount > 100: - irc.reply('More than 100 keys matched that query; ' - 'please narrow your query.') - else: - keys = [repr(t[0]) for t in cursor.fetchall()] - s = utils.commaAndify(keys) - irc.reply(s) + search = wrap(search, ['channeldb', + getopts({'values':'', 'regexp':'regexpMatcher'}), + additional('text')]) Class = Factoids diff --git a/plugins/FunDB.py b/plugins/FunDB.py index 2d329d800..3edf87881 100755 --- a/plugins/FunDB.py +++ b/plugins/FunDB.py @@ -144,8 +144,8 @@ class FunDB(callbacks.Privmsg): lowerTypes = [t.lower() for t in self._types] if type.lower() not in lowerTypes: if error: - irc.error('%r is not a valid type. Valid types include %s.' % - (type, utils.commaAndify(self._types))) + irc.error('%s is not a valid type. Valid types include %s.' % + (utils.quoted(type), utils.commaAndify(self._types))) return False else: return True @@ -315,8 +315,8 @@ class FunDB(callbacks.Privmsg): return try: x = self.db.get(channel, type, id) - reply = '%s #%s: %r; Created by %s.' % (type, x.id, x.text, - self._getBy(x.by)) + reply = '%s #%s: %s; Created by %s.' % \ + (type, x.id, utils.quoted(x.text), self._getBy(x.by)) irc.reply(reply) except KeyError: irc.error('There is no %s with that id.' % type) diff --git a/plugins/Google.py b/plugins/Google.py index b3920b156..d3b17a8b7 100644 --- a/plugins/Google.py +++ b/plugins/Google.py @@ -340,8 +340,8 @@ class Google(callbacks.PrivmsgCommandAndRegexp): categories = utils.commaAndify(categories) else: categories = '' - s = 'Search for %r returned %s %s results in %s seconds.%s' % \ - (meta.searchQuery, + s = 'Search for %s returned %s %s results in %s seconds.%s' % \ + (utils.quoted(meta.searchQuery), meta.estimateIsExact and 'exactly' or 'approximately', meta.estimatedTotalResultsCount, meta.searchTime, diff --git a/plugins/Infobot.py b/plugins/Infobot.py index c427da10e..8495c2dd3 100755 --- a/plugins/Infobot.py +++ b/plugins/Infobot.py @@ -618,10 +618,12 @@ class Infobot(callbacks.PrivmsgCommandAndRegexp): if isAre in ('was', 'is', 'am'): if self.db.hasIs(key): if also: - self.log.debug('Adding %r to %r.', key, value) + self.log.debug('Adding %s to %s.', + utils.quoted(key), utils.quoted(value)) value = '%s or %s' % (self.db.getIs(key), value) elif self.force: - self.log.debug('Forcing %r to %r.', key, value) + self.log.debug('Forcing %s to %s.', + utils.quoted(key), utils.quoted(value)) elif self.badForce: value = self.db.getIs(key) self.reply('... but %s is %s, %s ...' % (key, value, @@ -632,16 +634,19 @@ class Infobot(callbacks.PrivmsgCommandAndRegexp): self.reply('But %s is %s, %s.' % (key, value, msg.nick)) return else: - self.log.debug('Already have a %r key.', key) + self.log.debug('Already have a %s key.', + utils.quoted(key)) return self.db.setIs(key, value) else: if self.db.hasAre(key): if also: - self.log.debug('Adding %r to %r.', key, value) + self.log.debug('Adding %s to %s.', + utils.quoted(key), utils.quoted(value)) value = '%s or %s' % (self.db.getAre(key), value) elif self.force: - self.log.debug('Forcing %r to %r.', key, value) + self.log.debug('Forcing %s to %s.', + utils.quoted(key), utils.quoted(value)) elif self.badForce: value = self.db.getAre(key) self.reply('... but %s are %s, %s ...' % (key, value, @@ -652,7 +657,8 @@ class Infobot(callbacks.PrivmsgCommandAndRegexp): self.reply('But %s are %s, %s.' % (key, value, msg.nick)) return else: - self.log.debug('Already have a %r key.', key) + self.log.debug('Already have a %s key.', + utils.quoted(key)) return self.db.setAre(key, value) if self.addressed or self.force: diff --git a/plugins/Karma.py b/plugins/Karma.py index 3cab35c31..87cb48590 100644 --- a/plugins/Karma.py +++ b/plugins/Karma.py @@ -297,9 +297,9 @@ class Karma(callbacks.Privmsg): if self.registryValue('simpleOutput', channel): s = '%s: %s' % (name, total) else: - s = 'Karma for %r has been increased %s ' \ + s = 'Karma for %s has been increased %s ' \ 'and decreased %s for a total karma of %s.' % \ - (name, utils.nItems('time', added), + (utils.quoted(name), utils.nItems('time', added), utils.nItems('time', subtracted), total) irc.reply(s) elif len(args) > 1: @@ -317,8 +317,10 @@ class Karma(callbacks.Privmsg): else: # No name was given. Return the top/bottom N karmas. limit = self.registryValue('rankingDisplay', channel) top = self.db.top(channel, limit) - highest = ['%r (%s)' % t for t in self.db.top(channel, limit)] - lowest = ['%r (%s)' % t for t in self.db.bottom(channel, limit)] + highest = ['%s (%s)' % (utils.quoted(s), t) + for (s, t) in self.db.top(channel, limit)] + lowest = ['%s (%s)' % (utils.quoted(s), t) + for (s, t) in self.db.bottom(channel, limit)] if not (highest and lowest): irc.error('I have no karma for this channel.') return @@ -348,7 +350,7 @@ class Karma(callbacks.Privmsg): L = self.db.most(channel, kind, self.registryValue('mostDisplay', channel)) if L: - L = ['%r: %s' % (name, i) for (name, i) in L] + L = ['%s: %s' % (utils.quoted(name), i) for (name, i) in L] irc.reply(utils.commaAndify(L)) else: irc.error('I have no karma for this channel.') diff --git a/plugins/LogToIrc.py b/plugins/LogToIrc.py index 764676579..822057a4a 100644 --- a/plugins/LogToIrc.py +++ b/plugins/LogToIrc.py @@ -99,7 +99,7 @@ class IrcHandler(logging.Handler): # baaaaaad. irc.sendMsg(msg) else: - print '*** Not sending to %r' % target + print '*** Not sending to %s' % utils.quoted(target) class IrcFormatter(log.Formatter): diff --git a/plugins/Lookup.py b/plugins/Lookup.py index 0febd8e8b..efe41e908 100644 --- a/plugins/Lookup.py +++ b/plugins/Lookup.py @@ -93,7 +93,7 @@ def configure(advanced): conf.registerGlobalValue(lookups,command, registry.String(filename,'')) nokeyVal = yn('Would you like the key to be shown for random \ responses?') - conf.registerGlobalValue(lookups.get(command), 'nokey', + conf.registerGlobalValue(lookups.get(command), 'nokey', registry.Boolean(nokeyVal, '')) conf.registerPlugin('Lookup') @@ -115,7 +115,7 @@ class SqliteLookupDB(object): def close(self): self.db.close() - + def getRecordCount(self, tableName): cursor = self.db.cursor() cursor.execute("""SELECT COUNT(*) FROM %s""" % tableName) @@ -123,7 +123,7 @@ class SqliteLookupDB(object): if rows == 0: raise dbi.NoRecordError return rows - + def checkLookup(self, name): cursor = self.db.cursor() sql = "SELECT name FROM sqlite_master \ @@ -146,12 +146,12 @@ class SqliteLookupDB(object): key = key.replace('\\:', ':') except ValueError: cursor.execute("""DROP TABLE %s""" % name) - s = 'Invalid line in %s: %r' % (filename, line) + s = 'Invalid line in %s: %s' % (filename, utils.quoted(line)) raise callbacks.Error, s cursor.execute(sql, key, value) cursor.execute("CREATE INDEX %s_keys ON %s (key)" % (name, name)) self.db.commit() - + def dropLookup(self, name): cursor = self.db.cursor() if self.checkLookup(name): @@ -159,7 +159,7 @@ class SqliteLookupDB(object): self.db.commit() else: raise dbi.NoRecordError - + def getResults(self, name, key): cursor = self.db.cursor() sql = """SELECT value FROM %s WHERE key LIKE %%s""" % name @@ -168,7 +168,7 @@ class SqliteLookupDB(object): raise dbi.NoRecordError else: return cursor.fetchall() - + def getRandomResult(self, name, key): cursor = self.db.cursor() sql = """SELECT key, value FROM %s @@ -191,8 +191,7 @@ class SqliteLookupDB(object): try: r = utils.perlReToPythonRe(arg) except ValueError, e: - irc.error('%r is not a valid regular expression' % - arg) + irc.errorInvalid('regular expression' % arg) return def p(s, r=r): return int(bool(r.search(s))) @@ -215,7 +214,7 @@ class SqliteLookupDB(object): raise dbi.NoRecordError else: return cursor.fetchall() - + LookupDB = plugins.DB('Lookup', {'sqlite': SqliteLookupDB,}) class Lookup(callbacks.Privmsg): @@ -317,12 +316,12 @@ class Lookup(callbacks.Privmsg): def delRegistryValues(self, name): group = conf.supybot.plugins.Lookup.lookups group.unregister(name) - + def addDatabase(self, name, filename): dataDir = conf.supybot.directories.data() filename = os.path.join(dataDir, filename) fd = file(filename) - self.db.addLookup(name, fd, self._splitRe) + self.db.addLookup(name, fd, self._splitRe) def addCommand(self, name): def f(self, irc, msg, args): @@ -363,7 +362,7 @@ class Lookup(callbacks.Privmsg): irc.reply('No entries in %s matched that query.' % name) else: irc.error('I don\'t have a domain %s' % name) - + def _lookup(self, irc, msg, args): """ diff --git a/plugins/Math.py b/plugins/Math.py index 0dba62113..f4b729028 100644 --- a/plugins/Math.py +++ b/plugins/Math.py @@ -193,7 +193,8 @@ class Math(callbacks.Privmsg): return str(x) text = self._mathRe.sub(handleMatch, text) try: - self.log.info('evaluating %r from %s' % (text, msg.prefix)) + self.log.info('evaluating %s from %s' % + (utils.quoted(text), msg.prefix)) x = complex(eval(text, self._mathEnv, self._mathEnv)) irc.reply(self._complexToString(x)) except OverflowError: @@ -227,7 +228,8 @@ class Math(callbacks.Privmsg): return text = text.replace('lambda', '') try: - self.log.info('evaluating %r from %s' % (text, msg.prefix)) + self.log.info('evaluating %s from %s' % + (utils.quoted(text), msg.prefix)) irc.reply(str(eval(text, self._mathEnv, self._mathEnv))) except OverflowError: maxFloat = math.ldexp(0.9999999999999999, 1024) @@ -283,7 +285,8 @@ class Math(callbacks.Privmsg): try: stack.append(eval(s, self._mathEnv, self._mathEnv)) except SyntaxError: - irc.error('%r is not a defined function.' % arg) + irc.error('%s is not a defined function.' % + utils.quoted(arg)) return if len(stack) == 1: irc.reply(str(self._complexToString(complex(stack[0])))) diff --git a/plugins/MoobotFactoids.py b/plugins/MoobotFactoids.py index d7961a7e4..a815f6570 100644 --- a/plugins/MoobotFactoids.py +++ b/plugins/MoobotFactoids.py @@ -238,7 +238,7 @@ class SqliteMoobotDB(object): cursor.execute("""UPDATE factoids SET locked_by=%s, locked_at=%s WHERE key LIKE %s""", - locker_id, int(time.time()), key) + locker_id, int(time.time()), key) db.commit() def unlock(self, channel, key): @@ -255,7 +255,7 @@ class SqliteMoobotDB(object): cursor.execute("""SELECT created_by, count(key) FROM factoids GROUP BY created_by ORDER BY count(key) DESC LIMIT %s""", limit) - return cursor.fetchall() + return cursor.fetchall() def mostRecent(self, channel, limit): db = self._getDb(channel) @@ -380,7 +380,7 @@ class MoobotFactoids(callbacks.Privmsg): def _sanitizeKey(self, key): return key.rstrip('!? ') - + def _checkNotLocked(self, irc, channel, key): if self.db.locked(channel, key): irc.error('Factoid "%s" is locked.' % key, Raise=True) @@ -398,12 +398,13 @@ class MoobotFactoids(callbacks.Privmsg): elif 'is' in tokens: p = 'is'.__eq__ else: - s = 'Invalid tokens for {add,change}Factoid: %r' % tokens + s = 'Invalid tokens for {add,change}Factoid: %s' % \ + utils.quoted(tokens) raise ValueError, s (key, newfact) = map(' '.join, utils.itersplit(p, tokens, maxsplit=1)) key = self._sanitizeKey(key) return (key, newfact) - + def addFactoid(self, irc, msg, tokens): # First, check and see if the entire message matches a factoid key channel = getChannel(irc, msg) diff --git a/plugins/Note.py b/plugins/Note.py index 4027af58f..e1c9a73ea 100644 --- a/plugins/Note.py +++ b/plugins/Note.py @@ -366,7 +366,8 @@ class Note(callbacks.Privmsg): try: L.remove(user) except (KeyError, ValueError): - irc.error('%r was not in your list of ignores.' % user) + irc.error('%s was not in your list of ignores.' % + utils.quoted(user)) return else: L.add(user) diff --git a/plugins/Poll.py b/plugins/Poll.py index 317aa8ce0..4171dc27d 100644 --- a/plugins/Poll.py +++ b/plugins/Poll.py @@ -132,10 +132,12 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler): optionstr = 'This poll has no options yet' else: options = cursor.fetchall() - optionstr = 'Options: %s' %\ - ' '.join(['%s: %r' % tuple(t) for t in options]) - pollstr = 'Poll #%s: %r started by %s. %s. Poll is %s.' % \ - (poll_id, question, starter, optionstr, statusstr) + optionstr = 'Options: %s' % \ + ' '.join(['%s: %s' % (s, utils.quoted(t)) \ + for (s, t) in options]) + pollstr = 'Poll #%s: %s started by %s. %s. Poll is %s.' % \ + (poll_id, utils.quoted(question), starter, optionstr, + statusstr) irc.reply(pollstr) def open(self, irc, msg, args): @@ -304,7 +306,7 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler): WHERE id=%s AND poll_id=%s""", option_id, poll_id) option = cursor.fetchone()[0] - results.append('%r: %s' % (option, int(count))) + results.append('%s: %s' % (utils.quoted(option), int(count))) s = utils.commaAndify(results) reply += ' - %s' % s irc.reply(reply) @@ -322,7 +324,8 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler): if cursor.rowcount == 0: irc.reply('This channel currently has no open polls.') else: - polls = ['#%s: %r' % tuple(t) for t in cursor.fetchall()] + polls = ['#%s: %s' % (s, utils.quoted(t)) + for (s, t) in cursor.fetchall()] irc.reply(utils.commaAndify(polls)) Class = Poll diff --git a/plugins/QuoteGrabs.py b/plugins/QuoteGrabs.py index 5ef08bfc4..ecebdbf45 100644 --- a/plugins/QuoteGrabs.py +++ b/plugins/QuoteGrabs.py @@ -258,7 +258,7 @@ class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg): cursor.execute("""SELECT quote, hostmask, added_at, added_by FROM quotegrabs WHERE id = %s""", id) if cursor.rowcount == 0: - irc.error('No quotegrab for id %r' % id) + irc.error('No quotegrab for id %s' % utils.quoted(id)) return quote, hostmask, timestamp, grabber_mask = cursor.fetchone() time_str = time.strftime(conf.supybot.humanTimestampFormat(), diff --git a/plugins/Quotes.py b/plugins/Quotes.py index 357367f25..029ad2fe4 100644 --- a/plugins/Quotes.py +++ b/plugins/Quotes.py @@ -67,8 +67,8 @@ class QuoteRecord(dbi.Record): user = self.by except KeyError: user = 'a user that is no longer registered' - return 'Quote %r added by %s at %s.' % \ - (self.text, user, + return 'Quote %s added by %s at %s.' % \ + (utils.quoted(self.text), user, time.strftime(format, time.localtime(float(self.at)))) class SqliteQuotesDB(object): @@ -258,7 +258,8 @@ class Quotes(callbacks.Privmsg): irc.reply('More than 10 quotes matched your criteria. ' 'Please narrow your query.') else: - quotes = ['#%s: %r' % (q.id, utils.ellipsisify(q.text, 30)) + quotes = ['#%s: %s' % + (q.id, utils.quoted(utils.ellipsisify(q.text, 30))) for q in quote] irc.reply(utils.commaAndify(quotes)) @@ -331,7 +332,7 @@ class Quotes(callbacks.Privmsg): try: id = int(id) except ValueError: - irc.error('Invalid id: %r' % id) + irc.errorInvalid('id' % id) return try: quote = self.db.get(channel, id) @@ -350,7 +351,7 @@ class Quotes(callbacks.Privmsg): try: id = int(id) except ValueError: - irc.error('That\'s not a valid id: %r' % id) + irc.errorInvalid('id' % id) try: self.db.remove(channel, id) irc.replySuccess() diff --git a/plugins/RSS.py b/plugins/RSS.py index 662370a68..d590ac164 100644 --- a/plugins/RSS.py +++ b/plugins/RSS.py @@ -304,9 +304,8 @@ class RSS(callbacks.Privmsg): try: name = self._validFeedName(name) except ValueError: - irc.error('%r is not a valid feed name. Feed names must not ' - 'include dots, colons, or spaces.' % name) - return + irc.errorInvalid('feed name', name, 'Feed names must not ' + 'include dots, colons, or spaces.') self.makeFeedCommand(name, url) irc.replySuccess() diff --git a/plugins/Services.py b/plugins/Services.py index 2c40e0977..35f618b77 100644 --- a/plugins/Services.py +++ b/plugins/Services.py @@ -380,7 +380,8 @@ class Services(privmsgs.CapabilityCheckingPrivmsg): 'NickServ. Check email at %s and send the auth ' 'command to NickServ.', email) else: - self.log.debug('Unexpected notice from NickServ: %r.', s) + self.log.debug('Unexpected notice from NickServ: %s.', + utils.quoted(s)) def checkPrivileges(self, irc, channel): chanserv = self.registryValue('ChanServ') diff --git a/plugins/ShrinkUrl.py b/plugins/ShrinkUrl.py index cf0412a07..91f4cf5a7 100644 --- a/plugins/ShrinkUrl.py +++ b/plugins/ShrinkUrl.py @@ -170,7 +170,8 @@ class ShrinkUrl(callbacks.PrivmsgCommandAndRegexp): url = match.group(0) r = self.registryValue('nonSnarfingRegexp', channel) if r and r.search(url) is not None: - self.log.debug('Matched nonSnarfingRegexp: %r', url) + self.log.debug('Matched nonSnarfingRegexp: %s', + utils.quoted(url)) return minlen = self.registryValue('minimumLength', channel) cmd = self.registryValue('default', channel) @@ -181,7 +182,8 @@ class ShrinkUrl(callbacks.PrivmsgCommandAndRegexp): elif cmd == 'ln': (shorturl, _) = self._getLnUrl(url) if shorturl is None: - self.log.info('Couldn\'t get shorturl for %r', url) + self.log.info('Couldn\'t get shorturl for %s', + utils.quoted(url)) return domain = webutils.getDomain(url) s = '%s (at %s)' % (ircutils.bold(shorturl), domain) diff --git a/plugins/Success.py b/plugins/Success.py index ffaa0422f..bafae43e1 100644 --- a/plugins/Success.py +++ b/plugins/Success.py @@ -164,8 +164,8 @@ class Success(callbacks.Privmsg): return text.lower() in success.text.lower() ids = [str(success.id) for success in self.db.select(channel, p)] if ids: - s = 'Success search for %r (%s found): %s.' % \ - (text, len(ids), utils.commaAndify(ids)) + s = 'Success search for %s (%s found): %s.' % \ + (utils.quoted(text), len(ids), utils.commaAndify(ids)) irc.reply(s) else: irc.reply('No successes found matching that search criteria.') @@ -182,8 +182,8 @@ class Success(callbacks.Privmsg): name = ircdb.users.getUser(success.by).name at = time.localtime(success.at) timeStr = time.strftime(conf.supybot.humanTimestampFormat(), at) - irc.reply("Success #%s: %r (added by %s at %s)" % \ - (id, success.text, name, timeStr)) + irc.reply("Success #%s: %s (added by %s at %s)" % \ + (id, utils.quoted(success.text), name, timeStr)) except KeyError: irc.error('No success found with that id.') get = wrap(get, ['channeldb', ('id', 'success')]) diff --git a/plugins/Todo.py b/plugins/Todo.py index b6cd85798..aa86e1a9d 100644 --- a/plugins/Todo.py +++ b/plugins/Todo.py @@ -110,8 +110,7 @@ class Todo(callbacks.Privmsg): try: userid = ircdb.users.getUserId(arg) except KeyError: - irc.error( - '%r is not a valid task id or username' % arg) + irc.errorInvalid('task id or username', arg) return db = self.dbHandler.getDb() cursor = db.cursor() @@ -149,7 +148,7 @@ class Todo(callbacks.Privmsg): cursor.execute("""SELECT userid,priority,added_at,task,active FROM todo WHERE id = %s""", taskid) if cursor.rowcount == 0: - irc.error('%r is not a valid task id' % taskid) + irc.errorInvalid('task id', taskid) return (userid, pri, added_at, task, active) = cursor.fetchone() # Construct and return the reply @@ -189,7 +188,7 @@ class Todo(callbacks.Privmsg): try: priority = int(arg) except ValueError, e: - irc.error('%r is an invalid priority' % arg) + irc.errorInvalid('priority', arg) return text = privmsgs.getArgs(rest) db = self.dbHandler.getDb() @@ -270,8 +269,7 @@ class Todo(callbacks.Privmsg): try: r = utils.perlReToPythonRe(arg) except ValueError, e: - irc.error('%r is not a valid regular expression' % - arg) + irc.errorInvalid('regular expression', arg) return def p(s, r=r): return int(bool(r.search(s))) @@ -335,7 +333,7 @@ class Todo(callbacks.Privmsg): try: replacer = utils.perlReToReplacer(regexp) except ValueError: - irc.error('%r is not a valid regexp' % regexp) + irc.errorInvalid('regexp', regexp) return db = self.dbHandler.getDb() cursor = db.cursor() @@ -343,7 +341,7 @@ class Todo(callbacks.Privmsg): WHERE userid = %s AND id = %s AND active = 1""", userid, taskid) if cursor.rowcount == 0: - irc.error('%r is not a valid task id' % taskid) + irc.errorInvalid('task id', taskid) return newtext = replacer(cursor.fetchone()[0]) cursor.execute("""UPDATE todo SET task = %s diff --git a/plugins/Topic.py b/plugins/Topic.py index b8ee9ab00..96aa1f4f8 100644 --- a/plugins/Topic.py +++ b/plugins/Topic.py @@ -89,7 +89,7 @@ def canChangeTopic(irc, msg, args, state): c = irc.state.channels[state.channel] if irc.nick not in c.ops and 't' in c.modes: irc.error('I can\'t change the topic, ' - 'I\'m not opped and %s is +t.' % channel, Raise=True) + 'I\'m not opped and %s is +t.' % state.channel, Raise=True) def getTopic(irc, msg, args, state, format=True): separator = state.cb.registryValue('separator', state.channel) diff --git a/plugins/URL.py b/plugins/URL.py index a0bf8d727..32fb15dd5 100644 --- a/plugins/URL.py +++ b/plugins/URL.py @@ -108,9 +108,10 @@ class URL(callbacks.PrivmsgCommandAndRegexp): for url in webutils.urlRe.findall(text): r = self.registryValue('nonSnarfingRegexp', channel) if r and r.search(url): - self.log.debug('Skipping adding %r to db.', url) + self.log.debug('Skipping adding %s to db.', + utils.quoted(url)) continue - self.log.debug('Adding %r to db.', url) + self.log.debug('Adding %s to db.', utils.quoted(url)) self.db.add(channel, url, msg) self.__parent.doPrivmsg(irc, msg) @@ -125,7 +126,7 @@ class URL(callbacks.PrivmsgCommandAndRegexp): url = match.group(0) r = self.registryValue('nonSnarfingRegexp', channel) if r and r.search(url): - self.log.debug('Not titleSnarfing %r.', url) + self.log.debug('Not titleSnarfing %s.', utils.quoted(url)) return try: size = conf.supybot.protocols.http.peekSize() diff --git a/plugins/Unix.py b/plugins/Unix.py index c1ba2946c..bd9864164 100644 --- a/plugins/Unix.py +++ b/plugins/Unix.py @@ -223,8 +223,8 @@ class Unix(callbacks.Privmsg): resp = 'I could not find an alternate spelling for "%s"' % word elif line[0] == '&': matches = line.split(':')[1].strip() - resp = 'Possible spellings for %r: %s.' % \ - (word, utils.commaAndify(matches.split(', '))) + resp = 'Possible spellings for %s: %s.' % \ + (utils.quoted(word), utils.commaAndify(matches.split(', '))) else: resp = 'Something unexpected was seen in the [ai]spell output.' irc.reply(resp) diff --git a/plugins/WordStats.py b/plugins/WordStats.py index 7aad3661c..bbf6cd4ec 100644 --- a/plugins/WordStats.py +++ b/plugins/WordStats.py @@ -234,8 +234,8 @@ class WordStats(callbacks.Privmsg): self.db.delWord(channel, word) irc.replySuccess() else: - irc.error('%r doesn\'t look like a word I am keeping stats ' - 'on.' % word) + irc.error('%s doesn\'t look like a word I am keeping stats ' + 'on.' % utils.quoted(word)) return else: irc.error('I am not currently keeping any word stats.') @@ -277,14 +277,16 @@ class WordStats(callbacks.Privmsg): try: count = self.db.getWordCount(channel, id, word) except KeyError: - irc.error('I\'m not keeping stats on %r.' % word) + irc.error('I\'m not keeping stats on %s.' % + utils.quoted(word)) return if count: - s = '%s has said %r %s.' % \ - (user, word, utils.nItems('time', count)) + s = '%s has said %s %s.' % \ + (user, utils.quoted(word), utils.nItems('time', count)) irc.reply(s) else: - irc.error('%s has never said %r.' % (user, word)) + irc.error('%s has never said %s.' % + (user, utils.quoted(word))) elif arg1 in WordDict.fromkeys(self.db.getWords(channel)): word = arg1 total = self.db.getTotalWordCount(channel, word) @@ -300,7 +302,7 @@ class WordStats(callbacks.Privmsg): id = None rank = None number = None - ers = '%rer' % word + ers = '%ser' % utils.quoted(word) L = [] for (userid, count) in self.db.getTopUsers(channel, word, n): if userid == id: @@ -309,9 +311,7 @@ class WordStats(callbacks.Privmsg): username = ircdb.users.getUser(userid).name L.append('%s: %s' % (username, count)) except KeyError: - self.log.warning('Odd, I have a user in my WordStats ' - 'database that doesn\'t exist in my ' - 'user database: #%s.', userid) + L.append('%s: %s' % ('unregistered user', count)) ret = 'Top %s (out of a total of %s seen):' % \ (utils.nItems(ers, len(L)), utils.nItems(repr(word), total)) users = self.db.getNumUsers(channel) @@ -328,18 +328,19 @@ class WordStats(callbacks.Privmsg): try: id = ircdb.users.getUserId(user) except KeyError: - irc.error('%r doesn\'t look like a word I\'m keeping stats ' - 'on or a user in my database.' % user) + irc.error('%s doesn\'t look like a word I\'m keeping stats ' + 'on or a user in my database.' % utils.quoted(user)) return try: - L = ['%r: %s' % (word, count) + L = ['%s: %s' % (utils.quoted(word), count) for (word,count) in self.db.getUserWordCounts(channel,id)] if L: L.sort() irc.reply(utils.commaAndify(L)) else: - irc.error('%r doesn\'t look like a word I\'m keeping stats' - ' on or a user in my database.' % user) + irc.error('%s doesn\'t look like a word I\'m keeping stats' + ' on or a user in my database.' % + utils.quoted(user)) return except KeyError: irc.error('I have no word stats for that person.') diff --git a/plugins/Words.py b/plugins/Words.py index 9a2e24624..3851b5a3f 100644 --- a/plugins/Words.py +++ b/plugins/Words.py @@ -278,15 +278,18 @@ class Words(callbacks.Privmsg): del game.unused[game.unused.index(letter)] if letter in game.hidden: self._hangmanReply(irc, channel, - 'Yes, there is %s %r.' % - (game.letterArticle(letter), letter)) + 'Yes, there is %s %s.' % + (game.letterArticle(letter), + utils.quoted(letter))) game.guess = game.addLetter(letter, game.guess, game.letterPositions(letter, game.hidden)) if game.guess == game.hidden: game.guessed = True else: - self._hangmanReply(irc, channel, 'No, there is no %r.' % letter) + self._hangmanReply(irc, channel, + 'No, there is no %s.' % + utils.quoted(letter)) game.tries -= 1 self._hangmanReply(irc, channel, '%s (%s left)' % (game.guess, game.triesLeft())) @@ -316,12 +319,13 @@ class Words(callbacks.Privmsg): # Verify if the user won or lost if game.guessed and game.tries > 0: self._hangmanReply(irc, channel, - 'You win! The word was indeed %r.' % - game.hidden) + 'You win! The word was indeed %s.' % + utils.quoted(game.hidden)) self.endGame(channel) elif not game.guessed and game.tries == 0: self._hangmanReply(irc, channel, - 'You lose! The word was %r.' % game.hidden) + 'You lose! The word was %s.' % + utils.quoted(game.hidden)) self.endGame(channel) guess = wrap(guess, ['channel', 'somethingWithoutSpaces']) ### diff --git a/src/callbacks.py b/src/callbacks.py index 9b443fe07..e2880e6cb 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -1181,6 +1181,11 @@ class SimpleProxy(RichReplyMethods): self.msg = msg def error(self, s, msg=None, **kwargs): + if 'Raise' in kwargs and kwargs['Raise']: + if s: + raise Error, s + else: + raise ArgumentError if msg is None: msg = self.msg m = error(msg, s, **kwargs) diff --git a/src/commands.py b/src/commands.py index 7dade6109..799006706 100644 --- a/src/commands.py +++ b/src/commands.py @@ -659,7 +659,12 @@ class first(context): state.args.append(self.default) else: raise e - + +class reversed(context): + def __call__(self, irc, msg, args, state): + args[:] = args[::-1] + super(reversed, self).__call__(irc, msg, args, state) + args[:] = args[::-1] class getopts(context): """The empty string indicates that no argument is taken; None indicates @@ -775,7 +780,7 @@ def wrap(f, specList=[], **kw): __all__ = ['wrap', 'context', 'additional', 'optional', 'any', 'compose', - 'Spec', 'first', 'urlSnarfer', 'thread', + 'Spec', 'first', 'urlSnarfer', 'thread', 'reversed', 'many', 'getopts', 'getConverter', 'addConverter', 'callConverter'] if world.testing: diff --git a/test/test_Alias.py b/test/test_Alias.py index 3b28f7fb8..640a572d0 100644 --- a/test/test_Alias.py +++ b/test/test_Alias.py @@ -77,7 +77,7 @@ class AliasTestCase(ChannelPluginTestCase, PluginDocumentation): def testAliasHelp(self): self.assertNotError('alias add slashdot foo') - self.assertRegexp('help slashdot', "Alias for 'foo.*'") + self.assertRegexp('help slashdot', "Alias for <>") def testRemove(self): self.assertNotError('alias add foo echo bar') diff --git a/test/test_Todo.py b/test/test_Todo.py index 51e305561..027155a36 100644 --- a/test/test_Todo.py +++ b/test/test_Todo.py @@ -66,9 +66,8 @@ if sqlite is not None: 'Todos for tester: #1: wash my car and #2: moo') # Check error self.assertError('todo asfas') - self.assertResponse('todo asfas', - 'Error: \'asfas\' is not a valid task id or ' - 'username') + self.assertRegexp('todo asfas', + 'Error: \'asfas\' is not a valid task') # Check priority sorting self.assertNotError('todo setpriority 1 100') self.assertNotError('todo setpriority 2 10') diff --git a/test/test_commands.py b/test/test_commands.py index 2d5a13517..e2d609dea 100644 --- a/test/test_commands.py +++ b/test/test_commands.py @@ -69,6 +69,10 @@ class CommandsTestCase(SupyTestCase): self.assertRaises(callbacks.Error, self.assertState, spec, ['foo'], ['asdf']) + def testReversed(self): + spec = [reversed('positiveInt'), 'float', 'text'] + self.assertState(spec, ['-1.0', 'foo', '1'], [1, -1.0, 'foo']) + def testGetopts(self): spec = ['int', getopts({'foo': None, 'bar': 'int'}), 'int'] self.assertState(spec,