Massive updates. %r -> %s, some commands.wrap updates. Factoids is broke

until we get some stuff working in commands.py
This commit is contained in:
James Vega 2004-10-23 22:07:50 +00:00
parent 7ab8be2f64
commit 4dafdcdd57
35 changed files with 407 additions and 314 deletions

View File

@ -225,7 +225,8 @@ class Alias(callbacks.Privmsg):
raise AliasError, 'Names cannot coincide with names of plugins.' raise AliasError, 'Names cannot coincide with names of plugins.'
realName = callbacks.canonicalName(name) realName = callbacks.canonicalName(name)
if name != realName: 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 raise AliasError, s
name = realName name = realName
cbs = callbacks.findCallbackForCommand(irc, name) cbs = callbacks.findCallbackForCommand(irc, name)
@ -236,7 +237,7 @@ class Alias(callbacks.Privmsg):
if name in self.aliases: if name in self.aliases:
(currentAlias, locked) = self.aliases[name] (currentAlias, locked) = self.aliases[name]
if locked and currentAlias != alias: if locked and currentAlias != alias:
raise AliasError, 'Alias %r is locked.' % name raise AliasError, 'Alias %s is locked.' % utils.quoted(name)
try: try:
f = makeNewAlias(name, alias) f = makeNewAlias(name, alias)
except RecursiveAlias: except RecursiveAlias:
@ -280,8 +281,8 @@ class Alias(callbacks.Privmsg):
alias += ' $*' alias += ' $*'
try: try:
self.addAlias(irc, name, alias) self.addAlias(irc, name, alias)
self.log.info('Adding alias %r for %r (from %s)' % self.log.info('Adding alias %s for %s (from %s)' %
(name, alias, msg.prefix)) (utils.quoted(name), utils.quoted(alias),msg.prefix))
irc.replySuccess() irc.replySuccess()
except AliasError, e: except AliasError, e:
irc.error(str(e)) irc.error(str(e))
@ -294,7 +295,8 @@ class Alias(callbacks.Privmsg):
name = privmsgs.getArgs(args) name = privmsgs.getArgs(args)
try: try:
self.removeAlias(name) 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() irc.replySuccess()
except AliasError, e: except AliasError, e:
irc.error(str(e)) irc.error(str(e))

View File

@ -103,7 +103,8 @@ class Anonymous(callbacks.Privmsg):
""" """
(channel, text) = privmsgs.getArgs(args, required=2) (channel, text) = privmsgs.getArgs(args, required=2)
self._preCheck(irc, msg, channel) 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)) irc.queueMsg(ircmsgs.privmsg(channel, text))
def do(self, irc, msg, args): def do(self, irc, msg, args):
@ -113,8 +114,8 @@ class Anonymous(callbacks.Privmsg):
""" """
(channel, action) = privmsgs.getArgs(args, required=2) (channel, action) = privmsgs.getArgs(args, required=2)
self._preCheck(irc, msg, channel) self._preCheck(irc, msg, channel)
self.log.info('Performing %r in %s due to %s.', self.log.info('Performing %s in %s due to %s.',
action, channel, msg.prefix) utils.quoted(action), channel, msg.prefix)
irc.queueMsg(ircmsgs.action(channel, action)) irc.queueMsg(ircmsgs.action(channel, action))

View File

@ -135,7 +135,8 @@ class Bayes(callbacks.Privmsg):
(kind, prob) = kind (kind, prob) = kind
prob *= 100 prob *= 100
text = utils.ellipsisify(text, 30) 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) self.db.trainNick(channel, msg.nick, text)
def guess(self, irc, msg, args, channel, text): def guess(self, irc, msg, args, channel, text):

View File

@ -89,7 +89,7 @@ conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'bugSnarfer',
conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'bold', conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'bold',
registry.Boolean(True, """Determines whether results are bolded.""")) registry.Boolean(True, """Determines whether results are bolded."""))
conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'replyNoBugzilla', 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 to use when notifying the user that there is no information about that
bugzilla site.""")) bugzilla site."""))
conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'snarfTarget', conf.registerChannelValue(conf.supybot.plugins.Bugzilla, 'snarfTarget',
@ -333,8 +333,8 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp):
if not bugs: if not bugs:
irc.error('I could not find any bugs.') irc.error('I could not find any bugs.')
return return
s = '%s match %r (%s): %s.' % \ s = '%s match %s (%s): %s.' % \
(utils.nItems('bug', len(bugs)), searchstr, (utils.nItems('bug', len(bugs)), utils.quoted(searchstr),
' AND '.join(keywords), utils.commaAndify(map(str, bugids))) ' AND '.join(keywords), utils.commaAndify(map(str, bugids)))
irc.reply(s) irc.reply(s)
search = wrap(search, [getopts({'keywords':'text'}), 'text', 'text']) search = wrap(search, [getopts({'keywords':'text'}), 'text', 'text'])
@ -349,7 +349,7 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp):
(url, description) = self.db[name] (url, description) = self.db[name]
except KeyError: except KeyError:
s = self.registryValue('replyNoBugzilla', msg.args[0]) s = self.registryValue('replyNoBugzilla', msg.args[0])
irc.error(s % name) irc.error(s % utils.quoted(name))
return return
queryurl = '%s/xml.cgi?id=%s' % (url, number) queryurl = '%s/xml.cgi?id=%s' % (url, number)
try: try:

View File

@ -164,8 +164,9 @@ class Ctcp(callbacks.PrivmsgCommandAndRegexp):
L = [] L = []
for (reply, nicks) in self.versions.iteritems(): for (reply, nicks) in self.versions.iteritems():
if nicks: if nicks:
L.append('%s responded with %r' % L.append('%s responded with %s' %
(utils.commaAndify(nicks), reply)) (utils.commaAndify(nicks),
utils.quoted(reply)))
else: else:
L.append(reply) L.append(reply)
irc.reply(utils.commaAndify(L)) irc.reply(utils.commaAndify(L))

View File

@ -130,10 +130,11 @@ class Dict(callbacks.Privmsg):
dbs = sets.Set() dbs = sets.Set()
if not definitions: if not definitions:
if dictionary == '*': 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: else:
irc.reply('No definition for %r could be found in %s' % irc.reply('No definition for %s could be found in %s' %
(word, ircutils.bold(dictionary))) (utils.quoted(word), ircutils.bold(dictionary)))
return return
L = [] L = []
for d in definitions: for d in definitions:

View File

@ -162,8 +162,8 @@ class Dunno(callbacks.Privmsg):
return text.lower() in dunno.text.lower() return text.lower() in dunno.text.lower()
ids = [str(dunno.id) for dunno in self.db.select(channel, p)] ids = [str(dunno.id) for dunno in self.db.select(channel, p)]
if ids: if ids:
s = 'Dunno search for %r (%s found): %s.' % \ s = 'Dunno search for %s (%s found): %s.' % \
(text, len(ids), utils.commaAndify(ids)) (utils.quoted(text), len(ids), utils.commaAndify(ids))
irc.reply(s) irc.reply(s)
else: else:
irc.reply('No dunnos found matching that search criteria.') irc.reply('No dunnos found matching that search criteria.')
@ -180,8 +180,8 @@ class Dunno(callbacks.Privmsg):
name = ircdb.users.getUser(dunno.by).name name = ircdb.users.getUser(dunno.by).name
at = time.localtime(dunno.at) at = time.localtime(dunno.at)
timeStr = time.strftime(conf.supybot.humanTimestampFormat(), at) timeStr = time.strftime(conf.supybot.humanTimestampFormat(), at)
irc.reply("Dunno #%s: %r (added by %s at %s)" % \ irc.reply("Dunno #%s: %s (added by %s at %s)" % \
(id, dunno.text, name, timeStr)) (id, utils.quoted(dunno.text), name, timeStr))
except KeyError: except KeyError:
irc.error('No dunno found with that id.') irc.error('No dunno found with that id.')
get = wrap(get, ['channeldb', ('id', 'dunno')]) get = wrap(get, ['channeldb', ('id', 'dunno')])

View File

@ -34,8 +34,6 @@ available on demand via several commands.
__revision__ = "$Id$" __revision__ = "$Id$"
import supybot.plugins as plugins
import time import time
import getopt import getopt
import string import string
@ -45,6 +43,8 @@ from itertools import imap
import supybot.conf as conf import supybot.conf as conf
import supybot.utils as utils import supybot.utils as utils
import supybot.ircdb as ircdb import supybot.ircdb as ircdb
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.privmsgs as privmsgs import supybot.privmsgs as privmsgs
import supybot.registry as registry import supybot.registry as registry
@ -79,19 +79,36 @@ conf.registerChannelValue(conf.supybot.plugins.Factoids,
'factoidPrefix', registry.StringWithSpaceOnRight('could be ', """Determines 'factoidPrefix', registry.StringWithSpaceOnRight('could be ', """Determines
the string that factoids will be introduced by.""")) the string that factoids will be introduced by."""))
class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): class MultiKeyError(KeyError):
def __init__(self): pass
callbacks.Privmsg.__init__(self)
plugins.ChannelDBHandler.__init__(self)
def die(self): class LockError(Exception):
callbacks.Privmsg.die(self) pass
plugins.ChannelDBHandler.die(self)
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 ' \
'<http://pysqlite.sf.net/>'
filename = plugins.makeChannelFilename(self.filename, channel)
if filename in self.dbs:
return self.dbs[filename]
if os.path.exists(filename): if os.path.exists(filename):
return sqlite.connect(filename) self.dbs[filename] = sqlite.connect(filename)
return self.dbs[filename]
db = sqlite.connect(filename) db = sqlite.connect(filename)
self.dbs[filename] = db
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""CREATE TABLE keys ( cursor.execute("""CREATE TABLE keys (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
@ -114,25 +131,8 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg):
db.commit() db.commit()
return db return db
def learn(self, irc, msg, args): def add(self, channel, key, factoid, name):
"""[<channel>] <key> as <value> db = self._getDb(channel)
Associates <key> with <value>. <channel> 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)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key)
if cursor.rowcount == 0: if cursor.rowcount == 0:
@ -140,21 +140,15 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg):
db.commit() db.commit()
cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s",key) cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s",key)
(id, locked) = imap(int, cursor.fetchone()) (id, locked) = imap(int, cursor.fetchone())
capability = ircdb.makeChannelCapability(channel, 'factoids')
if not locked: 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 cursor.execute("""INSERT INTO factoids VALUES
(NULL, %s, %s, %s, %s)""", (NULL, %s, %s, %s, %s)""",
id, name, int(time.time()), factoid) id, name, int(time.time()), factoid)
db.commit() db.commit()
irc.replySuccess()
else: else:
irc.error('That factoid is locked.') raise LockError
def _lookupFactoid(self, channel, key): def get(self, channel, key):
db = self.getDb(channel) db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""SELECT factoids.fact FROM factoids, keys cursor.execute("""SELECT factoids.fact FROM factoids, keys
@ -163,17 +157,158 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg):
LIMIT 20""", key) LIMIT 20""", key)
return [t[0] for t in cursor.fetchall()] 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):
"""[<channel>] <key> as <value>
Associates <key> with <value>. <channel> 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): def _replyFactoids(self, irc, channel, key, factoids, number=0, error=True):
if factoids: if factoids:
if number: if number:
try: try:
irc.reply(factoids[number-1]) irc.reply(factoids[number])
except IndexError: except IndexError:
irc.error('That\'s not a valid number for that key.') irc.error('That\'s not a valid number for that key.')
return return
else: else:
intro = self.registryValue('factoidPrefix', channel) intro = self.registryValue('factoidPrefix', channel)
prefix = '%r %s' % (key, intro) prefix = '%s %s' % (utils.quoted(key), intro)
if len(factoids) == 1: if len(factoids) == 1:
irc.reply(prefix + factoids[0]) irc.reply(prefix + factoids[0])
else: else:
@ -192,64 +327,44 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg):
channel = msg.args[0] channel = msg.args[0]
if self.registryValue('replyWhenInvalidCommand', channel): if self.registryValue('replyWhenInvalidCommand', channel):
key = ' '.join(tokens) key = ' '.join(tokens)
factoids = self._lookupFactoid(channel, key) factoids = self.db.get(channel, key)
self._replyFactoids(irc, channel, key, factoids, error=False) self._replyFactoids(irc, channel, key, factoids, error=False)
def whatis(self, irc, msg, args): def whatis(self, irc, msg, args, channel, number, key):
"""[<channel>] <key> [<number>] """[<channel>] <key> [<number>]
Looks up the value of <key> in the factoid database. If given a Looks up the value of <key> in the factoid database. If given a
number, will return only that exact factoid. <channel> is only number, will return only that exact factoid. <channel> is only
necessary if the message isn't sent in the channel itself. necessary if the message isn't sent in the channel itself.
""" """
channel = privmsgs.getChannel(msg, args) factoids = self.db.get(channel, key)
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)
self._replyFactoids(irc, channel, key, factoids, number) 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):
"""[<channel>] <key> """[<channel>] <key>
Locks the factoid(s) associated with <key> so that they cannot be Locks the factoid(s) associated with <key> so that they cannot be
removed or added to. <channel> is only necessary if the message isn't removed or added to. <channel> is only necessary if the message isn't
sent in the channel itself. sent in the channel itself.
""" """
channel = privmsgs.getChannel(msg, args) self.db.lock(channel, key)
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()
irc.replySuccess() irc.replySuccess()
lock = wrap(lock, ['channeldb', 'text'], allowExtra=True)
def unlock(self, irc, msg, args): def unlock(self, irc, msg, args, channel, key):
"""[<channel>] <key> """[<channel>] <key>
Unlocks the factoid(s) associated with <key> so that they can be Unlocks the factoid(s) associated with <key> so that they can be
removed or added to. <channel> is only necessary if the message isn't removed or added to. <channel> is only necessary if the message isn't
sent in the channel itself. sent in the channel itself.
""" """
channel = privmsgs.getChannel(msg, args) self.db.unlock(channel, key)
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()
irc.replySuccess() irc.replySuccess()
unlock = wrap(unlock, ['channeldb', 'text'], allowExtra=True)
def forget(self, irc, msg, args): def forget(self, irc, msg, args, channel, number, key):
"""[<channel>] <key> [<number>|*] """[<channel>] <key> [<number>|*]
Removes the factoid <key> from the factoids database. If there are Removes the factoid <key> from the factoids database. If there are
@ -258,92 +373,49 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg):
factoids associated with a key. <channel> is only necessary if factoids associated with a key. <channel> is only necessary if
the message isn't sent in the channel itself. the message isn't sent in the channel itself.
""" """
channel = privmsgs.getChannel(msg, args) if number == '*':
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]
number = True number = True
else: else:
number = None number -= 1
key = privmsgs.getArgs(args) key = ' '.join(key)
db = self.getDb(channel) try:
cursor = db.cursor() self.db.remove(channel, key, number)
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()
irc.replySuccess() irc.replySuccess()
else: except dbi.NoRecordError:
if number is not None: irc.error('There is no such factoid.')
results = cursor.fetchall() except MultiKeyError, e:
try: irc.error('%s factoids have that key. '
(_, id) = results[number] 'Please specify which one to remove, '
except IndexError: 'or use * to designate all of them.' % str(e))
irc.error('Invalid factoid number.') forget = wrap(forget, ['channeldb',
return reversed(first('positiveInt', ('literal', '*'))),
cursor.execute("DELETE FROM factoids WHERE id=%s", id) 'text'], allowExtra=True)
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)
def random(self, irc, msg, args): def random(self, irc, msg, args, channel):
"""[<channel>] """[<channel>]
Returns a random factoid from the database for <channel>. <channel> Returns a random factoid from the database for <channel>. <channel>
is only necessary if the message isn't sent in the channel itself. is only necessary if the message isn't sent in the channel itself.
""" """
channel = privmsgs.getChannel(msg, args) try:
db = self.getDb(channel) L = ['"%s": %s' % (ircutils.bold(k), v)
cursor = db.cursor() for (k, v) in self.db.random(channel)]
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))
irc.reply('; '.join(L)) irc.reply('; '.join(L))
else: except dbi.NoRecordError:
irc.error('I couldn\'t find a factoid.') 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):
"""[<channel>] <key> """[<channel>] <key>
Gives information about the factoid(s) associated with <key>. Gives information about the factoid(s) associated with <key>.
<channel> is only necessary if the message isn't sent in the channel <channel> is only necessary if the message isn't sent in the channel
itself. itself.
""" """
channel = privmsgs.getChannel(msg, args) try:
key = privmsgs.getArgs(args) (locked, factoids) = self.db.info(channel, key)
db = self.getDb(channel) except dbi.NoRecordError:
cursor = db.cursor() irc.error('No factoid matches that key.', Raise=True)
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()
L = [] L = []
counter = 0 counter = 0
for (added_by, added_at) in factoids: for (added_by, added_at) in factoids:
@ -352,10 +424,11 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg):
time.localtime(int(added_at))) time.localtime(int(added_at)))
L.append('#%s was added by %s at %s' % (counter,added_by,added_at)) L.append('#%s was added by %s at %s' % (counter,added_by,added_at))
factoids = '; '.join(L) factoids = '; '.join(L)
s = 'Key %r is %s and has %s associated with it: %s' % \ s = 'Key %s is %s and has %s associated with it: %s' % \
(key, locked and 'locked' or 'not locked', (utils.quoted(key), locked and 'locked' or 'not locked',
utils.nItems('factoid', counter), factoids) utils.nItems('factoid', counter), factoids)
irc.reply(s) irc.reply(s)
info = wrap(info, ['channeldb', 'text'], allowExtra=True)
def change(self, irc, msg, args): def change(self, irc, msg, args):
"""[<channel>] <key> <number> <regexp> """[<channel>] <key> <number> <regexp>
@ -384,7 +457,7 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg):
WHERE keys.key LIKE %s AND WHERE keys.key LIKE %s AND
keys.id=factoids.key_id""", key) keys.id=factoids.key_id""", key)
if cursor.rowcount == 0: 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 return
elif cursor.rowcount < number: elif cursor.rowcount < number:
irc.error('That\'s not a valid key id.') irc.error('That\'s not a valid key id.')
@ -395,63 +468,42 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg):
db.commit() db.commit()
irc.replySuccess() irc.replySuccess()
_sqlTrans = string.maketrans('*?', '%_') def search(self, irc, msg, args, channel, optlist, globs):
def search(self, irc, msg, args):
"""[<channel>] [--values] [--{regexp}=<value>] [<glob>] """[<channel>] [--values] [--{regexp}=<value>] [<glob>]
Searches the keyspace for keys matching <glob>. If --regexp is given, Searches the keyspace for keys matching <glob>. If --regexp is given,
it associated value is taken as a regexp and matched against the keys. 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. If --values is given, search the value space instead of the keyspace.
""" """
channel = privmsgs.getChannel(msg, args) if not optlist and not globs:
(optlist, rest) = getopt.getopt(args, '', ['values', 'regexp='])
if not optlist and not rest:
raise callbacks.ArgumentError raise callbacks.ArgumentError
tables = ['keys'] values = False
formats = []
criteria = []
target = 'keys.key'
predicateName = 'p'
db = self.getDb(channel)
for (option, arg) in optlist: for (option, arg) in optlist:
if option == '--values': if option == 'values':
target = 'factoids.fact' values = True
if 'factoids' not in tables: elif option == 'regexp':
tables.append('factoids') predicates.append(r.search)
criteria.append('factoids.key_id=keys.id') L = []
elif option == '--regexp': for glob in globs:
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 '*' not in glob and '?' not in glob: if '*' not in glob and '?' not in glob:
glob = '*%s*' % glob glob = '*%s*' % glob
criteria.append('TARGET LIKE %s') L.append(glob)
formats.append(glob.translate(self._sqlTrans)) try:
cursor = db.cursor() factoids = self.db.select(channel, values, predicates, L)
sql = """SELECT keys.key FROM %s WHERE %s""" % \ if isinstance(factoids, basestring):
(', '.join(tables), ' AND '.join(criteria)) self.whatis(irc, msg, factoids)
sql = sql.replace('TARGET', target) elif factoids is None:
cursor.execute(sql, formats) irc.reply('More than 100 keys matched that query; '
if cursor.rowcount == 0: '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.') irc.reply('No keys matched that query.')
elif cursor.rowcount == 1 and \ search = wrap(search, ['channeldb',
conf.supybot.plugins.Factoids.showFactoidIfOnlyOneMatch.get(channel)(): getopts({'values':'', 'regexp':'regexpMatcher'}),
self.whatis(irc, msg, [cursor.fetchone()[0]]) additional('text')])
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)
Class = Factoids Class = Factoids

View File

@ -144,8 +144,8 @@ class FunDB(callbacks.Privmsg):
lowerTypes = [t.lower() for t in self._types] lowerTypes = [t.lower() for t in self._types]
if type.lower() not in lowerTypes: if type.lower() not in lowerTypes:
if error: if error:
irc.error('%r is not a valid type. Valid types include %s.' % irc.error('%s is not a valid type. Valid types include %s.' %
(type, utils.commaAndify(self._types))) (utils.quoted(type), utils.commaAndify(self._types)))
return False return False
else: else:
return True return True
@ -315,8 +315,8 @@ class FunDB(callbacks.Privmsg):
return return
try: try:
x = self.db.get(channel, type, id) x = self.db.get(channel, type, id)
reply = '%s #%s: %r; Created by %s.' % (type, x.id, x.text, reply = '%s #%s: %s; Created by %s.' % \
self._getBy(x.by)) (type, x.id, utils.quoted(x.text), self._getBy(x.by))
irc.reply(reply) irc.reply(reply)
except KeyError: except KeyError:
irc.error('There is no %s with that id.' % type) irc.error('There is no %s with that id.' % type)

View File

@ -340,8 +340,8 @@ class Google(callbacks.PrivmsgCommandAndRegexp):
categories = utils.commaAndify(categories) categories = utils.commaAndify(categories)
else: else:
categories = '' categories = ''
s = 'Search for %r returned %s %s results in %s seconds.%s' % \ s = 'Search for %s returned %s %s results in %s seconds.%s' % \
(meta.searchQuery, (utils.quoted(meta.searchQuery),
meta.estimateIsExact and 'exactly' or 'approximately', meta.estimateIsExact and 'exactly' or 'approximately',
meta.estimatedTotalResultsCount, meta.estimatedTotalResultsCount,
meta.searchTime, meta.searchTime,

View File

@ -618,10 +618,12 @@ class Infobot(callbacks.PrivmsgCommandAndRegexp):
if isAre in ('was', 'is', 'am'): if isAre in ('was', 'is', 'am'):
if self.db.hasIs(key): if self.db.hasIs(key):
if also: 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) value = '%s or %s' % (self.db.getIs(key), value)
elif self.force: 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: elif self.badForce:
value = self.db.getIs(key) value = self.db.getIs(key)
self.reply('... but %s is %s, %s ...' % (key, value, 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)) self.reply('But %s is %s, %s.' % (key, value, msg.nick))
return return
else: else:
self.log.debug('Already have a %r key.', key) self.log.debug('Already have a %s key.',
utils.quoted(key))
return return
self.db.setIs(key, value) self.db.setIs(key, value)
else: else:
if self.db.hasAre(key): if self.db.hasAre(key):
if also: 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) value = '%s or %s' % (self.db.getAre(key), value)
elif self.force: 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: elif self.badForce:
value = self.db.getAre(key) value = self.db.getAre(key)
self.reply('... but %s are %s, %s ...' % (key, value, 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)) self.reply('But %s are %s, %s.' % (key, value, msg.nick))
return return
else: else:
self.log.debug('Already have a %r key.', key) self.log.debug('Already have a %s key.',
utils.quoted(key))
return return
self.db.setAre(key, value) self.db.setAre(key, value)
if self.addressed or self.force: if self.addressed or self.force:

View File

@ -297,9 +297,9 @@ class Karma(callbacks.Privmsg):
if self.registryValue('simpleOutput', channel): if self.registryValue('simpleOutput', channel):
s = '%s: %s' % (name, total) s = '%s: %s' % (name, total)
else: 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.' % \ '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) utils.nItems('time', subtracted), total)
irc.reply(s) irc.reply(s)
elif len(args) > 1: elif len(args) > 1:
@ -317,8 +317,10 @@ class Karma(callbacks.Privmsg):
else: # No name was given. Return the top/bottom N karmas. else: # No name was given. Return the top/bottom N karmas.
limit = self.registryValue('rankingDisplay', channel) limit = self.registryValue('rankingDisplay', channel)
top = self.db.top(channel, limit) top = self.db.top(channel, limit)
highest = ['%r (%s)' % t for t in self.db.top(channel, limit)] highest = ['%s (%s)' % (utils.quoted(s), t)
lowest = ['%r (%s)' % t for t in self.db.bottom(channel, limit)] 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): if not (highest and lowest):
irc.error('I have no karma for this channel.') irc.error('I have no karma for this channel.')
return return
@ -348,7 +350,7 @@ class Karma(callbacks.Privmsg):
L = self.db.most(channel, kind, L = self.db.most(channel, kind,
self.registryValue('mostDisplay', channel)) self.registryValue('mostDisplay', channel))
if L: 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)) irc.reply(utils.commaAndify(L))
else: else:
irc.error('I have no karma for this channel.') irc.error('I have no karma for this channel.')

View File

@ -99,7 +99,7 @@ class IrcHandler(logging.Handler):
# baaaaaad. # baaaaaad.
irc.sendMsg(msg) irc.sendMsg(msg)
else: else:
print '*** Not sending to %r' % target print '*** Not sending to %s' % utils.quoted(target)
class IrcFormatter(log.Formatter): class IrcFormatter(log.Formatter):

View File

@ -93,7 +93,7 @@ def configure(advanced):
conf.registerGlobalValue(lookups,command, registry.String(filename,'')) conf.registerGlobalValue(lookups,command, registry.String(filename,''))
nokeyVal = yn('Would you like the key to be shown for random \ nokeyVal = yn('Would you like the key to be shown for random \
responses?') responses?')
conf.registerGlobalValue(lookups.get(command), 'nokey', conf.registerGlobalValue(lookups.get(command), 'nokey',
registry.Boolean(nokeyVal, '')) registry.Boolean(nokeyVal, ''))
conf.registerPlugin('Lookup') conf.registerPlugin('Lookup')
@ -115,7 +115,7 @@ class SqliteLookupDB(object):
def close(self): def close(self):
self.db.close() self.db.close()
def getRecordCount(self, tableName): def getRecordCount(self, tableName):
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("""SELECT COUNT(*) FROM %s""" % tableName) cursor.execute("""SELECT COUNT(*) FROM %s""" % tableName)
@ -123,7 +123,7 @@ class SqliteLookupDB(object):
if rows == 0: if rows == 0:
raise dbi.NoRecordError raise dbi.NoRecordError
return rows return rows
def checkLookup(self, name): def checkLookup(self, name):
cursor = self.db.cursor() cursor = self.db.cursor()
sql = "SELECT name FROM sqlite_master \ sql = "SELECT name FROM sqlite_master \
@ -146,12 +146,12 @@ class SqliteLookupDB(object):
key = key.replace('\\:', ':') key = key.replace('\\:', ':')
except ValueError: except ValueError:
cursor.execute("""DROP TABLE %s""" % name) 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 raise callbacks.Error, s
cursor.execute(sql, key, value) cursor.execute(sql, key, value)
cursor.execute("CREATE INDEX %s_keys ON %s (key)" % (name, name)) cursor.execute("CREATE INDEX %s_keys ON %s (key)" % (name, name))
self.db.commit() self.db.commit()
def dropLookup(self, name): def dropLookup(self, name):
cursor = self.db.cursor() cursor = self.db.cursor()
if self.checkLookup(name): if self.checkLookup(name):
@ -159,7 +159,7 @@ class SqliteLookupDB(object):
self.db.commit() self.db.commit()
else: else:
raise dbi.NoRecordError raise dbi.NoRecordError
def getResults(self, name, key): def getResults(self, name, key):
cursor = self.db.cursor() cursor = self.db.cursor()
sql = """SELECT value FROM %s WHERE key LIKE %%s""" % name sql = """SELECT value FROM %s WHERE key LIKE %%s""" % name
@ -168,7 +168,7 @@ class SqliteLookupDB(object):
raise dbi.NoRecordError raise dbi.NoRecordError
else: else:
return cursor.fetchall() return cursor.fetchall()
def getRandomResult(self, name, key): def getRandomResult(self, name, key):
cursor = self.db.cursor() cursor = self.db.cursor()
sql = """SELECT key, value FROM %s sql = """SELECT key, value FROM %s
@ -191,8 +191,7 @@ class SqliteLookupDB(object):
try: try:
r = utils.perlReToPythonRe(arg) r = utils.perlReToPythonRe(arg)
except ValueError, e: except ValueError, e:
irc.error('%r is not a valid regular expression' % irc.errorInvalid('regular expression' % arg)
arg)
return return
def p(s, r=r): def p(s, r=r):
return int(bool(r.search(s))) return int(bool(r.search(s)))
@ -215,7 +214,7 @@ class SqliteLookupDB(object):
raise dbi.NoRecordError raise dbi.NoRecordError
else: else:
return cursor.fetchall() return cursor.fetchall()
LookupDB = plugins.DB('Lookup', {'sqlite': SqliteLookupDB,}) LookupDB = plugins.DB('Lookup', {'sqlite': SqliteLookupDB,})
class Lookup(callbacks.Privmsg): class Lookup(callbacks.Privmsg):
@ -317,12 +316,12 @@ class Lookup(callbacks.Privmsg):
def delRegistryValues(self, name): def delRegistryValues(self, name):
group = conf.supybot.plugins.Lookup.lookups group = conf.supybot.plugins.Lookup.lookups
group.unregister(name) group.unregister(name)
def addDatabase(self, name, filename): def addDatabase(self, name, filename):
dataDir = conf.supybot.directories.data() dataDir = conf.supybot.directories.data()
filename = os.path.join(dataDir, filename) filename = os.path.join(dataDir, filename)
fd = file(filename) fd = file(filename)
self.db.addLookup(name, fd, self._splitRe) self.db.addLookup(name, fd, self._splitRe)
def addCommand(self, name): def addCommand(self, name):
def f(self, irc, msg, args): def f(self, irc, msg, args):
@ -363,7 +362,7 @@ class Lookup(callbacks.Privmsg):
irc.reply('No entries in %s matched that query.' % name) irc.reply('No entries in %s matched that query.' % name)
else: else:
irc.error('I don\'t have a domain %s' % name) irc.error('I don\'t have a domain %s' % name)
def _lookup(self, irc, msg, args): def _lookup(self, irc, msg, args):
"""<name> <key> """<name> <key>

View File

@ -193,7 +193,8 @@ class Math(callbacks.Privmsg):
return str(x) return str(x)
text = self._mathRe.sub(handleMatch, text) text = self._mathRe.sub(handleMatch, text)
try: 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)) x = complex(eval(text, self._mathEnv, self._mathEnv))
irc.reply(self._complexToString(x)) irc.reply(self._complexToString(x))
except OverflowError: except OverflowError:
@ -227,7 +228,8 @@ class Math(callbacks.Privmsg):
return return
text = text.replace('lambda', '') text = text.replace('lambda', '')
try: 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))) irc.reply(str(eval(text, self._mathEnv, self._mathEnv)))
except OverflowError: except OverflowError:
maxFloat = math.ldexp(0.9999999999999999, 1024) maxFloat = math.ldexp(0.9999999999999999, 1024)
@ -283,7 +285,8 @@ class Math(callbacks.Privmsg):
try: try:
stack.append(eval(s, self._mathEnv, self._mathEnv)) stack.append(eval(s, self._mathEnv, self._mathEnv))
except SyntaxError: except SyntaxError:
irc.error('%r is not a defined function.' % arg) irc.error('%s is not a defined function.' %
utils.quoted(arg))
return return
if len(stack) == 1: if len(stack) == 1:
irc.reply(str(self._complexToString(complex(stack[0])))) irc.reply(str(self._complexToString(complex(stack[0]))))

View File

@ -238,7 +238,7 @@ class SqliteMoobotDB(object):
cursor.execute("""UPDATE factoids cursor.execute("""UPDATE factoids
SET locked_by=%s, locked_at=%s SET locked_by=%s, locked_at=%s
WHERE key LIKE %s""", WHERE key LIKE %s""",
locker_id, int(time.time()), key) locker_id, int(time.time()), key)
db.commit() db.commit()
def unlock(self, channel, key): def unlock(self, channel, key):
@ -255,7 +255,7 @@ class SqliteMoobotDB(object):
cursor.execute("""SELECT created_by, count(key) FROM factoids cursor.execute("""SELECT created_by, count(key) FROM factoids
GROUP BY created_by GROUP BY created_by
ORDER BY count(key) DESC LIMIT %s""", limit) ORDER BY count(key) DESC LIMIT %s""", limit)
return cursor.fetchall() return cursor.fetchall()
def mostRecent(self, channel, limit): def mostRecent(self, channel, limit):
db = self._getDb(channel) db = self._getDb(channel)
@ -380,7 +380,7 @@ class MoobotFactoids(callbacks.Privmsg):
def _sanitizeKey(self, key): def _sanitizeKey(self, key):
return key.rstrip('!? ') return key.rstrip('!? ')
def _checkNotLocked(self, irc, channel, key): def _checkNotLocked(self, irc, channel, key):
if self.db.locked(channel, key): if self.db.locked(channel, key):
irc.error('Factoid "%s" is locked.' % key, Raise=True) irc.error('Factoid "%s" is locked.' % key, Raise=True)
@ -398,12 +398,13 @@ class MoobotFactoids(callbacks.Privmsg):
elif 'is' in tokens: elif 'is' in tokens:
p = 'is'.__eq__ p = 'is'.__eq__
else: else:
s = 'Invalid tokens for {add,change}Factoid: %r' % tokens s = 'Invalid tokens for {add,change}Factoid: %s' % \
utils.quoted(tokens)
raise ValueError, s raise ValueError, s
(key, newfact) = map(' '.join, utils.itersplit(p, tokens, maxsplit=1)) (key, newfact) = map(' '.join, utils.itersplit(p, tokens, maxsplit=1))
key = self._sanitizeKey(key) key = self._sanitizeKey(key)
return (key, newfact) return (key, newfact)
def addFactoid(self, irc, msg, tokens): def addFactoid(self, irc, msg, tokens):
# First, check and see if the entire message matches a factoid key # First, check and see if the entire message matches a factoid key
channel = getChannel(irc, msg) channel = getChannel(irc, msg)

View File

@ -366,7 +366,8 @@ class Note(callbacks.Privmsg):
try: try:
L.remove(user) L.remove(user)
except (KeyError, ValueError): 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 return
else: else:
L.add(user) L.add(user)

View File

@ -132,10 +132,12 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler):
optionstr = 'This poll has no options yet' optionstr = 'This poll has no options yet'
else: else:
options = cursor.fetchall() options = cursor.fetchall()
optionstr = 'Options: %s' %\ optionstr = 'Options: %s' % \
' '.join(['%s: %r' % tuple(t) for t in options]) ' '.join(['%s: %s' % (s, utils.quoted(t)) \
pollstr = 'Poll #%s: %r started by %s. %s. Poll is %s.' % \ for (s, t) in options])
(poll_id, question, starter, optionstr, statusstr) pollstr = 'Poll #%s: %s started by %s. %s. Poll is %s.' % \
(poll_id, utils.quoted(question), starter, optionstr,
statusstr)
irc.reply(pollstr) irc.reply(pollstr)
def open(self, irc, msg, args): def open(self, irc, msg, args):
@ -304,7 +306,7 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler):
WHERE id=%s AND poll_id=%s""", WHERE id=%s AND poll_id=%s""",
option_id, poll_id) option_id, poll_id)
option = cursor.fetchone()[0] option = cursor.fetchone()[0]
results.append('%r: %s' % (option, int(count))) results.append('%s: %s' % (utils.quoted(option), int(count)))
s = utils.commaAndify(results) s = utils.commaAndify(results)
reply += ' - %s' % s reply += ' - %s' % s
irc.reply(reply) irc.reply(reply)
@ -322,7 +324,8 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler):
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.reply('This channel currently has no open polls.') irc.reply('This channel currently has no open polls.')
else: 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)) irc.reply(utils.commaAndify(polls))
Class = Poll Class = Poll

View File

@ -258,7 +258,7 @@ class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg):
cursor.execute("""SELECT quote, hostmask, added_at, added_by cursor.execute("""SELECT quote, hostmask, added_at, added_by
FROM quotegrabs WHERE id = %s""", id) FROM quotegrabs WHERE id = %s""", id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error('No quotegrab for id %r' % id) irc.error('No quotegrab for id %s' % utils.quoted(id))
return return
quote, hostmask, timestamp, grabber_mask = cursor.fetchone() quote, hostmask, timestamp, grabber_mask = cursor.fetchone()
time_str = time.strftime(conf.supybot.humanTimestampFormat(), time_str = time.strftime(conf.supybot.humanTimestampFormat(),

View File

@ -67,8 +67,8 @@ class QuoteRecord(dbi.Record):
user = self.by user = self.by
except KeyError: except KeyError:
user = 'a user that is no longer registered' user = 'a user that is no longer registered'
return 'Quote %r added by %s at %s.' % \ return 'Quote %s added by %s at %s.' % \
(self.text, user, (utils.quoted(self.text), user,
time.strftime(format, time.localtime(float(self.at)))) time.strftime(format, time.localtime(float(self.at))))
class SqliteQuotesDB(object): class SqliteQuotesDB(object):
@ -258,7 +258,8 @@ class Quotes(callbacks.Privmsg):
irc.reply('More than 10 quotes matched your criteria. ' irc.reply('More than 10 quotes matched your criteria. '
'Please narrow your query.') 'Please narrow your query.')
else: 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] for q in quote]
irc.reply(utils.commaAndify(quotes)) irc.reply(utils.commaAndify(quotes))
@ -331,7 +332,7 @@ class Quotes(callbacks.Privmsg):
try: try:
id = int(id) id = int(id)
except ValueError: except ValueError:
irc.error('Invalid id: %r' % id) irc.errorInvalid('id' % id)
return return
try: try:
quote = self.db.get(channel, id) quote = self.db.get(channel, id)
@ -350,7 +351,7 @@ class Quotes(callbacks.Privmsg):
try: try:
id = int(id) id = int(id)
except ValueError: except ValueError:
irc.error('That\'s not a valid id: %r' % id) irc.errorInvalid('id' % id)
try: try:
self.db.remove(channel, id) self.db.remove(channel, id)
irc.replySuccess() irc.replySuccess()

View File

@ -304,9 +304,8 @@ class RSS(callbacks.Privmsg):
try: try:
name = self._validFeedName(name) name = self._validFeedName(name)
except ValueError: except ValueError:
irc.error('%r is not a valid feed name. Feed names must not ' irc.errorInvalid('feed name', name, 'Feed names must not '
'include dots, colons, or spaces.' % name) 'include dots, colons, or spaces.')
return
self.makeFeedCommand(name, url) self.makeFeedCommand(name, url)
irc.replySuccess() irc.replySuccess()

View File

@ -380,7 +380,8 @@ class Services(privmsgs.CapabilityCheckingPrivmsg):
'NickServ. Check email at %s and send the auth ' 'NickServ. Check email at %s and send the auth '
'command to NickServ.', email) 'command to NickServ.', email)
else: 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): def checkPrivileges(self, irc, channel):
chanserv = self.registryValue('ChanServ') chanserv = self.registryValue('ChanServ')

View File

@ -170,7 +170,8 @@ class ShrinkUrl(callbacks.PrivmsgCommandAndRegexp):
url = match.group(0) url = match.group(0)
r = self.registryValue('nonSnarfingRegexp', channel) r = self.registryValue('nonSnarfingRegexp', channel)
if r and r.search(url) is not None: 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 return
minlen = self.registryValue('minimumLength', channel) minlen = self.registryValue('minimumLength', channel)
cmd = self.registryValue('default', channel) cmd = self.registryValue('default', channel)
@ -181,7 +182,8 @@ class ShrinkUrl(callbacks.PrivmsgCommandAndRegexp):
elif cmd == 'ln': elif cmd == 'ln':
(shorturl, _) = self._getLnUrl(url) (shorturl, _) = self._getLnUrl(url)
if shorturl is None: 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 return
domain = webutils.getDomain(url) domain = webutils.getDomain(url)
s = '%s (at %s)' % (ircutils.bold(shorturl), domain) s = '%s (at %s)' % (ircutils.bold(shorturl), domain)

View File

@ -164,8 +164,8 @@ class Success(callbacks.Privmsg):
return text.lower() in success.text.lower() return text.lower() in success.text.lower()
ids = [str(success.id) for success in self.db.select(channel, p)] ids = [str(success.id) for success in self.db.select(channel, p)]
if ids: if ids:
s = 'Success search for %r (%s found): %s.' % \ s = 'Success search for %s (%s found): %s.' % \
(text, len(ids), utils.commaAndify(ids)) (utils.quoted(text), len(ids), utils.commaAndify(ids))
irc.reply(s) irc.reply(s)
else: else:
irc.reply('No successes found matching that search criteria.') irc.reply('No successes found matching that search criteria.')
@ -182,8 +182,8 @@ class Success(callbacks.Privmsg):
name = ircdb.users.getUser(success.by).name name = ircdb.users.getUser(success.by).name
at = time.localtime(success.at) at = time.localtime(success.at)
timeStr = time.strftime(conf.supybot.humanTimestampFormat(), at) timeStr = time.strftime(conf.supybot.humanTimestampFormat(), at)
irc.reply("Success #%s: %r (added by %s at %s)" % \ irc.reply("Success #%s: %s (added by %s at %s)" % \
(id, success.text, name, timeStr)) (id, utils.quoted(success.text), name, timeStr))
except KeyError: except KeyError:
irc.error('No success found with that id.') irc.error('No success found with that id.')
get = wrap(get, ['channeldb', ('id', 'success')]) get = wrap(get, ['channeldb', ('id', 'success')])

View File

@ -110,8 +110,7 @@ class Todo(callbacks.Privmsg):
try: try:
userid = ircdb.users.getUserId(arg) userid = ircdb.users.getUserId(arg)
except KeyError: except KeyError:
irc.error( irc.errorInvalid('task id or username', arg)
'%r is not a valid task id or username' % arg)
return return
db = self.dbHandler.getDb() db = self.dbHandler.getDb()
cursor = db.cursor() cursor = db.cursor()
@ -149,7 +148,7 @@ class Todo(callbacks.Privmsg):
cursor.execute("""SELECT userid,priority,added_at,task,active cursor.execute("""SELECT userid,priority,added_at,task,active
FROM todo WHERE id = %s""", taskid) FROM todo WHERE id = %s""", taskid)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error('%r is not a valid task id' % taskid) irc.errorInvalid('task id', taskid)
return return
(userid, pri, added_at, task, active) = cursor.fetchone() (userid, pri, added_at, task, active) = cursor.fetchone()
# Construct and return the reply # Construct and return the reply
@ -189,7 +188,7 @@ class Todo(callbacks.Privmsg):
try: try:
priority = int(arg) priority = int(arg)
except ValueError, e: except ValueError, e:
irc.error('%r is an invalid priority' % arg) irc.errorInvalid('priority', arg)
return return
text = privmsgs.getArgs(rest) text = privmsgs.getArgs(rest)
db = self.dbHandler.getDb() db = self.dbHandler.getDb()
@ -270,8 +269,7 @@ class Todo(callbacks.Privmsg):
try: try:
r = utils.perlReToPythonRe(arg) r = utils.perlReToPythonRe(arg)
except ValueError, e: except ValueError, e:
irc.error('%r is not a valid regular expression' % irc.errorInvalid('regular expression', arg)
arg)
return return
def p(s, r=r): def p(s, r=r):
return int(bool(r.search(s))) return int(bool(r.search(s)))
@ -335,7 +333,7 @@ class Todo(callbacks.Privmsg):
try: try:
replacer = utils.perlReToReplacer(regexp) replacer = utils.perlReToReplacer(regexp)
except ValueError: except ValueError:
irc.error('%r is not a valid regexp' % regexp) irc.errorInvalid('regexp', regexp)
return return
db = self.dbHandler.getDb() db = self.dbHandler.getDb()
cursor = db.cursor() cursor = db.cursor()
@ -343,7 +341,7 @@ class Todo(callbacks.Privmsg):
WHERE userid = %s AND id = %s WHERE userid = %s AND id = %s
AND active = 1""", userid, taskid) AND active = 1""", userid, taskid)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error('%r is not a valid task id' % taskid) irc.errorInvalid('task id', taskid)
return return
newtext = replacer(cursor.fetchone()[0]) newtext = replacer(cursor.fetchone()[0])
cursor.execute("""UPDATE todo SET task = %s cursor.execute("""UPDATE todo SET task = %s

View File

@ -89,7 +89,7 @@ def canChangeTopic(irc, msg, args, state):
c = irc.state.channels[state.channel] c = irc.state.channels[state.channel]
if irc.nick not in c.ops and 't' in c.modes: if irc.nick not in c.ops and 't' in c.modes:
irc.error('I can\'t change the topic, ' 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): def getTopic(irc, msg, args, state, format=True):
separator = state.cb.registryValue('separator', state.channel) separator = state.cb.registryValue('separator', state.channel)

View File

@ -108,9 +108,10 @@ class URL(callbacks.PrivmsgCommandAndRegexp):
for url in webutils.urlRe.findall(text): for url in webutils.urlRe.findall(text):
r = self.registryValue('nonSnarfingRegexp', channel) r = self.registryValue('nonSnarfingRegexp', channel)
if r and r.search(url): 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 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.db.add(channel, url, msg)
self.__parent.doPrivmsg(irc, msg) self.__parent.doPrivmsg(irc, msg)
@ -125,7 +126,7 @@ class URL(callbacks.PrivmsgCommandAndRegexp):
url = match.group(0) url = match.group(0)
r = self.registryValue('nonSnarfingRegexp', channel) r = self.registryValue('nonSnarfingRegexp', channel)
if r and r.search(url): if r and r.search(url):
self.log.debug('Not titleSnarfing %r.', url) self.log.debug('Not titleSnarfing %s.', utils.quoted(url))
return return
try: try:
size = conf.supybot.protocols.http.peekSize() size = conf.supybot.protocols.http.peekSize()

View File

@ -223,8 +223,8 @@ class Unix(callbacks.Privmsg):
resp = 'I could not find an alternate spelling for "%s"' % word resp = 'I could not find an alternate spelling for "%s"' % word
elif line[0] == '&': elif line[0] == '&':
matches = line.split(':')[1].strip() matches = line.split(':')[1].strip()
resp = 'Possible spellings for %r: %s.' % \ resp = 'Possible spellings for %s: %s.' % \
(word, utils.commaAndify(matches.split(', '))) (utils.quoted(word), utils.commaAndify(matches.split(', ')))
else: else:
resp = 'Something unexpected was seen in the [ai]spell output.' resp = 'Something unexpected was seen in the [ai]spell output.'
irc.reply(resp) irc.reply(resp)

View File

@ -234,8 +234,8 @@ class WordStats(callbacks.Privmsg):
self.db.delWord(channel, word) self.db.delWord(channel, word)
irc.replySuccess() irc.replySuccess()
else: else:
irc.error('%r doesn\'t look like a word I am keeping stats ' irc.error('%s doesn\'t look like a word I am keeping stats '
'on.' % word) 'on.' % utils.quoted(word))
return return
else: else:
irc.error('I am not currently keeping any word stats.') irc.error('I am not currently keeping any word stats.')
@ -277,14 +277,16 @@ class WordStats(callbacks.Privmsg):
try: try:
count = self.db.getWordCount(channel, id, word) count = self.db.getWordCount(channel, id, word)
except KeyError: 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 return
if count: if count:
s = '%s has said %r %s.' % \ s = '%s has said %s %s.' % \
(user, word, utils.nItems('time', count)) (user, utils.quoted(word), utils.nItems('time', count))
irc.reply(s) irc.reply(s)
else: 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)): elif arg1 in WordDict.fromkeys(self.db.getWords(channel)):
word = arg1 word = arg1
total = self.db.getTotalWordCount(channel, word) total = self.db.getTotalWordCount(channel, word)
@ -300,7 +302,7 @@ class WordStats(callbacks.Privmsg):
id = None id = None
rank = None rank = None
number = None number = None
ers = '%rer' % word ers = '%ser' % utils.quoted(word)
L = [] L = []
for (userid, count) in self.db.getTopUsers(channel, word, n): for (userid, count) in self.db.getTopUsers(channel, word, n):
if userid == id: if userid == id:
@ -309,9 +311,7 @@ class WordStats(callbacks.Privmsg):
username = ircdb.users.getUser(userid).name username = ircdb.users.getUser(userid).name
L.append('%s: %s' % (username, count)) L.append('%s: %s' % (username, count))
except KeyError: except KeyError:
self.log.warning('Odd, I have a user in my WordStats ' L.append('%s: %s' % ('unregistered user', count))
'database that doesn\'t exist in my '
'user database: #%s.', userid)
ret = 'Top %s (out of a total of %s seen):' % \ ret = 'Top %s (out of a total of %s seen):' % \
(utils.nItems(ers, len(L)), utils.nItems(repr(word), total)) (utils.nItems(ers, len(L)), utils.nItems(repr(word), total))
users = self.db.getNumUsers(channel) users = self.db.getNumUsers(channel)
@ -328,18 +328,19 @@ class WordStats(callbacks.Privmsg):
try: try:
id = ircdb.users.getUserId(user) id = ircdb.users.getUserId(user)
except KeyError: except KeyError:
irc.error('%r doesn\'t look like a word I\'m keeping stats ' irc.error('%s doesn\'t look like a word I\'m keeping stats '
'on or a user in my database.' % user) 'on or a user in my database.' % utils.quoted(user))
return return
try: try:
L = ['%r: %s' % (word, count) L = ['%s: %s' % (utils.quoted(word), count)
for (word,count) in self.db.getUserWordCounts(channel,id)] for (word,count) in self.db.getUserWordCounts(channel,id)]
if L: if L:
L.sort() L.sort()
irc.reply(utils.commaAndify(L)) irc.reply(utils.commaAndify(L))
else: else:
irc.error('%r doesn\'t look like a word I\'m keeping stats' irc.error('%s doesn\'t look like a word I\'m keeping stats'
' on or a user in my database.' % user) ' on or a user in my database.' %
utils.quoted(user))
return return
except KeyError: except KeyError:
irc.error('I have no word stats for that person.') irc.error('I have no word stats for that person.')

View File

@ -278,15 +278,18 @@ class Words(callbacks.Privmsg):
del game.unused[game.unused.index(letter)] del game.unused[game.unused.index(letter)]
if letter in game.hidden: if letter in game.hidden:
self._hangmanReply(irc, channel, self._hangmanReply(irc, channel,
'Yes, there is %s %r.' % 'Yes, there is %s %s.' %
(game.letterArticle(letter), letter)) (game.letterArticle(letter),
utils.quoted(letter)))
game.guess = game.addLetter(letter, game.guess, game.guess = game.addLetter(letter, game.guess,
game.letterPositions(letter, game.letterPositions(letter,
game.hidden)) game.hidden))
if game.guess == game.hidden: if game.guess == game.hidden:
game.guessed = True game.guessed = True
else: 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 game.tries -= 1
self._hangmanReply(irc, channel, self._hangmanReply(irc, channel,
'%s (%s left)' % (game.guess, game.triesLeft())) '%s (%s left)' % (game.guess, game.triesLeft()))
@ -316,12 +319,13 @@ class Words(callbacks.Privmsg):
# Verify if the user won or lost # Verify if the user won or lost
if game.guessed and game.tries > 0: if game.guessed and game.tries > 0:
self._hangmanReply(irc, channel, self._hangmanReply(irc, channel,
'You win! The word was indeed %r.' % 'You win! The word was indeed %s.' %
game.hidden) utils.quoted(game.hidden))
self.endGame(channel) self.endGame(channel)
elif not game.guessed and game.tries == 0: elif not game.guessed and game.tries == 0:
self._hangmanReply(irc, channel, 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) self.endGame(channel)
guess = wrap(guess, ['channel', 'somethingWithoutSpaces']) guess = wrap(guess, ['channel', 'somethingWithoutSpaces'])
### ###

View File

@ -1181,6 +1181,11 @@ class SimpleProxy(RichReplyMethods):
self.msg = msg self.msg = msg
def error(self, s, msg=None, **kwargs): 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: if msg is None:
msg = self.msg msg = self.msg
m = error(msg, s, **kwargs) m = error(msg, s, **kwargs)

View File

@ -659,7 +659,12 @@ class first(context):
state.args.append(self.default) state.args.append(self.default)
else: else:
raise e 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): class getopts(context):
"""The empty string indicates that no argument is taken; None indicates """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', __all__ = ['wrap', 'context', 'additional', 'optional', 'any', 'compose',
'Spec', 'first', 'urlSnarfer', 'thread', 'Spec', 'first', 'urlSnarfer', 'thread', 'reversed',
'many', 'getopts', 'getConverter', 'addConverter', 'callConverter'] 'many', 'getopts', 'getConverter', 'addConverter', 'callConverter']
if world.testing: if world.testing:

View File

@ -77,7 +77,7 @@ class AliasTestCase(ChannelPluginTestCase, PluginDocumentation):
def testAliasHelp(self): def testAliasHelp(self):
self.assertNotError('alias add slashdot foo') self.assertNotError('alias add slashdot foo')
self.assertRegexp('help slashdot', "Alias for 'foo.*'") self.assertRegexp('help slashdot', "Alias for <<foo.*>>")
def testRemove(self): def testRemove(self):
self.assertNotError('alias add foo echo bar') self.assertNotError('alias add foo echo bar')

View File

@ -66,9 +66,8 @@ if sqlite is not None:
'Todos for tester: #1: wash my car and #2: moo') 'Todos for tester: #1: wash my car and #2: moo')
# Check error # Check error
self.assertError('todo asfas') self.assertError('todo asfas')
self.assertResponse('todo asfas', self.assertRegexp('todo asfas',
'Error: \'asfas\' is not a valid task id or ' 'Error: \'asfas\' is not a valid task')
'username')
# Check priority sorting # Check priority sorting
self.assertNotError('todo setpriority 1 100') self.assertNotError('todo setpriority 1 100')
self.assertNotError('todo setpriority 2 10') self.assertNotError('todo setpriority 2 10')

View File

@ -69,6 +69,10 @@ class CommandsTestCase(SupyTestCase):
self.assertRaises(callbacks.Error, self.assertRaises(callbacks.Error,
self.assertState, spec, ['foo'], ['asdf']) 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): def testGetopts(self):
spec = ['int', getopts({'foo': None, 'bar': 'int'}), 'int'] spec = ['int', getopts({'foo': None, 'bar': 'int'}), 'int']
self.assertState(spec, self.assertState(spec,