Completely restructured our utils modules.

Tons of changes.  Here's the summary of things that matter most:

* There is no more supybot.fix.
* There is no more supybot.webutils; now there is supybot.utils.web.
  * It's no longer webutils.WebError, but just utils.web.Error.
* You shouldn't import itertools, ideally, but instead import utils.iter.
* No more using imap/ifilter in commands unless absolutely necessary.  It's
  premature optimization and annoying.
* utils.str.format isn't quite ready yet, but will be soon.  That'll be the
  next big thing to fix in our code.
This commit is contained in:
Jeremy Fincher 2005-01-27 06:59:08 +00:00
parent e62585092d
commit 5fd6bbb52d
46 changed files with 1744 additions and 1705 deletions

View File

@ -27,16 +27,9 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot
__author__ = supybot.authors.jemfinch
import supybot.fix as fix
import sys import sys
import time import time
import pprint import pprint
from itertools import imap
import supybot.log as log import supybot.log as log
import supybot.conf as conf import supybot.conf as conf
@ -164,7 +157,7 @@ class Admin(callbacks.Privmsg):
L = irc.state.channels.keys() L = irc.state.channels.keys()
if L: if L:
utils.sortBy(ircutils.toLower, L) utils.sortBy(ircutils.toLower, L)
irc.reply(utils.commaAndify(L)) irc.reply(utils.str.commaAndify(L))
else: else:
irc.reply('I\'m not currently in any channels.') irc.reply('I\'m not currently in any channels.')
channels = wrap(channels, ['private']) channels = wrap(channels, ['private'])
@ -194,7 +187,7 @@ class Admin(callbacks.Privmsg):
irc = self.pendingNickChanges.get(irc, None) irc = self.pendingNickChanges.get(irc, None)
if irc is not None: if irc is not None:
irc.error('I can\'t change nicks, the server said %s.' % irc.error('I can\'t change nicks, the server said %s.' %
utils.quoted(msg.args[2]), private=True) utils.str.quoted(msg.args[2]), private=True)
else: else:
self.log.debug('Got 438 without Admin.nick being called.') self.log.debug('Got 438 without Admin.nick being called.')
@ -332,7 +325,7 @@ class Admin(callbacks.Privmsg):
""" """
# XXX Add the expirations. # XXX Add the expirations.
if ircdb.ignores.hostmasks: if ircdb.ignores.hostmasks:
irc.reply(utils.commaAndify(imap(repr, ircdb.ignores.hostmasks))) irc.reply(utils.str.commaAndify(map(repr,ircdb.ignores.hostmasks)))
else: else:
irc.reply('I\'m not currently globally ignoring anyone.') irc.reply('I\'m not currently globally ignoring anyone.')
ignores = wrap(ignores) ignores = wrap(ignores)

View File

@ -27,11 +27,7 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot
import sets
import random import random
from itertools import imap
import babelfish import babelfish
@ -69,7 +65,7 @@ class Babelfish(callbacks.Privmsg):
Returns the languages that Babelfish can translate to/from. Returns the languages that Babelfish can translate to/from.
""" """
irc.reply(utils.commaAndify(babelfish.available_languages)) irc.reply(utils.str.commaAndify(babelfish.available_languages))
def translate(self, irc, msg, args, fromLang, toLang, text): def translate(self, irc, msg, args, fromLang, toLang, text):
"""<from-language> [to] <to-language> <text> """<from-language> [to] <to-language> <text>
@ -87,15 +83,15 @@ class Babelfish(callbacks.Privmsg):
irc.error('I do not speak any other languages.') irc.error('I do not speak any other languages.')
return return
else: else:
irc.error('I only speak %s.' % utils.commaAndify(langs)) irc.error('I only speak %s.' % utils.str.commaAndify(langs))
return return
translation = babelfish.translate(text, fromLang, toLang) translation = babelfish.translate(text, fromLang, toLang)
irc.reply(utils.htmlToText(translation)) irc.reply(utils.web.htmlToText(translation))
except (KeyError, babelfish.LanguageNotAvailableError), e: except (KeyError, babelfish.LanguageNotAvailableError), e:
languages = self.registryValue('languages', chan) languages = self.registryValue('languages', chan)
if languages: if languages:
languages = 'Valid languages include %s' % \ languages = 'Valid languages include %s' % \
utils.commaAndify(sorted(languages)) utils.str.commaAndify(sorted(languages))
else: else:
languages = 'I do not speak any other languages.' languages = 'I do not speak any other languages.'
irc.errorInvalid('language', str(e), languages) irc.errorInvalid('language', str(e), languages)
@ -125,16 +121,16 @@ class Babelfish(callbacks.Privmsg):
irc.error('I do not speak any other languages.') irc.error('I do not speak any other languages.')
return return
else: else:
irc.error('I only speak %s.' % utils.commaAndify(langs, irc.error('I only speak %s.' % utils.str.commaAndify(langs,
And='or')) And='or'))
return return
translations = babelfish.babelize(text, fromLang, toLang) translations = babelfish.babelize(text, fromLang, toLang)
irc.reply(utils.htmlToText(translations[-1])) irc.reply(utils.web.htmlToText(translations[-1]))
except (KeyError, babelfish.LanguageNotAvailableError), e: except (KeyError, babelfish.LanguageNotAvailableError), e:
languages = self.registryValue('languages', chan) languages = self.registryValue('languages', chan)
if languages: if languages:
languages = 'Valid languages include %s' % \ languages = 'Valid languages include %s' % \
utils.commaAndify(sorted(languages)) utils.str.commaAndify(sorted(languages))
else: else:
languages = 'I do not speak any other languages.' languages = 'I do not speak any other languages.'
irc.errorInvalid('language', str(e), languages) irc.errorInvalid('language', str(e), languages)

View File

@ -47,12 +47,12 @@ class BanmaskStyle(registry.SpaceSeparatedSetOfStrings):
'This is a bug.' 'This is a bug.'
registry.SpaceSeparatedSetOfStrings.__init__(self, *args, **kwargs) registry.SpaceSeparatedSetOfStrings.__init__(self, *args, **kwargs)
self.__doc__ = 'Valid values include %s.' % \ self.__doc__ = 'Valid values include %s.' % \
utils.commaAndify(map(repr, self.validStrings)) utils.str.commaAndify(map(repr, self.validStrings))
def help(self): def help(self):
strings = [s for s in self.validStrings if s] strings = [s for s in self.validStrings if s]
return '%s Valid strings: %s.' % \ return '%s Valid strings: %s.' % \
(self._help, utils.commaAndify(strings)) (self._help, utils.str.commaAndify(strings))
def normalize(self, s): def normalize(self, s):
lowered = s.lower() lowered = s.lower()

View File

@ -30,8 +30,6 @@
import sys import sys
import time import time
from itertools import imap
import supybot.conf as conf import supybot.conf as conf
import supybot.ircdb as ircdb import supybot.ircdb as ircdb
import supybot.utils as utils import supybot.utils as utils
@ -287,12 +285,12 @@ class Channel(callbacks.Privmsg):
self.log.debug('In kban') self.log.debug('In kban')
if not irc.isNick(bannedNick): if not irc.isNick(bannedNick):
self.log.warning('%s tried to kban a non nick: %s', self.log.warning('%s tried to kban a non nick: %s',
utils.quoted(msg.prefix), utils.str.quoted(msg.prefix),
utils.quoted(bannedNick)) utils.str.quoted(bannedNick))
raise callbacks.ArgumentError raise callbacks.ArgumentError
elif bannedNick == irc.nick: elif bannedNick == irc.nick:
self.log.warning('%s tried to make me kban myself.', self.log.warning('%s tried to make me kban myself.',
utils.quoted(msg.prefix)) utils.str.quoted(msg.prefix))
irc.error('I cowardly refuse to kickban myself.') irc.error('I cowardly refuse to kickban myself.')
return return
if not reason: if not reason:
@ -330,7 +328,7 @@ class Channel(callbacks.Privmsg):
if ircutils.hostmaskPatternEqual(banmask, irc.prefix): if ircutils.hostmaskPatternEqual(banmask, irc.prefix):
if ircutils.hostmaskPatternEqual(banmask, irc.prefix): if ircutils.hostmaskPatternEqual(banmask, irc.prefix):
self.log.warning('%s tried to make me kban myself.', self.log.warning('%s tried to make me kban myself.',
utils.quoted(msg.prefix)) utils.str.quoted(msg.prefix))
irc.error('I cowardly refuse to ban myself.') irc.error('I cowardly refuse to ban myself.')
return return
else: else:
@ -354,7 +352,7 @@ class Channel(callbacks.Privmsg):
elif ircdb.checkCapability(msg.prefix, capability): elif ircdb.checkCapability(msg.prefix, capability):
if ircdb.checkCapability(bannedHostmask, capability): if ircdb.checkCapability(bannedHostmask, capability):
self.log.warning('%s tried to ban %s, but both have %s', self.log.warning('%s tried to ban %s, but both have %s',
msg.prefix, utils.quoted(bannedHostmask), msg.prefix, utils.str.quoted(bannedHostmask),
capability) capability)
irc.error('%s has %s too, you can\'t ban him/her/it.' % irc.error('%s has %s too, you can\'t ban him/her/it.' %
(bannedNick, capability)) (bannedNick, capability))
@ -362,7 +360,7 @@ class Channel(callbacks.Privmsg):
doBan() doBan()
else: else:
self.log.warning('%s attempted kban without %s', self.log.warning('%s attempted kban without %s',
utils.quoted(msg.prefix), capability) utils.str.quoted(msg.prefix), capability)
irc.errorNoCapability(capability) irc.errorNoCapability(capability)
exact,nick,user,host exact,nick,user,host
kban = wrap(kban, kban = wrap(kban,
@ -515,7 +513,7 @@ class Channel(callbacks.Privmsg):
# XXX Add the expirations. # XXX Add the expirations.
c = ircdb.channels.getChannel(channel) c = ircdb.channels.getChannel(channel)
if c.bans: if c.bans:
irc.reply(utils.commaAndify(map(utils.dqrepr, c.bans))) irc.reply(utils.str.commaAndify(map(utils.str.dqrepr, c.bans)))
else: else:
irc.reply('There are currently no permanent bans on %s' % channel) irc.reply('There are currently no permanent bans on %s' % channel)
permbans = wrap(permbans, [('checkChannelCapability', 'op')]) permbans = wrap(permbans, [('checkChannelCapability', 'op')])
@ -561,11 +559,11 @@ class Channel(callbacks.Privmsg):
c = ircdb.channels.getChannel(channel) c = ircdb.channels.getChannel(channel)
if len(c.ignores) == 0: if len(c.ignores) == 0:
s = 'I\'m not currently ignoring any hostmasks in %s' % \ s = 'I\'m not currently ignoring any hostmasks in %s' % \
utils.quoted(channel) utils.str.quoted(channel)
irc.reply(s) irc.reply(s)
else: else:
L = sorted(c.ignores) L = sorted(c.ignores)
irc.reply(utils.commaAndify(imap(repr, L))) irc.reply(utils.str.commaAndify(map(repr, L)))
ignores = wrap(ignores, [('checkChannelCapability', 'op')]) ignores = wrap(ignores, [('checkChannelCapability', 'op')])
def addcapability(self, irc, msg, args, channel, user, capabilities): def addcapability(self, irc, msg, args, channel, user, capabilities):
@ -602,8 +600,9 @@ class Channel(callbacks.Privmsg):
ircdb.users.setUser(user) ircdb.users.setUser(user)
if fail: if fail:
irc.error('That user didn\'t have the %s %s.' % irc.error('That user didn\'t have the %s %s.' %
(utils.commaAndify(fail), (utils.str.commaAndify(fail),
utils.pluralize('capability', len(fail))), Raise=True) utils.str.pluralize('capability', len(fail))),
Raise=True)
irc.replySuccess() irc.replySuccess()
removecapability = wrap(removecapability, removecapability = wrap(removecapability,
[('checkChannelCapability', 'op'), [('checkChannelCapability', 'op'),
@ -662,8 +661,9 @@ class Channel(callbacks.Privmsg):
ircdb.channels.setChannel(channel, chan) ircdb.channels.setChannel(channel, chan)
if fail: if fail:
irc.error('I do not know about the %s %s.' % irc.error('I do not know about the %s %s.' %
(utils.commaAndify(fail), (utils.str.commaAndify(fail),
utils.pluralize('capability', len(fail))), Raise=True) utils.str.pluralize('capability', len(fail))),
Raise=True)
irc.replySuccess() irc.replySuccess()
unsetcapability = wrap(unsetcapability, unsetcapability = wrap(unsetcapability,
[('checkChannelCapability', 'op'), [('checkChannelCapability', 'op'),
@ -774,7 +774,7 @@ class Channel(callbacks.Privmsg):
L.append(channel) L.append(channel)
if L: if L:
L.sort() L.sort()
s = 'I\'m currently lobotomized in %s.' % utils.commaAndify(L) s = 'I\'m currently lobotomized in %s.' % utils.str.commaAndify(L)
irc.reply(s) irc.reply(s)
else: else:
irc.reply('I\'m not currently lobotomized in any channels.') irc.reply('I\'m not currently lobotomized in any channels.')
@ -787,7 +787,7 @@ class Channel(callbacks.Privmsg):
""" """
L = list(irc.state.channels[channel].users) L = list(irc.state.channels[channel].users)
utils.sortBy(str.lower, L) utils.sortBy(str.lower, L)
irc.reply(utils.commaAndify(L)) irc.reply(utils.str.commaAndify(L))
nicks = wrap(nicks, ['inChannel']) # XXX Check that the caller is in chan. nicks = wrap(nicks, ['inChannel']) # XXX Check that the caller is in chan.
def alertOps(self, irc, channel, s, frm=None): def alertOps(self, irc, channel, s, frm=None):

View File

@ -28,7 +28,6 @@
### ###
import os import os
import getopt
import signal import signal
import supybot.log as log import supybot.log as log
@ -131,7 +130,7 @@ class Config(callbacks.Privmsg):
""" """
L = self._list(group) L = self._list(group)
if L: if L:
irc.reply(utils.commaAndify(L)) irc.reply(utils.str.commaAndify(L))
else: else:
irc.error('There don\'t seem to be any values in %s.' % group._name) irc.error('There don\'t seem to be any values in %s.' % group._name)
list = wrap(list, ['configVar']) list = wrap(list, ['configVar'])
@ -148,7 +147,7 @@ class Config(callbacks.Privmsg):
if not ircutils.isChannel(possibleChannel): if not ircutils.isChannel(possibleChannel):
L.append(name) L.append(name)
if L: if L:
irc.reply(utils.commaAndify(L)) irc.reply(utils.str.commaAndify(L))
else: else:
irc.reply('There were no matching configuration variables.') irc.reply('There were no matching configuration variables.')
search = wrap(search, ['lowered']) # XXX compose with withoutSpaces? search = wrap(search, ['lowered']) # XXX compose with withoutSpaces?

View File

@ -53,7 +53,7 @@ class Dict(callbacks.Privmsg):
conn = dictclient.Connection(server) conn = dictclient.Connection(server)
dbs = conn.getdbdescs().keys() dbs = conn.getdbdescs().keys()
dbs.sort() dbs.sort()
irc.reply(utils.commaAndify(dbs)) irc.reply(utils.str.commaAndify(dbs))
except socket.error, e: except socket.error, e:
irc.error(webutils.strError(e)) irc.error(webutils.strError(e))
dictionaries = wrap(dictionaries) dictionaries = wrap(dictionaries)
@ -102,21 +102,21 @@ class Dict(callbacks.Privmsg):
if not definitions: if not definitions:
if dictionary == '*': if dictionary == '*':
irc.reply('No definition for %s could be found.' % irc.reply('No definition for %s could be found.' %
utils.quoted(word)) utils.str.quoted(word))
else: else:
irc.reply('No definition for %s could be found in %s' % irc.reply('No definition for %s could be found in %s' %
(utils.quoted(word), ircutils.bold(dictionary))) (utils.str.quoted(word), ircutils.bold(dictionary)))
return return
L = [] L = []
for d in definitions: for d in definitions:
dbs.add(ircutils.bold(d.getdb().getname())) dbs.add(ircutils.bold(d.getdb().getname()))
(db, s) = (d.getdb().getname(), d.getdefstr()) (db, s) = (d.getdb().getname(), d.getdefstr())
db = ircutils.bold(db) db = ircutils.bold(db)
s = utils.normalizeWhitespace(s).rstrip(';.,') s = utils.str.normalizeWhitespace(s).rstrip(';.,')
L.append('%s: %s' % (db, s)) L.append('%s: %s' % (db, s))
utils.sortBy(len, L) utils.sortBy(len, L)
if dictionary == '*' and len(dbs) > 1: if dictionary == '*' and len(dbs) > 1:
s = '%s responded: %s' % (utils.commaAndify(dbs), '; '.join(L)) s = '%s responded: %s' % (utils.str.commaAndify(dbs), '; '.join(L))
else: else:
s = '; '.join(L) s = '; '.join(L)
irc.reply(s) irc.reply(s)

View File

@ -144,7 +144,7 @@ class Format(callbacks.Privmsg):
Returns the text surrounded by double quotes. Returns the text surrounded by double quotes.
""" """
irc.reply(utils.dqrepr(text)) irc.reply(utils.str.dqrepr(text))
repr = wrap(repr, ['text']) repr = wrap(repr, ['text'])
def concat(self, irc, msg, args, first, second): def concat(self, irc, msg, args, first, second):

View File

@ -29,14 +29,11 @@
from __future__ import division from __future__ import division
import supybot.plugins as plugins
import re import re
import math import math
import cmath import cmath
import types import types
import string import string
from itertools import imap
import supybot.utils as utils import supybot.utils as utils
from supybot.commands import * from supybot.commands import *
@ -160,12 +157,12 @@ class Math(callbacks.Privmsg):
crash to the bot with something like 10**10**10**10. One consequence crash to the bot with something like 10**10**10**10. One consequence
is that large values such as 10**24 might not be exact. is that large values such as 10**24 might not be exact.
""" """
if text != text.translate(string.ascii, '_[]'): if text != text.translate(utils.str.chars, '_[]'):
irc.error('There\'s really no reason why you should have ' irc.error('There\'s really no reason why you should have '
'underscores or brackets in your mathematical ' 'underscores or brackets in your mathematical '
'expression. Please remove them.') 'expression. Please remove them.')
return return
#text = text.translate(string.ascii, '_[] \t') #text = text.translate(utils.str.chars, '_[] \t')
if 'lambda' in text: if 'lambda' in text:
irc.error('You can\'t use lambda in this command.') irc.error('You can\'t use lambda in this command.')
return return
@ -188,7 +185,7 @@ class Math(callbacks.Privmsg):
text = self._mathRe.sub(handleMatch, text) text = self._mathRe.sub(handleMatch, text)
try: try:
self.log.info('evaluating %s from %s' % self.log.info('evaluating %s from %s' %
(utils.quoted(text), msg.prefix)) (utils.str.quoted(text), msg.prefix))
x = complex(eval(text, self._mathEnv, self._mathEnv)) x = complex(eval(text, self._mathEnv, self._mathEnv))
irc.reply(self._complexToString(x)) irc.reply(self._complexToString(x))
except OverflowError: except OverflowError:
@ -209,21 +206,21 @@ class Math(callbacks.Privmsg):
math, and can thus cause the bot to suck up CPU. Hence it requires math, and can thus cause the bot to suck up CPU. Hence it requires
the 'trusted' capability to use. the 'trusted' capability to use.
""" """
if text != text.translate(string.ascii, '_[]'): if text != text.translate(utils.str.chars, '_[]'):
irc.error('There\'s really no reason why you should have ' irc.error('There\'s really no reason why you should have '
'underscores or brackets in your mathematical ' 'underscores or brackets in your mathematical '
'expression. Please remove them.') 'expression. Please remove them.')
return return
# This removes spaces, too, but we'll leave the removal of _[] for # This removes spaces, too, but we'll leave the removal of _[] for
# safety's sake. # safety's sake.
text = text.translate(string.ascii, '_[] \t') text = text.translate(utils.str.chars, '_[] \t')
if 'lambda' in text: if 'lambda' in text:
irc.error('You can\'t use lambda in this command.') irc.error('You can\'t use lambda in this command.')
return return
text = text.replace('lambda', '') text = text.replace('lambda', '')
try: try:
self.log.info('evaluating %s from %s' % self.log.info('evaluating %s from %s' %
(utils.quoted(text), msg.prefix)) (utils.str.quoted(text), msg.prefix))
irc.reply(str(eval(text, self._mathEnv, self._mathEnv))) irc.reply(str(eval(text, self._mathEnv, self._mathEnv)))
except OverflowError: except OverflowError:
maxFloat = math.ldexp(0.9999999999999999, 1024) maxFloat = math.ldexp(0.9999999999999999, 1024)
@ -280,12 +277,12 @@ class Math(callbacks.Privmsg):
stack.append(eval(s, self._mathEnv, self._mathEnv)) stack.append(eval(s, self._mathEnv, self._mathEnv))
except SyntaxError: except SyntaxError:
irc.error('%s is not a defined function.' % irc.error('%s is not a defined function.' %
utils.quoted(arg)) utils.str.quoted(arg))
return return
if len(stack) == 1: if len(stack) == 1:
irc.reply(str(self._complexToString(complex(stack[0])))) irc.reply(str(self._complexToString(complex(stack[0]))))
else: else:
s = ', '.join(imap(self._complexToString, imap(complex, stack))) s = ', '.join(map(self._complexToString, map(complex, stack)))
irc.reply('Stack: [%s]' % s) irc.reply('Stack: [%s]' % s)
def convert(self, irc, msg, args, number, unit1, unit2): def convert(self, irc, msg, args, number, unit1, unit2):

View File

@ -27,16 +27,10 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot
import supybot.fix as fix
import os import os
import sys import sys
import time import time
from itertools import imap, ifilter
import supybot.log as log import supybot.log as log
import supybot.conf as conf import supybot.conf as conf
import supybot.utils as utils import supybot.utils as utils
@ -46,9 +40,10 @@ import supybot.ircdb as ircdb
import supybot.irclib as irclib import supybot.irclib as irclib
import supybot.ircmsgs as ircmsgs import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.webutils as webutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
from supybot.utils.iter import ifilter
class Misc(callbacks.Privmsg): class Misc(callbacks.Privmsg):
def __init__(self): def __init__(self):
super(Misc, self).__init__() super(Misc, self).__init__()
@ -123,7 +118,7 @@ class Misc(callbacks.Privmsg):
(not private and isPublic(cb))] (not private and isPublic(cb))]
names.sort() names.sort()
if names: if names:
irc.reply(utils.commaAndify(names)) irc.reply(utils.str.commaAndify(names))
else: else:
if private: if private:
irc.reply('There are no private plugins.') irc.reply('There are no private plugins.')
@ -148,7 +143,7 @@ class Misc(callbacks.Privmsg):
commands.append(s) commands.append(s)
if commands: if commands:
commands.sort() commands.sort()
irc.reply(utils.commaAndify(commands)) irc.reply(utils.str.commaAndify(commands))
else: else:
irc.error('That plugin exists, but it has no ' irc.error('That plugin exists, but it has no '
'commands with help.') 'commands with help.')
@ -177,7 +172,7 @@ class Misc(callbacks.Privmsg):
L.append('%s %s' % (name, key)) L.append('%s %s' % (name, key))
if L: if L:
L.sort() L.sort()
irc.reply(utils.commaAndify(L)) irc.reply(utils.str.commaAndify(L))
else: else:
irc.reply('No appropriate commands were found.') irc.reply('No appropriate commands were found.')
apropos = wrap(apropos, ['lowered']) apropos = wrap(apropos, ['lowered'])
@ -211,7 +206,7 @@ class Misc(callbacks.Privmsg):
names = sorted([cb.name() for cb in cbs]) names = sorted([cb.name() for cb in cbs])
irc.error('That command exists in the %s plugins. ' irc.error('That command exists in the %s plugins. '
'Please specify exactly which plugin command ' 'Please specify exactly which plugin command '
'you want help with.'% utils.commaAndify(names)) 'you want help with.'% utils.str.commaAndify(names))
else: else:
getHelp(cbs[0]) getHelp(cbs[0])
else: else:
@ -235,9 +230,9 @@ class Misc(callbacks.Privmsg):
Returns the version of the current bot. Returns the version of the current bot.
""" """
try: try:
newest = webutils.getUrl('http://supybot.sf.net/version.txt') newest = utils.web.getUrl('http://supybot.sf.net/version.txt')
newest ='The newest version available online is %s.'%newest.strip() newest ='The newest version available online is %s.'%newest.strip()
except webutils.WebError, e: except utils.web.Error, e:
self.log.warning('Couldn\'t get website version: %r', e) self.log.warning('Couldn\'t get website version: %r', e)
newest = 'I couldn\'t fetch the newest version ' \ newest = 'I couldn\'t fetch the newest version ' \
'from the Supybot website.' 'from the Supybot website.'
@ -265,13 +260,13 @@ class Misc(callbacks.Privmsg):
if cbs: if cbs:
names = [cb.name() for cb in cbs] names = [cb.name() for cb in cbs]
names.sort() names.sort()
plugin = utils.commaAndify(names) plugin = utils.str.commaAndify(names)
if irc.nested: if irc.nested:
irc.reply(utils.commaAndify(names)) irc.reply(utils.str.commaAndify(names))
else: else:
irc.reply('The %s command is available in the %s %s.' % irc.reply('The %s command is available in the %s %s.' %
(utils.quoted(command), plugin, (utils.str.quoted(command), plugin,
utils.pluralize('plugin', len(names)))) utils.str.pluralize('plugin', len(names))))
else: else:
irc.error('There is no such command %s.' % command) irc.error('There is no such command %s.' % command)
plugin = wrap(plugin, ['commandName']) plugin = wrap(plugin, ['commandName'])
@ -287,7 +282,7 @@ class Misc(callbacks.Privmsg):
return return
module = sys.modules[cb.__class__.__module__] module = sys.modules[cb.__class__.__module__]
if hasattr(module, '__author__') and module.__author__: if hasattr(module, '__author__') and module.__author__:
irc.reply(utils.mungeEmailForWeb(str(module.__author__))) irc.reply(utils.web.mungeEmail(str(module.__author__)))
else: else:
irc.reply('That plugin doesn\'t have an author that claims it.') irc.reply('That plugin doesn\'t have an author that claims it.')
author = wrap(author, [('plugin')]) author = wrap(author, [('plugin')])
@ -317,7 +312,7 @@ class Misc(callbacks.Privmsg):
chunk = L.pop() chunk = L.pop()
if L: if L:
chunk += ' \x02(%s)\x0F' % \ chunk += ' \x02(%s)\x0F' % \
utils.nItems('message', len(L), 'more') utils.str.nItems('message', len(L), 'more')
irc.reply(chunk, True) irc.reply(chunk, True)
except KeyError: except KeyError:
irc.error('You haven\'t asked me a command; perhaps you want ' irc.error('You haven\'t asked me a command; perhaps you want '
@ -380,7 +375,7 @@ class Misc(callbacks.Privmsg):
nolimit = True nolimit = True
iterable = ifilter(self._validLastMsg, reversed(irc.state.history)) iterable = ifilter(self._validLastMsg, reversed(irc.state.history))
iterable.next() # Drop the first message. iterable.next() # Drop the first message.
predicates = list(utils.flatten(predicates.itervalues())) predicates = list(utils.iter.flatten(predicates.itervalues()))
resp = [] resp = []
if irc.nested and not \ if irc.nested and not \
self.registryValue('last.nested.includeTimestamp'): self.registryValue('last.nested.includeTimestamp'):
@ -409,7 +404,7 @@ class Misc(callbacks.Privmsg):
irc.error('I couldn\'t find a message matching that criteria in ' irc.error('I couldn\'t find a message matching that criteria in '
'my history of %s messages.' % len(irc.state.history)) 'my history of %s messages.' % len(irc.state.history))
else: else:
irc.reply(utils.commaAndify(resp)) irc.reply(utils.str.commaAndify(resp))
last = wrap(last, [getopts({'nolimit': '', last = wrap(last, [getopts({'nolimit': '',
'on': 'something', 'on': 'something',
'with': 'something', 'with': 'something',
@ -496,7 +491,7 @@ class Misc(callbacks.Privmsg):
shortname[, shortname and shortname]. shortname[, shortname and shortname].
""" """
L = [getShortName(n) for n in longList] L = [getShortName(n) for n in longList]
return utils.commaAndify(L) return utils.str.commaAndify(L)
def sortAuthors(): def sortAuthors():
""" """
Sort the list of 'long names' based on the number of contributions Sort the list of 'long names' based on the number of contributions
@ -520,7 +515,7 @@ class Misc(callbacks.Privmsg):
hasContribs = False hasContribs = False
if getattr(module, '__author__', None): if getattr(module, '__author__', None):
author = 'was written by %s' % \ author = 'was written by %s' % \
utils.mungeEmailForWeb(str(module.__author__)) utils.web.mungeEmail(str(module.__author__))
hasAuthor = True hasAuthor = True
if getattr(module, '__contributors__', None): if getattr(module, '__contributors__', None):
contribs = sortAuthors() contribs = sortAuthors()
@ -532,7 +527,7 @@ class Misc(callbacks.Privmsg):
if contribs: if contribs:
contrib = '%s %s contributed to it.' % \ contrib = '%s %s contributed to it.' % \
(buildContributorsString(contribs), (buildContributorsString(contribs),
utils.has(len(contribs))) utils.str.has(len(contribs)))
hasContribs = True hasContribs = True
elif hasAuthor: elif hasAuthor:
contrib = 'has no additional contributors listed' contrib = 'has no additional contributors listed'
@ -549,7 +544,7 @@ class Misc(callbacks.Privmsg):
if not authorInfo: if not authorInfo:
return 'The nick specified (%s) is not a registered ' \ return 'The nick specified (%s) is not a registered ' \
'contributor' % nick 'contributor' % nick
fullName = utils.mungeEmailForWeb(str(authorInfo)) fullName = utils.web.mungeEmail(str(authorInfo))
contributions = [] contributions = []
if hasattr(module, '__contributors__'): if hasattr(module, '__contributors__'):
if authorInfo not in module.__contributors__: if authorInfo not in module.__contributors__:
@ -558,22 +553,21 @@ class Misc(callbacks.Privmsg):
contributions = module.__contributors__[authorInfo] contributions = module.__contributors__[authorInfo]
if getattr(module, '__author__', False) == authorInfo: if getattr(module, '__author__', False) == authorInfo:
isAuthor = True isAuthor = True
# XXX Partition needs moved to utils. (nonCommands, commands) = utils.iter.partition(lambda s: ' ' in s,
(nonCommands, commands) = fix.partition(lambda s: ' ' in s,
contributions) contributions)
results = [] results = []
if commands: if commands:
results.append( results.append(
'the %s %s' %(utils.commaAndify(commands), 'the %s %s' %(utils.str.commaAndify(commands),
utils.pluralize('command',len(commands)))) utils.str.pluralize('command',len(commands))))
if nonCommands: if nonCommands:
results.append('the %s' % utils.commaAndify(nonCommands)) results.append('the %s' % utils.str.commaAndify(nonCommands))
if results and isAuthor: if results and isAuthor:
return '%s wrote the %s plugin and also contributed %s' % \ return '%s wrote the %s plugin and also contributed %s' % \
(fullName, cb.name(), utils.commaAndify(results)) (fullName, cb.name(), utils.str.commaAndify(results))
elif results and not isAuthor: elif results and not isAuthor:
return '%s contributed %s to the %s plugin' % \ return '%s contributed %s to the %s plugin' % \
(fullName, utils.commaAndify(results), cb.name()) (fullName, utils.str.commaAndify(results), cb.name())
elif isAuthor and not results: elif isAuthor and not results:
return '%s wrote the %s plugin' % (fullName, cb.name()) return '%s wrote the %s plugin' % (fullName, cb.name())
else: else:

View File

@ -27,10 +27,6 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot
import supybot.plugins as plugins
import time import time
import supybot.conf as conf import supybot.conf as conf
@ -172,19 +168,19 @@ class Network(callbacks.Privmsg):
normal.append(channel) normal.append(channel)
L = [] L = []
if ops: if ops:
L.append('is an op on %s' % utils.commaAndify(ops)) L.append('is an op on %s' % utils.str.commaAndify(ops))
if halfops: if halfops:
L.append('is a halfop on %s' % utils.commaAndify(halfops)) L.append('is a halfop on %s' % utils.str.commaAndify(halfops))
if voices: if voices:
L.append('is voiced on %s' % utils.commaAndify(voices)) L.append('is voiced on %s' % utils.str.commaAndify(voices))
if normal: if normal:
if L: if L:
L.append('is also on %s' % utils.commaAndify(normal)) L.append('is also on %s' % utils.str.commaAndify(normal))
else: else:
L.append('is on %s' % utils.commaAndify(normal)) L.append('is on %s' % utils.str.commaAndify(normal))
else: else:
L = ['isn\'t on any non-secret channels'] L = ['isn\'t on any non-secret channels']
channels = utils.commaAndify(L) channels = utils.str.commaAndify(L)
if '317' in d: if '317' in d:
idle = utils.timeElapsed(d['317'].args[2]) idle = utils.timeElapsed(d['317'].args[2])
signon = time.strftime(conf.supybot.reply.format.time(), signon = time.strftime(conf.supybot.reply.format.time(),
@ -246,7 +242,7 @@ class Network(callbacks.Privmsg):
""" """
L = ['%s: %s' % (ircd.network, ircd.server) for ircd in world.ircs] L = ['%s: %s' % (ircd.network, ircd.server) for ircd in world.ircs]
utils.sortBy(str.lower, L) utils.sortBy(str.lower, L)
irc.reply(utils.commaAndify(L)) irc.reply(utils.str.commaAndify(L))
networks = wrap(networks) networks = wrap(networks)
def doPong(self, irc, msg): def doPong(self, irc, msg):

View File

@ -27,16 +27,12 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot.fix as fix
import gc import gc
import os import os
import imp import imp
import sre import sre
import sys import sys
import getopt
import socket import socket
import logging
import linecache import linecache
import supybot.log as log import supybot.log as log
@ -424,7 +420,7 @@ class Owner(callbacks.Privmsg):
return return
except ImportError, e: except ImportError, e:
if name in str(e): if name in str(e):
irc.error('No plugin named %s exists.' % utils.dqrepr(name)) irc.error('No plugin named %s exists.' % utils.str.dqrepr(name))
else: else:
irc.error(str(e)) irc.error(str(e))
return return

View File

@ -76,9 +76,9 @@ class Status(callbacks.Privmsg):
networks.setdefault(Irc.network, []).append(Irc.nick) networks.setdefault(Irc.network, []).append(Irc.nick)
networks = networks.items() networks = networks.items()
networks.sort() networks.sort()
networks = ['%s as %s' % (net, utils.commaAndify(nicks)) networks = ['%s as %s' % (net, utils.str.commaAndify(nicks))
for (net, nicks) in networks] for (net, nicks) in networks]
L = ['I am connected to %s.' % utils.commaAndify(networks)] L = ['I am connected to %s.' % utils.str.commaAndify(networks)]
if world.profiling: if world.profiling:
L.append('I am currently in code profiling mode.') L.append('I am currently in code profiling mode.')
irc.reply(' '.join(L)) irc.reply(' '.join(L))
@ -92,9 +92,10 @@ class Status(callbacks.Privmsg):
threads = [t.getName() for t in threading.enumerate()] threads = [t.getName() for t in threading.enumerate()]
threads.sort() threads.sort()
s = 'I have spawned %s; %s %s still currently active: %s.' % \ s = 'I have spawned %s; %s %s still currently active: %s.' % \
(utils.nItems('thread', world.threadsSpawned), (utils.str.nItems('thread', world.threadsSpawned),
utils.nItems('thread', len(threads)), utils.be(len(threads)), utils.str.nItems('thread', len(threads)),
utils.commaAndify(threads)) utils.str.be(len(threads)),
utils.str.commaAndify(threads))
irc.reply(s) irc.reply(s)
threads = wrap(threads) threads = wrap(threads)
@ -137,7 +138,7 @@ class Status(callbacks.Privmsg):
'of system time, for a total of %.2f seconds of CPU ' \ 'of system time, for a total of %.2f seconds of CPU ' \
'time. %s' % (user, system, user + system, children) 'time. %s' % (user, system, user + system, children)
if self.registryValue('cpu.threads', target): if self.registryValue('cpu.threads', target):
spawned = utils.nItems('thread', world.threadsSpawned) spawned = utils.str.nItems('thread', world.threadsSpawned)
response += 'I have spawned %s; I currently have %s still ' \ response += 'I have spawned %s; I currently have %s still ' \
'running.' % (spawned, activeThreads) 'running.' % (spawned, activeThreads)
if self.registryValue('cpu.memory', target): if self.registryValue('cpu.memory', target):
@ -159,7 +160,7 @@ class Status(callbacks.Privmsg):
response += ' I\'m taking up %s kB of memory.' % mem response += ' I\'m taking up %s kB of memory.' % mem
except Exception: except Exception:
self.log.exception('Uncaught exception in cpu.memory:') self.log.exception('Uncaught exception in cpu.memory:')
irc.reply(utils.normalizeWhitespace(response)) irc.reply(utils.str.normalizeWhitespace(response))
cpu = wrap(cpu) cpu = wrap(cpu)
def cmd(self, irc, msg, args): def cmd(self, irc, msg, args):
@ -178,9 +179,9 @@ class Status(callbacks.Privmsg):
attr == callbacks.canonicalName(attr): attr == callbacks.canonicalName(attr):
commands += 1 commands += 1
s = 'I offer a total of %s in %s. I have processed %s.' % \ s = 'I offer a total of %s in %s. I have processed %s.' % \
(utils.nItems('command', commands), (utils.str.nItems('command', commands),
utils.nItems('plugin', callbacksPrivmsg, 'command-based'), utils.str.nItems('plugin', callbacksPrivmsg, 'command-based'),
utils.nItems('command', world.commandsProcessed)) utils.str.nItems('command', world.commandsProcessed))
irc.reply(s) irc.reply(s)
cmd = wrap(cmd) cmd = wrap(cmd)
@ -199,7 +200,7 @@ class Status(callbacks.Privmsg):
commands.add(attr) commands.add(attr)
commands = list(commands) commands = list(commands)
commands.sort() commands.sort()
irc.reply(utils.commaAndify(commands)) irc.reply(utils.str.commaAndify(commands))
commands = wrap(commands) commands = wrap(commands)
def uptime(self, irc, msg, args): def uptime(self, irc, msg, args):

View File

@ -27,12 +27,8 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot.fix as fix
import re import re
import getopt
import fnmatch import fnmatch
from itertools import imap, ifilter
import supybot.conf as conf import supybot.conf as conf
import supybot.utils as utils import supybot.utils as utils
@ -75,7 +71,7 @@ class User(callbacks.Privmsg):
users.append(u.name) users.append(u.name)
if users: if users:
utils.sortBy(str.lower, users) utils.sortBy(str.lower, users)
irc.reply(utils.commaAndify(users)) irc.reply(utils.str.commaAndify(users))
else: else:
if predicates: if predicates:
irc.reply('There are no matching registered users.') irc.reply('There are no matching registered users.')
@ -158,7 +154,7 @@ class User(callbacks.Privmsg):
""" """
try: try:
id = ircdb.users.getUserId(newname) id = ircdb.users.getUserId(newname)
irc.error('%s is already registered.' % utils.quoted(newname)) irc.error('%s is already registered.' % utils.str.quoted(newname))
return return
except KeyError: except KeyError:
pass pass
@ -295,7 +291,7 @@ class User(callbacks.Privmsg):
def getHostmasks(user): def getHostmasks(user):
hostmasks = map(repr, user.hostmasks) hostmasks = map(repr, user.hostmasks)
hostmasks.sort() hostmasks.sort()
return utils.commaAndify(hostmasks) return utils.str.commaAndify(hostmasks)
try: try:
user = ircdb.users.getUser(msg.prefix) user = ircdb.users.getUser(msg.prefix)
if name: if name:
@ -418,8 +414,8 @@ class User(callbacks.Privmsg):
irc.reply('I have %s registered users ' irc.reply('I have %s registered users '
'with %s registered hostmasks; ' 'with %s registered hostmasks; '
'%s and %s.' % (users, hostmasks, '%s and %s.' % (users, hostmasks,
utils.nItems('owner', owners), utils.str.nItems('owner', owners),
utils.nItems('admin', admins))) utils.str.nItems('admin', admins)))
stats = wrap(stats) stats = wrap(stats)

View File

@ -27,10 +27,6 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
__revision__ = "$Id: __init__.py,v 1.44 2005/01/08 07:22:46 jamessan Exp $"
import supybot.fix as fix
import gc import gc
import os import os
import re import re
@ -106,7 +102,7 @@ class NoSuitableDatabase(Exception):
return 'No suitable databases were found. Suitable databases ' \ return 'No suitable databases were found. Suitable databases ' \
'include %s. If you have one of these databases installed, ' \ 'include %s. If you have one of these databases installed, ' \
'make sure it is listed in the supybot.databases ' \ 'make sure it is listed in the supybot.databases ' \
'configuration variable.' % utils.commaAndify(self.suitable) 'configuration variable.' % utils.str.commaAndify(self.suitable)
def DB(filename, types): def DB(filename, types):
filename = conf.supybot.directories.data.dirize(filename) filename = conf.supybot.directories.data.dirize(filename)
@ -325,7 +321,7 @@ class ChannelUserDB(ChannelUserDictionary):
log.debug('Exception: %s', utils.exnToString(e)) log.debug('Exception: %s', utils.exnToString(e))
def flush(self): def flush(self):
fd = utils.transactionalFile(self.filename, makeBackupIfSmaller=False) fd = utils.file.AtomicFile(self.filename, makeBackupIfSmaller=False)
writer = csv.writer(fd) writer = csv.writer(fd)
items = self.items() items = self.items()
if not items: if not items:
@ -389,9 +385,9 @@ class ChannelIdDatabasePlugin(callbacks.Privmsg):
def getCommandHelp(self, name): def getCommandHelp(self, name):
help = self.__parent.getCommandHelp(name) help = self.__parent.getCommandHelp(name)
help = help.replace('$Types', utils.pluralize(self.name())) help = help.replace('$Types', utils.str.pluralize(self.name()))
help = help.replace('$Type', self.name()) help = help.replace('$Type', self.name())
help = help.replace('$types', utils.pluralize(self.name().lower())) help = help.replace('$types', utils.str.pluralize(self.name().lower()))
help = help.replace('$type', self.name().lower()) help = help.replace('$type', self.name().lower())
return help return help
@ -442,7 +438,7 @@ class ChannelIdDatabasePlugin(callbacks.Privmsg):
remove = wrap(remove, ['user', 'channeldb', 'id']) remove = wrap(remove, ['user', 'channeldb', 'id'])
def searchSerializeRecord(self, record): def searchSerializeRecord(self, record):
text = utils.quoted(utils.ellipsisify(record.text, 50)) text = utils.str.quoted(utils.str.ellipsisify(record.text, 50))
return '#%s: %s' % (record.id, text) return '#%s: %s' % (record.id, text)
def search(self, irc, msg, args, channel, optlist, glob): def search(self, irc, msg, args, channel, optlist, glob):
@ -471,10 +467,10 @@ class ChannelIdDatabasePlugin(callbacks.Privmsg):
L.append(self.searchSerializeRecord(record)) L.append(self.searchSerializeRecord(record))
if L: if L:
L.sort() L.sort()
irc.reply('%s found: %s' % (len(L), utils.commaAndify(L))) irc.reply('%s found: %s' % (len(L), utils.str.commaAndify(L)))
else: else:
irc.reply('No matching %s were found.' % irc.reply('No matching %s were found.' %
utils.pluralize(self.name().lower())) utils.str.pluralize(self.name().lower()))
search = wrap(search, ['channeldb', search = wrap(search, ['channeldb',
getopts({'by': 'otherUser', getopts({'by': 'otherUser',
'regexp': 'regexpMatcher'}), 'regexp': 'regexpMatcher'}),
@ -485,7 +481,8 @@ class ChannelIdDatabasePlugin(callbacks.Privmsg):
at = time.localtime(record.at) at = time.localtime(record.at)
timeS = time.strftime(conf.supybot.reply.format.time(), at) timeS = time.strftime(conf.supybot.reply.format.time(), at)
return '%s #%s: %s (added by %s at %s)' % \ return '%s #%s: %s (added by %s at %s)' % \
(self.name(), record.id, utils.quoted(record.text), name, timeS) (self.name(), record.id,
utils.str.quoted(record.text), name, timeS)
def get(self, irc, msg, args, channel, id): def get(self, irc, msg, args, channel, id):
"""[<channel>] <id> """[<channel>] <id>
@ -527,7 +524,7 @@ class ChannelIdDatabasePlugin(callbacks.Privmsg):
""" """
n = self.db.size(channel) n = self.db.size(channel)
irc.reply('There %s %s in my database.' % irc.reply('There %s %s in my database.' %
(utils.be(n), utils.nItems(self.name().lower(), n))) (utils.str.be(n), utils.str.nItems(self.name().lower(), n)))
stats = wrap(stats, ['channeldb']) stats = wrap(stats, ['channeldb'])
@ -592,7 +589,7 @@ class PeriodicFileDownloader(object):
self.log.warning('Error downloading %s: %s', url, e) self.log.warning('Error downloading %s: %s', url, e)
return return
confDir = conf.supybot.directories.data() confDir = conf.supybot.directories.data()
newFilename = os.path.join(confDir, utils.mktemp()) newFilename = os.path.join(confDir, utils.file.mktemp())
outfd = file(newFilename, 'wb') outfd = file(newFilename, 'wb')
start = time.time() start = time.time()
s = infd.read(4096) s = infd.read(4096)

View File

@ -33,7 +33,7 @@
This is the main program to run Supybot. This is the main program to run Supybot.
""" """
import supybot
import re import re
import os import os

View File

@ -103,11 +103,13 @@ if clean:
sys.exit(-1) sys.exit(-1)
packages = ['supybot', packages = ['supybot',
'supybot.utils',
'supybot.drivers', 'supybot.drivers',
'supybot.plugins',] + \ 'supybot.plugins',] + \
['supybot.plugins.'+s for s in plugins] ['supybot.plugins.'+s for s in plugins]
package_dir = {'supybot': 'src', package_dir = {'supybot': 'src',
'supybot.utils': 'src/utils',
'supybot.plugins': 'plugins', 'supybot.plugins': 'plugins',
'supybot.drivers': 'src/drivers',} 'supybot.drivers': 'src/drivers',}

View File

@ -38,10 +38,6 @@ how to use them.
import supybot import supybot
import supybot.fix as fix
import re import re
import copy import copy
import sets import sets
@ -166,7 +162,7 @@ def canonicalName(command):
while command and command[-1] in special: while command and command[-1] in special:
reAppend = command[-1] + reAppend reAppend = command[-1] + reAppend
command = command[:-1] command = command[:-1]
return command.translate(string.ascii, special).lower() + reAppend return command.translate(utils.str.chars, special).lower() + reAppend
def reply(msg, s, prefixName=None, private=None, def reply(msg, s, prefixName=None, private=None,
notice=None, to=None, action=None, error=False): notice=None, to=None, action=None, error=False):
@ -233,7 +229,7 @@ def getHelp(method, name=None):
if doclines: if doclines:
help = ' '.join(doclines) help = ' '.join(doclines)
s = '(%s) -- %s' % (ircutils.bold(s), help) s = '(%s) -- %s' % (ircutils.bold(s), help)
return utils.normalizeWhitespace(s) return utils.str.normalizeWhitespace(s)
def getSyntax(method, name=None): def getSyntax(method, name=None):
if name is None: if name is None:
@ -256,10 +252,10 @@ class Tokenizer(object):
# #
# These are the characters valid in a token. Everything printable except # These are the characters valid in a token. Everything printable except
# double-quote, left-bracket, and right-bracket. # double-quote, left-bracket, and right-bracket.
validChars = string.ascii.translate(string.ascii, '\x00\r\n \t') validChars = utils.str.chars.translate(utils.str.chars, '\x00\r\n \t')
def __init__(self, brackets='', pipe=False, quotes='"'): def __init__(self, brackets='', pipe=False, quotes='"'):
if brackets: if brackets:
self.validChars = self.validChars.translate(string.ascii, brackets) self.validChars=self.validChars.translate(utils.str.chars, brackets)
self.left = brackets[0] self.left = brackets[0]
self.right = brackets[1] self.right = brackets[1]
else: else:
@ -267,9 +263,9 @@ class Tokenizer(object):
self.right = '' self.right = ''
self.pipe = pipe self.pipe = pipe
if self.pipe: if self.pipe:
self.validChars = self.validChars.translate(string.ascii, '|') self.validChars = self.validChars.translate(utils.str.chars, '|')
self.quotes = quotes self.quotes = quotes
self.validChars = self.validChars.translate(string.ascii, quotes) self.validChars = self.validChars.translate(utils.str.chars, quotes)
def _handleToken(self, token): def _handleToken(self, token):
@ -459,7 +455,7 @@ class RichReplyMethods(object):
if prefixer is None: if prefixer is None:
prefixer = '' prefixer = ''
if joiner is None: if joiner is None:
joiner = utils.commaAndify joiner = utils.str.commaAndify
if isinstance(prefixer, basestring): if isinstance(prefixer, basestring):
prefixer = prefixer.__add__ prefixer = prefixer.__add__
if isinstance(joiner, basestring): if isinstance(joiner, basestring):
@ -494,7 +490,7 @@ class RichReplyMethods(object):
kwargs['Raise'] = True kwargs['Raise'] = True
if isinstance(capability, basestring): # checkCommandCapability! if isinstance(capability, basestring): # checkCommandCapability!
log.warning('Denying %s for lacking %s capability.', log.warning('Denying %s for lacking %s capability.',
self.msg.prefix, utils.quoted(capability)) self.msg.prefix, utils.str.quoted(capability))
if not self._getConfig(conf.supybot.reply.error.noCapability): if not self._getConfig(conf.supybot.reply.error.noCapability):
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)
@ -750,7 +746,7 @@ class IrcObjectProxy(RichReplyMethods):
'Please specify the plugin whose command you ' 'Please specify the plugin whose command you '
'wish to call by using its name as a command ' 'wish to call by using its name as a command '
'before %s.' % 'before %s.' %
(command, utils.commaAndify(names), command)) (command, utils.str.commaAndify(names), command))
else: else:
cb = cbs[0] cb = cbs[0]
del self.args[0] # Remove the command. del self.args[0] # Remove the command.
@ -870,7 +866,7 @@ class IrcObjectProxy(RichReplyMethods):
response = msgs.pop() response = msgs.pop()
if msgs: if msgs:
n = ircutils.bold('(%s)') n = ircutils.bold('(%s)')
n %= utils.nItems('message', len(msgs), 'more') n %= utils.str.nItems('message', len(msgs), 'more')
response = '%s %s' % (response, n) response = '%s %s' % (response, n)
prefix = msg.prefix prefix = msg.prefix
if self.to and ircutils.isNick(self.to): if self.to and ircutils.isNick(self.to):
@ -1123,7 +1119,7 @@ class Privmsg(irclib.IrcCallback):
"""Gets the given command from this plugin.""" """Gets the given command from this plugin."""
name = canonicalName(name) name = canonicalName(name)
assert self.isCommand(name), '%s is not a command.' % \ assert self.isCommand(name), '%s is not a command.' % \
utils.quoted(name) utils.str.quoted(name)
return getattr(self, name) return getattr(self, name)
def callCommand(self, name, irc, msg, *L, **kwargs): def callCommand(self, name, irc, msg, *L, **kwargs):
@ -1155,11 +1151,11 @@ class Privmsg(irclib.IrcCallback):
command = self.getCommand(name) command = self.getCommand(name)
if hasattr(command, 'isDispatcher') and \ if hasattr(command, 'isDispatcher') and \
command.isDispatcher and self.__doc__: command.isDispatcher and self.__doc__:
return utils.normalizeWhitespace(self.__doc__) return utils.str.normalizeWhitespace(self.__doc__)
elif hasattr(command, '__doc__'): elif hasattr(command, '__doc__'):
return getHelp(command) return getHelp(command)
else: else:
return 'The %s command has no help.' % utils.quoted(name) return 'The %s command has no help.' % utils.str.quoted(name)
def registryValue(self, name, channel=None, value=True): def registryValue(self, name, channel=None, value=True):
plugin = self.name() plugin = self.name()
@ -1291,7 +1287,7 @@ class PrivmsgRegexp(Privmsg):
self.res.append((r, name)) self.res.append((r, name))
except re.error, e: except re.error, e:
self.log.warning('Invalid regexp: %s (%s)', self.log.warning('Invalid regexp: %s (%s)',
utils.quoted(value.__doc__), e) utils.str.quoted(value.__doc__), e)
utils.sortBy(operator.itemgetter(1), self.res) utils.sortBy(operator.itemgetter(1), self.res)
def callCommand(self, name, irc, msg, *L, **kwargs): def callCommand(self, name, irc, msg, *L, **kwargs):

View File

@ -32,10 +32,6 @@ Database module, similar to dbhash. Uses a format similar to (if not entirely
the same as) DJB's CDB <http://cr.yp.to/cdb.html>. the same as) DJB's CDB <http://cr.yp.to/cdb.html>.
""" """
import supybot.fix as fix
import os import os
import sys import sys
import sets import sets
@ -134,7 +130,7 @@ def make(dbFilename, readFilename=None):
class Maker(object): class Maker(object):
"""Class for making CDB databases.""" """Class for making CDB databases."""
def __init__(self, filename): def __init__(self, filename):
self.fd = utils.transactionalFile(filename) self.fd = utils.file.AtomicFile(filename)
self.filename = filename self.filename = filename
self.fd.seek(2048) self.fd.seek(2048)
self.hashPointers = [(0, 0)] * 256 self.hashPointers = [(0, 0)] * 256

View File

@ -31,10 +31,6 @@
Includes wrappers for commands. Includes wrappers for commands.
""" """
import supybot.fix as fix
import time import time
import types import types
import getopt import getopt
@ -233,7 +229,7 @@ def getExpiry(irc, msg, args, state):
def getBoolean(irc, msg, args, state): def getBoolean(irc, msg, args, state):
try: try:
state.args.append(utils.toBool(args[0])) state.args.append(utils.str.toBool(args[0]))
del args[0] del args[0]
except ValueError: except ValueError:
irc.errorInvalid('boolean', args[0]) irc.errorInvalid('boolean', args[0])
@ -324,8 +320,8 @@ def _getRe(f):
irc.errorInvalid('regular expression', s) irc.errorInvalid('regular expression', s)
return get return get
getMatcher = _getRe(utils.perlReToPythonRe) getMatcher = _getRe(utils.str.perlReToPythonRe)
getReplacer = _getRe(utils.perlReToReplacer) getReplacer = _getRe(utils.str.perlReToReplacer)
def getNick(irc, msg, args, state): def getNick(irc, msg, args, state):
if ircutils.isNick(args[0]): if ircutils.isNick(args[0]):
@ -494,7 +490,7 @@ def getCommandName(irc, msg, args, state):
state.args.append(callbacks.canonicalName(args.pop(0))) state.args.append(callbacks.canonicalName(args.pop(0)))
def getIp(irc, msg, args, state): def getIp(irc, msg, args, state):
if utils.isIP(args[0]): if utils.net.isIP(args[0]):
state.args.append(args.pop(0)) state.args.append(args.pop(0))
else: else:
irc.errorInvalid('ip', args[0]) irc.errorInvalid('ip', args[0])
@ -845,7 +841,7 @@ class Spec(object):
def __init__(self, types, allowExtra=False): def __init__(self, types, allowExtra=False):
self.types = types self.types = types
self.allowExtra = allowExtra self.allowExtra = allowExtra
utils.mapinto(contextify, self.types) utils.seq.mapinto(contextify, self.types)
def __call__(self, irc, msg, args, stateAttrs={}): def __call__(self, irc, msg, args, stateAttrs={}):
state = self._state(self.types[:], stateAttrs) state = self._state(self.types[:], stateAttrs)

View File

@ -27,8 +27,6 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot.fix as fix
import os import os
import sys import sys
import time import time
@ -70,6 +68,12 @@ daemonized = False
### ###
allowDefaultOwner = False allowDefaultOwner = False
###
# Here we replace values in other modules as appropriate.
###
utils.web.defaultHeaders['User-agent'] = \
'Mozilla/5.0 (Compatible; Supybot %s)' % version
### ###
# The standard registry. # The standard registry.
### ###
@ -405,7 +409,7 @@ registerChannelValue(supybot.reply, 'showSimpleSyntax',
class ValidPrefixChars(registry.String): class ValidPrefixChars(registry.String):
"""Value must contain only ~!@#$%^&*()_-+=[{}]\\|'\";:,<.>/?""" """Value must contain only ~!@#$%^&*()_-+=[{}]\\|'\";:,<.>/?"""
def setValue(self, v): def setValue(self, v):
if v.translate(string.ascii, '`~!@#$%^&*()_-+=[{}]\\|\'";:,<.>/?'): if v.translate(utils.str.chars, '`~!@#$%^&*()_-+=[{}]\\|\'";:,<.>/?'):
self.error() self.error()
registry.String.setValue(self, v) registry.String.setValue(self, v)
@ -579,9 +583,9 @@ registerChannelValue(supybot.commands.nested, 'pipeSyntax',
example: 'bot: foo | bar'.""")) example: 'bot: foo | bar'."""))
registerGroup(supybot.commands, 'defaultPlugins', registerGroup(supybot.commands, 'defaultPlugins',
orderAlphabetically=True, help=utils.normalizeWhitespace("""Determines orderAlphabetically=True, help="""Determines what commands have default
what commands have default plugins set, and which plugins are set to plugins set, and which plugins are set to be the default for each of those
be the default for each of those commands.""")) commands.""")
registerGlobalValue(supybot.commands.defaultPlugins, 'importantPlugins', registerGlobalValue(supybot.commands.defaultPlugins, 'importantPlugins',
registry.SpaceSeparatedSetOfStrings( registry.SpaceSeparatedSetOfStrings(
['Admin', 'Channel', 'Config', 'Misc', 'Owner', 'User'], ['Admin', 'Channel', 'Config', 'Misc', 'Owner', 'User'],
@ -704,12 +708,8 @@ registerGlobalValue(supybot.directories.data, 'tmp',
DataFilenameDirectory('tmp', """Determines what directory temporary files DataFilenameDirectory('tmp', """Determines what directory temporary files
are put into.""")) are put into."""))
# Remember, we're *meant* to replace this nice little wrapper. utils.file.AtomicFile.default.tmpDir = supybot.directories.data.tmp
def transactionalFile(*args, **kwargs): utils.file.AtomicFile.default.backupDir = supybot.directories.backup
kwargs['tmpDir'] = supybot.directories.data.tmp()
kwargs['backupDir'] = supybot.directories.backup()
return utils.AtomicFile(*args, **kwargs)
utils.transactionalFile = transactionalFile
class PluginDirectories(registry.CommaSeparatedListOfStrings): class PluginDirectories(registry.CommaSeparatedListOfStrings):
def __call__(self): def __call__(self):
@ -931,6 +931,7 @@ registerGlobalValue(supybot.protocols.http, 'peekSize',
registerGlobalValue(supybot.protocols.http, 'proxy', registerGlobalValue(supybot.protocols.http, 'proxy',
registry.String('', """Determines what proxy all HTTP requests should go registry.String('', """Determines what proxy all HTTP requests should go
through. The value should be of the form 'host:port'.""")) through. The value should be of the form 'host:port'."""))
utils.web.proxy = supybot.protocols.http.proxy
### ###
@ -945,7 +946,7 @@ registerGlobalValue(supybot, 'defaultIgnore',
class IP(registry.String): class IP(registry.String):
"""Value must be a valid IP.""" """Value must be a valid IP."""
def setValue(self, v): def setValue(self, v):
if v and not (utils.isIP(v) or utils.isIPV6(v)): if v and not (utils.net.isIP(v) or utils.net.isIPV6(v)):
self.error() self.error()
else: else:
registry.String.setValue(self, v) registry.String.setValue(self, v)

View File

@ -31,10 +31,6 @@
Module for some slight database-independence for simple databases. Module for some slight database-independence for simple databases.
""" """
import supybot.fix as fix
import csv import csv
import math import math
import sets import sets
@ -262,8 +258,7 @@ class FlatfileMapping(MappingInterface):
def vacuum(self): def vacuum(self):
infd = file(self.filename) infd = file(self.filename)
outfd = utils.transactionalFile(self.filename, outfd = utils.file.AstomicFile(self.filename,makeBackupIfSmaller=False)
makeBackupIfSmaller=False)
outfd.write(infd.readline()) # First line, nextId. outfd.write(infd.readline()) # First line, nextId.
for line in infd: for line in infd:
if not line.startswith('-'): if not line.startswith('-'):

View File

@ -1,239 +0,0 @@
###
# Copyright (c) 2002-2005, 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.
###
"""
Fixes stuff that Python should have but doesn't.
"""
from __future__ import division
__all__ = []
exported = ['ignore', 'window', 'group', 'partition', 'set', 'frozenset',
'any', 'all', 'rsplit', 'dynamic']
import sys
import new
import atexit
import string
string.ascii = string.maketrans('', '')
import random
_choice = random.choice
def choice(iterable):
if isinstance(iterable, (list, tuple)):
return _choice(iterable)
else:
n = 1
m = new.module('') # Guaranteed unique value.
ret = m
for x in iterable:
if random.random() < 1/n:
ret = x
n += 1
if ret is m:
raise IndexError
return ret
random.choice = choice
def ignore(*args, **kwargs):
"""Simply ignore the arguments sent to it."""
pass
class DynamicScope(object):
def _getLocals(self, name):
f = sys._getframe().f_back.f_back # _getLocals <- __[gs]etattr__ <- ...
while f:
if name in f.f_locals:
return f.f_locals
f = f.f_back
raise NameError, name
def __getattr__(self, name):
try:
return self._getLocals(name)[name]
except (NameError, KeyError):
return None
def __setattr__(self, name, value):
self._getLocals(name)[name] = value
dynamic = DynamicScope()
if sys.version_info < (2, 4, 0):
def reversed(L):
"""Iterates through a sequence in reverse."""
for i in xrange(len(L) - 1, -1, -1):
yield L[i]
exported.append('reversed')
def window(L, size):
"""Returns a sliding 'window' through the list L of size size."""
assert not isinstance(L, int), 'Argument order swapped: window(L, size)'
if size < 1:
raise ValueError, 'size <= 0 disallowed.'
for i in xrange(len(L) - (size-1)):
yield L[i:i+size]
import itertools
def ilen(iterable):
"""Returns the length of an iterator."""
i = 0
for _ in iterable:
i += 1
return i
def trueCycle(iterable):
while 1:
yielded = False
for x in iterable:
yield x
yielded = True
if not yielded:
raise StopIteration
itertools.trueCycle = trueCycle
itertools.ilen = ilen
def groupby(key, iterable):
if key is None:
key = lambda x: x
it = iter(iterable)
value = it.next() # If there are no items, this takes an early exit
oldkey = key(value)
group = [value]
for value in it:
newkey = key(value)
if newkey != oldkey:
yield group
group = []
oldkey = newkey
group.append(value)
yield group
itertools.groupby = groupby
def group(seq, groupSize, noneFill=True):
"""Groups a given sequence into sublists of length groupSize."""
ret = []
L = []
i = groupSize
for elt in seq:
if i > 0:
L.append(elt)
else:
ret.append(L)
i = groupSize
L = []
L.append(elt)
i -= 1
if L:
if noneFill:
while len(L) < groupSize:
L.append(None)
ret.append(L)
return ret
def partition(p, L):
"""Partitions a list L based on a predicate p. Returns a (yes,no) tuple"""
no = []
yes = []
for elt in L:
if p(elt):
yes.append(elt)
else:
no.append(elt)
return (yes, no)
def any(p, seq):
"""Returns true if any element in seq satisfies predicate p."""
for elt in itertools.ifilter(p, seq):
return True
else:
return False
def all(p, seq):
"""Returns true if all elements in seq satisfy predicate p."""
for elt in itertools.ifilterfalse(p, seq):
return False
else:
return True
def rsplit(s, sep=None, maxsplit=-1):
"""Equivalent to str.split, except splitting from the right."""
if sys.version_info < (2, 4, 0):
if sep is not None:
sep = sep[::-1]
L = s[::-1].split(sep, maxsplit)
L.reverse()
return [s[::-1] for s in L]
else:
return s.rsplit(sep, maxsplit)
if sys.version_info < (2, 4, 0):
import operator
def itemgetter(i):
return lambda x: x[i]
def attrgetter(attr):
return lambda x: getattr(x, attr)
operator.itemgetter = itemgetter
operator.attrgetter = attrgetter
import csv
import cStringIO as StringIO
def join(L):
fd = StringIO.StringIO()
writer = csv.writer(fd)
writer.writerow(L)
return fd.getvalue().rstrip('\r\n')
def split(s):
fd = StringIO.StringIO(s)
reader = csv.reader(fd)
return reader.next()
csv.join = join
csv.split = split
import sets
set = sets.Set
frozenset = sets.ImmutableSet
import socket
# Some socket modules don't have sslerror, so we'll just make it an error.
if not hasattr(socket, 'sslerror'):
socket.sslerror = socket.error
g = globals()
for name in exported:
__builtins__[name] = g[name]
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -27,18 +27,13 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
from __future__ import division from __future__ import division
import supybot.fix as fix
import os import os
import sets import sets
import time import time
import string import string
import operator import operator
from itertools import imap, ilen, ifilter
import supybot.log as log import supybot.log as log
import supybot.conf as conf import supybot.conf as conf
@ -47,6 +42,7 @@ import supybot.world as world
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.registry as registry import supybot.registry as registry
import supybot.unpreserve as unpreserve import supybot.unpreserve as unpreserve
from utils.iter import imap, ilen, ifilter
def isCapability(capability): def isCapability(capability):
return len(capability.split(None, 1)) == 1 return len(capability.split(None, 1)) == 1
@ -114,7 +110,7 @@ def canonicalCapability(capability):
return capability.lower() return capability.lower()
def unWildcardHostmask(hostmask): def unWildcardHostmask(hostmask):
return hostmask.translate(string.ascii, '!@*?') return hostmask.translate(utils.str.chars, '!@*?')
_invert = invertCapability _invert = invertCapability
class CapabilitySet(sets.Set): class CapabilitySet(sets.Set):
@ -228,7 +224,7 @@ class IrcUser(object):
return '%s(id=%s, ignore=%s, password="", name=%s, hashed=%r, ' \ return '%s(id=%s, ignore=%s, password="", name=%s, hashed=%r, ' \
'capabilities=%r, hostmasks=[], secure=%r)\n' % \ 'capabilities=%r, hostmasks=[], secure=%r)\n' % \
(self.__class__.__name__, self.id, self.ignore, (self.__class__.__name__, self.id, self.ignore,
utils.quoted(self.name), self.hashed, self.capabilities, utils.str.quoted(self.name), self.hashed, self.capabilities,
self.secure) self.secure)
def __hash__(self): def __hash__(self):
@ -611,7 +607,7 @@ class UsersDictionary(utils.IterableMap):
if self.filename is not None: if self.filename is not None:
L = self.users.items() L = self.users.items()
L.sort() L.sort()
fd = utils.transactionalFile(self.filename) fd = utils.file.AtomicFile(self.filename)
for (id, u) in L: for (id, u) in L:
fd.write('user %s' % id) fd.write('user %s' % id)
fd.write(os.linesep) fd.write(os.linesep)
@ -657,7 +653,7 @@ class UsersDictionary(utils.IterableMap):
'Removing the offending hostmasks.') 'Removing the offending hostmasks.')
for (id, hostmask) in ids.iteritems(): for (id, hostmask) in ids.iteritems():
log.error('Removing %s from user %s.', log.error('Removing %s from user %s.',
utils.quoted(hostmask), id) utils.str.quoted(hostmask), id)
self.users[id].removeHostmask(hostmask) self.users[id].removeHostmask(hostmask)
raise DuplicateHostmask, 'Ids %r matched.' % ids raise DuplicateHostmask, 'Ids %r matched.' % ids
else: # Not a hostmask, must be a name. else: # Not a hostmask, must be a name.
@ -789,7 +785,7 @@ class ChannelsDictionary(utils.IterableMap):
"""Flushes the channel database to its file.""" """Flushes the channel database to its file."""
if not self.noFlush: if not self.noFlush:
if self.filename is not None: if self.filename is not None:
fd = utils.transactionalFile(self.filename) fd = utils.file.AtomicFile(self.filename)
for (channel, c) in self.channels.iteritems(): for (channel, c) in self.channels.iteritems():
fd.write('channel %s' % channel) fd.write('channel %s' % channel)
fd.write(os.linesep) fd.write(os.linesep)
@ -845,7 +841,7 @@ class IgnoresDB(object):
def open(self, filename): def open(self, filename):
self.filename = filename self.filename = filename
fd = file(self.filename) fd = file(self.filename)
for line in utils.nonCommentNonEmptyLines(fd): for line in utils.file.nonCommentNonEmptyLines(fd):
try: try:
line = line.rstrip('\r\n') line = line.rstrip('\r\n')
L = line.split() L = line.split()
@ -857,12 +853,12 @@ class IgnoresDB(object):
self.add(hostmask, expiration) self.add(hostmask, expiration)
except Exception, e: except Exception, e:
log.error('Invalid line in ignores database: %s', log.error('Invalid line in ignores database: %s',
utils.quoted(line)) utils.str.quoted(line))
fd.close() fd.close()
def flush(self): def flush(self):
if self.filename is not None: if self.filename is not None:
fd = utils.transactionalFile(self.filename) fd = utils.file.AtomicFile(self.filename)
now = time.time() now = time.time()
for (hostmask, expiration) in self.hostmasks.items(): for (hostmask, expiration) in self.hostmasks.items():
if now < expiration or not expiration: if now < expiration or not expiration:

View File

@ -27,16 +27,11 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot.fix as fix
import copy import copy
import sets import sets
import time import time
import random import random
import operator import operator
from itertools import imap, chain, cycle
import supybot.log as log import supybot.log as log
import supybot.conf as conf import supybot.conf as conf
@ -45,6 +40,7 @@ import supybot.world as world
import supybot.ircdb as ircdb import supybot.ircdb as ircdb
import supybot.ircmsgs as ircmsgs import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
from utils.iter import imap, chain, cycle
from supybot.structures import queue, smallqueue, RingBuffer from supybot.structures import queue, smallqueue, RingBuffer
### ###
@ -173,7 +169,7 @@ class IrcMsgQueue(object):
not conf.supybot.protocols.irc.queueDuplicateMessages(): not conf.supybot.protocols.irc.queueDuplicateMessages():
s = str(msg).strip() s = str(msg).strip()
log.info('Not adding message %s to queue, already added.', log.info('Not adding message %s to queue, already added.',
utils.quoted(s)) utils.str.quoted(s))
return False return False
else: else:
self.msgs.add(msg) self.msgs.add(msg)
@ -642,7 +638,7 @@ class Irc(IrcCommandDispatcher):
name = name.lower() name = name.lower()
def nameMatches(cb): def nameMatches(cb):
return cb.name().lower() == name return cb.name().lower() == name
(bad, good) = partition(nameMatches, self.callbacks) (bad, good) = utils.iter.partition(nameMatches, self.callbacks)
self.callbacks[:] = good self.callbacks[:] = good
return bad return bad

View File

@ -34,10 +34,6 @@ construct such messages in an easier way than the constructor for the IrcMsg
object (which, as you'll read later, is quite...full-featured :)) object (which, as you'll read later, is quite...full-featured :))
""" """
import supybot.fix as fix
import re import re
import time import time
import string import string
@ -192,8 +188,8 @@ class IrcMsg(object):
if self._repr is not None: if self._repr is not None:
return self._repr return self._repr
self._repr = 'IrcMsg(prefix=%s, command=%s, args=%r)' % \ self._repr = 'IrcMsg(prefix=%s, command=%s, args=%r)' % \
(utils.quoted(self.prefix), utils.quoted(self.command), (utils.str.quoted(self.prefix),
self.args) utils.str.quoted(self.command), self.args)
return self._repr return self._repr
def __reduce__(self): def __reduce__(self):
@ -586,7 +582,8 @@ def join(channel, key=None, prefix='', msg=None):
return IrcMsg(prefix=prefix, command='JOIN', args=(channel,), msg=msg) return IrcMsg(prefix=prefix, command='JOIN', args=(channel,), msg=msg)
else: else:
if conf.supybot.protocols.irc.strictRfc(): if conf.supybot.protocols.irc.strictRfc():
assert key.translate(string.ascii, string.ascii[128:]) == key and \ assert key.translate(utils.str.chars,
utils.str.chars[128:]) == key and \
'\x00' not in key and \ '\x00' not in key and \
'\r' not in key and \ '\r' not in key and \
'\n' not in key and \ '\n' not in key and \
@ -613,7 +610,8 @@ def joins(channels, keys=None, prefix='', msg=None):
else: else:
for key in keys: for key in keys:
if conf.supybot.protocols.irc.strictRfc(): if conf.supybot.protocols.irc.strictRfc():
assert key.translate(string.ascii,string.ascii[128:])==key and\ assert key.translate(utils.str.chars,
utils.str.chars[128:])==key and \
'\x00' not in key and \ '\x00' not in key and \
'\r' not in key and \ '\r' not in key and \
'\n' not in key and \ '\n' not in key and \

View File

@ -34,16 +34,11 @@ nick class to handle nicks (so comparisons and hashing and whatnot work in an
IRC-case-insensitive fashion), and numerous other things. IRC-case-insensitive fashion), and numerous other things.
""" """
import supybot.fix as fix
import re import re
import time import time
import random import random
import string import string
import textwrap import textwrap
from itertools import imap, ilen
from cStringIO import StringIO as sio from cStringIO import StringIO as sio
import supybot.utils as utils import supybot.utils as utils
@ -109,7 +104,7 @@ def toLower(s, casemapping=None):
elif casemapping == 'ascii': # freenode elif casemapping == 'ascii': # freenode
return s.lower() return s.lower()
else: else:
raise ValueError, 'Invalid casemapping: %s' % utils.quoted(casemapping) raise ValueError, 'Invalid casemapping: %r' % casemapping
def strEqual(nick1, nick2): def strEqual(nick1, nick2):
"""s1, s2 => bool """s1, s2 => bool
@ -197,11 +192,11 @@ def banmask(hostmask):
""" """
assert isUserHostmask(hostmask) assert isUserHostmask(hostmask)
host = hostFromHostmask(hostmask) host = hostFromHostmask(hostmask)
if utils.isIP(host): if utils.net.isIP(host):
L = host.split('.') L = host.split('.')
L[-1] = '*' L[-1] = '*'
return '*!*@' + '.'.join(L) return '*!*@' + '.'.join(L)
elif utils.isIPV6(host): elif utils.net.isIPV6(host):
L = host.split(':') L = host.split(':')
L[-1] = '*' L[-1] = '*'
return '*!*@' + ':'.join(L) return '*!*@' + ':'.join(L)
@ -450,7 +445,7 @@ def safeArgument(s):
if isinstance(s, unicode): if isinstance(s, unicode):
s = s.encode('utf-8') s = s.encode('utf-8')
elif not isinstance(s, basestring): elif not isinstance(s, basestring):
debug('Got a non-string in safeArgument: %s', utils.quoted(s)) debug('Got a non-string in safeArgument: %r', s)
s = str(s) s = str(s)
if isValidArgument(s): if isValidArgument(s):
return s return s
@ -466,7 +461,7 @@ def replyTo(msg):
def dccIP(ip): def dccIP(ip):
"""Returns in IP in the proper for DCC.""" """Returns in IP in the proper for DCC."""
assert utils.isIP(ip), \ assert utils.net.isIP(ip), \
'argument must be a string ip in xxx.yyy.zzz.www format.' 'argument must be a string ip in xxx.yyy.zzz.www format.'
i = 0 i = 0
x = 256**3 x = 256**3
@ -483,7 +478,7 @@ def unDccIP(i):
L.append(i % 256) L.append(i % 256)
i /= 256 i /= 256
L.reverse() L.reverse()
return '.'.join(imap(str, L)) return '.'.join(utils.iter.imap(str, L))
class IrcString(str): class IrcString(str):
"""This class does case-insensitive comparison and hashing of nicks.""" """This class does case-insensitive comparison and hashing of nicks."""
@ -662,7 +657,7 @@ def standardSubstitute(irc, msg, text, env=None):
}) })
if env is not None: if env is not None:
vars.update(env) vars.update(env)
return utils.perlVariableSubstitute(vars, text) return utils.str.perlVariableSubstitute(vars, text)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -27,8 +27,6 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot.fix as fix
import os import os
import sys import sys
import time import time

View File

@ -49,7 +49,7 @@ def loadPluginModule(name, ignoreDeprecation=False):
files.extend(os.listdir(dir)) files.extend(os.listdir(dir))
except EnvironmentError: # OSError, IOError superclass. except EnvironmentError: # OSError, IOError superclass.
log.warning('Invalid plugin directory: %s; removing.', log.warning('Invalid plugin directory: %s; removing.',
utils.quoted(dir)) utils.str.quoted(dir))
conf.supybot.directories.plugins().remove(dir) conf.supybot.directories.plugins().remove(dir)
loweredFiles = map(str.lower, files) loweredFiles = map(str.lower, files)
try: try:
@ -72,7 +72,7 @@ def loadPluginModule(name, ignoreDeprecation=False):
log.warning('Deprecated plugin loaded: %s', name) log.warning('Deprecated plugin loaded: %s', name)
else: else:
raise Deprecated, 'Attempted to load deprecated plugin %s' % \ raise Deprecated, 'Attempted to load deprecated plugin %s' % \
utils.quoted(name) utils.str.quoted(name)
if module.__name__ in sys.modules: if module.__name__ in sys.modules:
sys.modules[module.__name__] = module sys.modules[module.__name__] = module
linecache.checkcache() linecache.checkcache()

View File

@ -34,7 +34,6 @@ import time
import string import string
import textwrap import textwrap
import supybot.fix as fix
import supybot.utils as utils import supybot.utils as utils
def error(s): def error(s):
@ -68,7 +67,7 @@ def open(filename, clear=False):
if clear: if clear:
_cache.clear() _cache.clear()
_fd = file(filename) _fd = file(filename)
fd = utils.nonCommentNonEmptyLines(_fd) fd = utils.file.nonCommentNonEmptyLines(_fd)
acc = '' acc = ''
for line in fd: for line in fd:
line = line.rstrip('\r\n') line = line.rstrip('\r\n')
@ -94,7 +93,7 @@ def open(filename, clear=False):
def close(registry, filename, private=True): def close(registry, filename, private=True):
first = True first = True
fd = utils.transactionalFile(filename) fd = utils.file.AtomicFile(filename)
for (name, value) in registry.getValues(getChildren=True): for (name, value) in registry.getValues(getChildren=True):
help = value.help() help = value.help()
if help: if help:
@ -162,7 +161,7 @@ class Group(object):
"""A group; it doesn't hold a value unless handled by a subclass.""" """A group; it doesn't hold a value unless handled by a subclass."""
def __init__(self, help='', supplyDefault=False, def __init__(self, help='', supplyDefault=False,
orderAlphabetically=False, private=False): orderAlphabetically=False, private=False):
self._help = help self._help = utils.str.normalizeWhitespace(help)
self._name = 'unset' self._name = 'unset'
self._added = [] self._added = []
self._children = utils.InsensitivePreservingDict() self._children = utils.InsensitivePreservingDict()
@ -299,7 +298,7 @@ class Value(Group):
self.__parent.__init__(help, **kwargs) self.__parent.__init__(help, **kwargs)
self._default = default self._default = default
self._showDefault = showDefault self._showDefault = showDefault
self._help = utils.normalizeWhitespace(help.strip()) self._help = utils.str.normalizeWhitespace(help.strip())
if setDefault: if setDefault:
self.setValue(default) self.setValue(default)
@ -309,7 +308,7 @@ class Value(Group):
else: else:
s = """%s has no docstring. If you're getting this message, s = """%s has no docstring. If you're getting this message,
report it, because we forgot to put a proper help string here.""" report it, because we forgot to put a proper help string here."""
e = InvalidRegistryValue(utils.normalizeWhitespace(s % self._name)) e = InvalidRegistryValue(utils.str.normalizeWhitespace(s % self._name))
e.value = self e.value = self
raise e raise e
@ -356,7 +355,7 @@ class Boolean(Value):
"""Value must be either True or False (or On or Off).""" """Value must be either True or False (or On or Off)."""
def set(self, s): def set(self, s):
try: try:
v = utils.toBool(s) v = utils.str.toBool(s)
except ValueError: except ValueError:
if s.strip().lower() == 'toggle': if s.strip().lower() == 'toggle':
v = not self.value v = not self.value
@ -440,7 +439,7 @@ class String(Value):
_printable = string.printable[:-4] _printable = string.printable[:-4]
def _needsQuoting(self, s): def _needsQuoting(self, s):
return s.translate(string.ascii, self._printable) and s.strip() != s return s.translate(utils.str.chars, self._printable) and s.strip() != s
def __str__(self): def __str__(self):
s = self.value s = self.value
@ -456,12 +455,12 @@ class OnlySomeStrings(String):
self.__parent = super(OnlySomeStrings, self) self.__parent = super(OnlySomeStrings, self)
self.__parent.__init__(*args, **kwargs) self.__parent.__init__(*args, **kwargs)
self.__doc__ = 'Valid values include %s.' % \ self.__doc__ = 'Valid values include %s.' % \
utils.commaAndify(map(repr, self.validStrings)) utils.str.commaAndify(map(repr, self.validStrings))
def help(self): def help(self):
strings = [s for s in self.validStrings if s] strings = [s for s in self.validStrings if s]
return '%s Valid strings: %s.' % \ return '%s Valid strings: %s.' % \
(self._help, utils.commaAndify(strings)) (self._help, utils.str.commaAndify(strings))
def normalize(self, s): def normalize(self, s):
lowered = s.lower() lowered = s.lower()
@ -487,7 +486,7 @@ class NormalizedString(String):
self._showDefault = False self._showDefault = False
def normalize(self, s): def normalize(self, s):
return utils.normalizeWhitespace(s.strip()) return utils.str.normalizeWhitespace(s.strip())
def set(self, s): def set(self, s):
s = self.normalize(s) s = self.normalize(s)
@ -540,7 +539,7 @@ class Regexp(Value):
def set(self, s): def set(self, s):
try: try:
if s: if s:
self.setValue(utils.perlReToPythonRe(s), sr=s) self.setValue(utils.str.perlReToPythonRe(s), sr=s)
else: else:
self.setValue(None) self.setValue(None)
except ValueError, e: except ValueError, e:

View File

@ -32,10 +32,6 @@ Schedule plugin with a subclass of drivers.IrcDriver in order to be run as a
Supybot driver. Supybot driver.
""" """
import supybot.fix as fix
import time import time
import heapq import heapq

View File

@ -31,13 +31,9 @@
Data structures for Python. Data structures for Python.
""" """
import supybot.fix as fix
import time import time
import types import types
from itertools import imap, ilen from itertools import imap
class RingBuffer(object): class RingBuffer(object):
"""Class to represent a fixed-size ring buffer.""" """Class to represent a fixed-size ring buffer."""
@ -345,8 +341,12 @@ class TimeoutQueue(object):
yield elt yield elt
def __len__(self): def __len__(self):
return ilen(self) # No dependency on utils.
# return ilen(self)
i = 0
for _ in self:
i += 1
return i
class MaxLengthQueue(queue): class MaxLengthQueue(queue):
__slots__ = ('length',) __slots__ = ('length',)

View File

@ -27,8 +27,6 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot.fix as fix
import gc import gc
import os import os
import re import re
@ -96,7 +94,7 @@ class SupyTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
log.critical('Beginning test case %s', self.id()) log.critical('Beginning test case %s', self.id())
threads = [t.getName() for t in threading.enumerate()] threads = [t.getName() for t in threading.enumerate()]
log.critical('Threads: %s' % utils.commaAndify(threads)) log.critical('Threads: %s' % utils.str.commaAndify(threads))
unittest.TestCase.setUp(self) unittest.TestCase.setUp(self)
def tearDown(self): def tearDown(self):

View File

@ -27,8 +27,6 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
class Reader(object): class Reader(object):
def __init__(self, Creator, *args, **kwargs): def __init__(self, Creator, *args, **kwargs):
self.Creator = Creator self.Creator = Creator

View File

@ -1,864 +0,0 @@
###
# Copyright (c) 2002-2005, 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.
###
"""
Simple utility functions.
"""
import supybot.fix as fix
import os
import re
import sys
import md5
import new
import sha
import sets
import time
import types
import random
import shutil
import socket
import string
import sgmllib
import compiler
import textwrap
import UserDict
import itertools
import traceback
import htmlentitydefs
from itertools import imap, ifilter
from supybot.structures import TwoWayDictionary
curry = new.instancemethod
def normalizeWhitespace(s):
"""Normalizes the whitespace in a string; \s+ becomes one space."""
return ' '.join(s.split())
class HtmlToText(sgmllib.SGMLParser):
"""Taken from some eff-bot code on c.l.p."""
entitydefs = htmlentitydefs.entitydefs.copy()
entitydefs['nbsp'] = ' '
def __init__(self, tagReplace=' '):
self.data = []
self.tagReplace = tagReplace
sgmllib.SGMLParser.__init__(self)
def unknown_starttag(self, tag, attr):
self.data.append(self.tagReplace)
def unknown_endtag(self, tag):
self.data.append(self.tagReplace)
def handle_data(self, data):
self.data.append(data)
def getText(self):
text = ''.join(self.data).strip()
return normalizeWhitespace(text)
def htmlToText(s, tagReplace=' '):
"""Turns HTML into text. tagReplace is a string to replace HTML tags with.
"""
x = HtmlToText(tagReplace)
x.feed(s)
return x.getText()
def abbrev(strings, d=None):
"""Returns a dictionary mapping unambiguous abbreviations to full forms."""
def eachSubstring(s):
for i in xrange(1, len(s)+1):
yield s[:i]
if len(strings) != len(set(strings)):
raise ValueError, \
'strings given to utils.abbrev have duplicates: %r' % strings
if d is None:
d = {}
for s in strings:
for abbreviation in eachSubstring(s):
if abbreviation not in d:
d[abbreviation] = s
else:
if abbreviation not in strings:
d[abbreviation] = None
removals = []
for key in d:
if d[key] is None:
removals.append(key)
for key in removals:
del d[key]
return d
def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True,
weeks=True, days=True, hours=True, minutes=True, seconds=True):
"""Given <elapsed> seconds, returns a string with an English description of
how much time as passed. leadingZeroes determines whether 0 days, 0 hours,
etc. will be printed; the others determine what larger time periods should
be used.
"""
ret = []
def format(s, i):
if i or leadingZeroes or ret:
if short:
ret.append('%s%s' % (i, s[0]))
else:
ret.append(nItems(s, i))
elapsed = int(elapsed)
assert years or weeks or days or \
hours or minutes or seconds, 'One flag must be True'
if years:
(yrs, elapsed) = (elapsed // 31536000, elapsed % 31536000)
format('year', yrs)
if weeks:
(wks, elapsed) = (elapsed // 604800, elapsed % 604800)
format('week', wks)
if days:
(ds, elapsed) = (elapsed // 86400, elapsed % 86400)
format('day', ds)
if hours:
(hrs, elapsed) = (elapsed // 3600, elapsed % 3600)
format('hour', hrs)
if minutes or seconds:
(mins, secs) = (elapsed // 60, elapsed % 60)
if leadingZeroes or mins:
format('minute', mins)
if seconds:
leadingZeroes = True
format('second', secs)
if not ret:
raise ValueError, 'Time difference not great enough to be noted.'
if short:
return ' '.join(ret)
else:
return commaAndify(ret)
def distance(s, t):
"""Returns the levenshtein edit distance between two strings."""
n = len(s)
m = len(t)
if n == 0:
return m
elif m == 0:
return n
d = []
for i in range(n+1):
d.append([])
for j in range(m+1):
d[i].append(0)
d[0][j] = j
d[i][0] = i
for i in range(1, n+1):
cs = s[i-1]
for j in range(1, m+1):
ct = t[j-1]
cost = int(cs != ct)
d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+cost)
return d[n][m]
_soundextrans = string.maketrans(string.ascii_uppercase,
'01230120022455012623010202')
_notUpper = string.ascii.translate(string.ascii, string.ascii_uppercase)
def soundex(s, length=4):
"""Returns the soundex hash of a given string."""
s = s.upper() # Make everything uppercase.
s = s.translate(string.ascii, _notUpper) # Delete non-letters.
if not s:
raise ValueError, 'Invalid string for soundex: %s'
firstChar = s[0] # Save the first character.
s = s.translate(_soundextrans) # Convert to soundex numbers.
s = s.lstrip(s[0]) # Remove all repeated first characters.
L = [firstChar]
for c in s:
if c != L[-1]:
L.append(c)
L = [c for c in L if c != '0'] + (['0']*(length-1))
s = ''.join(L)
return length and s[:length] or s.rstrip('0')
def dqrepr(s):
"""Returns a repr() of s guaranteed to be in double quotes."""
# The wankers-that-be decided not to use double-quotes anymore in 2.3.
# return '"' + repr("'\x00" + s)[6:]
return '"%s"' % s.encode('string_escape').replace('"', '\\"')
def quoted(s):
"""Returns a quoted s."""
return '"%s"' % s
def _getSep(s):
if len(s) < 2:
raise ValueError, 'string given to _getSep is too short: %r' % s
if s.startswith('m') or s.startswith('s'):
separator = s[1]
else:
separator = s[0]
if separator.isalnum() or separator in '{}[]()<>':
raise ValueError, \
'Invalid separator: separator must not be alphanumeric or in ' \
'"{}[]()<>"'
return separator
def _getSplitterRe(s):
separator = _getSep(s)
return re.compile(r'(?<!\\)%s' % re.escape(separator))
def perlReToPythonRe(s):
"""Converts a string representation of a Perl regular expression (i.e.,
m/^foo$/i or /foo|bar/) to a Python regular expression.
"""
sep = _getSep(s)
splitter = _getSplitterRe(s)
try:
(kind, regexp, flags) = splitter.split(s)
except ValueError: # Unpack list of wrong size.
raise ValueError, 'Must be of the form m/.../ or /.../'
regexp = regexp.replace('\\'+sep, sep)
if kind not in ('', 'm'):
raise ValueError, 'Invalid kind: must be in ("", "m")'
flag = 0
try:
for c in flags.upper():
flag |= getattr(re, c)
except AttributeError:
raise ValueError, 'Invalid flag: %s' % c
try:
return re.compile(regexp, flag)
except re.error, e:
raise ValueError, str(e)
def perlReToReplacer(s):
"""Converts a string representation of a Perl regular expression (i.e.,
s/foo/bar/g or s/foo/bar/i) to a Python function doing the equivalent
replacement.
"""
sep = _getSep(s)
splitter = _getSplitterRe(s)
try:
(kind, regexp, replace, flags) = splitter.split(s)
except ValueError: # Unpack list of wrong size.
raise ValueError, 'Must be of the form s/.../.../'
regexp = regexp.replace('\x08', r'\b')
replace = replace.replace('\\'+sep, sep)
for i in xrange(10):
replace = replace.replace(chr(i), r'\%s' % i)
if kind != 's':
raise ValueError, 'Invalid kind: must be "s"'
g = False
if 'g' in flags:
g = True
flags = filter('g'.__ne__, flags)
r = perlReToPythonRe('/'.join(('', regexp, flags)))
if g:
return curry(r.sub, replace)
else:
return lambda s: r.sub(replace, s, 1)
_perlVarSubstituteRe = re.compile(r'\$\{([^}]+)\}|\$([a-zA-Z][a-zA-Z0-9]*)')
def perlVariableSubstitute(vars, text):
def replacer(m):
(braced, unbraced) = m.groups()
var = braced or unbraced
try:
x = vars[var]
if callable(x):
return x()
else:
return str(x)
except KeyError:
if braced:
return '${%s}' % braced
else:
return '$' + unbraced
return _perlVarSubstituteRe.sub(replacer, text)
def findBinaryInPath(s):
"""Return full path of a binary if it's in PATH, otherwise return None."""
cmdLine = None
for dir in os.getenv('PATH').split(':'):
filename = os.path.join(dir, s)
if os.path.exists(filename):
cmdLine = filename
break
return cmdLine
def commaAndify(seq, comma=',', And='and'):
"""Given a a sequence, returns an English clause for that sequence.
I.e., given [1, 2, 3], returns '1, 2, and 3'
"""
L = list(seq)
if len(L) == 0:
return ''
elif len(L) == 1:
return ''.join(L) # We need this because it raises TypeError.
elif len(L) == 2:
L.insert(1, And)
return ' '.join(L)
else:
L[-1] = '%s %s' % (And, L[-1])
sep = '%s ' % comma
return sep.join(L)
_unCommaTheRe = re.compile(r'(.*),\s*(the)$', re.I)
def unCommaThe(s):
"""Takes a string of the form 'foo, the' and turns it into 'the foo'."""
m = _unCommaTheRe.match(s)
if m is not None:
return '%s %s' % (m.group(2), m.group(1))
else:
return s
def ellipsisify(s, n):
"""Returns a shortened version of s. Produces up to the first n chars at
the nearest word boundary.
"""
if len(s) <= n:
return s
else:
return (textwrap.wrap(s, n-3)[0] + '...')
plurals = TwoWayDictionary({})
def matchCase(s1, s2):
"""Matches the case of s1 in s2"""
if s1.isupper():
return s2.upper()
else:
L = list(s2)
for (i, char) in enumerate(s1[:len(s2)]):
if char.isupper():
L[i] = L[i].upper()
return ''.join(L)
consonants = 'bcdfghjklmnpqrstvwxz'
_pluralizeRegex = re.compile('[%s]y$' % consonants)
def pluralize(s, i=2):
"""Returns the plural of s based on its number i. Put any exceptions to
the general English rule of appending 's' in the plurals dictionary.
"""
if i == 1:
return s
else:
lowered = s.lower()
# Exception dictionary
if lowered in plurals:
return matchCase(s, plurals[lowered])
# Words ending with 'ch', 'sh' or 'ss' such as 'punch(es)', 'fish(es)
# and miss(es)
elif any(lowered.endswith, ['x', 'ch', 'sh', 'ss']):
return matchCase(s, s+'es')
# Words ending with a consonant followed by a 'y' such as
# 'try (tries)' or 'spy (spies)'
elif _pluralizeRegex.search(lowered):
return matchCase(s, s[:-1] + 'ies')
# In all other cases, we simply add an 's' to the base word
else:
return matchCase(s, s+'s')
_depluralizeRegex = re.compile('[%s]ies' % consonants)
def depluralize(s):
"""Returns the singular of s."""
lowered = s.lower()
if lowered in plurals:
return matchCase(s, plurals[lowered])
elif any(lowered.endswith, ['ches', 'shes', 'sses']):
return s[:-2]
elif re.search(_depluralizeRegex, lowered):
return s[:-3] + 'y'
else:
if lowered.endswith('s'):
return s[:-1] # Chop off 's'.
else:
return s # Don't know what to do.
def nItems(item, n, between=None):
"""Works like this:
>>> nItems('clock', 1)
'1 clock'
>>> nItems('clock', 10)
'10 clocks'
>>> nItems('clock', 10, between='grandfather')
'10 grandfather clocks'
"""
if between is None:
return '%s %s' % (n, pluralize(item, n))
else:
return '%s %s %s' % (n, between, pluralize(item, n))
def be(i):
"""Returns the form of the verb 'to be' based on the number i."""
if i == 1:
return 'is'
else:
return 'are'
def has(i):
"""Returns the form of the verb 'to have' based on the number i."""
if i == 1:
return 'has'
else:
return 'have'
def sortBy(f, L):
"""Uses the decorate-sort-undecorate pattern to sort L by function f."""
for (i, elt) in enumerate(L):
L[i] = (f(elt), i, elt)
L.sort()
for (i, elt) in enumerate(L):
L[i] = L[i][2]
if sys.version_info < (2, 4, 0):
def sorted(iterable, cmp=None, key=None, reversed=False):
L = list(iterable)
if key is not None:
assert cmp is None, 'Can\'t use both cmp and key.'
sortBy(key, L)
else:
L.sort(cmp)
if reversed:
L.reverse()
return L
__builtins__['sorted'] = sorted
def mktemp(suffix=''):
"""Gives a decent random string, suitable for a filename."""
r = random.Random()
m = md5.md5(suffix)
r.seed(time.time())
s = str(r.getstate())
for x in xrange(0, random.randrange(400), random.randrange(1, 5)):
m.update(str(x))
m.update(s)
m.update(str(time.time()))
s = m.hexdigest()
return sha.sha(s + str(time.time())).hexdigest() + suffix
def itersplit(isSeparator, iterable, maxsplit=-1, yieldEmpty=False):
"""itersplit(isSeparator, iterable, maxsplit=-1, yieldEmpty=False)
Splits an iterator based on a predicate isSeparator."""
if isinstance(isSeparator, basestring):
f = lambda s: s == isSeparator
else:
f = isSeparator
acc = []
for element in iterable:
if maxsplit == 0 or not f(element):
acc.append(element)
else:
maxsplit -= 1
if acc or yieldEmpty:
yield acc
acc = []
if acc or yieldEmpty:
yield acc
def flatten(seq, strings=False):
"""Flattens a list of lists into a single list. See the test for examples.
"""
for elt in seq:
if not strings and type(elt) == str or type(elt) == unicode:
yield elt
else:
try:
for x in flatten(elt):
yield x
except TypeError:
yield elt
def saltHash(password, salt=None, hash='sha'):
if salt is None:
salt = mktemp()[:8]
if hash == 'sha':
hasher = sha.sha
elif hash == 'md5':
hasher = md5.md5
return '|'.join([salt, hasher(salt + password).hexdigest()])
def safeEval(s, namespace={'True': True, 'False': False, 'None': None}):
"""Evaluates s, safely. Useful for turning strings into tuples/lists/etc.
without unsafely using eval()."""
try:
node = compiler.parse(s)
except SyntaxError, e:
raise ValueError, 'Invalid string: %s.' % e
nodes = compiler.parse(s).node.nodes
if not nodes:
if node.__class__ is compiler.ast.Module:
return node.doc
else:
raise ValueError, 'Unsafe string: %s' % quoted(s)
node = nodes[0]
if node.__class__ is not compiler.ast.Discard:
raise ValueError, 'Invalid expression: %s' % quoted(s)
node = node.getChildNodes()[0]
def checkNode(node):
if node.__class__ is compiler.ast.Const:
return True
if node.__class__ in (compiler.ast.List,
compiler.ast.Tuple,
compiler.ast.Dict):
return all(checkNode, node.getChildNodes())
if node.__class__ is compiler.ast.Name:
if node.name in namespace:
return True
else:
return False
else:
return False
if checkNode(node):
return eval(s, namespace, namespace)
else:
raise ValueError, 'Unsafe string: %s' % quoted(s)
def exnToString(e):
"""Turns a simple exception instance into a string (better than str(e))"""
strE = str(e)
if strE:
return '%s: %s' % (e.__class__.__name__, strE)
else:
return e.__class__.__name__
class IterableMap(object):
"""Define .iteritems() in a class and subclass this to get the other iters.
"""
def iteritems(self):
raise NotImplementedError
def iterkeys(self):
for (key, _) in self.iteritems():
yield key
__iter__ = iterkeys
def itervalues(self):
for (_, value) in self.iteritems():
yield value
def items(self):
return list(self.iteritems())
def keys(self):
return list(self.iterkeys())
def values(self):
return list(self.itervalues())
def __len__(self):
ret = 0
for _ in self.iteritems():
ret += 1
return ret
def __nonzero__(self):
for _ in self.iteritems():
return True
return False
def nonCommentLines(fd):
for line in fd:
if not line.startswith('#'):
yield line
def nonEmptyLines(fd):
## for line in fd:
## if line.strip():
## yield line
return ifilter(str.strip, fd)
def nonCommentNonEmptyLines(fd):
return nonEmptyLines(nonCommentLines(fd))
def changeFunctionName(f, name, doc=None):
if doc is None:
doc = f.__doc__
newf = types.FunctionType(f.func_code, f.func_globals, name,
f.func_defaults, f.func_closure)
newf.__doc__ = doc
return newf
def getSocket(host):
"""Returns a socket of the correct AF_INET type (v4 or v6) in order to
communicate with host.
"""
host = socket.gethostbyname(host)
if isIP(host):
return socket.socket(socket.AF_INET, socket.SOCK_STREAM)
elif isIPV6(host):
return socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else:
raise socket.error, 'Something wonky happened.'
def isIP(s):
"""Returns whether or not a given string is an IPV4 address.
>>> isIP('255.255.255.255')
1
>>> isIP('abc.abc.abc.abc')
0
"""
try:
return bool(socket.inet_aton(s))
except socket.error:
return False
def bruteIsIPV6(s):
if s.count('::') <= 1:
L = s.split(':')
if len(L) <= 8:
for x in L:
if x:
try:
int(x, 16)
except ValueError:
return False
return True
return False
def isIPV6(s):
"""Returns whether or not a given string is an IPV6 address."""
try:
if hasattr(socket, 'inet_pton'):
return bool(socket.inet_pton(socket.AF_INET6, s))
else:
return bruteIsIPV6(s)
except socket.error:
try:
socket.inet_pton(socket.AF_INET6, '::')
except socket.error:
# We gotta fake it.
return bruteIsIPV6(s)
return False
class InsensitivePreservingDict(UserDict.DictMixin, object):
def key(self, s):
"""Override this if you wish."""
if s is not None:
s = s.lower()
return s
def __init__(self, dict=None, key=None):
if key is not None:
self.key = key
self.data = {}
if dict is not None:
self.update(dict)
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__,
super(InsensitivePreservingDict, self).__repr__())
def fromkeys(cls, keys, s=None, dict=None, key=None):
d = cls(dict=dict, key=key)
for key in keys:
d[key] = s
return d
fromkeys = classmethod(fromkeys)
def __getitem__(self, k):
return self.data[self.key(k)][1]
def __setitem__(self, k, v):
self.data[self.key(k)] = (k, v)
def __delitem__(self, k):
del self.data[self.key(k)]
def iteritems(self):
return self.data.itervalues()
def keys(self):
L = []
for (k, _) in self.iteritems():
L.append(k)
return L
def __reduce__(self):
return (self.__class__, (dict(self.data.values()),))
class NormalizingSet(sets.Set):
def __init__(self, iterable=()):
iterable = itertools.imap(self.normalize, iterable)
super(NormalizingSet, self).__init__(iterable)
def normalize(self, x):
return x
def add(self, x):
return super(NormalizingSet, self).add(self.normalize(x))
def remove(self, x):
return super(NormalizingSet, self).remove(self.normalize(x))
def discard(self, x):
return super(NormalizingSet, self).discard(self.normalize(x))
def __contains__(self, x):
return super(NormalizingSet, self).__contains__(self.normalize(x))
has_key = __contains__
def mungeEmailForWeb(s):
s = s.replace('@', ' AT ')
s = s.replace('.', ' DOT ')
return s
class AtomicFile(file):
"""Used for files that need to be atomically written -- i.e., if there's a
failure, the original file remains, unmodified. mode must be 'w' or 'wb'"""
def __init__(self, filename, mode='w', allowEmptyOverwrite=True,
makeBackupIfSmaller=True, tmpDir=None, backupDir=None):
if mode not in ('w', 'wb'):
raise ValueError, 'Invalid mode: %s' % quoted(mode)
self.rolledback = False
self.allowEmptyOverwrite = allowEmptyOverwrite
self.makeBackupIfSmaller = makeBackupIfSmaller
self.filename = filename
self.backupDir = backupDir
if tmpDir is None:
# If not given a tmpDir, we'll just put a random token on the end
# of our filename and put it in the same directory.
self.tempFilename = '%s.%s' % (self.filename, mktemp())
else:
# If given a tmpDir, we'll get the basename (just the filename, no
# directory), put our random token on the end, and put it in tmpDir
tempFilename = '%s.%s' % (os.path.basename(self.filename), mktemp())
self.tempFilename = os.path.join(tmpDir, tempFilename)
# This doesn't work because of the uncollectable garbage effect.
# self.__parent = super(AtomicFile, self)
super(AtomicFile, self).__init__(self.tempFilename, mode)
def rollback(self):
if not self.closed:
super(AtomicFile, self).close()
if os.path.exists(self.tempFilename):
os.remove(self.tempFilename)
self.rolledback = True
def close(self):
if not self.rolledback:
super(AtomicFile, self).close()
# We don't mind writing an empty file if the file we're overwriting
# doesn't exist.
newSize = os.path.getsize(self.tempFilename)
originalExists = os.path.exists(self.filename)
if newSize or self.allowEmptyOverwrite or not originalExists:
if originalExists:
oldSize = os.path.getsize(self.filename)
if self.makeBackupIfSmaller and newSize < oldSize:
now = int(time.time())
backupFilename = '%s.backup.%s' % (self.filename, now)
if self.backupDir is not None:
backupFilename = os.path.basename(backupFilename)
backupFilename = os.path.join(self.backupDir,
backupFilename)
shutil.copy(self.filename, backupFilename)
# We use shutil.move here instead of os.rename because
# the latter doesn't work on Windows when self.filename
# (the target) already exists. shutil.move handles those
# intricacies for us.
# This raises IOError if we can't write to the file. Since
# in *nix, it only takes write perms to the *directory* to
# rename a file (and shutil.move will use os.rename if
# possible), we first check if we have the write permission
# and only then do we write.
fd = file(self.filename, 'a')
fd.close()
shutil.move(self.tempFilename, self.filename)
else:
raise ValueError, 'AtomicFile.close called after rollback.'
def __del__(self):
# We rollback because if we're deleted without being explicitly closed,
# that's bad. We really should log this here, but as of yet we've got
# no logging facility in utils. I've got some ideas for this, though.
self.rollback()
def transactionalFile(*args, **kwargs):
# This exists so it can be replaced by a function that provides the tmpDir.
# We do that replacement in conf.py.
return AtomicFile(*args, **kwargs)
def stackTrace(frame=None, compact=True):
if frame is None:
frame = sys._getframe()
if compact:
L = []
while frame:
lineno = frame.f_lineno
funcname = frame.f_code.co_name
filename = os.path.basename(frame.f_code.co_filename)
L.append('[%s|%s|%s]' % (filename, funcname, lineno))
frame = frame.f_back
return textwrap.fill(' '.join(L))
else:
return traceback.format_stack(frame)
def callTracer(fd=None, basename=True):
if fd is None:
fd = sys.stdout
def tracer(frame, event, _):
if event == 'call':
code = frame.f_code
lineno = frame.f_lineno
funcname = code.co_name
filename = code.co_filename
if basename:
filename = os.path.basename(filename)
print >>fd, '%s: %s(%s)' % (filename, funcname, lineno)
return tracer
def toBool(s):
s = s.strip().lower()
if s in ('true', 'on', 'enable', 'enabled', '1'):
return True
elif s in ('false', 'off', 'disable', 'disabled', '0'):
return False
else:
raise ValueError, 'Invalid string for toBool: %s' % quoted(s)
def mapinto(f, L):
for (i, x) in enumerate(L):
L[i] = f(x)
if __name__ == '__main__':
import doctest
doctest.testmod(sys.modules['__main__'])
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

106
src/utils/__init__.py Normal file
View File

@ -0,0 +1,106 @@
###
# Copyright (c) 2002-2005, 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.
###
import sys
###
# csv.{join,split} -- useful functions that should exist.
###
import csv
import cStringIO as StringIO
def join(L):
fd = StringIO.StringIO()
writer = csv.writer(fd)
writer.writerow(L)
return fd.getvalue().rstrip('\r\n')
def split(s):
fd = StringIO.StringIO(s)
reader = csv.reader(fd)
return reader.next()
csv.join = join
csv.split = split
# We use this often enough that we're going to stick it in builtins.
def force(x):
if callable(x):
return x()
else:
return x
__builtins__['force'] = force
if sys.version_info < (2, 4, 0):
def reversed(L):
"""Iterates through a sequence in reverse."""
for i in xrange(len(L) - 1, -1, -1):
yield L[i]
__builtins__['reversed'] = reversed
def sorted(iterable, cmp=None, key=None, reversed=False):
L = list(iterable)
if key is not None:
assert cmp is None, 'Can\'t use both cmp and key.'
sortBy(key, L)
else:
L.sort(cmp)
if reversed:
L.reverse()
return L
__builtins__['sorted'] = sorted
import operator
def itemgetter(i):
return lambda x: x[i]
def attrgetter(attr):
return lambda x: getattr(x, attr)
operator.itemgetter = itemgetter
operator.attrgetter = attrgetter
import sets
__builtins__['set'] = sets.Set
__builtins__['frozenset'] = sets.ImmutableSet
import socket
# Some socket modules don't have sslerror, so we'll just make it an error.
if not hasattr(socket, 'sslerror'):
socket.sslerror = socket.error
# These imports need to happen below the block above, so things get put into
# __builtins__ appropriately.
from gen import *
import net
import web
import seq
import str
import file
import iter
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

150
src/utils/file.py Normal file
View File

@ -0,0 +1,150 @@
###
# Copyright (c) 2002-2005, 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.
###
import os
import md5
import sha
import time
import random
import shutil
import os.path
from iter import ifilter
def mktemp(suffix=''):
"""Gives a decent random string, suitable for a filename."""
r = random.Random()
m = md5.md5(suffix)
r.seed(time.time())
s = str(r.getstate())
for x in xrange(0, random.randrange(400), random.randrange(1, 5)):
m.update(str(x))
m.update(s)
m.update(str(time.time()))
s = m.hexdigest()
return sha.sha(s + str(time.time())).hexdigest() + suffix
def nonCommentLines(fd):
for line in fd:
if not line.startswith('#'):
yield line
def nonEmptyLines(fd):
return ifilter(str.strip, fd)
def nonCommentNonEmptyLines(fd):
return nonEmptyLines(nonCommentLines(fd))
class AtomicFile(file):
"""Used for files that need to be atomically written -- i.e., if there's a
failure, the original file remains, unmodified. mode must be 'w' or 'wb'"""
class default(object): # Holder for values.
# Callables?
tmpDir = None
backupDir = None
makeBackupIfSmaller = True
allowEmptyOverwrite = True
def __init__(self, filename, mode='w', allowEmptyOverwrite=None,
makeBackupIfSmaller=None, tmpDir=None, backupDir=None):
if tmpDir is None:
tmpDir = force(self.default.tmpDir)
if backupDir is None:
backupDir = force(self.default.backupDir)
if makeBackupIfSmaller is None:
makeBackupIfSmaller = force(self.default.makeBackupIfSmaller)
if allowEmptyOverwrite is None:
allowEmptyOverwrite = force(self.default.allowEmptyOverwrite)
if mode not in ('w', 'wb'):
raise ValueError, 'Invalid mode: %s' % quoted(mode)
self.rolledback = False
self.allowEmptyOverwrite = allowEmptyOverwrite
self.makeBackupIfSmaller = makeBackupIfSmaller
self.filename = filename
self.backupDir = backupDir
if tmpDir is None:
# If not given a tmpDir, we'll just put a random token on the end
# of our filename and put it in the same directory.
self.tempFilename = '%s.%s' % (self.filename, mktemp())
else:
# If given a tmpDir, we'll get the basename (just the filename, no
# directory), put our random token on the end, and put it in tmpDir
tempFilename = '%s.%s' % (os.path.basename(self.filename), mktemp())
self.tempFilename = os.path.join(tmpDir, tempFilename)
# This doesn't work because of the uncollectable garbage effect.
# self.__parent = super(AtomicFile, self)
super(AtomicFile, self).__init__(self.tempFilename, mode)
def rollback(self):
if not self.closed:
super(AtomicFile, self).close()
if os.path.exists(self.tempFilename):
os.remove(self.tempFilename)
self.rolledback = True
def close(self):
if not self.rolledback:
super(AtomicFile, self).close()
# We don't mind writing an empty file if the file we're overwriting
# doesn't exist.
newSize = os.path.getsize(self.tempFilename)
originalExists = os.path.exists(self.filename)
if newSize or self.allowEmptyOverwrite or not originalExists:
if originalExists:
oldSize = os.path.getsize(self.filename)
if self.makeBackupIfSmaller and newSize < oldSize:
now = int(time.time())
backupFilename = '%s.backup.%s' % (self.filename, now)
if self.backupDir is not None:
backupFilename = os.path.basename(backupFilename)
backupFilename = os.path.join(self.backupDir,
backupFilename)
shutil.copy(self.filename, backupFilename)
# We use shutil.move here instead of os.rename because
# the latter doesn't work on Windows when self.filename
# (the target) already exists. shutil.move handles those
# intricacies for us.
# This raises IOError if we can't write to the file. Since
# in *nix, it only takes write perms to the *directory* to
# rename a file (and shutil.move will use os.rename if
# possible), we first check if we have the write permission
# and only then do we write.
fd = file(self.filename, 'a')
fd.close()
shutil.move(self.tempFilename, self.filename)
else:
raise ValueError, 'AtomicFile.close called after rollback.'
def __del__(self):
# We rollback because if we're deleted without being explicitly closed,
# that's bad. We really should log this here, but as of yet we've got
# no logging facility in utils. I've got some ideas for this, though.
self.rollback()
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

326
src/utils/gen.py Normal file
View File

@ -0,0 +1,326 @@
###
# Copyright (c) 2002-2005, 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.
###
import os
import sys
import md5
import new
import sha
import time
import types
import compiler
import textwrap
import UserDict
import traceback
from iter import imap
from file import mktemp
from str import quoted, nItems, commaAndify
def abbrev(strings, d=None):
"""Returns a dictionary mapping unambiguous abbreviations to full forms."""
def eachSubstring(s):
for i in xrange(1, len(s)+1):
yield s[:i]
if len(strings) != len(set(strings)):
raise ValueError, \
'strings given to utils.abbrev have duplicates: %r' % strings
if d is None:
d = {}
for s in strings:
for abbreviation in eachSubstring(s):
if abbreviation not in d:
d[abbreviation] = s
else:
if abbreviation not in strings:
d[abbreviation] = None
removals = []
for key in d:
if d[key] is None:
removals.append(key)
for key in removals:
del d[key]
return d
def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True,
weeks=True, days=True, hours=True, minutes=True, seconds=True):
"""Given <elapsed> seconds, returns a string with an English description of
how much time as passed. leadingZeroes determines whether 0 days, 0 hours,
etc. will be printed; the others determine what larger time periods should
be used.
"""
ret = []
def format(s, i):
if i or leadingZeroes or ret:
if short:
ret.append('%s%s' % (i, s[0]))
else:
ret.append(nItems(s, i))
elapsed = int(elapsed)
assert years or weeks or days or \
hours or minutes or seconds, 'One flag must be True'
if years:
(yrs, elapsed) = (elapsed // 31536000, elapsed % 31536000)
format('year', yrs)
if weeks:
(wks, elapsed) = (elapsed // 604800, elapsed % 604800)
format('week', wks)
if days:
(ds, elapsed) = (elapsed // 86400, elapsed % 86400)
format('day', ds)
if hours:
(hrs, elapsed) = (elapsed // 3600, elapsed % 3600)
format('hour', hrs)
if minutes or seconds:
(mins, secs) = (elapsed // 60, elapsed % 60)
if leadingZeroes or mins:
format('minute', mins)
if seconds:
leadingZeroes = True
format('second', secs)
if not ret:
raise ValueError, 'Time difference not great enough to be noted.'
if short:
return ' '.join(ret)
else:
return commaAndify(ret)
def findBinaryInPath(s):
"""Return full path of a binary if it's in PATH, otherwise return None."""
cmdLine = None
for dir in os.getenv('PATH').split(':'):
filename = os.path.join(dir, s)
if os.path.exists(filename):
cmdLine = filename
break
return cmdLine
def sortBy(f, L):
"""Uses the decorate-sort-undecorate pattern to sort L by function f."""
for (i, elt) in enumerate(L):
L[i] = (f(elt), i, elt)
L.sort()
for (i, elt) in enumerate(L):
L[i] = L[i][2]
def saltHash(password, salt=None, hash='sha'):
if salt is None:
salt = mktemp()[:8]
if hash == 'sha':
hasher = sha.sha
elif hash == 'md5':
hasher = md5.md5
return '|'.join([salt, hasher(salt + password).hexdigest()])
def safeEval(s, namespace={'True': True, 'False': False, 'None': None}):
"""Evaluates s, safely. Useful for turning strings into tuples/lists/etc.
without unsafely using eval()."""
try:
node = compiler.parse(s)
except SyntaxError, e:
raise ValueError, 'Invalid string: %s.' % e
nodes = compiler.parse(s).node.nodes
if not nodes:
if node.__class__ is compiler.ast.Module:
return node.doc
else:
raise ValueError, 'Unsafe string: %s' % quoted(s)
node = nodes[0]
if node.__class__ is not compiler.ast.Discard:
raise ValueError, 'Invalid expression: %s' % quoted(s)
node = node.getChildNodes()[0]
def checkNode(node):
if node.__class__ is compiler.ast.Const:
return True
if node.__class__ in (compiler.ast.List,
compiler.ast.Tuple,
compiler.ast.Dict):
return all(checkNode, node.getChildNodes())
if node.__class__ is compiler.ast.Name:
if node.name in namespace:
return True
else:
return False
else:
return False
if checkNode(node):
return eval(s, namespace, namespace)
else:
raise ValueError, 'Unsafe string: %s' % quoted(s)
def exnToString(e):
"""Turns a simple exception instance into a string (better than str(e))"""
strE = str(e)
if strE:
return '%s: %s' % (e.__class__.__name__, strE)
else:
return e.__class__.__name__
class IterableMap(object):
"""Define .iteritems() in a class and subclass this to get the other iters.
"""
def iteritems(self):
raise NotImplementedError
def iterkeys(self):
for (key, _) in self.iteritems():
yield key
__iter__ = iterkeys
def itervalues(self):
for (_, value) in self.iteritems():
yield value
def items(self):
return list(self.iteritems())
def keys(self):
return list(self.iterkeys())
def values(self):
return list(self.itervalues())
def __len__(self):
ret = 0
for _ in self.iteritems():
ret += 1
return ret
def __nonzero__(self):
for _ in self.iteritems():
return True
return False
def changeFunctionName(f, name, doc=None):
if doc is None:
doc = f.__doc__
newf = types.FunctionType(f.func_code, f.func_globals, name,
f.func_defaults, f.func_closure)
newf.__doc__ = doc
return newf
class InsensitivePreservingDict(UserDict.DictMixin, object):
def key(self, s):
"""Override this if you wish."""
if s is not None:
s = s.lower()
return s
def __init__(self, dict=None, key=None):
if key is not None:
self.key = key
self.data = {}
if dict is not None:
self.update(dict)
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__,
super(InsensitivePreservingDict, self).__repr__())
def fromkeys(cls, keys, s=None, dict=None, key=None):
d = cls(dict=dict, key=key)
for key in keys:
d[key] = s
return d
fromkeys = classmethod(fromkeys)
def __getitem__(self, k):
return self.data[self.key(k)][1]
def __setitem__(self, k, v):
self.data[self.key(k)] = (k, v)
def __delitem__(self, k):
del self.data[self.key(k)]
def iteritems(self):
return self.data.itervalues()
def keys(self):
L = []
for (k, _) in self.iteritems():
L.append(k)
return L
def __reduce__(self):
return (self.__class__, (dict(self.data.values()),))
class NormalizingSet(set):
def __init__(self, iterable=()):
iterable = imap(self.normalize, iterable)
super(NormalizingSet, self).__init__(iterable)
def normalize(self, x):
return x
def add(self, x):
return super(NormalizingSet, self).add(self.normalize(x))
def remove(self, x):
return super(NormalizingSet, self).remove(self.normalize(x))
def discard(self, x):
return super(NormalizingSet, self).discard(self.normalize(x))
def __contains__(self, x):
return super(NormalizingSet, self).__contains__(self.normalize(x))
has_key = __contains__
def stackTrace(frame=None, compact=True):
if frame is None:
frame = sys._getframe()
if compact:
L = []
while frame:
lineno = frame.f_lineno
funcname = frame.f_code.co_name
filename = os.path.basename(frame.f_code.co_filename)
L.append('[%s|%s|%s]' % (filename, funcname, lineno))
frame = frame.f_back
return textwrap.fill(' '.join(L))
else:
return traceback.format_stack(frame)
def callTracer(fd=None, basename=True):
if fd is None:
fd = sys.stdout
def tracer(frame, event, _):
if event == 'call':
code = frame.f_code
lineno = frame.f_lineno
funcname = code.co_name
filename = code.co_filename
if basename:
filename = os.path.basename(filename)
print >>fd, '%s: %s(%s)' % (filename, funcname, lineno)
return tracer
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

147
src/utils/iter.py Normal file
View File

@ -0,0 +1,147 @@
###
# Copyright (c) 2002-2005, 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.
###
import new
import random
from itertools import *
def len(iterable):
"""Returns the length of an iterator."""
i = 0
for _ in iterable:
i += 1
return i
def trueCycle(iterable):
while 1:
yielded = False
for x in iterable:
yield x
yielded = True
if not yielded:
raise StopIteration
def groupby(key, iterable):
if key is None:
key = lambda x: x
it = iter(iterable)
value = it.next() # If there are no items, this takes an early exit
oldkey = key(value)
group = [value]
for value in it:
newkey = key(value)
if newkey != oldkey:
yield group
group = []
oldkey = newkey
group.append(value)
yield group
def partition(p, iterable):
"""Partitions an iterable based on a predicate p.
Returns a (yes,no) tuple"""
no = []
yes = []
for elt in iterable:
if p(elt):
yes.append(elt)
else:
no.append(elt)
return (yes, no)
def any(p, iterable):
"""Returns true if any element in iterable satisfies predicate p."""
for elt in ifilter(p, iterable):
return True
else:
return False
def all(p, iterable):
"""Returns true if all elements in iterable satisfy predicate p."""
for elt in ifilterfalse(p, iterable):
return False
else:
return True
def choice(iterable):
if isinstance(iterable, (list, tuple)):
return random.choice(iterable)
else:
n = 1
m = new.module('') # Guaranteed unique value.
ret = m
for x in iterable:
if random.random() < 1/n:
ret = x
n += 1
if ret is m:
raise IndexError
return ret
def flatten(iterable, strings=False):
"""Flattens a list of lists into a single list. See the test for examples.
"""
for elt in iterable:
if not strings and isinstance(elt, basestring):
yield elt
else:
try:
for x in flatten(elt):
yield x
except TypeError:
yield elt
def split(isSeparator, iterable, maxsplit=-1, yieldEmpty=False):
"""split(isSeparator, iterable, maxsplit=-1, yieldEmpty=False)
Splits an iterator based on a predicate isSeparator."""
if isinstance(isSeparator, basestring):
f = lambda s: s == isSeparator
else:
f = isSeparator
acc = []
for element in iterable:
if maxsplit == 0 or not f(element):
acc.append(element)
else:
maxsplit -= 1
if acc or yieldEmpty:
yield acc
acc = []
if acc or yieldEmpty:
yield acc
def ilen(iterable):
i = 0
for _ in iterable:
i += 1
return i
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

90
src/utils/net.py Normal file
View File

@ -0,0 +1,90 @@
###
# Copyright (c) 2002-2005, 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.
###
"""
Simple utility modules.
"""
import socket
def getSocket(host):
"""Returns a socket of the correct AF_INET type (v4 or v6) in order to
communicate with host.
"""
host = socket.gethostbyname(host)
if isIP(host):
return socket.socket(socket.AF_INET, socket.SOCK_STREAM)
elif isIPV6(host):
return socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else:
raise socket.error, 'Something wonky happened.'
def isIP(s):
"""Returns whether or not a given string is an IPV4 address.
>>> isIP('255.255.255.255')
1
>>> isIP('abc.abc.abc.abc')
0
"""
try:
return bool(socket.inet_aton(s))
except socket.error:
return False
def bruteIsIPV6(s):
if s.count('::') <= 1:
L = s.split(':')
if len(L) <= 8:
for x in L:
if x:
try:
int(x, 16)
except ValueError:
return False
return True
return False
def isIPV6(s):
"""Returns whether or not a given string is an IPV6 address."""
try:
if hasattr(socket, 'inet_pton'):
return bool(socket.inet_pton(socket.AF_INET6, s))
else:
return bruteIsIPV6(s)
except socket.error:
try:
socket.inet_pton(socket.AF_INET6, '::')
except socket.error:
# We gotta fake it.
return bruteIsIPV6(s)
return False
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -1,5 +1,5 @@
### ###
# Copyright (c) 2004-2005, Jeremiah Fincher # Copyright (c) 2002-2005, Jeremiah Fincher
# 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
@ -27,21 +27,19 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
from supybot.test import * def window(L, size):
"""Returns a sliding 'window' through the list L of size size."""
assert not isinstance(L, int), 'Argument order swapped: window(L, size)'
if size < 1:
raise ValueError, 'size <= 0 disallowed.'
for i in xrange(len(L) - (size-1)):
yield L[i:i+size]
import supybot.webutils as webutils def mapinto(f, L):
for (i, x) in enumerate(L):
L[i] = f(x)
class WebutilsTestCase(SupyTestCase):
def testGetDomain(self):
self.assertEqual(webutils.getDomain('http://slashdot.org/foo/bar.exe'),
'slashdot.org')
if network:
def testGetUrlWithSize(self):
url = 'http://slashdot.org/'
self.failUnless(len(webutils.getUrl(url, 1024)) == 1024)
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

354
src/utils/str.py Normal file
View File

@ -0,0 +1,354 @@
###
# Copyright (c) 2002-2005, 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.
###
"""
Simple utility functions related to strings.
"""
import re
import new
import string
import textwrap
import supybot.structures as structures
curry = new.instancemethod
chars = string.maketrans('', '')
def rsplit(s, sep=None, maxsplit=-1):
"""Equivalent to str.split, except splitting from the right."""
if sys.version_info < (2, 4, 0):
if sep is not None:
sep = sep[::-1]
L = s[::-1].split(sep, maxsplit)
L.reverse()
return [s[::-1] for s in L]
else:
return s.rsplit(sep, maxsplit)
def normalizeWhitespace(s):
"""Normalizes the whitespace in a string; \s+ becomes one space."""
return ' '.join(s.split())
def distance(s, t):
"""Returns the levenshtein edit distance between two strings."""
n = len(s)
m = len(t)
if n == 0:
return m
elif m == 0:
return n
d = []
for i in xrange(n+1):
d.append([])
for j in xrange(m+1):
d[i].append(0)
d[0][j] = j
d[i][0] = i
for i in xrange(1, n+1):
cs = s[i-1]
for j in xrange(1, m+1):
ct = t[j-1]
cost = int(cs != ct)
d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+cost)
return d[n][m]
_soundextrans = string.maketrans(string.ascii_uppercase,
'01230120022455012623010202')
_notUpper = chars.translate(chars, string.ascii_uppercase)
def soundex(s, length=4):
"""Returns the soundex hash of a given string."""
s = s.upper() # Make everything uppercase.
s = s.translate(chars, _notUpper) # Delete non-letters.
if not s:
raise ValueError, 'Invalid string for soundex: %s'
firstChar = s[0] # Save the first character.
s = s.translate(_soundextrans) # Convert to soundex numbers.
s = s.lstrip(s[0]) # Remove all repeated first characters.
L = [firstChar]
for c in s:
if c != L[-1]:
L.append(c)
L = [c for c in L if c != '0'] + (['0']*(length-1))
s = ''.join(L)
return length and s[:length] or s.rstrip('0')
def dqrepr(s):
"""Returns a repr() of s guaranteed to be in double quotes."""
# The wankers-that-be decided not to use double-quotes anymore in 2.3.
# return '"' + repr("'\x00" + s)[6:]
return '"%s"' % s.encode('string_escape').replace('"', '\\"')
def quoted(s):
"""Returns a quoted s."""
return '"%s"' % s
def _getSep(s):
if len(s) < 2:
raise ValueError, 'string given to _getSep is too short: %r' % s
if s.startswith('m') or s.startswith('s'):
separator = s[1]
else:
separator = s[0]
if separator.isalnum() or separator in '{}[]()<>':
raise ValueError, \
'Invalid separator: separator must not be alphanumeric or in ' \
'"{}[]()<>"'
return separator
def _getSplitterRe(s):
separator = _getSep(s)
return re.compile(r'(?<!\\)%s' % re.escape(separator))
def perlReToPythonRe(s):
"""Converts a string representation of a Perl regular expression (i.e.,
m/^foo$/i or /foo|bar/) to a Python regular expression.
"""
sep = _getSep(s)
splitter = _getSplitterRe(s)
try:
(kind, regexp, flags) = splitter.split(s)
except ValueError: # Unpack list of wrong size.
raise ValueError, 'Must be of the form m/.../ or /.../'
regexp = regexp.replace('\\'+sep, sep)
if kind not in ('', 'm'):
raise ValueError, 'Invalid kind: must be in ("", "m")'
flag = 0
try:
for c in flags.upper():
flag |= getattr(re, c)
except AttributeError:
raise ValueError, 'Invalid flag: %s' % c
try:
return re.compile(regexp, flag)
except re.error, e:
raise ValueError, str(e)
def perlReToReplacer(s):
"""Converts a string representation of a Perl regular expression (i.e.,
s/foo/bar/g or s/foo/bar/i) to a Python function doing the equivalent
replacement.
"""
sep = _getSep(s)
splitter = _getSplitterRe(s)
try:
(kind, regexp, replace, flags) = splitter.split(s)
except ValueError: # Unpack list of wrong size.
raise ValueError, 'Must be of the form s/.../.../'
regexp = regexp.replace('\x08', r'\b')
replace = replace.replace('\\'+sep, sep)
for i in xrange(10):
replace = replace.replace(chr(i), r'\%s' % i)
if kind != 's':
raise ValueError, 'Invalid kind: must be "s"'
g = False
if 'g' in flags:
g = True
flags = filter('g'.__ne__, flags)
r = perlReToPythonRe('/'.join(('', regexp, flags)))
if g:
return curry(r.sub, replace)
else:
return lambda s: r.sub(replace, s, 1)
_perlVarSubstituteRe = re.compile(r'\$\{([^}]+)\}|\$([a-zA-Z][a-zA-Z0-9]*)')
def perlVariableSubstitute(vars, text):
def replacer(m):
(braced, unbraced) = m.groups()
var = braced or unbraced
try:
x = vars[var]
if callable(x):
return x()
else:
return str(x)
except KeyError:
if braced:
return '${%s}' % braced
else:
return '$' + unbraced
return _perlVarSubstituteRe.sub(replacer, text)
def commaAndify(seq, comma=',', And='and'):
"""Given a a sequence, returns an English clause for that sequence.
I.e., given [1, 2, 3], returns '1, 2, and 3'
"""
L = list(seq)
if len(L) == 0:
return ''
elif len(L) == 1:
return ''.join(L) # We need this because it raises TypeError.
elif len(L) == 2:
L.insert(1, And)
return ' '.join(L)
else:
L[-1] = '%s %s' % (And, L[-1])
sep = '%s ' % comma
return sep.join(L)
_unCommaTheRe = re.compile(r'(.*),\s*(the)$', re.I)
def unCommaThe(s):
"""Takes a string of the form 'foo, the' and turns it into 'the foo'."""
m = _unCommaTheRe.match(s)
if m is not None:
return '%s %s' % (m.group(2), m.group(1))
else:
return s
def ellipsisify(s, n):
"""Returns a shortened version of s. Produces up to the first n chars at
the nearest word boundary.
"""
if len(s) <= n:
return s
else:
return (textwrap.wrap(s, n-3)[0] + '...')
plurals = structures.TwoWayDictionary({})
def matchCase(s1, s2):
"""Matches the case of s1 in s2"""
if s1.isupper():
return s2.upper()
else:
L = list(s2)
for (i, char) in enumerate(s1[:len(s2)]):
if char.isupper():
L[i] = L[i].upper()
return ''.join(L)
consonants = 'bcdfghjklmnpqrstvwxz'
_pluralizeRegex = re.compile('[%s]y$' % consonants)
def pluralize(s, i=2):
"""Returns the plural of s based on its number i. Put any exceptions to
the general English rule of appending 's' in the plurals dictionary.
"""
if i == 1:
return s
else:
lowered = s.lower()
# Exception dictionary
if lowered in plurals:
return matchCase(s, plurals[lowered])
# Words ending with 'ch', 'sh' or 'ss' such as 'punch(es)', 'fish(es)
# and miss(es)
elif any(lowered.endswith, ['x', 'ch', 'sh', 'ss']):
return matchCase(s, s+'es')
# Words ending with a consonant followed by a 'y' such as
# 'try (tries)' or 'spy (spies)'
elif _pluralizeRegex.search(lowered):
return matchCase(s, s[:-1] + 'ies')
# In all other cases, we simply add an 's' to the base word
else:
return matchCase(s, s+'s')
_depluralizeRegex = re.compile('[%s]ies' % consonants)
def depluralize(s):
"""Returns the singular of s."""
lowered = s.lower()
if lowered in plurals:
return matchCase(s, plurals[lowered])
elif any(lowered.endswith, ['ches', 'shes', 'sses']):
return s[:-2]
elif re.search(_depluralizeRegex, lowered):
return s[:-3] + 'y'
else:
if lowered.endswith('s'):
return s[:-1] # Chop off 's'.
else:
return s # Don't know what to do.
def nItems(item, n, between=None):
"""Works like this:
>>> nItems('clock', 1)
'1 clock'
>>> nItems('clock', 10)
'10 clocks'
>>> nItems('clock', 10, between='grandfather')
'10 grandfather clocks'
"""
if between is None:
return '%s %s' % (n, pluralize(item, n))
else:
return '%s %s %s' % (n, between, pluralize(item, n))
def be(i):
"""Returns the form of the verb 'to be' based on the number i."""
if i == 1:
return 'is'
else:
return 'are'
def has(i):
"""Returns the form of the verb 'to have' based on the number i."""
if i == 1:
return 'has'
else:
return 'have'
_formatRe = re.compile('%([isfbhL])')
def format(s, *args, **kwargs):
kwargs.setdefault('decimalSeparator', decimalSeparator)
kwargs.setdefault('thousandsSeparator', thousandsSeparator)
args = list(args)
args.reverse() # For more efficiency popping.
def sub(match):
char = match.group(1)
if char == 's': # Plain string.
return str(args.pop())
elif char == 'i': # Integer
# XXX Improve me!
return str(args.pop())
elif char == 'f': # Float
# XXX Improve me!
return str(args.pop())
elif char == 'b': # form of the verb 'to be'
return be(args.pop())
elif char == 'h': # form of the verb 'to have'
return has(args.pop())
elif char == 'L': # commaAndify the list.
return commaAndify(args.pop())
else:
assert False, 'Invalid char in sub (in format).'
return _formatRe.sub(sub, s)
def toBool(s):
s = s.strip().lower()
if s in ('true', 'on', 'enable', 'enabled', '1'):
return True
elif s in ('false', 'off', 'disable', 'disabled', '0'):
return False
else:
raise ValueError, 'Invalid string for toBool: %s' % quoted(s)
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -27,24 +27,22 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot.fix as fix
import re import re
import socket import socket
import urllib import urllib
import urllib2 import urllib2
import httplib import httplib
import sgmllib
import urlparse import urlparse
import htmlentitydefs
import supybot.conf as conf from str import normalizeWhitespace
Request = urllib2.Request Request = urllib2.Request
urlquote = urllib.quote urlquote = urllib.quote
urlunquote = urllib.unquote urlunquote = urllib.unquote
class WebError(Exception): class Error(Exception):
pass pass
# XXX We should tighten this up a bit. # XXX We should tighten this up a bit.
@ -75,14 +73,18 @@ def strError(e):
else: else:
return str(e) return str(e)
_headers = { defaultHeaders = {
'User-agent': 'Mozilla/4.0 (compatible; Supybot %s)' % conf.version, 'User-agent': 'Mozilla/5.0 (compatible; utils.web python module)'
} }
# Other modules should feel free to replace this with an appropriate
# application-specific function. Feel free to use a callable here.
proxy = None
def getUrlFd(url, headers=None): def getUrlFd(url, headers=None):
"""Gets a file-like object for a url.""" """Gets a file-like object for a url."""
if headers is None: if headers is None:
headers = _headers headers = defaultHeaders
try: try:
if not isinstance(url, urllib2.Request): if not isinstance(url, urllib2.Request):
if '#' in url: if '#' in url:
@ -90,7 +92,7 @@ def getUrlFd(url, headers=None):
request = urllib2.Request(url, headers=headers) request = urllib2.Request(url, headers=headers)
else: else:
request = url request = url
httpProxy = conf.supybot.protocols.http.proxy() httpProxy = force(proxy)
if httpProxy: if httpProxy:
request.set_proxy(httpProxy, 'http') request.set_proxy(httpProxy, 'http')
fd = urllib2.urlopen(request) fd = urllib2.urlopen(request)
@ -125,5 +127,39 @@ def getUrl(url, size=None, headers=None):
def getDomain(url): def getDomain(url):
return urlparse.urlparse(url)[1] return urlparse.urlparse(url)[1]
class HtmlToText(sgmllib.SGMLParser):
"""Taken from some eff-bot code on c.l.p."""
entitydefs = htmlentitydefs.entitydefs.copy()
entitydefs['nbsp'] = ' '
def __init__(self, tagReplace=' '):
self.data = []
self.tagReplace = tagReplace
sgmllib.SGMLParser.__init__(self)
def unknown_starttag(self, tag, attr):
self.data.append(self.tagReplace)
def unknown_endtag(self, tag):
self.data.append(self.tagReplace)
def handle_data(self, data):
self.data.append(data)
def getText(self):
text = ''.join(self.data).strip()
return normalizeWhitespace(text)
def htmlToText(s, tagReplace=' '):
"""Turns HTML into text. tagReplace is a string to replace HTML tags with.
"""
x = HtmlToText(tagReplace)
x.feed(s)
return x.getText()
def mungeEmail(s):
s = s.replace('@', ' AT ')
s = s.replace('.', ' DOT ')
return s
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -31,10 +31,6 @@
Module for general worldly stuff, like global variables and whatnot. Module for general worldly stuff, like global variables and whatnot.
""" """
import supybot.fix as fix
import gc import gc
import os import os
import sys import sys
@ -54,8 +50,6 @@ startedAt = time.time() # Just in case it doesn't get set later.
starting = False starting = False
mainThread = threading.currentThread() mainThread = threading.currentThread()
# ??? Should we do this? What do we gain?
# assert 'MainThread' in repr(mainThread)
def isMainThread(): def isMainThread():
return mainThread is threading.currentThread() return mainThread is threading.currentThread()

View File

@ -27,6 +27,7 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
# We're just masquerading as a plugin :)
import test import test
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -42,7 +42,7 @@ class TokenizerTestCase(SupyTestCase):
self.assertEqual(tokenize(''), []) self.assertEqual(tokenize(''), [])
def testNullCharacter(self): def testNullCharacter(self):
self.assertEqual(tokenize(utils.dqrepr('\0')), ['\0']) self.assertEqual(tokenize(utils.str.dqrepr('\0')), ['\0'])
def testSingleDQInDQString(self): def testSingleDQInDQString(self):
self.assertEqual(tokenize('"\\""'), ['"']) self.assertEqual(tokenize('"\\""'), ['"'])

View File

@ -28,11 +28,24 @@
### ###
from supybot.test import * from supybot.test import *
import sets
import time
import supybot.utils as utils import supybot.utils as utils
class UtilsTest(SupyTestCase): class GenTest(SupyTestCase):
def testInsensitivePreservingDict(self):
ipd = utils.InsensitivePreservingDict
d = ipd(dict(Foo=10))
self.failUnless(d['foo'] == 10)
self.assertEqual(d.keys(), ['Foo'])
self.assertEqual(d.get('foo'), 10)
self.assertEqual(d.get('Foo'), 10)
def testFindBinaryInPath(self):
if os.name == 'posix':
self.assertEqual(None, utils.findBinaryInPath('asdfhjklasdfhjkl'))
self.failUnless(utils.findBinaryInPath('sh').endswith('/bin/sh'))
def testExnToString(self): def testExnToString(self):
try: try:
raise KeyError, 1 raise KeyError, 1
@ -43,270 +56,21 @@ class UtilsTest(SupyTestCase):
except Exception, e: except Exception, e:
self.assertEqual(utils.exnToString(e), 'EOFError') self.assertEqual(utils.exnToString(e), 'EOFError')
def testMatchCase(self): def testSaltHash(self):
f = utils.matchCase s = utils.saltHash('jemfinch')
self.assertEqual('bar', f('foo', 'bar')) (salt, hash) = s.split('|')
self.assertEqual('Bar', f('Foo', 'bar')) self.assertEqual(utils.saltHash('jemfinch', salt=salt), s)
self.assertEqual('BAr', f('FOo', 'bar'))
self.assertEqual('BAR', f('FOO', 'bar'))
self.assertEqual('bAR', f('fOO', 'bar'))
self.assertEqual('baR', f('foO', 'bar'))
self.assertEqual('BaR', f('FoO', 'bar'))
def testPluralize(self): def testSafeEval(self):
f = utils.pluralize for s in ['1', '()', '(1,)', '[]', '{}', '{1:2}', '{1:(2,3)}',
self.assertEqual('bike', f('bike', 1)) '1.0', '[1,2,3]', 'True', 'False', 'None',
self.assertEqual('bikes', f('bike', 2)) '(True,False,None)', '"foo"', '{"foo": "bar"}']:
self.assertEqual('BIKE', f('BIKE', 1)) self.assertEqual(eval(s), utils.safeEval(s))
self.assertEqual('BIKES', f('BIKE', 2)) for s in ['lambda: 2', 'import foo', 'foo.bar']:
self.assertEqual('match', f('match', 1)) self.assertRaises(ValueError, utils.safeEval, s)
self.assertEqual('matches', f('match', 2))
self.assertEqual('Patch', f('Patch', 1))
self.assertEqual('Patches', f('Patch', 2))
self.assertEqual('fish', f('fish', 1))
self.assertEqual('fishes', f('fish', 2))
self.assertEqual('try', f('try', 1))
self.assertEqual('tries', f('try', 2))
self.assertEqual('day', f('day', 1))
self.assertEqual('days', f('day', 2))
def testDepluralize(self): def testSafeEvalTurnsSyntaxErrorIntoValueError(self):
f = utils.depluralize self.assertRaises(ValueError, utils.safeEval, '/usr/local/')
self.assertEqual('bike', f('bikes'))
self.assertEqual('Bike', f('Bikes'))
self.assertEqual('BIKE', f('BIKES'))
self.assertEqual('match', f('matches'))
self.assertEqual('Match', f('Matches'))
self.assertEqual('fish', f('fishes'))
self.assertEqual('try', f('tries'))
def testTimeElapsed(self):
self.assertRaises(ValueError, utils.timeElapsed, 0,
leadingZeroes=False, seconds=False)
then = 0
now = 0
for now, expected in [(0, '0 seconds'),
(1, '1 second'),
(60, '1 minute and 0 seconds'),
(61, '1 minute and 1 second'),
(62, '1 minute and 2 seconds'),
(122, '2 minutes and 2 seconds'),
(3722, '1 hour, 2 minutes, and 2 seconds'),
(7322, '2 hours, 2 minutes, and 2 seconds'),
(90061,'1 day, 1 hour, 1 minute, and 1 second'),
(180122, '2 days, 2 hours, 2 minutes, '
'and 2 seconds')]:
self.assertEqual(utils.timeElapsed(now - then), expected)
def timeElapsedShort(self):
self.assertEqual(utils.timeElapsed(123, short=True), '2m 3s')
def testDistance(self):
self.assertEqual(utils.distance('', ''), 0)
self.assertEqual(utils.distance('a', 'b'), 1)
self.assertEqual(utils.distance('a', 'a'), 0)
self.assertEqual(utils.distance('foobar', 'jemfinch'), 8)
self.assertEqual(utils.distance('a', 'ab'), 1)
self.assertEqual(utils.distance('foo', ''), 3)
self.assertEqual(utils.distance('', 'foo'), 3)
self.assertEqual(utils.distance('appel', 'nappe'), 2)
self.assertEqual(utils.distance('nappe', 'appel'), 2)
def testAbbrev(self):
L = ['abc', 'bcd', 'bbe', 'foo', 'fool']
d = utils.abbrev(L)
def getItem(s):
return d[s]
self.assertRaises(KeyError, getItem, 'f')
self.assertRaises(KeyError, getItem, 'fo')
self.assertRaises(KeyError, getItem, 'b')
self.assertEqual(d['bb'], 'bbe')
self.assertEqual(d['bc'], 'bcd')
self.assertEqual(d['a'], 'abc')
self.assertEqual(d['ab'], 'abc')
self.assertEqual(d['fool'], 'fool')
self.assertEqual(d['foo'], 'foo')
def testAbbrevFailsWithDups(self):
L = ['english', 'english']
self.assertRaises(ValueError, utils.abbrev, L)
def testSoundex(self):
L = [('Euler', 'E460'),
('Ellery', 'E460'),
('Gauss', 'G200'),
('Ghosh', 'G200'),
('Hilbert', 'H416'),
('Heilbronn', 'H416'),
('Knuth', 'K530'),
('Kant', 'K530'),
('Lloyd', 'L300'),
('Ladd', 'L300'),
('Lukasiewicz', 'L222'),
('Lissajous', 'L222')]
for (name, key) in L:
soundex = utils.soundex(name)
self.assertEqual(soundex, key,
'%s was %s, not %s' % (name, soundex, key))
self.assertRaises(ValueError, utils.soundex, '3')
self.assertRaises(ValueError, utils.soundex, "'")
def testDQRepr(self):
L = ['foo', 'foo\'bar', 'foo"bar', '"', '\\', '', '\x00']
for s in L:
r = utils.dqrepr(s)
self.assertEqual(s, eval(r), s)
self.failUnless(r[0] == '"' and r[-1] == '"', s)
## def testQuoted(self):
## s = 'foo'
## t = 'let\'s'
## self.assertEqual("'%s'" % s, utils.quoted(s), s)
## self.assertEqual('"%s"' % t, utils.quoted(t), t)
def testPerlReToPythonRe(self):
r = utils.perlReToPythonRe('m/foo/')
self.failUnless(r.search('foo'))
r = utils.perlReToPythonRe('/foo/')
self.failUnless(r.search('foo'))
r = utils.perlReToPythonRe('m/\\//')
self.failUnless(r.search('/'))
r = utils.perlReToPythonRe('m/cat/i')
self.failUnless(r.search('CAT'))
self.assertRaises(ValueError, utils.perlReToPythonRe, 'm/?/')
def testP2PReDifferentSeparator(self):
r = utils.perlReToPythonRe('m!foo!')
self.failUnless(r.search('foo'))
def testPerlReToReplacer(self):
f = utils.perlReToReplacer('s/foo/bar/')
self.assertEqual(f('foobarbaz'), 'barbarbaz')
f = utils.perlReToReplacer('s/fool/bar/')
self.assertEqual(f('foobarbaz'), 'foobarbaz')
f = utils.perlReToReplacer('s/foo//')
self.assertEqual(f('foobarbaz'), 'barbaz')
f = utils.perlReToReplacer('s/ba//')
self.assertEqual(f('foobarbaz'), 'foorbaz')
f = utils.perlReToReplacer('s/ba//g')
self.assertEqual(f('foobarbaz'), 'foorz')
f = utils.perlReToReplacer('s/ba\\///g')
self.assertEqual(f('fooba/rba/z'), 'foorz')
f = utils.perlReToReplacer('s/cat/dog/i')
self.assertEqual(f('CATFISH'), 'dogFISH')
f = utils.perlReToReplacer('s/foo/foo\/bar/')
self.assertEqual(f('foo'), 'foo/bar')
f = utils.perlReToReplacer('s/^/foo/')
self.assertEqual(f('bar'), 'foobar')
def testPReToReplacerDifferentSeparator(self):
f = utils.perlReToReplacer('s#foo#bar#')
self.assertEqual(f('foobarbaz'), 'barbarbaz')
def testPerlReToReplacerBug850931(self):
f = utils.perlReToReplacer('s/\b(\w+)\b/\1./g')
self.assertEqual(f('foo bar baz'), 'foo. bar. baz.')
def testPerlVariableSubstitute(self):
f = utils.perlVariableSubstitute
vars = {'foo': 'bar', 'b a z': 'baz', 'b': 'c', 'i': 100,
'f': lambda: 'called'}
self.assertEqual(f(vars, '$foo'), 'bar')
self.assertEqual(f(vars, '${foo}'), 'bar')
self.assertEqual(f(vars, '$b'), 'c')
self.assertEqual(f(vars, '${b}'), 'c')
self.assertEqual(f(vars, '$i'), '100')
self.assertEqual(f(vars, '${i}'), '100')
self.assertEqual(f(vars, '$f'), 'called')
self.assertEqual(f(vars, '${f}'), 'called')
self.assertEqual(f(vars, '${b a z}'), 'baz')
self.assertEqual(f(vars, '$b:$i'), 'c:100')
def testFindBinaryInPath(self):
if os.name == 'posix':
self.assertEqual(None, utils.findBinaryInPath('asdfhjklasdfhjkl'))
self.failUnless(utils.findBinaryInPath('sh').endswith('/bin/sh'))
def testCommaAndify(self):
L = ['foo']
original = L[:]
self.assertEqual(utils.commaAndify(L), 'foo')
self.assertEqual(utils.commaAndify(L, And='or'), 'foo')
self.assertEqual(L, original)
L.append('bar')
original = L[:]
self.assertEqual(utils.commaAndify(L), 'foo and bar')
self.assertEqual(utils.commaAndify(L, And='or'), 'foo or bar')
self.assertEqual(L, original)
L.append('baz')
original = L[:]
self.assertEqual(utils.commaAndify(L), 'foo, bar, and baz')
self.assertEqual(utils.commaAndify(L, And='or'), 'foo, bar, or baz')
self.assertEqual(utils.commaAndify(L, comma=';'), 'foo; bar; and baz')
self.assertEqual(utils.commaAndify(L, comma=';', And='or'),
'foo; bar; or baz')
self.assertEqual(L, original)
self.failUnless(utils.commaAndify(sets.Set(L)))
def testCommaAndifyRaisesTypeError(self):
L = [(2,)]
self.assertRaises(TypeError, utils.commaAndify, L)
L.append((3,))
self.assertRaises(TypeError, utils.commaAndify, L)
def testUnCommaThe(self):
self.assertEqual(utils.unCommaThe('foo bar'), 'foo bar')
self.assertEqual(utils.unCommaThe('foo bar, the'), 'the foo bar')
self.assertEqual(utils.unCommaThe('foo bar, The'), 'The foo bar')
self.assertEqual(utils.unCommaThe('foo bar,the'), 'the foo bar')
def testNormalizeWhitespace(self):
self.assertEqual(utils.normalizeWhitespace('foo bar'), 'foo bar')
self.assertEqual(utils.normalizeWhitespace('foo\nbar'), 'foo bar')
self.assertEqual(utils.normalizeWhitespace('foo\tbar'), 'foo bar')
def testSortBy(self):
L = ['abc', 'z', 'AD']
utils.sortBy(len, L)
self.assertEqual(L, ['z', 'AD', 'abc'])
utils.sortBy(str.lower, L)
self.assertEqual(L, ['abc', 'AD', 'z'])
L = ['supybot', 'Supybot']
utils.sortBy(str.lower, L)
self.assertEqual(L, ['supybot', 'Supybot'])
def testSorted(self):
L = ['a', 'c', 'b']
self.assertEqual(utils.sorted(L), ['a', 'b', 'c'])
self.assertEqual(L, ['a', 'c', 'b'])
def mycmp(x, y):
return -cmp(x, y)
self.assertEqual(utils.sorted(L, mycmp), ['c', 'b', 'a'])
def testNItems(self):
self.assertEqual(utils.nItems('tool', 1, 'crazy'), '1 crazy tool')
self.assertEqual(utils.nItems('tool', 1), '1 tool')
self.assertEqual(utils.nItems('tool', 2, 'crazy'), '2 crazy tools')
self.assertEqual(utils.nItems('tool', 2), '2 tools')
def testItersplit(self):
itersplit = utils.itersplit
L = [1, 2, 3] * 3
s = 'foo bar baz'
self.assertEqual(list(itersplit(lambda x: x == 3, L)),
[[1, 2], [1, 2], [1, 2]])
self.assertEqual(list(itersplit(lambda x: x == 3, L, yieldEmpty=True)),
[[1, 2], [1, 2], [1, 2], []])
self.assertEqual(list(itersplit(lambda x: x, [])), [])
self.assertEqual(list(itersplit(lambda c: c.isspace(), s)),
map(list, s.split()))
self.assertEqual(list(itersplit('for'.__eq__, ['foo', 'for', 'bar'])),
[['foo'], ['bar']])
self.assertEqual(list(itersplit('for'.__eq__,
['foo','for','bar','for', 'baz'], 1)),
[['foo'], ['bar', 'for', 'baz']])
def testIterableMap(self): def testIterableMap(self):
class alist(utils.IterableMap): class alist(utils.IterableMap):
@ -333,9 +97,277 @@ class UtilsTest(SupyTestCase):
self.assertEqual(list(AL.itervalues()), [2, 3, 4]) self.assertEqual(list(AL.itervalues()), [2, 3, 4])
self.assertEqual(len(AL), 3) self.assertEqual(len(AL), 3)
def testSortBy(self):
L = ['abc', 'z', 'AD']
utils.sortBy(len, L)
self.assertEqual(L, ['z', 'AD', 'abc'])
utils.sortBy(str.lower, L)
self.assertEqual(L, ['abc', 'AD', 'z'])
L = ['supybot', 'Supybot']
utils.sortBy(str.lower, L)
self.assertEqual(L, ['supybot', 'Supybot'])
def testSorted(self):
L = ['a', 'c', 'b']
self.assertEqual(utils.sorted(L), ['a', 'b', 'c'])
self.assertEqual(L, ['a', 'c', 'b'])
def mycmp(x, y):
return -cmp(x, y)
self.assertEqual(utils.sorted(L, mycmp), ['c', 'b', 'a'])
def testTimeElapsed(self):
self.assertRaises(ValueError, utils.timeElapsed, 0,
leadingZeroes=False, seconds=False)
then = 0
now = 0
for now, expected in [(0, '0 seconds'),
(1, '1 second'),
(60, '1 minute and 0 seconds'),
(61, '1 minute and 1 second'),
(62, '1 minute and 2 seconds'),
(122, '2 minutes and 2 seconds'),
(3722, '1 hour, 2 minutes, and 2 seconds'),
(7322, '2 hours, 2 minutes, and 2 seconds'),
(90061,'1 day, 1 hour, 1 minute, and 1 second'),
(180122, '2 days, 2 hours, 2 minutes, '
'and 2 seconds')]:
self.assertEqual(utils.timeElapsed(now - then), expected)
def timeElapsedShort(self):
self.assertEqual(utils.timeElapsed(123, short=True), '2m 3s')
def testAbbrev(self):
L = ['abc', 'bcd', 'bbe', 'foo', 'fool']
d = utils.abbrev(L)
def getItem(s):
return d[s]
self.assertRaises(KeyError, getItem, 'f')
self.assertRaises(KeyError, getItem, 'fo')
self.assertRaises(KeyError, getItem, 'b')
self.assertEqual(d['bb'], 'bbe')
self.assertEqual(d['bc'], 'bcd')
self.assertEqual(d['a'], 'abc')
self.assertEqual(d['ab'], 'abc')
self.assertEqual(d['fool'], 'fool')
self.assertEqual(d['foo'], 'foo')
def testAbbrevFailsWithDups(self):
L = ['english', 'english']
self.assertRaises(ValueError, utils.abbrev, L)
class StrTest(SupyTestCase):
def testMatchCase(self):
f = utils.str.matchCase
self.assertEqual('bar', f('foo', 'bar'))
self.assertEqual('Bar', f('Foo', 'bar'))
self.assertEqual('BAr', f('FOo', 'bar'))
self.assertEqual('BAR', f('FOO', 'bar'))
self.assertEqual('bAR', f('fOO', 'bar'))
self.assertEqual('baR', f('foO', 'bar'))
self.assertEqual('BaR', f('FoO', 'bar'))
def testPluralize(self):
f = utils.str.pluralize
self.assertEqual('bike', f('bike', 1))
self.assertEqual('bikes', f('bike', 2))
self.assertEqual('BIKE', f('BIKE', 1))
self.assertEqual('BIKES', f('BIKE', 2))
self.assertEqual('match', f('match', 1))
self.assertEqual('matches', f('match', 2))
self.assertEqual('Patch', f('Patch', 1))
self.assertEqual('Patches', f('Patch', 2))
self.assertEqual('fish', f('fish', 1))
self.assertEqual('fishes', f('fish', 2))
self.assertEqual('try', f('try', 1))
self.assertEqual('tries', f('try', 2))
self.assertEqual('day', f('day', 1))
self.assertEqual('days', f('day', 2))
def testDepluralize(self):
f = utils.str.depluralize
self.assertEqual('bike', f('bikes'))
self.assertEqual('Bike', f('Bikes'))
self.assertEqual('BIKE', f('BIKES'))
self.assertEqual('match', f('matches'))
self.assertEqual('Match', f('Matches'))
self.assertEqual('fish', f('fishes'))
self.assertEqual('try', f('tries'))
def testDistance(self):
self.assertEqual(utils.str.distance('', ''), 0)
self.assertEqual(utils.str.distance('a', 'b'), 1)
self.assertEqual(utils.str.distance('a', 'a'), 0)
self.assertEqual(utils.str.distance('foobar', 'jemfinch'), 8)
self.assertEqual(utils.str.distance('a', 'ab'), 1)
self.assertEqual(utils.str.distance('foo', ''), 3)
self.assertEqual(utils.str.distance('', 'foo'), 3)
self.assertEqual(utils.str.distance('appel', 'nappe'), 2)
self.assertEqual(utils.str.distance('nappe', 'appel'), 2)
def testSoundex(self):
L = [('Euler', 'E460'),
('Ellery', 'E460'),
('Gauss', 'G200'),
('Ghosh', 'G200'),
('Hilbert', 'H416'),
('Heilbronn', 'H416'),
('Knuth', 'K530'),
('Kant', 'K530'),
('Lloyd', 'L300'),
('Ladd', 'L300'),
('Lukasiewicz', 'L222'),
('Lissajous', 'L222')]
for (name, key) in L:
soundex = utils.str.soundex(name)
self.assertEqual(soundex, key,
'%s was %s, not %s' % (name, soundex, key))
self.assertRaises(ValueError, utils.str.soundex, '3')
self.assertRaises(ValueError, utils.str.soundex, "'")
def testDQRepr(self):
L = ['foo', 'foo\'bar', 'foo"bar', '"', '\\', '', '\x00']
for s in L:
r = utils.str.dqrepr(s)
self.assertEqual(s, eval(r), s)
self.failUnless(r[0] == '"' and r[-1] == '"', s)
def testPerlReToPythonRe(self):
f = utils.str.perlReToPythonRe
r = f('m/foo/')
self.failUnless(r.search('foo'))
r = f('/foo/')
self.failUnless(r.search('foo'))
r = f('m/\\//')
self.failUnless(r.search('/'))
r = f('m/cat/i')
self.failUnless(r.search('CAT'))
self.assertRaises(ValueError, f, 'm/?/')
def testP2PReDifferentSeparator(self):
r = utils.str.perlReToPythonRe('m!foo!')
self.failUnless(r.search('foo'))
def testPerlReToReplacer(self):
PRTR = utils.str.perlReToReplacer
f = PRTR('s/foo/bar/')
self.assertEqual(f('foobarbaz'), 'barbarbaz')
f = PRTR('s/fool/bar/')
self.assertEqual(f('foobarbaz'), 'foobarbaz')
f = PRTR('s/foo//')
self.assertEqual(f('foobarbaz'), 'barbaz')
f = PRTR('s/ba//')
self.assertEqual(f('foobarbaz'), 'foorbaz')
f = PRTR('s/ba//g')
self.assertEqual(f('foobarbaz'), 'foorz')
f = PRTR('s/ba\\///g')
self.assertEqual(f('fooba/rba/z'), 'foorz')
f = PRTR('s/cat/dog/i')
self.assertEqual(f('CATFISH'), 'dogFISH')
f = PRTR('s/foo/foo\/bar/')
self.assertEqual(f('foo'), 'foo/bar')
f = PRTR('s/^/foo/')
self.assertEqual(f('bar'), 'foobar')
def testPReToReplacerDifferentSeparator(self):
f = utils.str.perlReToReplacer('s#foo#bar#')
self.assertEqual(f('foobarbaz'), 'barbarbaz')
def testPerlReToReplacerBug850931(self):
f = utils.str.perlReToReplacer('s/\b(\w+)\b/\1./g')
self.assertEqual(f('foo bar baz'), 'foo. bar. baz.')
def testPerlVariableSubstitute(self):
f = utils.str.perlVariableSubstitute
vars = {'foo': 'bar', 'b a z': 'baz', 'b': 'c', 'i': 100,
'f': lambda: 'called'}
self.assertEqual(f(vars, '$foo'), 'bar')
self.assertEqual(f(vars, '${foo}'), 'bar')
self.assertEqual(f(vars, '$b'), 'c')
self.assertEqual(f(vars, '${b}'), 'c')
self.assertEqual(f(vars, '$i'), '100')
self.assertEqual(f(vars, '${i}'), '100')
self.assertEqual(f(vars, '$f'), 'called')
self.assertEqual(f(vars, '${f}'), 'called')
self.assertEqual(f(vars, '${b a z}'), 'baz')
self.assertEqual(f(vars, '$b:$i'), 'c:100')
def testCommaAndify(self):
f = utils.str.commaAndify
L = ['foo']
original = L[:]
self.assertEqual(f(L), 'foo')
self.assertEqual(f(L, And='or'), 'foo')
self.assertEqual(L, original)
L.append('bar')
original = L[:]
self.assertEqual(f(L), 'foo and bar')
self.assertEqual(f(L, And='or'), 'foo or bar')
self.assertEqual(L, original)
L.append('baz')
original = L[:]
self.assertEqual(f(L), 'foo, bar, and baz')
self.assertEqual(f(L, And='or'), 'foo, bar, or baz')
self.assertEqual(f(L, comma=';'), 'foo; bar; and baz')
self.assertEqual(f(L, comma=';', And='or'),
'foo; bar; or baz')
self.assertEqual(L, original)
self.failUnless(f(set(L)))
def testCommaAndifyRaisesTypeError(self):
L = [(2,)]
self.assertRaises(TypeError, utils.str.commaAndify, L)
L.append((3,))
self.assertRaises(TypeError, utils.str.commaAndify, L)
def testUnCommaThe(self):
f = utils.str.unCommaThe
self.assertEqual(f('foo bar'), 'foo bar')
self.assertEqual(f('foo bar, the'), 'the foo bar')
self.assertEqual(f('foo bar, The'), 'The foo bar')
self.assertEqual(f('foo bar,the'), 'the foo bar')
def testNormalizeWhitespace(self):
f = utils.str.normalizeWhitespace
self.assertEqual(f('foo bar'), 'foo bar')
self.assertEqual(f('foo\nbar'), 'foo bar')
self.assertEqual(f('foo\tbar'), 'foo bar')
def testNItems(self):
nItems = utils.str.nItems
self.assertEqual(nItems('tool', 1, 'crazy'), '1 crazy tool')
self.assertEqual(nItems('tool', 1), '1 tool')
self.assertEqual(nItems('tool', 2, 'crazy'), '2 crazy tools')
self.assertEqual(nItems('tool', 2), '2 tools')
def testEllipsisify(self):
f = utils.str.ellipsisify
self.assertEqual(f('x'*30, 30), 'x'*30)
self.failUnless(len(f('x'*35, 30)) <= 30)
self.failUnless(f(' '.join(['xxxx']*10), 30)[:-3].endswith('xxxx'))
class IterTest(SupyTestCase):
def testSplit(self):
itersplit = utils.iter.split
L = [1, 2, 3] * 3
s = 'foo bar baz'
self.assertEqual(list(itersplit(lambda x: x == 3, L)),
[[1, 2], [1, 2], [1, 2]])
self.assertEqual(list(itersplit(lambda x: x == 3, L, yieldEmpty=True)),
[[1, 2], [1, 2], [1, 2], []])
self.assertEqual(list(itersplit(lambda x: x, [])), [])
self.assertEqual(list(itersplit(lambda c: c.isspace(), s)),
map(list, s.split()))
self.assertEqual(list(itersplit('for'.__eq__, ['foo', 'for', 'bar'])),
[['foo'], ['bar']])
self.assertEqual(list(itersplit('for'.__eq__,
['foo','for','bar','for', 'baz'], 1)),
[['foo'], ['bar', 'for', 'baz']])
def testFlatten(self): def testFlatten(self):
def lflatten(seq): def lflatten(seq):
return list(utils.flatten(seq)) return list(utils.iter.flatten(seq))
self.assertEqual(lflatten([]), []) self.assertEqual(lflatten([]), [])
self.assertEqual(lflatten([1]), [1]) self.assertEqual(lflatten([1]), [1])
self.assertEqual(lflatten(range(10)), range(10)) self.assertEqual(lflatten(range(10)), range(10))
@ -347,60 +379,43 @@ class UtilsTest(SupyTestCase):
self.assertEqual(lflatten([1, [2, [3, 4], 5], 6]), [1, 2, 3, 4, 5, 6]) self.assertEqual(lflatten([1, [2, [3, 4], 5], 6]), [1, 2, 3, 4, 5, 6])
self.assertRaises(TypeError, lflatten, 1) self.assertRaises(TypeError, lflatten, 1)
def testEllipsisify(self):
f = utils.ellipsisify
self.assertEqual(f('x'*30, 30), 'x'*30)
self.failUnless(len(f('x'*35, 30)) <= 30)
self.failUnless(f(' '.join(['xxxx']*10), 30)[:-3].endswith('xxxx'))
def testSaltHash(self):
s = utils.saltHash('jemfinch')
(salt, hash) = s.split('|')
self.assertEqual(utils.saltHash('jemfinch', salt=salt), s)
def testSafeEval(self):
for s in ['1', '()', '(1,)', '[]', '{}', '{1:2}', '{1:(2,3)}',
'1.0', '[1,2,3]', 'True', 'False', 'None',
'(True,False,None)', '"foo"', '{"foo": "bar"}']:
self.assertEqual(eval(s), utils.safeEval(s))
for s in ['lambda: 2', 'import foo', 'foo.bar']:
self.assertRaises(ValueError, utils.safeEval, s)
def testSafeEvalTurnsSyntaxErrorIntoValueError(self):
self.assertRaises(ValueError, utils.safeEval, '/usr/local/')
class FileTest(SupyTestCase):
def testLines(self): def testLines(self):
L = ['foo', 'bar', '#baz', ' ', 'biff'] L = ['foo', 'bar', '#baz', ' ', 'biff']
self.assertEqual(list(utils.nonEmptyLines(L)), self.assertEqual(list(utils.file.nonEmptyLines(L)),
['foo', 'bar', '#baz', 'biff']) ['foo', 'bar', '#baz', 'biff'])
self.assertEqual(list(utils.nonCommentLines(L)), self.assertEqual(list(utils.file.nonCommentLines(L)),
['foo', 'bar', ' ', 'biff']) ['foo', 'bar', ' ', 'biff'])
self.assertEqual(list(utils.nonCommentNonEmptyLines(L)), self.assertEqual(list(utils.file.nonCommentNonEmptyLines(L)),
['foo', 'bar', 'biff']) ['foo', 'bar', 'biff'])
class NetTest(SupyTestCase):
def testIsIP(self): def testIsIP(self):
self.failIf(utils.isIP('a.b.c')) isIP = utils.net.isIP
self.failIf(utils.isIP('256.0.0.0')) self.failIf(isIP('a.b.c'))
self.failUnless(utils.isIP('127.1')) self.failIf(isIP('256.0.0.0'))
self.failUnless(utils.isIP('0.0.0.0')) self.failUnless(isIP('127.1'))
self.failUnless(utils.isIP('100.100.100.100')) self.failUnless(isIP('0.0.0.0'))
self.failUnless(isIP('100.100.100.100'))
# This test is too flaky to bother with. # This test is too flaky to bother with.
# self.failUnless(utils.isIP('255.255.255.255')) # self.failUnless(utils.isIP('255.255.255.255'))
def testIsIPV6(self): def testIsIPV6(self):
f = utils.isIPV6 f = utils.net.isIPV6
self.failUnless(f('2001::')) self.failUnless(f('2001::'))
self.failUnless(f('2001:888:0:1::666')) self.failUnless(f('2001:888:0:1::666'))
def testInsensitivePreservingDict(self): class WebTest(SupyTestCase):
ipd = utils.InsensitivePreservingDict def testGetDomain(self):
d = ipd(dict(Foo=10)) url = 'http://slashdot.org/foo/bar.exe'
self.failUnless(d['foo'] == 10) self.assertEqual(utils.web.getDomain(url), 'slashdot.org')
self.assertEqual(d.keys(), ['Foo'])
self.assertEqual(d.get('foo'), 10)
self.assertEqual(d.get('Foo'), 10)
if network:
def testGetUrlWithSize(self):
url = 'http://slashdot.org/'
self.failUnless(len(utils.web.getUrl(url, 1024)) == 1024)
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: