mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-27 21:22:39 +01:00
Add network-specific config values.
This commit is contained in:
parent
d4cac026d4
commit
4f024cb0b2
@ -151,10 +151,10 @@ class Config(callbacks.Plugin):
|
||||
def _list(self, irc, group):
|
||||
L = []
|
||||
for (vname, v) in group._children.items():
|
||||
if hasattr(group, 'channelValue') and group.channelValue and \
|
||||
if hasattr(group, '_channelValue') and group._channelValue and \
|
||||
irc.isChannel(vname) and not v._children:
|
||||
continue
|
||||
if hasattr(v, 'channelValue') and v.channelValue:
|
||||
if hasattr(v, '_channelValue') and v._channelValue:
|
||||
vname = '#' + vname
|
||||
if v._added and not all(irc.isChannel, v._added):
|
||||
vname = '@' + vname
|
||||
@ -198,11 +198,20 @@ class Config(callbacks.Plugin):
|
||||
irc.reply(_('There were no matching configuration variables.'))
|
||||
search = wrap(search, ['lowered']) # XXX compose with withoutSpaces?
|
||||
|
||||
def _getValue(self, irc, msg, group, addChannel=False):
|
||||
def _getValue(self, irc, msg, group, network=None, channel=None, addGlobal=False):
|
||||
global_value = str(group) or ' '
|
||||
group = group.getSpecific(
|
||||
network=network.network, channel=channel, check=None)
|
||||
value = str(group) or ' '
|
||||
if addChannel and irc.isChannel(msg.args[0]) and not irc.nested:
|
||||
s = str(group.get(msg.args[0]))
|
||||
value = _('Global: %s; %s: %s') % (value, msg.args[0], s)
|
||||
if addGlobal and not irc.nested:
|
||||
value = _(
|
||||
'Global: %(global_value)s; '
|
||||
'%(channel_name)s @ %(network_name)s: %(channel_value)s') % {
|
||||
'global_value': global_value,
|
||||
'channel_name': msg.args[0],
|
||||
'network_name': irc.network,
|
||||
'channel_value': value,
|
||||
}
|
||||
if hasattr(group, 'value'):
|
||||
if not group._private:
|
||||
return (value, None)
|
||||
@ -230,28 +239,44 @@ class Config(callbacks.Plugin):
|
||||
irc.errorNoCapability(capability, Raise=True)
|
||||
|
||||
@internationalizeDocstring
|
||||
def channel(self, irc, msg, args, channels, group, value):
|
||||
"""[<channel>] <name> [<value>]
|
||||
def channel(self, irc, msg, args, network, channels, group, value):
|
||||
"""[<network>] [<channel>] <name> [<value>]
|
||||
|
||||
If <value> is given, sets the channel configuration variable for <name>
|
||||
to <value> for <channel>. Otherwise, returns the current channel
|
||||
to <value> for <channel> on the <network>.
|
||||
Otherwise, returns the current channel
|
||||
configuration value of <name>. <channel> is only necessary if the
|
||||
message isn't sent in the channel itself. More than one channel may
|
||||
be given at once by separating them with commas."""
|
||||
if not group.channelValue:
|
||||
be given at once by separating them with commas.
|
||||
<network> defaults to the current network."""
|
||||
if not group._channelValue:
|
||||
irc.error(_('That configuration variable is not a channel-specific '
|
||||
'configuration variable.'))
|
||||
return
|
||||
if value is not None:
|
||||
for channel in channels:
|
||||
assert irc.isChannel(channel)
|
||||
|
||||
# Sets the non-network-specific value, for forward
|
||||
# compatibility, ie. this will work even if the owner rolls
|
||||
# back Limnoria to an older version.
|
||||
# It's also an easy way to support plugins which are not
|
||||
# network-aware.
|
||||
self._setValue(irc, msg, group.get(channel), value)
|
||||
|
||||
if network != '*':
|
||||
# Set the network-specific value
|
||||
self._setValue(irc, msg, group.get(':' + network.network).get(channel), value)
|
||||
|
||||
irc.replySuccess()
|
||||
else:
|
||||
if network == '*':
|
||||
network = None
|
||||
values = []
|
||||
private = None
|
||||
for channel in channels:
|
||||
(value, private_value) = self._getValue(irc, msg, group.get(channel))
|
||||
(value, private_value) = \
|
||||
self._getValue(irc, msg, group, network, channel)
|
||||
values.append((channel, value))
|
||||
if private_value:
|
||||
private = True
|
||||
@ -261,7 +286,8 @@ class Config(callbacks.Plugin):
|
||||
for (channel, value) in values]))
|
||||
else:
|
||||
irc.reply(values[0][1])
|
||||
channel = wrap(channel, ['channels', 'settableConfigVar',
|
||||
channel = wrap(channel, [optional(first(('literal', '*'), 'networkIrc')),
|
||||
'channels', 'settableConfigVar',
|
||||
additional('text')])
|
||||
|
||||
@internationalizeDocstring
|
||||
@ -276,7 +302,10 @@ class Config(callbacks.Plugin):
|
||||
self._setValue(irc, msg, group, value)
|
||||
irc.replySuccess()
|
||||
else:
|
||||
(value, private) = self._getValue(irc, msg, group, addChannel=group.channelValue)
|
||||
(value, private) = self._getValue(
|
||||
irc, msg, group, network=irc,
|
||||
channel=msg.args[0] if irc.isChannel(msg.args[0]) else None,
|
||||
addGlobal=group._channelValue)
|
||||
irc.reply(value, private=private)
|
||||
config = wrap(config, ['settableConfigVar', additional('text')])
|
||||
|
||||
|
@ -133,13 +133,13 @@ class ConfigTestCase(ChannelPluginTestCase):
|
||||
'^Completely: Error: ',
|
||||
frm=self.prefix3)
|
||||
self.assertResponse('config plugins.Config.%s' % var_name,
|
||||
'Global: 0; #test: 0')
|
||||
'Global: 0; #test @ test: 0')
|
||||
|
||||
self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name,
|
||||
'^Completely: Error: ',
|
||||
frm=self.prefix3)
|
||||
self.assertResponse('config plugins.Config.%s' % var_name,
|
||||
'Global: 0; #test: 1')
|
||||
'Global: 0; #test @ test: 1')
|
||||
|
||||
def testOpNonEditable(self):
|
||||
var_name = 'testOpNonEditable' + random_string()
|
||||
@ -154,18 +154,18 @@ class ConfigTestCase(ChannelPluginTestCase):
|
||||
'^Completely: Error: ',
|
||||
frm=self.prefix3)
|
||||
self.assertResponse('config plugins.Config.%s' % var_name,
|
||||
'Global: 0; #test: 0')
|
||||
'Global: 0; #test @ test: 0')
|
||||
|
||||
self.assertRegexp('config channel plugins.Config.%s 1' % var_name,
|
||||
'^Completely: Error: ',
|
||||
frm=self.prefix3)
|
||||
self.assertResponse('config plugins.Config.%s' % var_name,
|
||||
'Global: 0; #test: 0')
|
||||
'Global: 0; #test @ test: 0')
|
||||
|
||||
self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name,
|
||||
'^Completely: Error: ')
|
||||
self.assertResponse('config plugins.Config.%s' % var_name,
|
||||
'Global: 0; #test: 1')
|
||||
'Global: 0; #test @ test: 1')
|
||||
|
||||
def testChannel(self):
|
||||
self.assertResponse('config reply.whenAddressedBy.strings ^',
|
||||
@ -181,6 +181,54 @@ class ConfigTestCase(ChannelPluginTestCase):
|
||||
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '.')
|
||||
self.assertResponse('config channel #testchan2 reply.whenAddressedBy.strings', '.')
|
||||
|
||||
def testChannelNetwork(self):
|
||||
irc = self.irc
|
||||
irc1 = getTestIrc('testnet1')
|
||||
irc2 = getTestIrc('testnet2')
|
||||
irc3 = getTestIrc('testnet3')
|
||||
conf.supybot.reply.whenAddressedBy.strings.get('#test')._wasSet = False
|
||||
# 1. Set global
|
||||
self.assertResponse('config reply.whenAddressedBy.strings ^',
|
||||
'The operation succeeded.')
|
||||
|
||||
# 2. Set for current net + #testchan1
|
||||
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings @',
|
||||
'The operation succeeded.')
|
||||
|
||||
# Exact match for #2:
|
||||
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '@')
|
||||
|
||||
# 3: Set for #testchan1 for all nets:
|
||||
self.assertNotError('config channel * #testchan1 reply.whenAddressedBy.strings $')
|
||||
|
||||
# Still exact match for #2:
|
||||
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '@')
|
||||
|
||||
# Inherit from *:
|
||||
self.assertResponse('config channel testnet1 #testchan1 reply.whenAddressedBy.strings', '$')
|
||||
self.assertResponse('config channel testnet2 #testchan1 reply.whenAddressedBy.strings', '$')
|
||||
|
||||
# 4: Set for testnet1 for #testchan1 and #testchan2:
|
||||
self.assertNotError('config channel testnet1 #testchan1,#testchan2 reply.whenAddressedBy.strings .')
|
||||
|
||||
# 5: Set for testnet2 for #testchan1:
|
||||
self.assertNotError('config channel testnet2 #testchan1 reply.whenAddressedBy.strings :')
|
||||
|
||||
# Inherit from global value (nothing was set of current net or current
|
||||
# chan):
|
||||
self.assertResponse('config channel reply.whenAddressedBy.strings', '^')
|
||||
|
||||
# Still exact match for #2:
|
||||
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '@')
|
||||
self.assertResponse('config channel %s #testchan1 reply.whenAddressedBy.strings' % irc.network, '@')
|
||||
|
||||
# Exact match for #4:
|
||||
self.assertResponse('config channel testnet1 #testchan1 reply.whenAddressedBy.strings', '.')
|
||||
self.assertResponse('config channel testnet1 #testchan2 reply.whenAddressedBy.strings', '.')
|
||||
|
||||
# Inherit from #5, which set for #testchan1 on all nets
|
||||
self.assertResponse('config channel testnet3 #testchan1 reply.whenAddressedBy.strings', ':')
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
||||
|
||||
|
@ -59,6 +59,9 @@ supybot.log.plugins.individualLogfiles: False
|
||||
supybot.protocols.irc.throttleTime: 0
|
||||
supybot.reply.whenAddressedBy.chars: @
|
||||
supybot.networks.test.server: should.not.need.this
|
||||
supybot.networks.testnet1.server: should.not.need.this
|
||||
supybot.networks.testnet2.server: should.not.need.this
|
||||
supybot.networks.testnet3.server: should.not.need.this
|
||||
supybot.nick: test
|
||||
supybot.databases.users.allowUnregistration: True
|
||||
""" % {'base_dir': os.getcwd()})
|
||||
|
@ -1391,33 +1391,33 @@ class PluginMixin(BasePlugin, irclib.IrcCallback):
|
||||
else:
|
||||
self.__parent.__call__(irc, msg)
|
||||
|
||||
def registryValue(self, name, channel=None, value=True):
|
||||
def registryValue(self, name, channel=None, network=None, value=True):
|
||||
if isinstance(network, bool):
|
||||
# Network-unaware plugin that uses 'value' as a positional
|
||||
# argument.
|
||||
(network, value) = (value, network)
|
||||
plugin = self.name()
|
||||
group = conf.supybot.plugins.get(plugin)
|
||||
names = registry.split(name)
|
||||
for name in names:
|
||||
group = group.get(name)
|
||||
if channel is not None:
|
||||
if ircutils.isChannel(channel):
|
||||
group = group.get(channel)
|
||||
else:
|
||||
self.log.debug('%s: registryValue got channel=%r', plugin,
|
||||
channel)
|
||||
if value:
|
||||
group = group.getSpecific(network=network, channel=channel)
|
||||
return group()
|
||||
else:
|
||||
return group
|
||||
|
||||
def setRegistryValue(self, name, value, channel=None):
|
||||
def setRegistryValue(self, name, value, channel=None, network=None):
|
||||
plugin = self.name()
|
||||
group = conf.supybot.plugins.get(plugin)
|
||||
names = registry.split(name)
|
||||
for name in names:
|
||||
group = group.get(name)
|
||||
if channel is None:
|
||||
group.setValue(value)
|
||||
else:
|
||||
group.get(channel).setValue(value)
|
||||
if network:
|
||||
group = group.get(':' + network)
|
||||
if channel:
|
||||
group = group.get(channel)
|
||||
group.setValue(value)
|
||||
|
||||
def userValue(self, name, prefixOrName, default=None):
|
||||
try:
|
||||
|
15
src/conf.py
15
src/conf.py
@ -83,12 +83,14 @@ def registerGroup(Group, name, group=None, **kwargs):
|
||||
return Group.register(name, group)
|
||||
|
||||
def registerGlobalValue(group, name, value):
|
||||
value.channelValue = False
|
||||
value._networkValue = False
|
||||
value._channelValue = False
|
||||
return group.register(name, value)
|
||||
|
||||
def registerChannelValue(group, name, value, opSettable=True):
|
||||
value._supplyDefault = True
|
||||
value.channelValue = True
|
||||
value._networkValue = True
|
||||
value._channelValue = True
|
||||
value._opSettable = opSettable
|
||||
g = group.register(name, value)
|
||||
gname = g._name.lower()
|
||||
@ -96,8 +98,13 @@ def registerChannelValue(group, name, value, opSettable=True):
|
||||
if name.lower().startswith(gname) and len(gname) < len(name):
|
||||
name = name[len(gname)+1:] # +1 for .
|
||||
parts = registry.split(name)
|
||||
if len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]):
|
||||
# This gets the channel values so they always persist.
|
||||
if len(parts) == 2 and parts[0] and parts[0].startswith(':') \
|
||||
and parts[1] and ircutils.isChannel(parts[1]):
|
||||
# This gets the network+channel values so they always persist.
|
||||
g.get(parts[0])()
|
||||
g.get(parts[0]).get(parts[1])()
|
||||
elif len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]):
|
||||
# Old-style variant of the above, without a network
|
||||
g.get(parts[0])()
|
||||
|
||||
def registerPlugin(name, currentValue=None, public=True):
|
||||
|
@ -212,7 +212,7 @@ class Group(object):
|
||||
s = '%r is not a valid entry in %r' % (attr, self._name)
|
||||
raise NonExistentRegistryEntry(s)
|
||||
|
||||
def __makeChild(self, attr, s):
|
||||
def _makeChild(self, attr, s):
|
||||
v = self.__class__(self._default, self._help)
|
||||
v.set(s)
|
||||
v._wasSet = False
|
||||
@ -225,10 +225,12 @@ class Group(object):
|
||||
return attr in self._children
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self._children:
|
||||
if attr.startswith('_'):
|
||||
object.__getattr__(self, attr)
|
||||
elif attr in self._children:
|
||||
return self._children[attr]
|
||||
elif self._supplyDefault:
|
||||
return self.__makeChild(attr, str(self))
|
||||
return self._makeChild(attr, str(self))
|
||||
else:
|
||||
self.__nonExistentEntry(attr)
|
||||
|
||||
@ -253,7 +255,7 @@ class Group(object):
|
||||
parts = split(rest)
|
||||
if len(parts) == 1 and parts[0] == name:
|
||||
try:
|
||||
self.__makeChild(name, v)
|
||||
self._makeChild(name, v)
|
||||
except InvalidRegistryValue:
|
||||
# It's probably supposed to be registered later.
|
||||
pass
|
||||
@ -328,7 +330,7 @@ class Value(Group):
|
||||
"""Invalid registry value. If you're getting this message, report it,
|
||||
because we forgot to put a proper help string here."""
|
||||
__slots__ = ('__parent', '_default', '_showDefault', '_help', '_callbacks',
|
||||
'value', 'channelValue', '_opSettable')
|
||||
'value', '_networkValue', '_channelValue', '_opSettable')
|
||||
def __init__(self, default, help, setDefault=True,
|
||||
showDefault=True, **kwargs):
|
||||
self.__parent = super(Value, self)
|
||||
@ -340,6 +342,24 @@ class Value(Group):
|
||||
if setDefault:
|
||||
self.setValue(default)
|
||||
|
||||
def _makeChild(self, attr, s):
|
||||
v = self.__class__(self._default, self._help)
|
||||
v.set(s)
|
||||
v._wasSet = False
|
||||
if self._networkValue and self._channelValue:
|
||||
# If this is both a network-specific and channel-specific value,
|
||||
# then the child is (only) channel-specific.
|
||||
v._networkValue = False
|
||||
v._channelValue = True
|
||||
v._supplyDefault = True
|
||||
else:
|
||||
# Otherwise, the child is neither network-specific or
|
||||
# channel-specific.
|
||||
v._supplyDefault = False
|
||||
v._help = '' # Clear this so it doesn't print a bazillion times.
|
||||
self.register(attr, v)
|
||||
return v
|
||||
|
||||
def error(self, value=_NoValueGiven):
|
||||
if hasattr(self, 'errormsg') and value is not _NoValueGiven:
|
||||
try:
|
||||
@ -356,6 +376,58 @@ class Value(Group):
|
||||
e.value = self
|
||||
raise e
|
||||
|
||||
def getSpecific(self, network=None, channel=None, check=True):
|
||||
"""Gets the network-specific and/or channel-specific value of this
|
||||
Value.
|
||||
If `check=True` (the default), this will raise an error if `network`
|
||||
(resp. `channel`) is provided but this Value is not network-specific
|
||||
(resp. channel-specific). If `check=False`, then `network` and/or
|
||||
`channel` may be silently ignored.
|
||||
"""
|
||||
if network and not self._networkValue:
|
||||
if check:
|
||||
raise NonExistentRegistryEntry('%s is not network-specific' %
|
||||
self._name)
|
||||
else:
|
||||
network = None
|
||||
if channel and not self._channelValue:
|
||||
if check:
|
||||
raise NonExistentRegistryEntry('%s is not channel-specific' %
|
||||
self._name)
|
||||
else:
|
||||
channel = None
|
||||
if network and channel:
|
||||
# The complicated case. We want a net+chan specific value,
|
||||
# which may come in three different ways:
|
||||
#
|
||||
# 1. it was set explicitely net+chan
|
||||
# 2. it's inherited from a net specific value (which may itself be
|
||||
# inherited from the base value)
|
||||
# 3. it's inherited from the chan specific value (which is not a
|
||||
# actually a parent in the registry tree, but we need this to
|
||||
# load configuration from old bots).
|
||||
#
|
||||
# The choice between 2 and 3 is done by checking which of the
|
||||
# net-specific and chan-specific values was set explicitely by
|
||||
# a user/admin. In case both were, the net-specific value is used
|
||||
# (there is no particular reason for this, I just think it makes
|
||||
# more sense).
|
||||
network_value = self.get(':' + network)
|
||||
network_channel_value = network_value.get(channel)
|
||||
channel_value = self.get(channel)
|
||||
if network_value._wasSet or network_channel_value._wasSet:
|
||||
# cases 1 and 2
|
||||
return network_channel_value
|
||||
else:
|
||||
# case 3
|
||||
return channel_value
|
||||
elif network:
|
||||
return self.get(':' + network)
|
||||
elif channel:
|
||||
return self.get(channel)
|
||||
else:
|
||||
return self
|
||||
|
||||
def setName(self, *args):
|
||||
if self._name == 'unset':
|
||||
self._lastModified = 0
|
||||
|
@ -96,8 +96,8 @@ def retry(tries=3):
|
||||
return newf
|
||||
return decorator
|
||||
|
||||
def getTestIrc():
|
||||
irc = irclib.Irc('test')
|
||||
def getTestIrc(name='test'):
|
||||
irc = irclib.Irc(name)
|
||||
# Gotta clear the connect messages (USER, NICK, etc.)
|
||||
while irc.takeMsg():
|
||||
pass
|
||||
|
Loading…
Reference in New Issue
Block a user