Updated for database independence

This commit is contained in:
James Vega 2004-08-13 03:50:38 +00:00
parent 3a949e420a
commit c2d5ac56ce
2 changed files with 191 additions and 125 deletions

View File

@ -42,6 +42,7 @@ import time
import getopt import getopt
import os.path import os.path
import supybot.dbi as dbi
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
@ -54,12 +55,22 @@ except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at <http://pysqlite.sf.net/>' 'plugin. Download it at <http://pysqlite.sf.net/>'
class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg): class QuoteRecord(object):
def __init__(self): __metaclass__ = dbi.Record
plugins.ChannelDBHandler.__init__(self) __fields__ = [
callbacks.Privmsg.__init__(self) 'at',
'by',
'text'
]
def __str__(self):
format = conf.supybot.humanTimestampFormat()
return 'Quote %r added by %s at %s.' % \
(self.text, ircdb.users.getUser(self.by).name,
time.strftime(format, time.localtime(float(self.at))))
def makeDb(self, filename): class SqliteQuotesDB(object):
def _getDb(self, channel):
filename = plugins.makeChannelFilename('Quotes.db', channel)
if os.path.exists(filename): if os.path.exists(filename):
return sqlite.connect(db=filename, mode=0755, return sqlite.connect(db=filename, mode=0755,
converters={'bool': bool}) converters={'bool': bool})
@ -75,6 +86,100 @@ class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg):
db.commit() db.commit()
return db return db
def add(self, channel, by, quote):
db = self._getDb(channel)
cursor = db.cursor()
at = int(time.time())
cursor.execute("""INSERT INTO quotes VALUES (NULL, %s, %s, %s)""",
by, at, quote)
cursor.execute("""SELECT id FROM quotes
WHERE added_by=%s AND added_at=%s AND quote=%s""",
by, at, quote)
db.commit()
return int(cursor.fetchone()[0])
def size(self, channel):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT COUNT(*) FROM quotes""")
return int(cursor.fetchone()[0])
def random(self, channel):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT id, added_by, added_at, quote FROM quotes
ORDER BY random() LIMIT 1""")
(id, by, at, text) = cursor.fetchone()
return QuoteRecord(id, by=by, at=int(at), text=text)
def search(self, channel, **kwargs):
criteria = []
formats = []
predicateName = ''
db = self._getDb(channel)
for v in kwargs['id']:
criteria.append('id=%s' % v)
for v in kwargs['with']:
criteria.append('quote LIKE %s')
formats.append('%%%s%%' % v)
for v in kwargs['by']:
criteria.append('added_by=%s')
formats.append(arg)
for v in kwargs['predicate']:
def p(s):
return int(bool(v.search(s)))
predicateName += 'p'
db.create_function(predicateName, 1, p)
criteria.append('%s(quote)' % predicateName)
for s in kwargs['args']:
try:
i = int(s)
criteria.append('id=%s' % i)
except ValueError:
s = '%%%s%%' % s
criteria.append('quote LIKE %s')
formats.append(s)
sql = """SELECT id, added_by, added_at, quote FROM quotes
WHERE %s""" % ' AND '.join(criteria)
cursor = db.cursor()
cursor.execute(sql, *formats)
if cursor.rowcount == 0:
return None
elif cursor.rowcount == 1:
(id, by, at, text) = cursor.fetchone()
return QuoteRecord(id, by=by, at=int(at), text=text)
else:
quotes = []
for (id, by, at, text) in cursor.fetchall():
quotes.append(QuoteRecord(id, by=by, at=int(at), text=text))
return quotes
def get(self, channel, id):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT added_by, added_at, quote FROM quotes
WHERE id=%s""", id)
if cursor.rowcount == 0:
raise KeyError, id
(by, at, text) = cursor.fetchone()
return QuoteRecord(id, by=by, at=int(at), text=text)
def remove(self, channel, id):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""DELETE FROM quotes WHERE id=%s""", id)
if cursor.rowcount == 0:
raise KeyError, id
db.commit()
def QuotesDB():
return SqliteQuotesDB()
class Quotes(callbacks.Privmsg):
def __init__(self):
self.db = QuotesDB()
callbacks.Privmsg.__init__(self)
def add(self, irc, msg, args): def add(self, irc, msg, args):
"""[<channel>] <quote> """[<channel>] <quote>
@ -83,18 +188,8 @@ class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg):
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
quote = privmsgs.getArgs(args) quote = privmsgs.getArgs(args)
db = self.getDb(channel) id = self.db.add(channel, msg.nick, quote)
cursor = db.cursor() irc.replySuccess('(Quote #%s added)' % id)
quotetime = int(time.time())
cursor.execute("""INSERT INTO quotes
VALUES(NULL, %s, %s, %s)""",
msg.nick, quotetime, quote)
db.commit()
sql = """SELECT id FROM quotes
WHERE added_by=%s AND added_at=%s AND quote=%s"""
cursor.execute(sql, msg.nick, quotetime, quote)
quoteid = cursor.fetchone()[0]
irc.replySuccess('(Quote #%s added)' % quoteid)
def stats(self, irc, msg, args): def stats(self, irc, msg, args):
"""[<channel>] """[<channel>]
@ -104,17 +199,23 @@ class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg):
itself. itself.
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
db = self.getDb(channel) size = self.db.size(channel)
cursor = db.cursor()
cursor.execute("""SELECT COUNT(*) FROM quotes""")
maxid = int(cursor.fetchone()[0])
if maxid is None:
maxid = 0
s = 'There %s %s in my database.' % \ s = 'There %s %s in my database.' % \
(utils.be(maxid), utils.nItems('quote', maxid)) (utils.be(size), utils.nItems('quote', size))
irc.reply(s) irc.reply(s)
def get(self, irc, msg, args): def _replyQuote(self, irc, quote):
if isinstance(quote, QuoteRecord):
irc.reply('#%s: %s' % (quote.id, quote.text))
elif len(quote) > 10:
irc.reply('More than 10 quotes matched your criteria. '
'Please narrow your query.')
else:
quotes = ['#%s: "%s"' % (q.id, utils.ellipsisify(q.text, 30))
for q in quote]
irc.reply(utils.commaAndify(quotes))
def search(self, irc, msg, args):
"""[<channel>] --{id,regexp,from,with}=<value> ] """[<channel>] --{id,regexp,from,with}=<value> ]
Returns quote(s) matching the given criteria. --from is who added the Returns quote(s) matching the given criteria. --from is who added the
@ -126,66 +227,35 @@ class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg):
'from=', 'with=']) 'from=', 'with='])
if not optlist and not rest: if not optlist and not rest:
raise callbacks.ArgumentError raise callbacks.ArgumentError
criteria = [] kwargs = {'args': rest, 'id': [], 'with': [], 'by': [], 'predicate': []}
formats = [] for (option, arg) in optlist:
predicateName = ''
db = self.getDb(channel)
for (option, argument) in optlist:
option = option.lstrip('-') option = option.lstrip('-')
if option == 'id': if option == 'id':
try: try:
argument = int(argument) arg = int(arg)
criteria.append('id=%s' % argument) kwargs[option].append(arg)
except ValueError: except ValueError:
irc.error('--id value must be an integer.') irc.error('--id value must be an integer.')
return return
elif option == 'with': elif option == 'with':
criteria.append('quote LIKE %s') kwargs[option].append(arg)
formats.append('%%%s%%' % argument)
elif option == 'from': elif option == 'from':
criteria.append('added_by=%s') kwargs['by'].append(arg)
formats.append(argument)
elif option == 'regexp': elif option == 'regexp':
try: try:
r = utils.perlReToPythonRe(argument) r = utils.perlReToPythonRe(arg)
except ValueError: except ValueError:
try: try:
r = re.compile(argument, re.I) r = re.compile(arg, re.I)
except re.error, e: except re.error, e:
irc.error(str(e)) irc.error(str(e))
return return
def p(s): kwargs['predicate'].append(r)
return int(bool(r.search(s))) quote = self.db.search(channel, **kwargs)
predicateName += 'p' if quote is None:
db.create_function(predicateName, 1, p)
criteria.append('%s(quote)' % predicateName)
for s in rest:
try:
i = int(s)
criteria.append('id=%s' % i)
except ValueError:
s = '%%%s%%' % s
criteria.append('quote LIKE %s')
formats.append(s)
sql = """SELECT id, quote FROM quotes
WHERE %s""" % ' AND '.join(criteria)
cursor = db.cursor()
cursor.execute(sql, *formats)
if cursor.rowcount == 0:
irc.reply('No quotes matched that criteria.') irc.reply('No quotes matched that criteria.')
elif cursor.rowcount == 1:
(id, quote) = cursor.fetchone()
irc.reply('#%s: %s' % (id, quote))
elif cursor.rowcount > 10:
irc.reply('More than 10 quotes matched your criteria. '
'Please narrow your query.')
else: else:
results = cursor.fetchall() self._replyQuote(irc, quote)
idsWithSnippets = []
for (id, quote) in results:
s = '#%s: "%s..."' % (id, quote[:30])
idsWithSnippets.append(s)
irc.reply(utils.commaAndify(idsWithSnippets))
### FIXME: we need to remove those predicates from the database. ### FIXME: we need to remove those predicates from the database.
def random(self, irc, msg, args): def random(self, irc, msg, args):
@ -195,16 +265,11 @@ class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg):
the message isn't sent in the channel itself. the message isn't sent in the channel itself.
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
db = self.getDb(channel) quote = self.db.random(channel)
cursor = db.cursor() if quote:
cursor.execute("""SELECT id FROM quotes self._replyQuote(irc, quote)
ORDER BY random() else:
LIMIT 1""") self.error('I have no quotes for this channel.')
if cursor.rowcount != 1:
irc.error('It seems that quote database is empty.')
return
(id,) = cursor.fetchone()
self.get(irc, msg, [channel, '--id', str(id)])
def info(self, irc, msg, args): def info(self, irc, msg, args):
"""[<channel>] <id> """[<channel>] <id>
@ -215,16 +280,15 @@ class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg):
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
id = privmsgs.getArgs(args) id = privmsgs.getArgs(args)
db = self.getDb(channel) try:
cursor = db.cursor() id = int(id)
cursor.execute("""SELECT * FROM quotes WHERE id=%s""", id) except ValueError:
if cursor.rowcount == 1: irc.error('Invalid id: %r' % id)
(id, added_by, added_at, quote) = cursor.fetchone() return
timestamp = time.strftime(conf.supybot.humanTimestampFormat(), try:
time.localtime(int(added_at))) quote = self.db.get(channel, id)
irc.reply('Quote %r added by %s at %s.' % irc.reply(str(quote))
(quote, added_by, timestamp)) except KeyError:
else:
irc.error('There isn\'t a quote with that id.') irc.error('There isn\'t a quote with that id.')
def remove(self, irc, msg, args): def remove(self, irc, msg, args):
@ -235,13 +299,15 @@ class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg):
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
id = privmsgs.getArgs(args) id = privmsgs.getArgs(args)
db = self.getDb(channel) try:
cursor = db.cursor() id = int(id)
cursor.execute("""DELETE FROM quotes WHERE id=%s""", id) except ValueError:
if cursor.rowcount == 0: irc.error('That\'s not a valid id: %r' % id)
irc.error('There was no such quote.') try:
else: self.db.remove(channel, id)
irc.replySuccess() irc.replySuccess()
except KeyError:
irc.error('There was no such quote.')
Class = Quotes Class = Quotes

View File

@ -37,39 +37,39 @@ except ImportError:
sqlite = None sqlite = None
if sqlite is not None: if sqlite is not None:
class QuotesTestCase(PluginTestCase, PluginDocumentation): class QuotesTestCase(ChannelPluginTestCase, PluginDocumentation):
plugins = ('Quotes',) plugins = ('Quotes',)
def test(self): def test(self):
self.assertRegexp('stats #foo', '0') self.assertRegexp('stats', '0')
self.assertRegexp('add #foo foo', 'Quote #1 added') self.assertRegexp('add foo', 'Quote #1 added')
self.assertRegexp('stats #foo', '1') self.assertRegexp('stats', '1')
self.assertResponse('get #foo --id 1', '#1: foo') self.assertResponse('quotes search --id 1', '#1: foo')
self.assertResponse('get #foo 1', '#1: foo') self.assertResponse('quotes search 1', '#1: foo')
self.assertRegexp('add #foo bar','Quote #2 added') self.assertRegexp('add bar','Quote #2 added')
self.assertResponse('get #foo 2', '#2: bar') self.assertResponse('quotes search 2', '#2: bar')
self.assertResponse('get #foo --id 2', '#2: bar') self.assertResponse('quotes search --id 2', '#2: bar')
self.assertRegexp('add #foo baz','Quote #3 added') self.assertRegexp('add baz','Quote #3 added')
self.assertRegexp('stats #foo', '3') self.assertRegexp('stats', '3')
self.assertResponse('get #foo 3', '#3: baz') self.assertResponse('quotes search 3', '#3: baz')
self.assertRegexp('get #foo --regexp m/ba/', 'bar.*baz') self.assertRegexp('quotes search --regexp m/ba/', 'bar.*baz')
self.assertRegexp('get #foo --regexp ba', 'bar.*baz') self.assertRegexp('quotes search --regexp ba', 'bar.*baz')
self.assertRegexp('get #foo --with bar', '#2: bar') self.assertRegexp('quotes search --with bar', '#2: bar')
self.assertRegexp('get #foo bar', '#2: bar') self.assertRegexp('quotes search bar', '#2: bar')
self.assertNotError('info #foo 1') self.assertNotError('info 1')
self.assertNotError('random #foo') self.assertNotError('random')
self.assertError('remove #foo 4') self.assertError('remove 4')
self.assertError('info #foo 4') self.assertError('info 4')
self.assertNotError('get #foo 3') self.assertNotError('quotes search 3')
self.assertNotError('remove #foo 3') self.assertNotError('remove 3')
self.assertRegexp('stats #foo', '2') self.assertRegexp('stats', '2')
self.assertNotError('remove #foo 1') self.assertNotError('remove 1')
self.assertError('info #foo 3') self.assertError('info 3')
self.assertError('info #foo 1') self.assertError('info 1')
self.assertRegexp('random #foo', '#2') self.assertRegexp('random', '#2')
self.assertError('remove #foo 3') self.assertError('remove 3')
self.assertNotError('remove #foo 2') self.assertNotError('remove 2')
self.assertRegexp('stats #foo', '0') self.assertRegexp('stats', '0')
self.assertError('random #foo') self.assertError('random')