Merge branch 'maint/0.83.4'

This commit is contained in:
James McCoy 2012-09-01 09:13:52 -04:00
commit 232aa86a1e
22 changed files with 172 additions and 130 deletions

15
INSTALL
View File

@ -41,17 +41,12 @@ registry file for your bot.
Local Install Local Install
You can install Supybot in a local directory by using the '--prefix' You can install Supybot in a local directory by using the '--user'
option when running 'setup.py'. E.g., 'python setup.py install option when running 'setup.py'. E.g., 'python setup.py install
--prefix=$HOME' to install into your home directory. You'll now have --user' to install into your home directory. You'll now have
a $HOME/bin directory containing Supybot programs ('supybot', a $HOME/.local/bin directory containing Supybot programs ('supybot',
'supybot-wizard', etc.) and a $HOME/lib directory containing the 'supybot-wizard', etc.) and a $HOME/.local/lib directory containing the
Supybot libraries. It is also recommended that you setup a proper Supybot libraries.
PYTHONPATH environment variable in your shell's init file.
bash -- 'export PYTHONPATH=$HOME/lib/python2.x/site-packages'
(t)csh -- 'setenv PYTHONPATH $HOME/lib/python2.x/site-packages'
Windows Windows

View File

@ -1,6 +1,6 @@
### ###
# Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2009, James Vega # Copyright (c) 2009-2012, James McCoy
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -153,14 +153,7 @@ class Channel(callbacks.Plugin):
halfop = wrap(halfop, ['halfop', ('haveOp', 'halfop someone'), halfop = wrap(halfop, ['halfop', ('haveOp', 'halfop someone'),
any('nickInChannel')]) any('nickInChannel')])
def voice(self, irc, msg, args, channel, nicks): def _voice(self, irc, msg, args, channel, nicks, fn):
"""[<channel>] [<nick> ...]
If you have the #channel,voice capability, this will voice all the
<nick>s you provide. If you don't provide any <nick>s, this will
voice you. <channel> is only necessary if the message isn't sent in the
channel itself.
"""
if nicks: if nicks:
if len(nicks) == 1 and msg.nick in nicks: if len(nicks) == 1 and msg.nick in nicks:
capability = 'voice' capability = 'voice'
@ -172,10 +165,20 @@ class Channel(callbacks.Plugin):
capability = ircdb.makeChannelCapability(channel, capability) capability = ircdb.makeChannelCapability(channel, capability)
if ircdb.checkCapability(msg.prefix, capability): if ircdb.checkCapability(msg.prefix, capability):
def f(L): def f(L):
return ircmsgs.voices(channel, L) return fn(channel, L)
self._sendMsgs(irc, nicks, f) self._sendMsgs(irc, nicks, f)
else: else:
irc.errorNoCapability(capability) irc.errorNoCapability(capability)
def voice(self, irc, msg, args, channel, nicks):
"""[<channel>] [<nick> ...]
If you have the #channel,voice capability, this will voice all the
<nick>s you provide. If you don't provide any <nick>s, this will
voice you. <channel> is only necessary if the message isn't sent in the
channel itself.
"""
self._voice(irc, msg, args, channel, nicks, ircmsgs.voices)
voice = wrap(voice, ['channel', ('haveOp', 'voice someone'), voice = wrap(voice, ['channel', ('haveOp', 'voice someone'),
any('nickInChannel')]) any('nickInChannel')])
@ -228,12 +231,8 @@ class Channel(callbacks.Plugin):
irc.error('I cowardly refuse to devoice myself. If you really ' irc.error('I cowardly refuse to devoice myself. If you really '
'want me devoiced, tell me to op you and then devoice ' 'want me devoiced, tell me to op you and then devoice '
'me yourself.', Raise=True) 'me yourself.', Raise=True)
if not nicks: self._voice(irc, msg, args, channel, nicks, ircmsgs.devoices)
nicks = [msg.nick] devoice = wrap(devoice, ['channel', ('haveOp', 'devoice someone'),
def f(L):
return ircmsgs.devoices(channel, L)
self._sendMsgs(irc, nicks, f)
devoice = wrap(devoice, ['voice', ('haveOp', 'devoice someone'),
any('nickInChannel')]) any('nickInChannel')])
def cycle(self, irc, msg, args, channel): def cycle(self, irc, msg, args, channel):

View File

@ -205,11 +205,19 @@ class ChannelTestCase(ChannelPluginTestCase):
def testIgnore(self): def testIgnore(self):
orig = conf.supybot.protocols.irc.banmask() orig = conf.supybot.protocols.irc.banmask()
def ignore(given, expect=None):
if expect is None:
expect = given
self.assertNotError('channel ignore add %s' % given)
self.assertResponse('channel ignore list', "'%s'" % expect)
self.assertNotError('channel ignore remove %s' % expect)
self.assertRegexp('channel ignore list', 'not currently')
try: try:
ignore('foo!bar@baz', '*!bar@baz')
ignore('foo!*@*')
conf.supybot.protocols.irc.banmask.setValue(['exact']) conf.supybot.protocols.irc.banmask.setValue(['exact'])
self.assertNotError('channel ignore add foo!bar@baz') ignore('foo!bar@baz')
self.assertResponse('channel ignore list', "'foo!bar@baz'") ignore('foo!*@*')
self.assertNotError('channel ignore remove foo!bar@baz')
self.assertError('ban add not!a.hostmask') self.assertError('ban add not!a.hostmask')
finally: finally:
conf.supybot.protocols.irc.banmask.setValue(orig) conf.supybot.protocols.irc.banmask.setValue(orig)

View File

@ -354,7 +354,8 @@ class Filter(callbacks.Plugin):
if c == ' ': if c == ' ':
return c return c
if fg is None: if fg is None:
fg = str(random.randint(2, 15)).zfill(2) fg = random.randint(2, 15)
fg = str(fg).zfill(2)
return '\x03%s%s' % (fg, c) return '\x03%s%s' % (fg, c)
def colorize(self, irc, msg, args, text): def colorize(self, irc, msg, args, text):

View File

@ -103,6 +103,8 @@ class Math(callbacks.Plugin):
_mathEnv['abs'] = abs _mathEnv['abs'] = abs
_mathEnv['max'] = max _mathEnv['max'] = max
_mathEnv['min'] = min _mathEnv['min'] = min
_mathSafeEnv = dict([(x,y) for x,y in _mathEnv.items()
if x not in ['factorial']])
_mathRe = re.compile(r'((?:(?<![A-Fa-f\d)])-)?' _mathRe = re.compile(r'((?:(?<![A-Fa-f\d)])-)?'
r'(?:0x[A-Fa-f\d]+|' r'(?:0x[A-Fa-f\d]+|'
r'0[0-7]+|' r'0[0-7]+|'
@ -191,7 +193,7 @@ class Math(callbacks.Plugin):
text = self._mathRe.sub(handleMatch, text) text = self._mathRe.sub(handleMatch, text)
try: try:
self.log.info('evaluating %q from %s', text, msg.prefix) self.log.info('evaluating %q from %s', text, msg.prefix)
x = complex(eval(text, self._mathEnv, self._mathEnv)) x = complex(eval(text, self._mathSafeEnv, self._mathSafeEnv))
irc.reply(self._complexToString(x)) irc.reply(self._complexToString(x))
except OverflowError: except OverflowError:
maxFloat = math.ldexp(0.9999999999999999, 1024) maxFloat = math.ldexp(0.9999999999999999, 1024)

View File

@ -110,6 +110,7 @@ class MathTestCase(PluginTestCase):
self.assertNotError('calc (1600 * 1200) - 2*(1024*1280)') self.assertNotError('calc (1600 * 1200) - 2*(1024*1280)')
self.assertNotError('calc 3-2*4') self.assertNotError('calc 3-2*4')
self.assertNotError('calc (1600 * 1200)-2*(1024*1280)') self.assertNotError('calc (1600 * 1200)-2*(1024*1280)')
self.assertError('calc factorial(99)')
def testCalcNoNameError(self): def testCalcNoNameError(self):
self.assertNotRegexp('calc foobar(x)', 'NameError') self.assertNotRegexp('calc foobar(x)', 'NameError')
@ -134,6 +135,7 @@ class MathTestCase(PluginTestCase):
self.assertResponse('icalc 1^1', '0') self.assertResponse('icalc 1^1', '0')
self.assertResponse('icalc 10**24', '1' + '0'*24) self.assertResponse('icalc 10**24', '1' + '0'*24)
self.assertRegexp('icalc 49/6', '8.16') self.assertRegexp('icalc 49/6', '8.16')
self.assertNotError('icalc factorial(99)')
def testRpn(self): def testRpn(self):
self.assertResponse('rpn 5 2 +', '7') self.assertResponse('rpn 5 2 +', '7')

View File

@ -47,7 +47,7 @@ class Success(plugins.ChannelIdDatabasePlugin):
self.originalClass = conf.supybot.replies.success.__class__ self.originalClass = conf.supybot.replies.success.__class__
class MySuccessClass(self.originalClass): class MySuccessClass(self.originalClass):
def __call__(self): def __call__(self):
ret = pluginSelf.db.random(pluginSelf.target) ret = pluginSelf.db.random(dynamic.msg.args[0])
if ret is None: if ret is None:
try: try:
self.__class__ = pluginSelf.originalClass self.__class__ = pluginSelf.originalClass
@ -68,14 +68,6 @@ class Success(plugins.ChannelIdDatabasePlugin):
self.__parent.die() self.__parent.die()
conf.supybot.replies.success.__class__ = self.originalClass conf.supybot.replies.success.__class__ = self.originalClass
def inFilter(self, irc, msg):
# We need the target, but we need it before Owner.doPrivmsg is called,
# so this seems like the only way to do it.
self.target = msg.args[0]
return msg
Class = Success Class = Success
# vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78:

View File

@ -4,6 +4,9 @@ import os
import re import re
import sys import sys
import shutil import shutil
import subprocess
from optparse import OptionParser
def firstLines(filename, n): def firstLines(filename, n):
fd = file(filename) fd = file(filename)
@ -11,6 +14,7 @@ def firstLines(filename, n):
while n: while n:
n -= 1 n -= 1
lines.append(fd.readline().rstrip('\r\n')) lines.append(fd.readline().rstrip('\r\n'))
fd.close()
return lines return lines
def firstLine(filename): def firstLine(filename):
@ -20,62 +24,64 @@ def error(s):
sys.stderr.write(s+'\n') sys.stderr.write(s+'\n')
sys.exit(-1) sys.exit(-1)
def system(sh, errmsg=None): def system(sh, errmsg=None, **kwargs):
if errmsg is None: if errmsg is None:
if isinstance(sh, basestring):
errmsg = repr(sh) errmsg = repr(sh)
ret = os.system(sh) else:
errmsg = repr(' '.join(sh))
ret = subprocess.call(sh, **kwargs)
if ret: if ret:
error(errmsg + ' (error code: %s)' % ret) error(errmsg + ' (error code: %s)' % ret)
def usage():
error('Usage: %s [-s|-n] <sf username> <version>\nSpecify -s to pass it on '
'to the relevant git commands.\nSpecify -n to perform a dry-run '
'release. No commits or tags are pushed and no files are uploaded.'
'\nMust be called from a git checkout.'
% sys.argv[0])
def checkGitRepo(): def checkGitRepo():
system('test "$(git rev-parse --is-inside-work-tree)" = "true"', system('test "$(git rev-parse --is-inside-work-tree)" = "true"',
'Must be run from a git checkout.') 'Must be run from a git checkout.',
shell=True)
system('test "$(git rev-parse --show-cdup >/dev/null)" = ""', system('test "$(git rev-parse --show-cdup >/dev/null)" = ""',
'Must be run from the top-level directory of the git checkout.') 'Must be run from the top-level directory of the git checkout.',
shell=True)
system('git rev-parse --verify HEAD >/dev/null ' system('git rev-parse --verify HEAD >/dev/null '
'&& git update-index --refresh' '&& git update-index --refresh'
'&& git diff-files --quiet' '&& git diff-files --quiet'
'&& git diff-index --cached --quiet HEAD --', '&& git diff-index --cached --quiet HEAD --',
'Your tree is unclean. Can\'t run from here.') 'Your tree is unclean. Can\'t run from here.',
shell=True)
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) < 3 or len(sys.argv) > 5: usage = 'usage: %prog [options] <username> <version>'
usage() parser = OptionParser(usage=usage)
parser.set_defaults(sign=False, verbose=False, branch='master')
parser.add_option('-s', '--sign', action='store_true', dest='sign',
help='Pass on -s to relevant git commands')
parser.add_option('-n', '--dry-run', action='store_true', dest='dry_run',
help='Build the release, but do not push to the git '
'remote or upload the release archives.')
parser.add_option('-b', '--branch', metavar='BRANCH', dest='branch',
help='Branch to use for the release. Default: %default')
(options, args) = parser.parse_args()
if len(args) != 2:
parser.error('Both username and version must be specified')
(u, v) = args
if not re.match(r'^\d+\.\d+\.\d+(\.\d+)?\w*$', v):
parser.error('Invalid version string: '
'must be of the form MAJOR.MINOR.PATCHLEVEL')
checkGitRepo() checkGitRepo()
sign = '' sign = options.sign
dryrun = False dryrun = options.dry_run
while len(sys.argv) > 3: branch = options.branch
if sys.argv[1] == '-s':
sign = '-s'
sys.argv.pop(1)
elif sys.argv[1] == '-n':
dryrun = True
sys.argv.pop(1)
else:
usage()
print 'Check version string for validity.'
(u, v) = sys.argv[1:]
if not re.match(r'^\d+\.\d+\.\d+(\.\d+)?\w*$', v):
error('Invalid version string: '
'must be of the form MAJOR.MINOR.PATCHLEVEL.')
if os.path.exists('supybot'): if os.path.exists('supybot'):
error('I need to make the directory "supybot" but it already exists.' error('I need to make the directory "supybot" but it already exists.'
' Change to an appropriate directory or remove the supybot ' ' Change to an appropriate directory or remove the supybot '
'directory to continue.') 'directory to continue.')
print 'Checking out fresh tree from git.' print 'Checking out fresh tree from git.'
system('git clone git+ssh://%s@supybot.git.sourceforge.net/gitroot/supybot' repo = 'git+ssh://%s@supybot.git.sourceforge.net/gitroot/supybot/supybot' % u
% u) system(['git', 'clone', '-b', branch, repo])
os.chdir('supybot') os.chdir('supybot')
print 'Checking RELNOTES version line.' print 'Checking RELNOTES version line.'
@ -90,38 +96,47 @@ if __name__ == '__main__':
error('Invalid third line in ChangeLog.') error('Invalid third line in ChangeLog.')
print 'Updating version in version files.' print 'Updating version in version files.'
versionFiles = ('src/conf.py', 'scripts/supybot', 'setup.py') versionFiles = ['src/version.py']
for fn in versionFiles: for fn in versionFiles:
sh = 'perl -pi -e "s/^version\s*=.*/version = \'%s\'/" %s' % (v, fn) sh = ['perl', '-pi', '-e', 's/^version\s*=.*/version = \'%s\'/' % v, fn]
system(sh, 'Error changing version in %s' % fn) system(sh, 'Error changing version in %s' % fn)
system('git commit %s -m \'Updated to %s.\' %s' commit = ['git', 'commit']
% (sign, v, ' '.join(versionFiles))) if sign:
commit.append('-s')
system(commit + ['-m', 'Updated to %s.' % v] + versionFiles)
print 'Tagging release.' print 'Tagging release.'
system('git tag %s -m "Release %s" %s' % (sign or '-a', v, v)) tag = ['git', 'tag']
if sign:
tag.append('-s')
system(tag + ['-m', "Release %s" % v, 'v%s' % v])
print 'Committing %s+git to version files.' % v print 'Committing %s+git to version files.' % v
for fn in versionFiles: for fn in versionFiles:
sh = 'perl -pi -e "s/^version\s*=.*/version = \'%s\'/" %s' % \ system(['perl', '-pi', '-e',
(v + '+git', fn) 's/^version\s*=.*/version = \'%s+git\'/' % v, fn],
system(sh, 'Error changing version in %s' % fn) 'Error changing version in %s' % fn)
system('git commit %s -m \'Updated to %s+git.\' %s' system(commit + ['-m', 'Updated to %s+git.' % v] + versionFiles)
% (sign, v, ' '.join(versionFiles)))
if not dryrun: if not dryrun:
print 'Pushing commits and tag.' print 'Pushing commits and tag.'
system('git push origin master') system(['git', 'push', 'origin', branch])
system('git push --tags') system(['git', 'push', '--tags'])
archive = ['git', 'archive', '--prefix=Supybot-%s/' % v]
print 'Creating tarball (gzip).' print 'Creating tarball (gzip).'
system('git archive --prefix=Supybot-%s/ --format=tar %s ' system(archive + ['-o', '../Supybot-%s.tar.gz' % v,
'| gzip -c >../Supybot-%s.tar.gz' % (v, v, v)) '--format=tgz', 'v%s' % v])
system(['git', 'config', 'tar.bz2.command', 'bzip2 -c'])
print 'Creating tarball (bzip2).' print 'Creating tarball (bzip2).'
system('git archive --prefix=Supybot-%s/ --format=tar %s ' system(archive + ['-o', '../Supybot-%s.tar.bz2' % v,
'| bzip2 -c >../Supybot-%s.tar.bz2' % (v, v, v)) '--format=bz2', 'v%s' % v])
print 'Creating zip.' print 'Creating zip.'
system('git archive --prefix=Supybot-%s/ --format=zip %s ' system(archive + ['-o', '../Supybot-%s.zip' % v,
'>../Supybot-%s.zip' % (v, v, v)) '--format=zip', 'v%s' % v])
os.chdir('..') os.chdir('..')
shutil.rmtree('supybot') shutil.rmtree('supybot')

View File

@ -65,6 +65,8 @@ import supybot.utils as utils
import supybot.registry as registry import supybot.registry as registry
import supybot.questions as questions import supybot.questions as questions
from supybot.version import version
def main(): def main():
import supybot.conf as conf import supybot.conf as conf
import supybot.world as world import supybot.world as world
@ -125,7 +127,6 @@ def main():
log.info('Total CPU time taken: %s seconds.', user+system) log.info('Total CPU time taken: %s seconds.', user+system)
log.info('No more Irc objects, exiting.') log.info('No more Irc objects, exiting.')
version = '0.83.4.1+git'
if __name__ == '__main__': if __name__ == '__main__':
parser = optparse.OptionParser(usage='Usage: %prog [options] configFile', parser = optparse.OptionParser(usage='Usage: %prog [options] configFile',
version='Supybot %s' % version) version='Supybot %s' % version)
@ -296,7 +297,7 @@ if __name__ == '__main__':
log.error('Could not remove pid file: %s', e) log.error('Could not remove pid file: %s', e)
atexit.register(removePidFile) atexit.register(removePidFile)
except EnvironmentError, e: except EnvironmentError, e:
log.fatal('Error opening/writing pid file %s: %s', pidFile, e) log.critical('Error opening/writing pid file %s: %s', pidFile, e)
sys.exit(-1) sys.exit(-1)
conf.allowDefaultOwner = options.allowDefaultOwner conf.allowDefaultOwner = options.allowDefaultOwner

View File

@ -47,6 +47,8 @@ import glob
import shutil import shutil
import os.path import os.path
from src.version import version
plugins = [s for s in os.listdir('plugins') if plugins = [s for s in os.listdir('plugins') if
os.path.exists(os.path.join('plugins', s, 'plugin.py'))] os.path.exists(os.path.join('plugins', s, 'plugin.py'))]
@ -116,13 +118,12 @@ package_dir = {'supybot': 'src',
for plugin in plugins: for plugin in plugins:
package_dir['supybot.plugins.' + plugin] = 'plugins/' + plugin package_dir['supybot.plugins.' + plugin] = 'plugins/' + plugin
version = '0.83.4.1+git'
setup( setup(
# Metadata # Metadata
name='supybot', name='supybot',
version=version, version=version,
author='Jeremy Fincher', author='Jeremy Fincher',
url='http://supybot.com/', url='http://sourceforge.net/projects/supybot/',
author_email='jemfinch@supybot.com', author_email='jemfinch@supybot.com',
download_url='http://www.sf.net/project/showfiles.php?group_id=58965', download_url='http://www.sf.net/project/showfiles.php?group_id=58965',
description='A flexible and extensible Python IRC bot and framework.', description='A flexible and extensible Python IRC bot and framework.',

View File

@ -465,13 +465,15 @@ class RichReplyMethods(object):
if isinstance(capability, basestring): # checkCommandCapability! if isinstance(capability, basestring): # checkCommandCapability!
log.warning('Denying %s for lacking %q capability.', log.warning('Denying %s for lacking %q capability.',
self.msg.prefix, capability) self.msg.prefix, capability)
if not self._getConfig(conf.supybot.reply.error.noCapability): # noCapability means "don't send a specific capability error
# message" not "don't send a capability error message at all", like
# one would think
if self._getConfig(conf.supybot.reply.error.noCapability):
v = self._getConfig(conf.supybot.replies.genericNoCapability)
else:
v = self._getConfig(conf.supybot.replies.noCapability) v = self._getConfig(conf.supybot.replies.noCapability)
s = self.__makeReply(v % capability, s) s = self.__makeReply(v % capability, s)
return self._error(s, **kwargs) return self._error(s, **kwargs)
else:
log.debug('Not sending capability error, '
'supybot.reply.error.noCapability is False.')
else: else:
log.warning('Denying %s for some unspecified capability ' log.warning('Denying %s for some unspecified capability '
'(or a default).', self.msg.prefix) '(or a default).', self.msg.prefix)
@ -1167,7 +1169,12 @@ class Commands(BasePlugin):
method(irc, msg, *args, **kwargs) method(irc, msg, *args, **kwargs)
def _callCommand(self, command, irc, msg, *args, **kwargs): def _callCommand(self, command, irc, msg, *args, **kwargs):
self.log.info('%s called by %q.', formatCommand(command), msg.prefix) if irc.nick == msg.args[0]:
self.log.info('%s called in private by %q.', formatCommand(command),
msg.prefix)
else:
self.log.info('%s called on %s by %q.', formatCommand(command),
msg.args[0], msg.prefix)
# XXX I'm being extra-special-careful here, but we need to refactor # XXX I'm being extra-special-careful here, but we need to refactor
# this. # this.
try: try:

View File

@ -40,7 +40,7 @@ import supybot.ircutils as ircutils
### ###
# version: This should be pretty obvious. # version: This should be pretty obvious.
### ###
version = '0.83.4.1+git' from supybot.version import version
### ###
# *** The following variables are affected by command-line options. They are # *** The following variables are affected by command-line options. They are
@ -949,6 +949,9 @@ class Banmask(registry.SpaceSeparatedSetOfStrings):
bhost = host bhost = host
elif option == 'exact': elif option == 'exact':
return hostmask return hostmask
if (bnick, buser, bhost) == ('*', '*', '*') and \
ircutils.isUserHostmask(hostmask):
return hostmask
return ircutils.joinHostmask(bnick, buser, bhost) return ircutils.joinHostmask(bnick, buser, bhost)
registerChannelValue(supybot.protocols.irc, 'banmask', registerChannelValue(supybot.protocols.irc, 'banmask',

View File

@ -31,6 +31,7 @@
Module for some slight database-independence for simple databases. Module for some slight database-independence for simple databases.
""" """
import os
import csv import csv
import math import math
@ -113,7 +114,7 @@ class DirMapping(MappingInterface):
def _makeFilename(self, id): def _makeFilename(self, id):
return os.path.join(self.dirname, str(id)) return os.path.join(self.dirname, str(id))
def get(id): def get(self, id):
try: try:
fd = file(self._makeFilename(id)) fd = file(self._makeFilename(id))
return fd.read() return fd.read()
@ -122,7 +123,7 @@ class DirMapping(MappingInterface):
exn.realException = e exn.realException = e
raise exn raise exn
def set(id, s): def set(self, id, s):
fd = file(self._makeFilename(id), 'w') fd = file(self._makeFilename(id), 'w')
fd.write(s) fd.write(s)
fd.close() fd.close()

View File

@ -155,9 +155,6 @@ class SupyReconnectingFactory(ReconnectingClientFactory, drivers.ServersMixin):
return protocol return protocol
Driver = SupyReconnectingFactory Driver = SupyReconnectingFactory
try:
ignore(poller)
except NameError:
poller = TwistedRunnerDriver() poller = TwistedRunnerDriver()
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -240,7 +240,7 @@ class ChannelState(utils.python.Object):
self.users = ircutils.IrcSet() self.users = ircutils.IrcSet()
self.voices = ircutils.IrcSet() self.voices = ircutils.IrcSet()
self.halfops = ircutils.IrcSet() self.halfops = ircutils.IrcSet()
self.modes = ircutils.IrcDict() self.modes = {}
def isOp(self, nick): def isOp(self, nick):
return nick in self.ops return nick in self.ops
@ -405,10 +405,10 @@ class IrcState(IrcCommandDispatcher):
"""Handles parsing the 004 reply """Handles parsing the 004 reply
Supported user and channel modes are cached""" Supported user and channel modes are cached"""
# msg.args = [server, ircd-version, umodes, modes, # msg.args = [nick, server, ircd-version, umodes, modes,
# modes that require arguments? (non-standard)] # modes that require arguments? (non-standard)]
self.supported['umodes'] = msg.args[2] self.supported['umodes'] = msg.args[3]
self.supported['chanmodes'] = msg.args[3] self.supported['chanmodes'] = msg.args[4]
_005converters = utils.InsensitivePreservingDict({ _005converters = utils.InsensitivePreservingDict({
'modes': int, 'modes': int,

View File

@ -519,11 +519,11 @@ def unbans(channel, hostmasks, prefix='', msg=None):
if conf.supybot.protocols.irc.strictRfc(): if conf.supybot.protocols.irc.strictRfc():
assert isChannel(channel), repr(channel) assert isChannel(channel), repr(channel)
assert all(isUserHostmask, hostmasks), hostmasks assert all(isUserHostmask, hostmasks), hostmasks
modes = [('-b', s) for s in hostmasks]
if msg and not prefix: if msg and not prefix:
prefix = msg.prefix prefix = msg.prefix
return IrcMsg(prefix=prefix, command='MODE', msg=msg, return IrcMsg(prefix=prefix, command='MODE',
args=(channel, '-' + ('b'*len(hostmasks)), args=[channel] + ircutils.joinModes(modes), msg=msg)
' '.join(hostmasks)))
def kick(channel, nick, s='', prefix='', msg=None): def kick(channel, nick, s='', prefix='', msg=None):
"""Returns a KICK to kick nick from channel with the message msg.""" """Returns a KICK to kick nick from channel with the message msg."""

View File

@ -406,10 +406,15 @@ class FormatParser(object):
i = 0 i = 0
setI = False setI = False
c = self.getChar() c = self.getChar()
while c.isdigit() and i < 100: while c.isdigit():
j = i * 10
j += int(c)
if j >= 16:
self.ungetChar(c)
break
else:
setI = True setI = True
i *= 10 i = j
i += int(c)
c = self.getChar() c = self.getChar()
self.ungetChar(c) self.ungetChar(c)
if setI: if setI:
@ -422,6 +427,8 @@ class FormatParser(object):
c = self.getChar() c = self.getChar()
if c == ',': if c == ',':
context.bg = self.getInt() context.bg = self.getInt()
else:
self.ungetChar(c)
def wrap(s, length): def wrap(s, length):
processed = [] processed = []

View File

@ -116,12 +116,12 @@ def close(registry, filename, private=True):
try: try:
x = value.__class__(value._default, value._help) x = value.__class__(value._default, value._help)
except Exception, e: except Exception, e:
exception('Exception instantiating default for %s:', exception('Exception instantiating default for %s:' %
value._name) value._name)
try: try:
lines.append('# Default value: %s\n' % x) lines.append('# Default value: %s\n' % x)
except Exception, e: except Exception, e:
exception('Exception printing default value of %s:', exception('Exception printing default value of %s:' %
value._name) value._name)
lines.append('###\n') lines.append('###\n')
fd.writelines(lines) fd.writelines(lines)
@ -231,7 +231,7 @@ class Group(object):
parts = split(rest) parts = split(rest)
if len(parts) == 1 and parts[0] == name: if len(parts) == 1 and parts[0] == name:
try: try:
self.__makeChild(group, v) self.__makeChild(name, v)
except InvalidRegistryValue: except InvalidRegistryValue:
# It's probably supposed to be registered later. # It's probably supposed to be registered later.
pass pass

View File

@ -131,11 +131,8 @@ class Schedule(drivers.IrcDriver):
except Exception, e: except Exception, e:
log.exception('Uncaught exception in scheduled function:') log.exception('Uncaught exception in scheduled function:')
try:
ignore(schedule)
except NameError:
schedule = Schedule()
schedule = Schedule()
addEvent = schedule.addEvent addEvent = schedule.addEvent
removeEvent = schedule.removeEvent removeEvent = schedule.removeEvent
rescheduleEvent = schedule.rescheduleEvent rescheduleEvent = schedule.rescheduleEvent

3
src/version.py Normal file
View File

@ -0,0 +1,3 @@
"""stick the various versioning attributes in here, so we only have to change
them once."""
version = '0.83.4.1+git'

View File

@ -30,6 +30,7 @@
from supybot.test import * from supybot.test import *
from supybot.commands import * from supybot.commands import *
import supybot.conf as conf
import supybot.irclib as irclib import supybot.irclib as irclib
import supybot.ircmsgs as ircmsgs import supybot.ircmsgs as ircmsgs
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
@ -72,6 +73,16 @@ class GeneralContextTestCase(CommandsTestCase):
self.assertState(['int'], ['1'], [1]) self.assertState(['int'], ['1'], [1])
self.assertState(['int', 'int', 'int'], ['1', '2', '3'], [1, 2, 3]) self.assertState(['int', 'int', 'int'], ['1', '2', '3'], [1, 2, 3])
def testSpecNick(self):
strict = conf.supybot.protocols.irc.strictRfc()
try:
conf.supybot.protocols.irc.strictRfc.setValue(True)
self.assertError(['nick'], ['1abc'])
conf.supybot.protocols.irc.strictRfc.setValue(False)
self.assertState(['nick'], ['1abc'], ['1abc'])
finally:
conf.supybot.protocols.irc.strictRfc.setValue(strict)
def testSpecLong(self): def testSpecLong(self):
self.assertState(['long'], ['1'], [1L]) self.assertState(['long'], ['1'], [1L])
self.assertState(['long', 'long', 'long'], ['1', '2', '3'], self.assertState(['long', 'long', 'long'], ['1', '2', '3'],

View File

@ -292,7 +292,7 @@ class IrcStateTestCase(SupyTestCase):
def testSupportedUmodes(self): def testSupportedUmodes(self):
state = irclib.IrcState() state = irclib.IrcState()
state.addMsg(self.irc, ircmsgs.IrcMsg(':charm.oftc.net 004 charm.oftc.net hybrid-7.2.2+oftc1.6.8 CDGPRSabcdfgiklnorsuwxyz biklmnopstveI bkloveI')) state.addMsg(self.irc, ircmsgs.IrcMsg(':coulomb.oftc.net 004 testnick coulomb.oftc.net hybrid-7.2.2+oftc1.6.8 CDGPRSabcdfgiklnorsuwxyz biklmnopstveI bkloveI'))
self.assertEqual(state.supported['umodes'], 'CDGPRSabcdfgiklnorsuwxyz') self.assertEqual(state.supported['umodes'], 'CDGPRSabcdfgiklnorsuwxyz')
self.assertEqual(state.supported['chanmodes'], self.assertEqual(state.supported['chanmodes'],
'biklmnopstveI') 'biklmnopstveI')