RFE 871111 : Added --nokey option

Fixed a bug in the remove command where lookup specific registry values were not being removed along with the lookup
Database abstraction
This commit is contained in:
Kevin Murphy 2004-09-20 06:15:52 +00:00
parent 50973a10fb
commit 4b0ed27fef

View File

@ -31,7 +31,12 @@
The Lookup plugin handles looking up various values by their key. The Lookup plugin handles looking up various values by their key.
""" """
import supybot
__revision__ = "$Id$" __revision__ = "$Id$"
__contributors__ = {
supybot.authors.skorobeus: ['--nokey parameter', 'database abstraction'],
}
import supybot.plugins as plugins import supybot.plugins as plugins
@ -42,6 +47,7 @@ import sets
import getopt import getopt
import string import string
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.privmsgs as privmsgs import supybot.privmsgs as privmsgs
@ -85,116 +91,58 @@ def configure(advanced):
continue continue
command = something('What would you like the command to be?') command = something('What would you like the command to be?')
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 \
responses?')
conf.registerGlobalValue(lookups.get(command), 'nokey',
registry.Boolean(nokeyVal, ''))
conf.registerPlugin('Lookup') conf.registerPlugin('Lookup')
conf.registerGroup(conf.supybot.plugins.Lookup, 'lookups') conf.registerGroup(conf.supybot.plugins.Lookup, 'lookups')
class LookupDB(plugins.DBHandler): class SqliteLookupDB(object):
def makeDb(self, filename): def __init__(self, filename):
return sqlite.connect(filename)
class Lookup(callbacks.Privmsg):
def __init__(self):
callbacks.Privmsg.__init__(self)
self.lookupDomains = sets.Set()
dataDir = conf.supybot.directories.data()
self.dbHandler = LookupDB(name=os.path.join(dataDir, 'Lookup'))
for (name, value) in registry._cache.iteritems():
name = name.lower()
if name.startswith('supybot.plugins.lookup.lookups.'):
name = name[len('supybot.plugins.lookup.lookups.'):]
if '.' in name:
continue
self.addRegistryValue(name, value)
group = conf.supybot.plugins.Lookup.lookups
for (name, value) in group.getValues(fullNames=False):
name = name.lower() # Just in case.
filename = value()
try: try:
self.addDatabase(name, filename) import sqlite
self.addCommand(name) except ImportError:
except Exception, e: raise callbacks.Error, 'You need to have PySQLite installed to '\
self.log.warning('Couldn\'t add lookup %s: %s', name, e) 'use this plugin. Download it at '\
'<http://pysqlite.sf.net/>'
def _shrink(self, s): self.filename = filename
return utils.ellipsisify(s, 50)
def die(self):
self.dbHandler.die()
def remove(self, irc, msg, args):
"""<name>
Removes the lookup for <name>.
"""
name = privmsgs.getArgs(args)
name = callbacks.canonicalName(name)
if name not in self.lookupDomains:
irc.error('That\'s not a valid lookup to remove.')
return
db = self.dbHandler.getDb()
cursor = db.cursor()
try: try:
cursor.execute("""DROP TABLE %s""" % name) self.db = sqlite.connect(self.filename)
db.commit() except sqlite.DatabaseError, e:
delattr(self.__class__, name) raise dbi.InvalidDBError, str(e)
irc.replySuccess()
except sqlite.DatabaseError:
irc.error('No such lookup exists.')
remove = privmsgs.checkCapability(remove, 'admin')
_splitRe = re.compile(r'(?<!\\):') def close(self):
def add(self, irc, msg, args): self.db.close()
"""<name> <filename>
Adds a lookup for <name> with the key/value pairs specified in the def getRecordCount(self, tableName):
colon-delimited file specified by <filename>. <filename> is searched cursor = self.db.cursor()
for in conf.supybot.directories.data. If <name> is not singular, we cursor.execute("""SELECT COUNT(*) FROM %s""" % tableName)
try to make it singular before creating the command. rows = int(cursor.fetchone()[0])
""" if rows == 0:
(name, filename) = privmsgs.getArgs(args, required=2) raise dbi.NoRecordError
name = utils.depluralize(name) return rows
name = callbacks.canonicalName(name)
if hasattr(self, name):
s = 'I already have a command in this plugin named %s' % name
irc.error(s)
return
db = self.dbHandler.getDb()
cursor = db.cursor()
try:
cursor.execute("""SELECT * FROM %s LIMIT 1""" % name)
self.addCommand(name)
except sqlite.DatabaseError:
try:
self.addDatabase(name, filename)
except EnvironmentError, e:
irc.error('Could not open %s: %s' % (filename, e.args[1]))
return
self.addCommand(name)
self.addRegistryValue(name, filename)
irc.replySuccess('Lookup %s added.' % name)
add = privmsgs.checkCapability(add, 'admin')
def addRegistryValue(self, name, filename): def checkLookup(self, name):
v = registry.String(filename, '') cursor = self.db.cursor()
conf.supybot.plugins.Lookup.lookups.register(name, v) sql = "SELECT name FROM sqlite_master \
WHERE type='table' \
AND name='%s'" % name
cursor.execute(sql)
if cursor.rowcount == 0:
return False
else:
return True
def addDatabase(self, name, filename): def addLookup(self, name, fd, splitRe):
db = self.dbHandler.getDb() cursor = self.db.cursor()
cursor = db.cursor()
dataDir = conf.supybot.directories.data()
filename = os.path.join(dataDir, filename)
fd = file(filename)
try:
cursor.execute("""SELECT COUNT(*) FROM %s""" % name)
except sqlite.DatabaseError:
cursor.execute("CREATE TABLE %s (key TEXT, value TEXT)" % name) cursor.execute("CREATE TABLE %s (key TEXT, value TEXT)" % name)
sql = "INSERT INTO %s VALUES (%%s, %%s)" % name sql = "INSERT INTO %s VALUES (%%s, %%s)" % name
for line in utils.nonCommentNonEmptyLines(fd): for line in utils.nonCommentNonEmptyLines(fd):
line = line.rstrip('\r\n') line = line.rstrip('\r\n')
try: try:
(key, value) = self._splitRe.split(line, 1) (key, value) = splitRe.split(line, 1)
key = key.replace('\\:', ':') key = key.replace('\\:', ':')
except ValueError: except ValueError:
cursor.execute("""DROP TABLE %s""" % name) cursor.execute("""DROP TABLE %s""" % name)
@ -202,42 +150,38 @@ class Lookup(callbacks.Privmsg):
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))
db.commit() self.db.commit()
def addCommand(self, name): def dropLookup(self, name):
def f(self, irc, msg, args): cursor = self.db.cursor()
args.insert(0, name) if self.checkLookup(name):
self._lookup(irc, msg, args) cursor.execute("""DROP TABLE %s""" % name)
db = self.dbHandler.getDb() self.db.commit()
cursor = db.cursor() else:
cursor.execute("""SELECT COUNT(*) FROM %s""" % name) raise dbi.NoRecordError
rows = int(cursor.fetchone()[0])
docstring = """[<key>]
If <key> is given, looks up <key> in the %s database. Otherwise, def getResults(self, name, key):
returns a random key: value pair from the database. There are cursor = self.db.cursor()
%s in the database. sql = """SELECT value FROM %s WHERE key LIKE %%s""" % name
""" % (name, utils.nItems(name, rows)) cursor.execute(sql, key)
f = utils.changeFunctionName(f, name, docstring) if cursor.rowcount == 0:
self.lookupDomains.add(name) raise dbi.NoRecordError
setattr(self.__class__, name, f) else:
return cursor.fetchall()
def getRandomResult(self, name, key):
cursor = self.db.cursor()
sql = """SELECT key, value FROM %s
ORDER BY random() LIMIT 1""" % name
cursor.execute(sql)
if cursor.rowcount == 0:
raise dbi.NoRecordError
else:
return cursor.fetchone()
_sqlTrans = string.maketrans('*?', '%_') _sqlTrans = string.maketrans('*?', '%_')
def search(self, irc, msg, args): def searchResults(self, name, options, globs, column):
"""[--{regexp}=<value>] [--values] <name> <glob> cursor = self.db.cursor()
Searches the domain <name> for lookups matching <glob>. If --regexp
is given, its associated value is taken as a regexp and matched
against the lookups. If --values is given, search the values rather
than the keys.
"""
column = 'key'
while '--values' in args:
column = 'value'
args.remove('--values')
(options, rest) = getopt.getopt(args, '', ['regexp='])
(name, globs) = privmsgs.getArgs(rest, optional=1)
db = self.dbHandler.getDb()
criteria = [] criteria = []
formats = [] formats = []
predicateName = 'p' predicateName = 'p'
@ -252,7 +196,7 @@ class Lookup(callbacks.Privmsg):
return return
def p(s, r=r): def p(s, r=r):
return int(bool(r.search(s))) return int(bool(r.search(s)))
db.create_function(predicateName, 1, p) self.db.create_function(predicateName, 1, p)
predicateName += 'p' predicateName += 'p'
for glob in globs.split(): for glob in globs.split():
if '?' not in glob and '*' not in glob: if '?' not in glob and '*' not in glob:
@ -263,16 +207,160 @@ class Lookup(callbacks.Privmsg):
raise callbacks.ArgumentError raise callbacks.ArgumentError
#print 'criteria: %s' % repr(criteria) #print 'criteria: %s' % repr(criteria)
#print 'formats: %s' % repr(formats) #print 'formats: %s' % repr(formats)
cursor = db.cursor()
sql = """SELECT key, value FROM %s WHERE %s""" % \ sql = """SELECT key, value FROM %s WHERE %s""" % \
(name, ' AND '.join(criteria)) (name, ' AND '.join(criteria))
#print 'sql: %s' % sql #print 'sql: %s' % sql
cursor.execute(sql, formats) cursor.execute(sql, formats)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.reply('No entries in %s matched that query.' % name) raise dbi.NoRecordError
else: else:
return cursor.fetchall()
LookupDB = plugins.DB('Lookup',
{'sqlite': SqliteLookupDB,
}
)
class Lookup(callbacks.Privmsg):
def __init__(self):
callbacks.Privmsg.__init__(self)
self.lookupDomains = sets.Set()
try:
self.db = LookupDB()
except Exception:
self.log.exception('Error loading %s:', self.filename)
raise # So it doesn't get loaded without its database.
for (name, value) in registry._cache.iteritems():
name = name.lower()
if name.startswith('supybot.plugins.lookup.lookups.'):
name = name[len('supybot.plugins.lookup.lookups.'):]
if '.' in name:
continue
self.addRegistryValue(name, value)
group = conf.supybot.plugins.Lookup.lookups
for (name, value) in group.getValues(fullNames=False):
name = name.lower() # Just in case.
filename = value()
try:
if not self.db.checkLookup(name):
self.addDatabase(name, filename)
self.addCommand(name)
except Exception, e:
self.log.warning('Couldn\'t add lookup %s: %s', name, e)
def _shrink(self, s):
return utils.ellipsisify(s, 50)
def die(self):
self.db.close()
def remove(self, irc, msg, args):
"""<name>
Removes the lookup for <name>.
"""
name = privmsgs.getArgs(args)
name = callbacks.canonicalName(name)
if name not in self.lookupDomains:
irc.error('That\'s not a valid lookup to remove.')
return
try:
self.db.dropLookup(name)
delattr(self.__class__, name)
self.delRegistryValues(name)
irc.replySuccess()
except dbi.NoRecordError:
irc.error('No such lookup exists.')
remove = privmsgs.checkCapability(remove, 'admin')
_splitRe = re.compile(r'(?<!\\):')
def add(self, irc, msg, args):
"""[--nokey] <name> <filename>
Adds a lookup for <name> with the key/value pairs specified in the
colon-delimited file specified by <filename>. <filename> is searched
for in conf.supybot.directories.data. If <name> is not singular, we
try to make it singular before creating the command. If the --nokey
option is specified, the new lookup will display only the value when
queried, and will omit the key from the response.
"""
opts = ['nokey']
(optlist, rest) = getopt.getopt(args, '', opts)
(name, filename) = privmsgs.getArgs(rest, required=2)
nokey = False
for (option, argument) in optlist:
option = option.lstrip('-')
if option == 'nokey':
nokey = True
#print 'nokey: %s' % nokey
name = utils.depluralize(name)
name = callbacks.canonicalName(name)
if hasattr(self, name):
s = 'I already have a command in this plugin named %s' % name
irc.error(s)
return
if not self.db.checkLookup(name):
try:
self.addDatabase(name, filename)
except EnvironmentError, e:
irc.error('Could not open %s: %s' % (filename, e.args[1]))
return
self.addCommand(name)
self.addRegistryValue(name, filename, nokey)
irc.replySuccess('Lookup %s added.' % name)
add = privmsgs.checkCapability(add, 'admin')
def addRegistryValue(self, name, filename, nokey = False):
group = conf.supybot.plugins.Lookup.lookups
conf.registerGlobalValue(group, name, registry.String(filename, ''))
#print 'nokey: %s' % nokey
conf.registerGlobalValue(group.get(name), 'nokey', registry.Boolean(nokey, ''))
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)
def addCommand(self, name):
def f(self, irc, msg, args):
args.insert(0, name)
self._lookup(irc, msg, args)
rows = self.db.getRecordCount(name)
docstring = """[<key>]
If <key> is given, looks up <key> in the %s database. Otherwise,
returns a random key: value pair from the database. There are
%s in the database.
""" % (name, utils.nItems(name, rows))
f = utils.changeFunctionName(f, name, docstring)
self.lookupDomains.add(name)
setattr(self.__class__, name, f)
def search(self, irc, msg, args):
"""[--{regexp}=<value>] [--values] <name> <glob>
Searches the domain <name> for lookups matching <glob>. If --regexp
is given, its associated value is taken as a regexp and matched
against the lookups. If --values is given, search the values rather
than the keys.
"""
column = 'key'
while '--values' in args:
column = 'value'
args.remove('--values')
(options, rest) = getopt.getopt(args, '', ['regexp='])
(name, globs) = privmsgs.getArgs(rest, optional=1)
try:
results = self.db.searchResults(name, options, globs, column)
except dbi.NoRecordError:
irc.reply('No entries in %s matched that query.' % name)
lookups = ['%s: %s' % (item[0], self._shrink(item[1])) lookups = ['%s: %s' % (item[0], self._shrink(item[1]))
for item in cursor.fetchall()] for item in results]
irc.reply(utils.commaAndify(lookups)) irc.reply(utils.commaAndify(lookups))
def _lookup(self, irc, msg, args): def _lookup(self, irc, msg, args):
@ -281,39 +369,29 @@ class Lookup(callbacks.Privmsg):
Looks up the value of <key> in the domain <name>. Looks up the value of <key> in the domain <name>.
""" """
(name, key) = privmsgs.getArgs(args, optional=1) (name, key) = privmsgs.getArgs(args, optional=1)
db = self.dbHandler.getDb() if self.db.checkLookup(name):
cursor = db.cursor() results = []
if key: if key:
sql = """SELECT value FROM %s WHERE key LIKE %%s""" % name
try: try:
cursor.execute(sql, key) results = self.db.getResults(name, key)
except sqlite.DatabaseError, e: except dbi.NoRecordError:
if 'no such table' in str(e):
irc.error('I don\'t have a domain %s' % name)
else:
irc.error(str(e))
return
if cursor.rowcount == 0:
irc.error('I couldn\'t find %s in %s.' % (key, name)) irc.error('I couldn\'t find %s in %s.' % (key, name))
elif cursor.rowcount == 1: return
irc.reply(cursor.fetchone()[0]) if len(results) == 1:
irc.reply(results[0][0])
else: else:
values = [t[0] for t in cursor.fetchall()] values = [t[0] for t in results]
irc.reply('%s could be %s' % (key, ', or '.join(values))) irc.reply('%s could be %s' % (key, ', or '.join(values)))
else: else:
sql = """SELECT key, value FROM %s (key, value) = self.db.getRandomResult(name, key)
ORDER BY random() LIMIT 1""" % name nokeyRegKey = 'lookups.%s.nokey' % name
try: if not self.registryValue(nokeyRegKey):
cursor.execute(sql)
except sqlite.DatabaseError, e:
if 'no such table' in str(e):
irc.error('I don\'t have a domain %r' % name)
else:
irc.error(str(e))
return
(key, value) = cursor.fetchone()
irc.reply('%s: %s' % (key, value)) irc.reply('%s: %s' % (key, value))
else:
irc.reply('%s' % value)
else:
irc.error('I don\'t have a domain %s' % name)
return
Class = Lookup Class = Lookup