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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -55,6 +55,7 @@ import ircmsgs
import ircutils
import privmsgs
import callbacks
import configurable
tableCreateStatements = {
'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
database-backed commands for crossword puzzle solving, anagram searching,
larting, praising, excusing, and insulting.
"""
configurables = plugins.ConfigurableDictionary(
[('show-ids', plugins.ConfigurableBoolType, False,
configurables = configurable.Dictionary(
[('show-ids', configurable.BoolType, False,
"""Determines whether the bot will show the id of an
excuse/insult/praise/lart.""")]
)
_tables = sets.Set(['lart', 'insult', 'excuse', 'praise'])
def __init__(self):
callbacks.Privmsg.__init__(self)
plugins.Configurable.__init__(self)
configurable.Mixin.__init__(self)
plugins.ChannelDBHandler.__init__(self)
def die(self):
callbacks.Privmsg.die(self)
plugins.Configurable.die(self)
configurable.Mixin.die(self)
plugins.ChannelDBHandler.die(self)
def makeDb(self, dbfilename, replace=False):

View File

@ -47,6 +47,7 @@ import plugins
import ircutils
import privmsgs
import callbacks
import configurable
def configure(onStart, afterConnect, advanced):
@ -69,15 +70,15 @@ def configure(onStart, afterConnect, advanced):
onStart.append('Gameknot toggle stat off')
class Gameknot(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
class Gameknot(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
threaded = True
regexps = ['gameknotSnarfer', 'gameknotStatsSnarfer']
configurables = plugins.ConfigurableDictionary(
[('game-snarfer', plugins.ConfigurableBoolType, True,
configurables = configurable.Dictionary(
[('game-snarfer', configurable.BoolType, True,
"""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
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
will reply to the channel with a summary of the stats of any player
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>')
_gkseen = re.compile(r'(seen on GK:\s+([^[]+ago)|.*?is hiding.*?)')
def __init__(self):
plugins.Configurable.__init__(self)
configurable.Mixin.__init__(self)
callbacks.PrivmsgCommandAndRegexp.__init__(self)
def die(self):
plugins.Configurable.die(self)
configurable.Mixin.die(self)
callbacks.PrivmsgCommandAndRegexp.die(self)
def getStats(self, name):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -47,6 +47,7 @@ import ircutils
import privmsgs
import webutils
import callbacks
import configurable
def configure(onStart, afterConnect, advanced):
@ -94,7 +95,7 @@ def configure(onStart, afterConnect, advanced):
class TrackerError(Exception):
pass
class Sourceforge(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable):
class Sourceforge(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
"""
Module for Sourceforge stuff. Currently contains commands to query a
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)
_res =(_resolution, _assigned, _submitted, _priority, _status)
configurables = plugins.ConfigurableDictionary(
[('tracker-snarfer', plugins.ConfigurableBoolType, False,
configurables = configurable.Dictionary(
[('tracker-snarfer', configurable.BoolType, False,
"""Determines whether the bot will reply to SF.net Tracker URLs in
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
case that no explicit project is given).""")]
)
_projectURL = 'http://sourceforge.net/projects/'
def __init__(self):
plugins.Configurable.__init__(self)
configurable.Mixin.__init__(self)
callbacks.PrivmsgCommandAndRegexp.__init__(self)
def die(self):
plugins.Configurable.die(self)
configurable.Mixin.die(self)
callbacks.PrivmsgCommandAndRegexp.die(self)
def _formatResp(self, text, num=''):

View File

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

View File

@ -54,6 +54,7 @@ import ircmsgs
import ircutils
import privmsgs
import callbacks
import configurable
def configure(onStart, afterConnect, advanced):
# 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?""")
class URL(callbacks.PrivmsgCommandAndRegexp,
plugins.Configurable,
configurable.Mixin,
plugins.ChannelDBHandler):
regexps = ['tinyurlSnarfer']
configurables = plugins.ConfigurableDictionary(
[('tinyurl-snarfer', plugins.ConfigurableBoolType, False,
configurables = configurable.Dictionary(
[('tinyurl-snarfer', configurable.BoolType, False,
"""Determines whether the bot will output shorter versions of URLs
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
snarf it and offer a tinyurl replacement."""),]
)
def __init__(self):
self.nextMsgs = {}
callbacks.PrivmsgCommandAndRegexp.__init__(self)
plugins.Configurable.__init__(self)
configurable.Mixin.__init__(self)
plugins.ChannelDBHandler.__init__(self)
def die(self):
callbacks.PrivmsgCommandAndRegexp.die(self)
plugins.Configurable.die(self)
configurable.Mixin.die(self)
plugins.ChannelDBHandler.die(self)
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
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)
_randomdateRe = re.compile(r'\$randomdate', re.I)
_randomintRe = re.compile(r'\$randomint', re.I)