Moved Configurable out to its own module.

This commit is contained in:
Jeremy Fincher 2003-12-03 23:14:09 +00:00
parent 6925ecf760
commit 2aaca31709
18 changed files with 314 additions and 251 deletions

View File

@ -52,6 +52,7 @@ import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import structures import structures
import configurable
dbfilename = os.path.join(conf.dataDir, 'Bugzilla.db') dbfilename = os.path.join(conf.dataDir, 'Bugzilla.db')
def makeDb(filename): def makeDb(filename):
@ -83,18 +84,18 @@ def configure(onStart, afterConnect, advanced):
replyNoBugzilla = 'I don\'t have a bugzilla %r' replyNoBugzilla = 'I don\'t have a bugzilla %r'
class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): class Bugzilla(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
"""Show a link to a bug report with a brief description""" """Show a link to a bug report with a brief description"""
threaded = True threaded = True
regexps = ['bzSnarfer'] regexps = ['bzSnarfer']
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('bug-snarfer', plugins.ConfigurableBoolType, False, [('bug-snarfer', configurable.BoolType, False,
"""Determines whether the bug snarfer will be enabled, such that any """Determines whether the bug snarfer will be enabled, such that any
Bugzilla URLs seen in the channel will have their information reported Bugzilla URLs seen in the channel will have their information reported
into the channel.""")] into the channel.""")]
) )
def __init__(self): def __init__(self):
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
callbacks.PrivmsgCommandAndRegexp.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self)
self.entre = re.compile('&(\S*?);') self.entre = re.compile('&(\S*?);')
# Schema: {name, [url, description]} # Schema: {name, [url, description]}
@ -102,7 +103,7 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
self.shorthand = utils.abbrev(self.db.keys()) self.shorthand = utils.abbrev(self.db.keys())
def die(self): def die(self):
plugins.Configurable.die(self) configurable.Mixin.die(self)
self.db.close() self.db.close()
del self.db del self.db

View File

@ -57,6 +57,7 @@ import plugins
import privmsgs import privmsgs
import ircutils import ircutils
import callbacks import callbacks
import configurable
smileys = (':)', ';)', ':]', ':-)', ':-D', ':D', ':P', ':p', '(=', '=)') smileys = (':)', ';)', ':]', ':-)', ':-D', ':D', ':P', ':p', '(=', '=)')
frowns = (':|', ':-/', ':-\\', ':\\', ':/', ':(', ':-(', ':\'(') frowns = (':|', ':-/', ':-\\', ':\\', ':/', ':(', ':-(', ':\'(')
@ -65,21 +66,21 @@ smileyre = re.compile('|'.join(imap(re.escape, smileys)))
frownre = re.compile('|'.join(imap(re.escape, frowns))) frownre = re.compile('|'.join(imap(re.escape, frowns)))
class ChannelDB(plugins.ChannelDBHandler, class ChannelDB(plugins.ChannelDBHandler,
plugins.Configurable, configurable.Mixin,
callbacks.Privmsg): callbacks.Privmsg):
noIgnore = True noIgnore = True
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('self-stats', plugins.ConfigurableBoolType, True, [('self-stats', configurable.BoolType, True,
"""Determines whether the bot will keep channel statistics on itself, """Determines whether the bot will keep channel statistics on itself,
possibly skewing the channel stats (especially in cases where he's possibly skewing the channel stats (especially in cases where he's
relaying between channels on a network."""), relaying between channels on a network."""),
('wordstats-top-n', plugins.ConfigurableIntType, 3, ('wordstats-top-n', configurable.IntType, 3,
"""Determines the maximum number of top users to show for a given """Determines the maximum number of top users to show for a given
wordstat when you request the wordstats for a particular word.""")] wordstat when you request the wordstats for a particular word.""")]
) )
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
plugins.ChannelDBHandler.__init__(self) plugins.ChannelDBHandler.__init__(self)
self.lastmsg = None self.lastmsg = None
self.laststate = None self.laststate = None
@ -87,7 +88,7 @@ class ChannelDB(plugins.ChannelDBHandler,
def die(self): def die(self):
callbacks.Privmsg.die(self) callbacks.Privmsg.die(self)
plugins.Configurable.die(self) configurable.Mixin.die(self)
plugins.ChannelDBHandler.die(self) plugins.ChannelDBHandler.die(self)
def makeDb(self, filename): def makeDb(self, filename):

View File

@ -53,6 +53,7 @@ import conf
import utils import utils
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
@ -82,7 +83,7 @@ def configure(onStart, afterConnect, advanced):
onStart.append('disable file') onStart.append('disable file')
class Debian(callbacks.Privmsg, class Debian(callbacks.Privmsg,
plugins.Configurable, configurable.Mixin,
plugins.PeriodicFileDownloader): plugins.PeriodicFileDownloader):
threaded = True threaded = True
periodicFiles = { periodicFiles = {
@ -93,8 +94,8 @@ class Debian(callbacks.Privmsg,
604800, None) 604800, None)
} }
contents = os.path.join(conf.dataDir, 'Contents-i386.gz') contents = os.path.join(conf.dataDir, 'Contents-i386.gz')
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('python-zegrep', plugins.ConfigurableBoolType, False, [('python-zegrep', configurable.BoolType, False,
"""An advanced option, mostly just for testing; uses a Python-coded """An advanced option, mostly just for testing; uses a Python-coded
zegrep rather than the actual zegrep executable, generally resulting zegrep rather than the actual zegrep executable, generally resulting
in a 50x slowdown. What would take 2 seconds will take 100 with this in a 50x slowdown. What would take 2 seconds will take 100 with this
@ -102,12 +103,12 @@ class Debian(callbacks.Privmsg,
) )
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
plugins.PeriodicFileDownloader.__init__(self) plugins.PeriodicFileDownloader.__init__(self)
def die(self): def die(self):
callbacks.Privmsg.die(self) callbacks.Privmsg.die(self)
plugins.Configurable.die(self) configurable.Mixin.die(self)
def file(self, irc, msg, args): def file(self, irc, msg, args):
"""[--{regexp,exact}=<value>] [<glob>] """[--{regexp,exact}=<value>] [<glob>]

View File

@ -49,6 +49,7 @@ import plugins
import ircutils import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
@ -64,20 +65,20 @@ def configure(onStart, afterConnect, advanced):
onStart.append('dict config server %s' % server) onStart.append('dict config server %s' % server)
replyTimeout = 'Timeout on the dictd server.' replyTimeout = 'Timeout on the dictd server.'
class Dict(callbacks.Privmsg, plugins.Configurable): class Dict(callbacks.Privmsg, configurable.Mixin):
threaded = True threaded = True
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('server', plugins.ConfigurableStrType, 'dict.org', [('server', configurable.StrType, 'dict.org',
"""Determines what server the bot will connect to to receive """Determines what server the bot will connect to to receive
definitions from."""),] definitions from."""),]
) )
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
def die(self): def die(self):
callbacks.Privmsg.die(self) callbacks.Privmsg.die(self)
plugins.Configurable.die(self) configurable.Mixin.die(self)
def dictionaries(self, irc, msg, args): def dictionaries(self, irc, msg, args):
"""takes no arguments. """takes no arguments.

View File

@ -48,6 +48,7 @@ import plugins
import ircutils import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
@ -67,24 +68,24 @@ def configure(onStart, afterConnect, advanced):
class EbayError(callbacks.Error): class EbayError(callbacks.Error):
pass pass
class Ebay(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): class Ebay(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
""" """
Module for eBay stuff. Currently contains a URL snarfer and a command to Module for eBay stuff. Currently contains a URL snarfer and a command to
get info about an auction. get info about an auction.
""" """
threaded = True threaded = True
regexps = ['ebaySnarfer'] regexps = ['ebaySnarfer']
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('auction-snarfer', plugins.ConfigurableBoolType, False, [('auction-snarfer', configurable.BoolType, False,
"""Determines whether the bot will automatically 'snarf' Ebay auction """Determines whether the bot will automatically 'snarf' Ebay auction
URLs and print information about them.""")] URLs and print information about them.""")]
) )
def __init__(self): def __init__(self):
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
callbacks.PrivmsgCommandAndRegexp.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self)
def die(self): def die(self):
plugins.Configurable.die(self) configurable.Mixin.die(self)
callbacks.PrivmsgCommandAndRegexp.die(self) callbacks.PrivmsgCommandAndRegexp.die(self)
_reopts = re.I | re.S _reopts = re.I | re.S

View File

@ -45,6 +45,7 @@ import plugins
import privmsgs import privmsgs
import ircutils import ircutils
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
from questions import expect, anything, something, yn from questions import expect, anything, something, yn
@ -61,18 +62,18 @@ def configure(onStart, afterConnect, advanced):
# Enforcer: Enforces capabilities on JOIN, MODE, KICK, etc. # Enforcer: Enforces capabilities on JOIN, MODE, KICK, etc.
### ###
_chanCap = ircdb.makeChannelCapability _chanCap = ircdb.makeChannelCapability
class Enforcer(callbacks.Privmsg, plugins.Configurable): class Enforcer(callbacks.Privmsg, configurable.Mixin):
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('auto-op', plugins.ConfigurableBoolType, False, [('auto-op', configurable.BoolType, False,
"""Determines whether the bot will automatically op people with """Determines whether the bot will automatically op people with
the <channel>.op capability when they join the channel."""), the <channel>.op capability when they join the channel."""),
('auto-voice', plugins.ConfigurableBoolType, False, ('auto-voice', configurable.BoolType, False,
"""Determines whether the bot will automatically voice people with """Determines whether the bot will automatically voice people with
the <channel>.voice capability when they join the channel."""), the <channel>.voice capability when they join the channel."""),
('auto-halfop', plugins.ConfigurableBoolType, False, ('auto-halfop', configurable.BoolType, False,
"""Determines whether the bot will automatically halfop people with """Determines whether the bot will automatically halfop people with
the <channel>.halfop capability when they join the channel."""), the <channel>.halfop capability when they join the channel."""),
('revenge', plugins.ConfigurableBoolType, False, ('revenge', configurable.BoolType, False,
"""Determines whether the bot will take revenge on people who do """Determines whether the bot will take revenge on people who do
things it doesn't like (somewhat like 'bitch mode' in other IRC things it doesn't like (somewhat like 'bitch mode' in other IRC
bots)."""),] bots)."""),]
@ -80,11 +81,11 @@ class Enforcer(callbacks.Privmsg, plugins.Configurable):
started = False started = False
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
def die(self): def die(self):
callbacks.Privmsg.die(self) callbacks.Privmsg.die(self)
plugins.Configurable.die(self) configurable.Mixin.die(self)
def start(self, irc, msg, args): def start(self, irc, msg, args):
"""[<CHANSERV>] """[<CHANSERV>]

View File

@ -55,6 +55,7 @@ import ircmsgs
import ircutils import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
tableCreateStatements = { tableCreateStatements = {
'larts': ("""CREATE TABLE larts ( 'larts': ("""CREATE TABLE larts (
@ -79,26 +80,26 @@ tableCreateStatements = {
)""",), )""",),
} }
class FunDB(callbacks.Privmsg, plugins.Configurable, plugins.ChannelDBHandler): class FunDB(callbacks.Privmsg, configurable.Mixin, plugins.ChannelDBHandler):
""" """
Contains the 'fun' commands that require a database. Currently includes Contains the 'fun' commands that require a database. Currently includes
database-backed commands for crossword puzzle solving, anagram searching, database-backed commands for crossword puzzle solving, anagram searching,
larting, praising, excusing, and insulting. larting, praising, excusing, and insulting.
""" """
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('show-ids', plugins.ConfigurableBoolType, False, [('show-ids', configurable.BoolType, False,
"""Determines whether the bot will show the id of an """Determines whether the bot will show the id of an
excuse/insult/praise/lart.""")] excuse/insult/praise/lart.""")]
) )
_tables = sets.Set(['lart', 'insult', 'excuse', 'praise']) _tables = sets.Set(['lart', 'insult', 'excuse', 'praise'])
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
plugins.ChannelDBHandler.__init__(self) plugins.ChannelDBHandler.__init__(self)
def die(self): def die(self):
callbacks.Privmsg.die(self) callbacks.Privmsg.die(self)
plugins.Configurable.die(self) configurable.Mixin.die(self)
plugins.ChannelDBHandler.die(self) plugins.ChannelDBHandler.die(self)
def makeDb(self, dbfilename, replace=False): def makeDb(self, dbfilename, replace=False):

View File

@ -47,6 +47,7 @@ import plugins
import ircutils import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
@ -69,15 +70,15 @@ def configure(onStart, afterConnect, advanced):
onStart.append('Gameknot toggle stat off') onStart.append('Gameknot toggle stat off')
class Gameknot(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): class Gameknot(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
threaded = True threaded = True
regexps = ['gameknotSnarfer', 'gameknotStatsSnarfer'] regexps = ['gameknotSnarfer', 'gameknotStatsSnarfer']
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('game-snarfer', plugins.ConfigurableBoolType, True, [('game-snarfer', configurable.BoolType, True,
"""Determines whether the game URL snarfer is active; if so, the bot """Determines whether the game URL snarfer is active; if so, the bot
will reply to the channel with a summary of the game data when it will reply to the channel with a summary of the game data when it
sees a Gameknot game on the channel."""), sees a Gameknot game on the channel."""),
('stats-snarfer', plugins.ConfigurableBoolType, True, ('stats-snarfer', configurable.BoolType, True,
"""Determines whether the stats URL snarfer is active; if so, the bot """Determines whether the stats URL snarfer is active; if so, the bot
will reply to the channel with a summary of the stats of any player will reply to the channel with a summary of the stats of any player
whose stats URL is seen on the channel.""")] whose stats URL is seen on the channel.""")]
@ -89,11 +90,11 @@ class Gameknot(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
_gkteam = re.compile(r'Team:(<.*?>)+(?P<name>.*?)</span>') _gkteam = re.compile(r'Team:(<.*?>)+(?P<name>.*?)</span>')
_gkseen = re.compile(r'(seen on GK:\s+([^[]+ago)|.*?is hiding.*?)') _gkseen = re.compile(r'(seen on GK:\s+([^[]+ago)|.*?is hiding.*?)')
def __init__(self): def __init__(self):
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
callbacks.PrivmsgCommandAndRegexp.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self)
def die(self): def die(self):
plugins.Configurable.die(self) configurable.Mixin.die(self)
callbacks.PrivmsgCommandAndRegexp.die(self) callbacks.PrivmsgCommandAndRegexp.die(self)
def getStats(self, name): def getStats(self, name):

View File

@ -55,6 +55,7 @@ import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import structures import structures
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
from questions import expect, anything, something, yn from questions import expect, anything, something, yn
@ -125,28 +126,28 @@ def search(log, *args, **kwargs):
log.exception('Uncaught SOAP error:') log.exception('Uncaught SOAP error:')
raise callbacks.Error, 'Invalid Google license key.' raise callbacks.Error, 'Invalid Google license key.'
class Google(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): class Google(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
threaded = True threaded = True
regexps = sets.Set(['googleSnarfer', 'googleGroups']) regexps = sets.Set(['googleSnarfer', 'googleGroups'])
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('groups-snarfer', plugins.ConfigurableBoolType, False, [('groups-snarfer', configurable.BoolType, False,
"""Determines whether the groups snarfer is enabled. If so, URLs at """Determines whether the groups snarfer is enabled. If so, URLs at
groups.google.com will be snarfed and their group/title messaged to groups.google.com will be snarfed and their group/title messaged to
the channel."""), the channel."""),
('search-snarfer', plugins.ConfigurableBoolType, False, ('search-snarfer', configurable.BoolType, False,
"""Determines whether the search snarfer is enabled. If so, messages """Determines whether the search snarfer is enabled. If so, messages
(even unaddressed ones) beginning with the word 'google' will result (even unaddressed ones) beginning with the word 'google' will result
in the first URL Google returns being sent to the channel.""")] in the first URL Google returns being sent to the channel.""")]
) )
def __init__(self): def __init__(self):
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
callbacks.PrivmsgCommandAndRegexp.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self)
self.total = 0 self.total = 0
self.totalTime = 0 self.totalTime = 0
self.last24hours = structures.queue() self.last24hours = structures.queue()
def die(self): def die(self):
plugins.Configurable.die(self) configurable.Mixin.die(self)
callbacks.PrivmsgCommandAndRegexp.die(self) callbacks.PrivmsgCommandAndRegexp.die(self)
def formatData(self, data): def formatData(self, data):

View File

@ -46,6 +46,7 @@ import utils
import plugins import plugins
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
@ -57,23 +58,23 @@ def configure(onStart, afterConnect, advanced):
onStart.append('load Karma') onStart.append('load Karma')
class Karma(callbacks.PrivmsgCommandAndRegexp, class Karma(callbacks.PrivmsgCommandAndRegexp,
plugins.Configurable, configurable.Mixin,
plugins.ChannelDBHandler): plugins.ChannelDBHandler):
addressedRegexps = ['increaseKarma', 'decreaseKarma'] addressedRegexps = ['increaseKarma', 'decreaseKarma']
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('simple-output', plugins.ConfigurableBoolType, False, [('simple-output', configurable.BoolType, False,
"""Determines whether the bot will output shorter versions of the """Determines whether the bot will output shorter versions of the
karma output when requesting a single thing's karma. (example: 'foo: karma output when requesting a single thing's karma. (example: 'foo:
1')""")] 1')""")]
) )
def __init__(self): def __init__(self):
callbacks.PrivmsgCommandAndRegexp.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self)
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
plugins.ChannelDBHandler.__init__(self) plugins.ChannelDBHandler.__init__(self)
def die(self): def die(self):
callbacks.PrivmsgCommandAndRegexp.die(self) callbacks.PrivmsgCommandAndRegexp.die(self)
plugins.Configurable.die(self) configurable.Mixin.die(self)
plugins.ChannelDBHandler.die(self) plugins.ChannelDBHandler.die(self)
def makeDb(self, filename): def makeDb(self, filename):

View File

@ -57,6 +57,7 @@ import webutils
import ircutils import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
L = [os.__file__] L = [os.__file__]
if hasattr(math, '__file__'): if hasattr(math, '__file__'):
@ -75,22 +76,22 @@ def configure(onStart, afterConnect, advanced):
Would you like to enable this snarfer?""") == 'y': Would you like to enable this snarfer?""") == 'y':
onStart.append('python config aspn-snarfer on') onStart.append('python config aspn-snarfer on')
class Python(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): class Python(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
modulechars = '%s%s%s' % (string.ascii_letters, string.digits, '_.') modulechars = '%s%s%s' % (string.ascii_letters, string.digits, '_.')
regexps = ['aspnRecipes'] regexps = ['aspnRecipes']
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('aspn-snarfer', plugins.ConfigurableBoolType, False, [('aspn-snarfer', configurable.BoolType, False,
"""Determines whether the ASPN Python recipe snarfer is enabled. If """Determines whether the ASPN Python recipe snarfer is enabled. If
so, it will message the channel with the name of the recipe when it so, it will message the channel with the name of the recipe when it
sees an ASPN Python recipe link on the channel.""")] sees an ASPN Python recipe link on the channel.""")]
) )
def __init__(self): def __init__(self):
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
callbacks.PrivmsgCommandAndRegexp.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self)
def die(self): def die(self):
plugins.Configurable.die(self) configurable.Mixin.die(self)
callbacks.PrivmsgCommandAndRegexp.die(self) callbacks.PrivmsgCommandAndRegexp.die(self)
def pydoc(self, irc, msg, args): def pydoc(self, irc, msg, args):

View File

@ -54,6 +54,7 @@ import plugins
import ircutils import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
@ -69,10 +70,10 @@ minRandomLength = 8
minRandomWords = 3 minRandomWords = 3
class QuoteGrabs(plugins.ChannelDBHandler, class QuoteGrabs(plugins.ChannelDBHandler,
plugins.Configurable, configurable.Mixin,
callbacks.Privmsg): callbacks.Privmsg):
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('random-grabber', plugins.ConfigurableBoolType, False, [('random-grabber', configurable.BoolType, False,
"""Determines whether the bot will randomly grab possibly-suitable """Determines whether the bot will randomly grab possibly-suitable
quotes for someone."""),] quotes for someone."""),]
) )

View File

@ -51,6 +51,7 @@ import ircmsgs
import ircutils import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
import socket import socket
@ -107,17 +108,17 @@ def reload(x=None):
else: else:
(ircs, ircstates, lastmsg, channels, abbreviations, originalIrc) = x (ircs, ircstates, lastmsg, channels, abbreviations, originalIrc) = x
class Relay(callbacks.Privmsg, plugins.Configurable): class Relay(callbacks.Privmsg, configurable.Mixin):
noIgnore = True noIgnore = True
priority = sys.maxint priority = sys.maxint
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('color', plugins.ConfigurableBoolType, True, [('color', configurable.BoolType, True,
"""Determines whether the bot will color relayed PRIVMSGs so as to """Determines whether the bot will color relayed PRIVMSGs so as to
make the messages easier to read."""),] make the messages easier to read."""),]
) )
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
self.ircs = ircs self.ircs = ircs
self._color = 0 self._color = 0
self._whois = {} self._whois = {}
@ -139,7 +140,7 @@ class Relay(callbacks.Privmsg, plugins.Configurable):
def die(self): def die(self):
callbacks.Privmsg.die(self) callbacks.Privmsg.die(self)
plugins.Configurable.die(self) configurable.Mixin.die(self)
for irc in self.abbreviations: for irc in self.abbreviations:
if irc != originalIrc: if irc != originalIrc:
irc.callbacks[:] = [] irc.callbacks[:] = []

View File

@ -47,6 +47,7 @@ import ircutils
import privmsgs import privmsgs
import webutils import webutils
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
@ -94,7 +95,7 @@ def configure(onStart, afterConnect, advanced):
class TrackerError(Exception): class TrackerError(Exception):
pass pass
class Sourceforge(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): class Sourceforge(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
""" """
Module for Sourceforge stuff. Currently contains commands to query a Module for Sourceforge stuff. Currently contains commands to query a
project's most recent bugs and rfes. project's most recent bugs and rfes.
@ -114,21 +115,21 @@ class Sourceforge(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
_status = re.compile(r'<b>(Status):</b> <a.+?<br>(.+?)</td>', _reopts) _status = re.compile(r'<b>(Status):</b> <a.+?<br>(.+?)</td>', _reopts)
_res =(_resolution, _assigned, _submitted, _priority, _status) _res =(_resolution, _assigned, _submitted, _priority, _status)
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('tracker-snarfer', plugins.ConfigurableBoolType, False, [('tracker-snarfer', configurable.BoolType, False,
"""Determines whether the bot will reply to SF.net Tracker URLs in """Determines whether the bot will reply to SF.net Tracker URLs in
the channel with a nice summary of the tracker item."""), the channel with a nice summary of the tracker item."""),
('default-project', plugins.ConfigurableStrType, '', ('default-project', configurable.StrType, '',
"""Sets the default project (used by the bugs/rfes commands in the """Sets the default project (used by the bugs/rfes commands in the
case that no explicit project is given).""")] case that no explicit project is given).""")]
) )
_projectURL = 'http://sourceforge.net/projects/' _projectURL = 'http://sourceforge.net/projects/'
def __init__(self): def __init__(self):
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
callbacks.PrivmsgCommandAndRegexp.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self)
def die(self): def die(self):
plugins.Configurable.die(self) configurable.Mixin.die(self)
callbacks.PrivmsgCommandAndRegexp.die(self) callbacks.PrivmsgCommandAndRegexp.die(self)
def _formatResp(self, text, num=''): def _formatResp(self, text, num=''):

View File

@ -47,25 +47,26 @@ import ircmsgs
import plugins import plugins
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
def ConfigurableTopicSeparator(s): def ConfigurableTopicSeparator(s):
s = plugins.ConfigurableStrType(s) s = configurable.StrType(s)
if s.lstrip() == s: if s.lstrip() == s:
s = ' ' + s s = ' ' + s
if s.rstrip() == s: if s.rstrip() == s:
s += ' ' s += ' '
return s return s
class Topic(callbacks.Privmsg, plugins.Configurable): class Topic(callbacks.Privmsg, configurable.Mixin):
topicFormatter = '%s (%s)' topicFormatter = '%s (%s)'
topicUnformatter = re.compile('(.*) \((\S+)\)') topicUnformatter = re.compile('(.*) \((\S+)\)')
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('separator', plugins.ConfigurableStrType, ' || ', [('separator', configurable.StrType, ' || ',
"The separator between individual topics in the channel topic.")] "The separator between individual topics in the channel topic.")]
) )
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
def _splitTopic(self, topic, channel): def _splitTopic(self, topic, channel):
separator = self.configurables.get('separator', channel) separator = self.configurables.get('separator', channel)

View File

@ -54,6 +54,7 @@ import ircmsgs
import ircutils import ircutils
import privmsgs import privmsgs
import callbacks import callbacks
import configurable
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
# This will be called by setup.py to configure this module. onStart and # This will be called by setup.py to configure this module. onStart and
@ -82,26 +83,26 @@ def configure(onStart, afterConnect, advanced):
length that will trigger this snarfer?""") length that will trigger this snarfer?""")
class URL(callbacks.PrivmsgCommandAndRegexp, class URL(callbacks.PrivmsgCommandAndRegexp,
plugins.Configurable, configurable.Mixin,
plugins.ChannelDBHandler): plugins.ChannelDBHandler):
regexps = ['tinyurlSnarfer'] regexps = ['tinyurlSnarfer']
configurables = plugins.ConfigurableDictionary( configurables = configurable.Dictionary(
[('tinyurl-snarfer', plugins.ConfigurableBoolType, False, [('tinyurl-snarfer', configurable.BoolType, False,
"""Determines whether the bot will output shorter versions of URLs """Determines whether the bot will output shorter versions of URLs
longer than the tinyurl-minimum-length config variable."""), longer than the tinyurl-minimum-length config variable."""),
('tinyurl-minimum-length', plugins.ConfigurableIntType, 46, ('tinyurl-minimum-length', configurable.IntType, 46,
"""The minimum length a URL must be before the tinyurl-snarfer will """The minimum length a URL must be before the tinyurl-snarfer will
snarf it and offer a tinyurl replacement."""),] snarf it and offer a tinyurl replacement."""),]
) )
def __init__(self): def __init__(self):
self.nextMsgs = {} self.nextMsgs = {}
callbacks.PrivmsgCommandAndRegexp.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self)
plugins.Configurable.__init__(self) configurable.Mixin.__init__(self)
plugins.ChannelDBHandler.__init__(self) plugins.ChannelDBHandler.__init__(self)
def die(self): def die(self):
callbacks.PrivmsgCommandAndRegexp.die(self) callbacks.PrivmsgCommandAndRegexp.die(self)
plugins.Configurable.die(self) configurable.Mixin.die(self)
plugins.ChannelDBHandler.die(self) plugins.ChannelDBHandler.die(self)
def makeDb(self, filename): def makeDb(self, filename):

212
src/configurable.py Normal file
View File

@ -0,0 +1,212 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
__revision__ = "$Id$"
import os
import conf
import utils
import ircdb
import ircutils
import privmsgs
import callbacks
class Dictionary(object):
"""This is a dictionary to handle configuration for individual channels,
including a default configuration for channels that haven't modified their
configuration from the default.
"""
def __init__(self, seq):
self.helps = {}
self.types = {}
self.defaults = {}
self.originalNames = {}
self.unparsedValues = {}
self.channels = ircutils.IrcDict()
for (name, type, default, help) in seq:
if ',' in name:
raise ValueError, 'There can be no commas in the name.'
original = name
name = callbacks.canonicalName(name)
self.originalNames[name] = original
self.helps[name] = utils.normalizeWhitespace(help)
self.types[name] = type
self.defaults[name] = default
def get(self, name, channel=None):
name = callbacks.canonicalName(name)
if channel is not None:
try:
return self.channels[channel][name]
except KeyError:
return self.defaults[name]
else:
return self.defaults[name]
def set(self, name, value, channel=None):
name = callbacks.canonicalName(name)
if name not in self.originalNames:
raise KeyError, name
if ',' in name:
raise ValueError, 'There can be no commas in the name.'
self.unparsedValues[(channel, name)] = value
if channel is not None:
d = self.channels.setdefault(channel, {})
d[name] = self.types[name](value)
else:
self.defaults[name] = self.types[name](value)
def help(self, name):
return self.helps[callbacks.canonicalName(name)]
def names(self):
L = self.originalNames.values()
L.sort()
return L
class Error(TypeError):
pass
def BoolType(s):
s = s.lower()
if s in ('true', 'enable', 'on'):
return True
elif s in ('false', 'disable', 'off'):
return False
else:
s = 'Value must be one of on/off/true/false/enable/disable.'
raise Error, s
def StrType(s):
if s and s[0] not in '\'"' and s[-1] not in '\'"':
s = repr(s)
try:
v = utils.safeEval(s)
if type(v) is not str:
raise ValueError
except ValueError: # This catches the utils.safeEval(s) errors too.
raise Error, 'Value must be a string.'
return v
def IntType(s):
try:
return int(s)
except ValueError:
raise Error, 'Value must be an int.'
class Mixin(object):
"""A mixin class to provide a "config" command that can be consistent
across all plugins, in order to unify the configuration for each plugin.
Plugins subclassing this should have a "configurables" attribute which is
a ConfigurableDictionary initialized with a list of 4-tuples of
(name, type, default, help). Name is the string name of the config
variable; type is a function taking a string and returning some value of
the type the variable is supposed to be; default is the default value the
variable should take on; help is a string that'll be returned to describe
the purpose of the config variable.
"""
def __init__(self):
className = self.__class__.__name__
self.filename = os.path.join(conf.confDir, '%s-configurable'%className)
if os.path.exists(self.filename):
fd = file(self.filename)
for line in fd:
line = line.rstrip()
(channel, name, value) = line.split(',', 2)
if channel == 'default':
channel = None
try:
# The eval here is to turn from "'foo'" to 'foo'.
self.configurables.set(name, eval(value), channel)
except Error, e:
s = 'Couldn\'t read configurable from file: %s'
self.log.warning(s, e)
except KeyError, e:
s = 'Configurable variable %s doesn\'t exist anymore.'
self.log.warning(s, name)
def die(self):
fd = file(self.filename, 'w')
L = self.configurables.unparsedValues.items()
L.sort()
for ((channel, name), value) in L:
if channel is None:
channel = 'default'
name = self.configurables.originalNames[name]
fd.write('%s,%s,%r\n' % (channel, name, value))
fd.close()
def config(self, irc, msg, args):
"""[<channel>] [<name>] [<value>]
Sets the value of config variable <name> to <value> on <channel>. If
<name> is given but <value> is not, returns the help and current value
for <name>. If neither <name> nor <value> is given, returns the valid
config variables for this plugin. <channel> is only necessary if the
message isn't sent in the channel itself.
"""
try:
channel = privmsgs.getChannel(msg, args)
capability = ircdb.makeChannelCapability(channel, 'op')
except callbacks.ArgumentError:
raise
except callbacks.Error:
channel = None
capability = 'admin'
if not ircdb.checkCapability(msg.prefix, capability):
irc.error(msg, conf.replyNoCapability % capability)
return
(name, value) = privmsgs.getArgs(args, required=0, optional=2)
if not name:
irc.reply(msg, utils.commaAndify(self.configurables.names()))
return
try:
if not value:
help = self.configurables.help(name)
value = self.configurables.get(name, channel=channel)
s = '%s: %s (Current value: %r)' % (name, help, value)
irc.reply(msg, s)
return
try:
self.configurables.set(name, value, channel)
irc.reply(msg, conf.replySuccess)
except Error, e:
irc.error(msg, str(e))
except KeyError:
irc.error(msg, 'There is no config variable %r' % name)
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -241,171 +241,6 @@ class PeriodicFileDownloader(object):
world.threadsSpawned += 1 world.threadsSpawned += 1
class ConfigurableDictionary(object):
"""This is a dictionary to handle configuration for individual channels,
including a default configuration for channels that haven't modified their
configuration from the default.
"""
def __init__(self, seq):
self.helps = {}
self.types = {}
self.defaults = {}
self.originalNames = {}
self.unparsedValues = {}
self.channels = ircutils.IrcDict()
for (name, type, default, help) in seq:
if ',' in name:
raise ValueError, 'There can be no commas in the name.'
original = name
name = callbacks.canonicalName(name)
self.originalNames[name] = original
self.helps[name] = utils.normalizeWhitespace(help)
self.types[name] = type
self.defaults[name] = default
def get(self, name, channel=None):
name = callbacks.canonicalName(name)
if channel is not None:
try:
return self.channels[channel][name]
except KeyError:
return self.defaults[name]
else:
return self.defaults[name]
def set(self, name, value, channel=None):
name = callbacks.canonicalName(name)
if name not in self.originalNames:
raise KeyError, name
if ',' in name:
raise ValueError, 'There can be no commas in the name.'
self.unparsedValues[(channel, name)] = value
if channel is not None:
d = self.channels.setdefault(channel, {})
d[name] = self.types[name](value)
else:
self.defaults[name] = self.types[name](value)
def help(self, name):
return self.helps[callbacks.canonicalName(name)]
def names(self):
L = self.originalNames.values()
L.sort()
return L
class ConfigurableTypeError(TypeError):
pass
def ConfigurableBoolType(s):
s = s.lower()
if s in ('true', 'enable', 'on'):
return True
elif s in ('false', 'disable', 'off'):
return False
else:
s = 'Value must be one of on/off/true/false/enable/disable.'
raise ConfigurableTypeError, s
def ConfigurableStrType(s):
if s and s[0] not in '\'"' and s[-1] not in '\'"':
s = repr(s)
try:
v = utils.safeEval(s)
if type(v) is not str:
raise ValueError
except ValueError:
raise ConfigurableTypeError, 'Value must be a string.'
return v
def ConfigurableIntType(s):
try:
return int(s)
except ValueError:
raise ConfigurableTypeError, 'Value must be an int.'
class Configurable(object):
"""A mixin class to provide a "config" command that can be consistent
across all plugins, in order to unify the configuration for each plugin.
Plugins subclassing this should have a "configurables" attribute which is
a ConfigurableDictionary initialized with a list of 4-tuples of
(name, type, default, help). Name is the string name of the config
variable; type is a function taking a string and returning some value of
the type the variable is supposed to be; default is the default value the
variable should take on; help is a string that'll be returned to describe
the purpose of the config variable.
"""
def __init__(self):
className = self.__class__.__name__
self.filename = os.path.join(conf.confDir, '%s-configurable'%className)
if os.path.exists(self.filename):
fd = file(self.filename)
for line in fd:
line = line.rstrip()
(channel, name, value) = line.split(',', 2)
if channel == 'default':
channel = None
try:
self.configurables.set(name, eval(value), channel)
except ConfigurableTypeError, e:
s = 'Couldn\'t read configurable from file: %s'
self.log.warning(s, e)
except KeyError, e:
s = 'Configurable variable %s doesn\'t exist anymore.'
self.log.warning(s, name)
def die(self):
fd = file(self.filename, 'w')
L = self.configurables.unparsedValues.items()
L.sort()
for ((channel, name), value) in L:
if channel is None:
channel = 'default'
name = self.configurables.originalNames[name]
fd.write('%s,%s,%r\n' % (channel, name, value))
fd.close()
def config(self, irc, msg, args):
"""[<channel>] [<name>] [<value>]
Sets the value of config variable <name> to <value> on <channel>. If
<name> is given but <value> is not, returns the help and current value
for <name>. If neither <name> nor <value> is given, returns the valid
config variables for this plugin. <channel> is only necessary if the
message isn't sent in the channel itself.
"""
try:
channel = privmsgs.getChannel(msg, args)
capability = ircdb.makeChannelCapability(channel, 'op')
except callbacks.ArgumentError:
raise
except callbacks.Error:
channel = None
capability = 'admin'
if not ircdb.checkCapability(msg.prefix, capability):
irc.error(msg, conf.replyNoCapability % capability)
return
(name, value) = privmsgs.getArgs(args, required=0, optional=2)
if not name:
irc.reply(msg, utils.commaAndify(self.configurables.names()))
return
try:
if not value:
help = self.configurables.help(name)
value = self.configurables.get(name, channel=channel)
s = '%s: %s (Current value: %r)' % (name, help, value)
irc.reply(msg, s)
return
try:
self.configurables.set(name, value, channel)
irc.reply(msg, conf.replySuccess)
except ConfigurableTypeError, e:
irc.error(msg, str(e))
except KeyError:
irc.error(msg, 'There is no config variable %r' % name)
_randomnickRe = re.compile(r'\$randomnick', re.I) _randomnickRe = re.compile(r'\$randomnick', re.I)
_randomdateRe = re.compile(r'\$randomdate', re.I) _randomdateRe = re.compile(r'\$randomdate', re.I)
_randomintRe = re.compile(r'\$randomint', re.I) _randomintRe = re.compile(r'\$randomint', re.I)