Merge remote-tracking branch 'supybot/master' into testing

Conflicts:
	plugins/Admin/plugin.py
	plugins/BadWords/config.py
	plugins/Filter/plugin.py
	plugins/Google/plugin.py
	plugins/Math/plugin.py
	plugins/Misc/plugin.py
	plugins/Note/plugin.py
	plugins/RSS/plugin.py
	plugins/Seen/test.py
	plugins/ShrinkUrl/config.py
	plugins/ShrinkUrl/plugin.py
	plugins/ShrinkUrl/test.py
	plugins/Status/plugin.py
	plugins/String/config.py
	plugins/String/plugin.py
	plugins/Time/plugin.py
	plugins/Todo/plugin.py
	plugins/Web/plugin.py
	plugins/__init__.py
	scripts/supybot-botchk
	setup.py
	src/__init__.py
	src/callbacks.py
	src/commands.py
	src/conf.py
	src/drivers/Socket.py
	src/ircdb.py
	src/irclib.py
	src/ircutils.py
	src/questions.py
	src/registry.py
	src/schedule.py
	src/test.py
	src/utils/file.py
	src/utils/gen.py
	src/utils/net.py
	src/utils/web.py
	src/world.py
This commit is contained in:
Valentin Lorentz 2013-08-24 11:28:29 +02:00
commit 40675ffdfa
71 changed files with 773 additions and 4152 deletions

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
backup backup
build build
dist
supybot.egg-info
test-data test-data
test-conf test-conf
test-logs test-logs

View File

@ -10,6 +10,8 @@ Factoids' config variable supybot.plugins.Factoids.factoidPrefix has been
replaced by supybot.plugins.Factoids.format, which allows the user to replaced by supybot.plugins.Factoids.format, which allows the user to
determine exactly how replies to Factoid queries are formatted. determine exactly how replies to Factoid queries are formatted.
supybot-botchk no longer runs supybot inside an instance of /bin/sh.
Version 0.83.4.1 Version 0.83.4.1

515
distribute_setup.py Normal file
View File

@ -0,0 +1,515 @@
#!python
"""Bootstrap distribute installation
If you want to use setuptools in your package's setup.py, just include this
file in the same directory with it, and add this to the top of your setup.py::
from distribute_setup import use_setuptools
use_setuptools()
If you want to require a specific version of setuptools, set a download
mirror, or use an alternate download directory, you can do so by supplying
the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools.
"""
import os
import sys
import time
import fnmatch
import tempfile
import tarfile
from distutils import log
try:
from site import USER_SITE
except ImportError:
USER_SITE = None
try:
import subprocess
def _python_cmd(*args):
args = (sys.executable,) + args
return subprocess.call(args) == 0
except ImportError:
# will be used for python 2.3
def _python_cmd(*args):
args = (sys.executable,) + args
# quoting arguments if windows
if sys.platform == 'win32':
def quote(arg):
if ' ' in arg:
return '"%s"' % arg
return arg
args = [quote(arg) for arg in args]
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
DEFAULT_VERSION = "0.6.28"
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
SETUPTOOLS_FAKED_VERSION = "0.6c11"
SETUPTOOLS_PKG_INFO = """\
Metadata-Version: 1.0
Name: setuptools
Version: %s
Summary: xxxx
Home-page: xxx
Author: xxx
Author-email: xxx
License: xxx
Description: xxx
""" % SETUPTOOLS_FAKED_VERSION
def _install(tarball, install_args=()):
# extracting the tarball
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
try:
os.chdir(tmpdir)
tar = tarfile.open(tarball)
_extractall(tar)
tar.close()
# going in the directory
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
os.chdir(subdir)
log.warn('Now working in %s', subdir)
# installing
log.warn('Installing Distribute')
if not _python_cmd('setup.py', 'install', *install_args):
log.warn('Something went wrong during the installation.')
log.warn('See the error message above.')
finally:
os.chdir(old_wd)
def _build_egg(egg, tarball, to_dir):
# extracting the tarball
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
try:
os.chdir(tmpdir)
tar = tarfile.open(tarball)
_extractall(tar)
tar.close()
# going in the directory
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
os.chdir(subdir)
log.warn('Now working in %s', subdir)
# building an egg
log.warn('Building a Distribute egg in %s', to_dir)
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
finally:
os.chdir(old_wd)
# returning the result
log.warn(egg)
if not os.path.exists(egg):
raise IOError('Could not build the egg.')
def _do_download(version, download_base, to_dir, download_delay):
egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
% (version, sys.version_info[0], sys.version_info[1]))
if not os.path.exists(egg):
tarball = download_setuptools(version, download_base,
to_dir, download_delay)
_build_egg(egg, tarball, to_dir)
sys.path.insert(0, egg)
import setuptools
setuptools.bootstrap_install_from = egg
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=os.curdir, download_delay=15, no_fake=True):
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
was_imported = 'pkg_resources' in sys.modules or \
'setuptools' in sys.modules
try:
try:
import pkg_resources
if not hasattr(pkg_resources, '_distribute'):
if not no_fake:
_fake_setuptools()
raise ImportError
except ImportError:
return _do_download(version, download_base, to_dir, download_delay)
try:
pkg_resources.require("distribute>=" + version)
return
except pkg_resources.VersionConflict:
e = sys.exc_info()[1]
if was_imported:
sys.stderr.write(
"The required version of distribute (>=%s) is not available,\n"
"and can't be installed while this script is running. Please\n"
"install a more recent version first, using\n"
"'easy_install -U distribute'."
"\n\n(Currently using %r)\n" % (version, e.args[0]))
sys.exit(2)
else:
del pkg_resources, sys.modules['pkg_resources'] # reload ok
return _do_download(version, download_base, to_dir,
download_delay)
except pkg_resources.DistributionNotFound:
return _do_download(version, download_base, to_dir,
download_delay)
finally:
if not no_fake:
_create_fake_setuptools_pkg_info(to_dir)
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=os.curdir, delay=15):
"""Download distribute from a specified location and return its filename
`version` should be a valid distribute version number that is available
as an egg for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download
attempt.
"""
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
tgz_name = "distribute-%s.tar.gz" % version
url = download_base + tgz_name
saveto = os.path.join(to_dir, tgz_name)
src = dst = None
if not os.path.exists(saveto): # Avoid repeated downloads
try:
log.warn("Downloading %s", url)
src = urlopen(url)
# Read/write all in one block, so we don't create a corrupt file
# if the download is interrupted.
data = src.read()
dst = open(saveto, "wb")
dst.write(data)
finally:
if src:
src.close()
if dst:
dst.close()
return os.path.realpath(saveto)
def _no_sandbox(function):
def __no_sandbox(*args, **kw):
try:
from setuptools.sandbox import DirectorySandbox
if not hasattr(DirectorySandbox, '_old'):
def violation(*args):
pass
DirectorySandbox._old = DirectorySandbox._violation
DirectorySandbox._violation = violation
patched = True
else:
patched = False
except ImportError:
patched = False
try:
return function(*args, **kw)
finally:
if patched:
DirectorySandbox._violation = DirectorySandbox._old
del DirectorySandbox._old
return __no_sandbox
def _patch_file(path, content):
"""Will backup the file then patch it"""
existing_content = open(path).read()
if existing_content == content:
# already patched
log.warn('Already patched.')
return False
log.warn('Patching...')
_rename_path(path)
f = open(path, 'w')
try:
f.write(content)
finally:
f.close()
return True
_patch_file = _no_sandbox(_patch_file)
def _same_content(path, content):
return open(path).read() == content
def _rename_path(path):
new_name = path + '.OLD.%s' % time.time()
log.warn('Renaming %s into %s', path, new_name)
os.rename(path, new_name)
return new_name
def _remove_flat_installation(placeholder):
if not os.path.isdir(placeholder):
log.warn('Unkown installation at %s', placeholder)
return False
found = False
for file in os.listdir(placeholder):
if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
found = True
break
if not found:
log.warn('Could not locate setuptools*.egg-info')
return
log.warn('Removing elements out of the way...')
pkg_info = os.path.join(placeholder, file)
if os.path.isdir(pkg_info):
patched = _patch_egg_dir(pkg_info)
else:
patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
if not patched:
log.warn('%s already patched.', pkg_info)
return False
# now let's move the files out of the way
for element in ('setuptools', 'pkg_resources.py', 'site.py'):
element = os.path.join(placeholder, element)
if os.path.exists(element):
_rename_path(element)
else:
log.warn('Could not find the %s element of the '
'Setuptools distribution', element)
return True
_remove_flat_installation = _no_sandbox(_remove_flat_installation)
def _after_install(dist):
log.warn('After install bootstrap.')
placeholder = dist.get_command_obj('install').install_purelib
_create_fake_setuptools_pkg_info(placeholder)
def _create_fake_setuptools_pkg_info(placeholder):
if not placeholder or not os.path.exists(placeholder):
log.warn('Could not find the install location')
return
pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
setuptools_file = 'setuptools-%s-py%s.egg-info' % \
(SETUPTOOLS_FAKED_VERSION, pyver)
pkg_info = os.path.join(placeholder, setuptools_file)
if os.path.exists(pkg_info):
log.warn('%s already exists', pkg_info)
return
if not os.access(pkg_info, os.W_OK):
log.warn("Don't have permissions to write %s, skipping", pkg_info)
log.warn('Creating %s', pkg_info)
f = open(pkg_info, 'w')
try:
f.write(SETUPTOOLS_PKG_INFO)
finally:
f.close()
pth_file = os.path.join(placeholder, 'setuptools.pth')
log.warn('Creating %s', pth_file)
f = open(pth_file, 'w')
try:
f.write(os.path.join(os.curdir, setuptools_file))
finally:
f.close()
_create_fake_setuptools_pkg_info = _no_sandbox(
_create_fake_setuptools_pkg_info
)
def _patch_egg_dir(path):
# let's check if it's already patched
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
if os.path.exists(pkg_info):
if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
log.warn('%s already patched.', pkg_info)
return False
_rename_path(path)
os.mkdir(path)
os.mkdir(os.path.join(path, 'EGG-INFO'))
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
f = open(pkg_info, 'w')
try:
f.write(SETUPTOOLS_PKG_INFO)
finally:
f.close()
return True
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
def _before_install():
log.warn('Before install bootstrap.')
_fake_setuptools()
def _under_prefix(location):
if 'install' not in sys.argv:
return True
args = sys.argv[sys.argv.index('install') + 1:]
for index, arg in enumerate(args):
for option in ('--root', '--prefix'):
if arg.startswith('%s=' % option):
top_dir = arg.split('root=')[-1]
return location.startswith(top_dir)
elif arg == option:
if len(args) > index:
top_dir = args[index + 1]
return location.startswith(top_dir)
if arg == '--user' and USER_SITE is not None:
return location.startswith(USER_SITE)
return True
def _fake_setuptools():
log.warn('Scanning installed packages')
try:
import pkg_resources
except ImportError:
# we're cool
log.warn('Setuptools or Distribute does not seem to be installed.')
return
ws = pkg_resources.working_set
try:
setuptools_dist = ws.find(
pkg_resources.Requirement.parse('setuptools', replacement=False)
)
except TypeError:
# old distribute API
setuptools_dist = ws.find(
pkg_resources.Requirement.parse('setuptools')
)
if setuptools_dist is None:
log.warn('No setuptools distribution found')
return
# detecting if it was already faked
setuptools_location = setuptools_dist.location
log.warn('Setuptools installation detected at %s', setuptools_location)
# if --root or --preix was provided, and if
# setuptools is not located in them, we don't patch it
if not _under_prefix(setuptools_location):
log.warn('Not patching, --root or --prefix is installing Distribute'
' in another location')
return
# let's see if its an egg
if not setuptools_location.endswith('.egg'):
log.warn('Non-egg installation')
res = _remove_flat_installation(setuptools_location)
if not res:
return
else:
log.warn('Egg installation')
pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
if (os.path.exists(pkg_info) and
_same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
log.warn('Already patched.')
return
log.warn('Patching...')
# let's create a fake egg replacing setuptools one
res = _patch_egg_dir(setuptools_location)
if not res:
return
log.warn('Patched done.')
_relaunch()
def _relaunch():
log.warn('Relaunching...')
# we have to relaunch the process
# pip marker to avoid a relaunch bug
_cmd = ['-c', 'install', '--single-version-externally-managed']
if sys.argv[:3] == _cmd:
sys.argv[0] = 'setup.py'
args = [sys.executable] + sys.argv
sys.exit(subprocess.call(args))
def _extractall(self, path=".", members=None):
"""Extract all members from the archive to the current working
directory and set owner, modification time and permissions on
directories afterwards. `path' specifies a different directory
to extract to. `members' is optional and must be a subset of the
list returned by getmembers().
"""
import copy
import operator
from tarfile import ExtractError
directories = []
if members is None:
members = self
for tarinfo in members:
if tarinfo.isdir():
# Extract directories with a safe mode.
directories.append(tarinfo)
tarinfo = copy.copy(tarinfo)
tarinfo.mode = 448 # decimal for oct 0700
self.extract(tarinfo, path)
# Reverse sort directories.
if sys.version_info < (2, 4):
def sorter(dir1, dir2):
return cmp(dir1.name, dir2.name)
directories.sort(sorter)
directories.reverse()
else:
directories.sort(key=operator.attrgetter('name'), reverse=True)
# Set correct owner, mtime and filemode on directories.
for tarinfo in directories:
dirpath = os.path.join(path, tarinfo.name)
try:
self.chown(tarinfo, dirpath)
self.utime(tarinfo, dirpath)
self.chmod(tarinfo, dirpath)
except ExtractError:
e = sys.exc_info()[1]
if self.errorlevel > 1:
raise
else:
self._dbg(1, "tarfile: %s" % e)
def _build_install_args(argv):
install_args = []
user_install = '--user' in argv
if user_install and sys.version_info < (2, 6):
log.warn("--user requires Python 2.6 or later")
raise SystemExit(1)
if user_install:
install_args.append('--user')
return install_args
def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall"""
tarball = download_setuptools()
_install(tarball, _build_install_args(argv))
if __name__ == '__main__':
main(sys.argv[1:])

View File

@ -203,6 +203,10 @@ optional, the default value is shown.
- Checks for a valid HTTP URL. - Checks for a valid HTTP URL.
* email
- Checks for a syntactically valid email address.
* long, type="long" * long, type="long"
- Basically the same as int minus the predicate, except that it converts the - Basically the same as int minus the predicate, except that it converts the
@ -417,10 +421,23 @@ optional, the default value is shown.
- Checks to make sure that the caller has the specified capability. - Checks to make sure that the caller has the specified capability.
"checkChannelCapability", capability * checkChannelCapability, capability
Checks to make sure that the caller has the specified capability on the
- Checks to make sure that the caller has the specified capability on the
channel the command is called in. channel the command is called in.
* op
- Checks whether the user has the op mode (+o) set.
* halfop
- Checks whether the user has the halfop mode (+h) set.
* voice
- Checks whether the user has the voice mode (+v) set.
Contexts List Contexts List
============= =============
What contexts are available for me to use? What contexts are available for me to use?

View File

@ -356,6 +356,15 @@ class Admin(callbacks.Plugin):
irc.reply(_('I\'m not currently globally ignoring anyone.')) irc.reply(_('I\'m not currently globally ignoring anyone.'))
list = wrap(list) list = wrap(list)
def clearq(self, irc, msg, args):
"""takes no arguments
Clears the current send queue for this network.
"""
irc.queue.reset()
irc.replySuccess()
clearq = wrap(clearq)
@internationalizeDocstring @internationalizeDocstring
def clearq(self, irc, msg, args): def clearq(self, irc, msg, args):

View File

@ -165,19 +165,20 @@ class Filter(callbacks.Plugin):
irc.reply(''.join(L)) irc.reply(''.join(L))
binary = wrap(binary, ['text']) binary = wrap(binary, ['text'])
@internationalizeDocstring
def unbinary(self, irc, msg, args, text): def unbinary(self, irc, msg, args, text):
"""<text> """<text>
Returns the character representation of binary <text>. Returns the character representation of binary <text>.
Assumes ASCII, 8 digits per character. Assumes ASCII, 8 digits per character.
""" """
try:
L = [chr(int(text[i:(i+8)], 2)) for i in xrange(0, len(text), 8)] L = [chr(int(text[i:(i+8)], 2)) for i in xrange(0, len(text), 8)]
irc.reply(''.join(L)) irc.reply(''.join(L))
except ValueError:
irc.errorInvalid('binary string', text)
unbinary = wrap(unbinary, ['text']) unbinary = wrap(unbinary, ['text'])
_hex_encoder = staticmethod(codecs.getencoder('hex_codec')) _hex_encoder = staticmethod(codecs.getencoder('hex_codec'))
@internationalizeDocstring
def hexlify(self, irc, msg, args, text): def hexlify(self, irc, msg, args, text):
"""<text> """<text>

View File

@ -92,6 +92,10 @@ class FilterTest(ChannelPluginTestCase):
def testUnbinary(self): def testUnbinary(self):
self.assertResponse('unbinary 011011010110111101101111', 'moo') self.assertResponse('unbinary 011011010110111101101111', 'moo')
def testUnbinary(self):
self.assertResponse('unbinary 011011010110111101101111', 'moo')
self.assertError('unbinary moo')
def testRot13(self): def testRot13(self):
for s in map(str, range(1000, 1010)): for s in map(str, range(1000, 1010)):
self.assertResponse('rot13 [rot13 %s]' % s, s) self.assertResponse('rot13 [rot13 %s]' % s, s)

View File

@ -1 +0,0 @@
# Stub so local is a module, used for third-party modules

View File

@ -31,6 +31,7 @@
import re import re
import sys import sys
import cgi import cgi
import json
import time import time
import socket import socket
import urllib import urllib

View File

@ -172,6 +172,13 @@ class Math(callbacks.Plugin):
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.
""" """
try:
text = str(text)
except UnicodeEncodeError:
irc.error(_("There's no reason you should have fancy non-ASCII "
"characters in your mathematical expression. "
"Please remove them."))
return
if self._calc_match_forbidden_chars.match(text): if self._calc_match_forbidden_chars.match(text):
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 '

View File

@ -39,6 +39,7 @@ from itertools import ifilter
import supybot import supybot
import supybot.conf as conf import supybot.conf as conf
from supybot import commands
import supybot.utils as utils import supybot.utils as utils
from supybot.commands import * from supybot.commands import *
import supybot.ircdb as ircdb import supybot.ircdb as ircdb
@ -431,18 +432,11 @@ class Misc(callbacks.Plugin):
return False return False
if ircmsgs.isAction(m): if ircmsgs.isAction(m):
m1 = ircmsgs.unAction(m) m1 = ircmsgs.unAction(m)
#return arg.search(ircmsgs.unAction(m))
else: else:
m1 = m.args[1] m1 = m.args[1]
#return arg.search(m.args[1]) return regexp_wrapper(m1, reobj=arg, timeout=0.1,
try: plugin_name=self.name(),
# use a subprocess here, since specially crafted regexps can fcn_name='last')
# take exponential time and hang up the bot.
# timeout of 0.1 should be more than enough for any normal regexp.
v = commands.process(f1, m1, arg, timeout=0.1, pn=self.name(), cn='last')
return v
except commands.ProcessTimeoutError:
return False
predicates.setdefault('regexp', []).append(f) predicates.setdefault('regexp', []).append(f)
elif option == 'nolimit': elif option == 'nolimit':
nolimit = True nolimit = True

View File

@ -512,7 +512,7 @@ class MoobotFactoids(callbacks.Plugin):
if not info: if not info:
irc.error(format(_('No such factoid: %q'), key)) irc.error(format(_('No such factoid: %q'), key))
return return
(created_by, _, _, _, _, _, _, locked_by, _) = info (created_by, a, a, a, a, a, a, locked_by, a) = info
# Don't perform redundant operations # Don't perform redundant operations
if locking and locked_by is not None: if locking and locked_by is not None:
irc.error(format(_('Factoid %q is already locked.'), key)) irc.error(format(_('Factoid %q is already locked.'), key))

View File

@ -295,8 +295,10 @@ class Note(callbacks.Plugin):
own = to own = to
for (option, arg) in optlist: for (option, arg) in optlist:
if option == 'regexp': if option == 'regexp':
criteria.append(lambda x: commands.regexp_wrapper(x, reobj=arg, criteria.append(lambda s:
timeout=0.1, plugin_name = self.name(), fcn_name='search')) regexp_wrapper(s, reobj=arg, timeout=0.1,
plugin_name=self.name(),
fcn_name='search'))
elif option == 'sent': elif option == 'sent':
own = frm own = frm
if glob: if glob:

View File

@ -1 +0,0 @@
# Stub so local is a module, used for third-party modules

View File

@ -34,6 +34,7 @@ import socket
import threading import threading
import re import re
import sys import sys
import feedparser
import supybot.conf as conf import supybot.conf as conf
import supybot.utils as utils import supybot.utils as utils
@ -45,14 +46,6 @@ import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('RSS') _ = PluginInternationalization('RSS')
try:
feedparser = utils.python.universalImport('feedparser.feedparser',
'local.feedparser.feedparser', 'feedparser', 'local.feedparser')
except ImportError:
raise callbacks.Error, \
'You need the feedparser module installed to use this plugin. ' \
'Download the module at <http://feedparser.org/>.'
def getFeedName(irc, msg, args, state): def getFeedName(irc, msg, args, state):
if not registry.isValidRegistryName(args[0]): if not registry.isValidRegistryName(args[0]):
state.errorInvalid('feed name', args[0], state.errorInvalid('feed name', args[0],
@ -283,7 +276,7 @@ class RSS(callbacks.Plugin):
# and DoS the website in question. # and DoS the website in question.
self.acquireLock(url) self.acquireLock(url)
if self.willGetNewFeed(url): if self.willGetNewFeed(url):
results = None results = {}
try: try:
self.log.debug('Downloading new feed from %u', url) self.log.debug('Downloading new feed from %u', url)
results = feedparser.parse(url) results = feedparser.parse(url)
@ -298,9 +291,7 @@ class RSS(callbacks.Plugin):
# These seem mostly harmless. We'll need reports of a # These seem mostly harmless. We'll need reports of a
# kind that isn't. # kind that isn't.
self.log.debug('Allowing bozo_exception %r through.', e) self.log.debug('Allowing bozo_exception %r through.', e)
if results is None: if results.get('feed', {}) and self.getHeadlines(results):
self.log.error('Could not fetch feed %s' % url)
elif results.get('feed', {}):
self.cachedFeeds[url] = results self.cachedFeeds[url] = results
self.lastRequest[url] = time.time() self.lastRequest[url] = time.time()
else: else:

View File

@ -77,7 +77,6 @@ class RSSTestCase(ChannelPluginTestCase):
def testNonAsciiFeeds(self): def testNonAsciiFeeds(self):
self.assertNotError('rss http://www.heise.de/newsticker/heise.rdf') self.assertNotError('rss http://www.heise.de/newsticker/heise.rdf')
self.assertNotError('rss http://www.golem.de/rss.php?feed=ATOM0.3')
self.assertNotError('rss info http://br-linux.org/main/index.xml') self.assertNotError('rss info http://br-linux.org/main/index.xml')

View File

@ -42,7 +42,7 @@ def configure(advanced):
conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True)
class ShrinkService(registry.OnlySomeStrings): class ShrinkService(registry.OnlySomeStrings):
"""Valid values include 'ln', 'tiny', 'xrl', 'goo', and 'x0'.""" """Valid values include 'ln', 'tiny', 'xrl', 'goo', 'ur1', and 'x0'."""
validStrings = ('ln', 'tiny', 'xrl', 'goo', 'ur1', 'x0') validStrings = ('ln', 'tiny', 'xrl', 'goo', 'ur1', 'x0')
class ShrinkCycle(registry.SpaceSeparatedListOfStrings): class ShrinkCycle(registry.SpaceSeparatedListOfStrings):

View File

@ -258,14 +258,16 @@ class ShrinkUrl(callbacks.PluginRegexp):
_gooApi = 'https://www.googleapis.com/urlshortener/v1/url' _gooApi = 'https://www.googleapis.com/urlshortener/v1/url'
@retry @retry
def _getGooUrl(self, url): def _getGooUrl(self, url):
url = utils.web.urlquote(url)
try: try:
return self.db.get('goo', url) return self.db.get('goo', url)
except KeyError: except KeyError:
text = utils.web.getUrl(self._gooApi, headers = utils.web.defaultHeaders.copy()
headers={'content-type':'application/json'}, headers['content-type'] = 'application/json'
data=json.dumps({'longUrl': url}).encode()) data = json.dumps({'longUrl': url})
googl = json.loads(text.decode())['id'] text = utils.web.getUrl(self._gooApi, data=data, headers=headers)
if len(googl) > 0 : googl = json.loads(text)['id']
if googl:
self.db.set('goo', url, googl) self.db.set('goo', url, googl)
return googl return googl
else: else:
@ -295,7 +297,7 @@ class ShrinkUrl(callbacks.PluginRegexp):
parameters = utils.web.urlencode({'longurl': url}) parameters = utils.web.urlencode({'longurl': url})
response = utils.web.getUrl(self._ur1Api, data=parameters) response = utils.web.getUrl(self._ur1Api, data=parameters)
ur1ca = self._ur1Regexp.search(response.decode()).group('url') ur1ca = self._ur1Regexp.search(response.decode()).group('url')
if len(ur1ca) > 0 : if ur1ca:
self.db.set('ur1', url, ur1ca) self.db.set('ur1', url, ur1ca)
return ur1ca return ur1ca
else: else:

View File

@ -41,12 +41,13 @@ class ShrinkUrlTestCase(ChannelPluginTestCase):
(udUrl, r'http://tinyurl.com/u479')], (udUrl, r'http://tinyurl.com/u479')],
'ln': [(sfUrl, r'http://ln-s.net/\+PE-'), 'ln': [(sfUrl, r'http://ln-s.net/\+PE-'),
(udUrl, r'http://ln-s.net/2\$K')], (udUrl, r'http://ln-s.net/2\$K')],
'xrl': [(udUrl, r'http://xrl.us/bfnyji')], 'xrl': [(sfUrl, r'http://xrl.us/bfq8ik'),
'goo': [(sfUrl, r'http://goo.gl/krnNC'), (udUrl, r'http://xrl.us/bfnyji')],
(udUrl, r'http://goo.gl/1ejCD')], 'goo': [(sfUrl, r'http://goo.gl/3c59N'),
'ur1': [(sfUrl, r'http://ur1.ca/ceqh8'), (udUrl, r'http://goo.gl/ocTga')],
'ur1': [(sfUrl, r'http://ur1.ca/9xl25'),
(udUrl, r'http://ur1.ca/9xl9k')], (udUrl, r'http://ur1.ca/9xl9k')],
'x0': [(sfUrl, r'http://x0.no/a53s'), 'x0': [(sfUrl, r'http://x0.no/0l2j'),
(udUrl, r'http://x0.no/0l2k')] (udUrl, r'http://x0.no/0l2k')]
} }
if network: if network:

View File

@ -32,6 +32,7 @@ import os
import sys import sys
import time import time
import threading import threading
import multiprocessing
import subprocess import subprocess
import supybot.conf as conf import supybot.conf as conf
@ -98,7 +99,21 @@ class Status(callbacks.Plugin):
irc.reply(s) irc.reply(s)
threads = wrap(threads) threads = wrap(threads)
@internationalizeDocstring def processes(self, irc, msg, args):
"""takes no arguments
Returns the number of processes that have been spawned, and list of
ones that are still active.
"""
ps = [multiprocessing.current_process().name]
ps = ps + [p.name for p in multiprocessing.active_children()]
s = format('I have spawned %n; %n %b still currently active: %L.',
(world.processesSpawned, 'process'),
(len(ps), 'process'),
len(ps), ps)
irc.reply(s)
processes = wrap(processes)
def net(self, irc, msg, args): def net(self, irc, msg, args):
"""takes no arguments """takes no arguments

View File

@ -72,6 +72,8 @@ class StatusTestCase(PluginTestCase):
def testThreads(self): def testThreads(self):
self.assertNotError('threads') self.assertNotError('threads')
def testProcesses(self):
self.assertNotError('processes')
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -55,9 +55,9 @@ conf.registerGlobalValue(String.levenshtein, 'max',
conf.registerGroup(String, 're') conf.registerGroup(String, 're')
conf.registerGlobalValue(String.re, 'timeout', conf.registerGlobalValue(String.re, 'timeout',
registry.PositiveFloat(0.1, """Determines the maximum time, in seconds, that registry.PositiveFloat(0.1, _("""Determines the maximum time, in seconds, that
a regular expression is given to execute before being terminated. Since a regular expression is given to execute before being terminated. Since
there is a possibility that user input for the re command can cause it to there is a possibility that user input for the re command can cause it to
eat up large amounts of ram or cpu time, it's a good idea to keep this eat up large amounts of ram or cpu time, it's a good idea to keep this
low. Most normal regexps should not take very long at all.""")) low. Most normal regexps should not take very long at all.""")))
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -38,6 +38,7 @@ import supybot.utils as utils
from supybot.commands import * from supybot.commands import *
import supybot.commands as commands import supybot.commands as commands
import supybot.plugins as plugins import supybot.plugins as plugins
import supybot.commands as commands
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring from supybot.i18n import PluginInternationalization, internationalizeDocstring
@ -156,7 +157,7 @@ class String(callbacks.Plugin):
'it with some smaller inputs.')) 'it with some smaller inputs.'))
else: else:
irc.reply(str(utils.str.distance(s1, s2))) irc.reply(str(utils.str.distance(s1, s2)))
levenshtein = wrap(levenshtein, ['something', 'text']) levenshtein = thread(wrap(levenshtein, ['something', 'text']))
@internationalizeDocstring @internationalizeDocstring
def soundex(self, irc, msg, args, text, length): def soundex(self, irc, msg, args, text, length):
@ -200,14 +201,13 @@ class String(callbacks.Plugin):
else: else:
t = self.registryValue('re.timeout') t = self.registryValue('re.timeout')
try: try:
v = commands.process(f, text, timeout=t, pn=self.name(), cn='re') v = process(f, text, timeout=t, pn=self.name(), cn='re')
irc.reply(v) irc.reply(v)
except commands.ProcessTimeoutError, e: except commands.ProcessTimeoutError, e:
irc.error("ProcessTimeoutError: %s" % (e,)) irc.error("ProcessTimeoutError: %s" % (e,))
re = thread(wrap(re, [first('regexpMatcher', 'regexpReplacer'), re = thread(wrap(re, [first('regexpMatcher', 'regexpReplacer'),
'text'])) 'text']))
@internationalizeDocstring
def xor(self, irc, msg, args, password, text): def xor(self, irc, msg, args, password, text):
"""<password> <text> """<password> <text>

View File

@ -1 +0,0 @@
# Stub so local is a module, used for third-party modules

View File

@ -1,9 +0,0 @@
"""
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
This module offers extensions to the standard python 2.3+
datetime module.
"""
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
__license__ = "PSF License"
__version__ = "1.4.1"

View File

@ -1,92 +0,0 @@
"""
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
This module offers extensions to the standard python 2.3+
datetime module.
"""
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
__license__ = "PSF License"
import datetime
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
EASTER_JULIAN = 1
EASTER_ORTHODOX = 2
EASTER_WESTERN = 3
def easter(year, method=EASTER_WESTERN):
"""
This method was ported from the work done by GM Arts,
on top of the algorithm by Claus Tondering, which was
based in part on the algorithm of Ouding (1940), as
quoted in "Explanatory Supplement to the Astronomical
Almanac", P. Kenneth Seidelmann, editor.
This algorithm implements three different easter
calculation methods:
1 - Original calculation in Julian calendar, valid in
dates after 326 AD
2 - Original method, with date converted to Gregorian
calendar, valid in years 1583 to 4099
3 - Revised method, in Gregorian calendar, valid in
years 1583 to 4099 as well
These methods are represented by the constants:
EASTER_JULIAN = 1
EASTER_ORTHODOX = 2
EASTER_WESTERN = 3
The default method is method 3.
More about the algorithm may be found at:
http://users.chariot.net.au/~gmarts/eastalg.htm
and
http://www.tondering.dk/claus/calendar.html
"""
if not (1 <= method <= 3):
raise ValueError, "invalid method"
# g - Golden year - 1
# c - Century
# h - (23 - Epact) mod 30
# i - Number of days from March 21 to Paschal Full Moon
# j - Weekday for PFM (0=Sunday, etc)
# p - Number of days from March 21 to Sunday on or before PFM
# (-6 to 28 methods 1 & 3, to 56 for method 2)
# e - Extra days to add for method 2 (converting Julian
# date to Gregorian date)
y = year
g = y % 19
e = 0
if method < 3:
# Old method
i = (19*g+15)%30
j = (y+y//4+i)%7
if method == 2:
# Extra dates to convert Julian to Gregorian date
e = 10
if y > 1600:
e = e+y//100-16-(y//100-16)//4
else:
# New method
c = y//100
h = (c-c//4-(8*c+13)//25+19*g+15)%30
i = h-(h//28)*(1-(h//28)*(29//(h+1))*((21-g)//11))
j = (y+y//4+i+2-c+c//4)%7
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
# (later dates apply to method 2, although 23 May never actually occurs)
p = i-j+e
d = 1+(p+27+(p+6)//40)%31
m = 3+(p+26)//30
return datetime.date(int(y),int(m),int(d))

View File

@ -1,886 +0,0 @@
# -*- coding:iso-8859-1 -*-
"""
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
This module offers extensions to the standard python 2.3+
datetime module.
"""
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
__license__ = "PSF License"
import datetime
import string
import time
import sys
import os
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
import relativedelta
import tz
__all__ = ["parse", "parserinfo"]
# Some pointers:
#
# http://www.cl.cam.ac.uk/~mgk25/iso-time.html
# http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html
# http://www.w3.org/TR/NOTE-datetime
# http://ringmaster.arc.nasa.gov/tools/time_formats.html
# http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.pm
# http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html
class _timelex(object):
def __init__(self, instream):
if isinstance(instream, basestring):
instream = StringIO(instream)
self.instream = instream
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
self.numchars = '0123456789'
self.whitespace = ' \t\r\n'
self.charstack = []
self.tokenstack = []
self.eof = False
def get_token(self):
if self.tokenstack:
return self.tokenstack.pop(0)
seenletters = False
token = None
state = None
wordchars = self.wordchars
numchars = self.numchars
whitespace = self.whitespace
while not self.eof:
if self.charstack:
nextchar = self.charstack.pop(0)
else:
nextchar = self.instream.read(1)
while nextchar == '\x00':
nextchar = self.instream.read(1)
if not nextchar:
self.eof = True
break
elif not state:
token = nextchar
if nextchar in wordchars:
state = 'a'
elif nextchar in numchars:
state = '0'
elif nextchar in whitespace:
token = ' '
break # emit token
else:
break # emit token
elif state == 'a':
seenletters = True
if nextchar in wordchars:
token += nextchar
elif nextchar == '.':
token += nextchar
state = 'a.'
else:
self.charstack.append(nextchar)
break # emit token
elif state == '0':
if nextchar in numchars:
token += nextchar
elif nextchar == '.':
token += nextchar
state = '0.'
else:
self.charstack.append(nextchar)
break # emit token
elif state == 'a.':
seenletters = True
if nextchar == '.' or nextchar in wordchars:
token += nextchar
elif nextchar in numchars and token[-1] == '.':
token += nextchar
state = '0.'
else:
self.charstack.append(nextchar)
break # emit token
elif state == '0.':
if nextchar == '.' or nextchar in numchars:
token += nextchar
elif nextchar in wordchars and token[-1] == '.':
token += nextchar
state = 'a.'
else:
self.charstack.append(nextchar)
break # emit token
if (state in ('a.', '0.') and
(seenletters or token.count('.') > 1 or token[-1] == '.')):
l = token.split('.')
token = l[0]
for tok in l[1:]:
self.tokenstack.append('.')
if tok:
self.tokenstack.append(tok)
return token
def __iter__(self):
return self
def next(self):
token = self.get_token()
if token is None:
raise StopIteration
return token
def split(cls, s):
return list(cls(s))
split = classmethod(split)
class _resultbase(object):
def __init__(self):
for attr in self.__slots__:
setattr(self, attr, None)
def _repr(self, classname):
l = []
for attr in self.__slots__:
value = getattr(self, attr)
if value is not None:
l.append("%s=%s" % (attr, `value`))
return "%s(%s)" % (classname, ", ".join(l))
def __repr__(self):
return self._repr(self.__class__.__name__)
class parserinfo(object):
# m from a.m/p.m, t from ISO T separator
JUMP = [" ", ".", ",", ";", "-", "/", "'",
"at", "on", "and", "ad", "m", "t", "of",
"st", "nd", "rd", "th"]
WEEKDAYS = [("Mon", "Monday"),
("Tue", "Tuesday"),
("Wed", "Wednesday"),
("Thu", "Thursday"),
("Fri", "Friday"),
("Sat", "Saturday"),
("Sun", "Sunday")]
MONTHS = [("Jan", "January"),
("Feb", "February"),
("Mar", "March"),
("Apr", "April"),
("May", "May"),
("Jun", "June"),
("Jul", "July"),
("Aug", "August"),
("Sep", "September"),
("Oct", "October"),
("Nov", "November"),
("Dec", "December")]
HMS = [("h", "hour", "hours"),
("m", "minute", "minutes"),
("s", "second", "seconds")]
AMPM = [("am", "a"),
("pm", "p")]
UTCZONE = ["UTC", "GMT", "Z"]
PERTAIN = ["of"]
TZOFFSET = {}
def __init__(self, dayfirst=False, yearfirst=False):
self._jump = self._convert(self.JUMP)
self._weekdays = self._convert(self.WEEKDAYS)
self._months = self._convert(self.MONTHS)
self._hms = self._convert(self.HMS)
self._ampm = self._convert(self.AMPM)
self._utczone = self._convert(self.UTCZONE)
self._pertain = self._convert(self.PERTAIN)
self.dayfirst = dayfirst
self.yearfirst = yearfirst
self._year = time.localtime().tm_year
self._century = self._year//100*100
def _convert(self, lst):
dct = {}
for i in range(len(lst)):
v = lst[i]
if isinstance(v, tuple):
for v in v:
dct[v.lower()] = i
else:
dct[v.lower()] = i
return dct
def jump(self, name):
return name.lower() in self._jump
def weekday(self, name):
if len(name) >= 3:
try:
return self._weekdays[name.lower()]
except KeyError:
pass
return None
def month(self, name):
if len(name) >= 3:
try:
return self._months[name.lower()]+1
except KeyError:
pass
return None
def hms(self, name):
try:
return self._hms[name.lower()]
except KeyError:
return None
def ampm(self, name):
try:
return self._ampm[name.lower()]
except KeyError:
return None
def pertain(self, name):
return name.lower() in self._pertain
def utczone(self, name):
return name.lower() in self._utczone
def tzoffset(self, name):
if name in self._utczone:
return 0
return self.TZOFFSET.get(name)
def convertyear(self, year):
if year < 100:
year += self._century
if abs(year-self._year) >= 50:
if year < self._year:
year += 100
else:
year -= 100
return year
def validate(self, res):
# move to info
if res.year is not None:
res.year = self.convertyear(res.year)
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
res.tzname = "UTC"
res.tzoffset = 0
elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
res.tzoffset = 0
return True
class parser(object):
def __init__(self, info=None):
self.info = info or parserinfo()
def parse(self, timestr, default=None,
ignoretz=False, tzinfos=None,
**kwargs):
if not default:
default = datetime.datetime.now().replace(hour=0, minute=0,
second=0, microsecond=0)
res = self._parse(timestr, **kwargs)
if res is None:
raise ValueError, "unknown string format"
repl = {}
for attr in ["year", "month", "day", "hour",
"minute", "second", "microsecond"]:
value = getattr(res, attr)
if value is not None:
repl[attr] = value
ret = default.replace(**repl)
if res.weekday is not None and not res.day:
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
if not ignoretz:
if callable(tzinfos) or tzinfos and res.tzname in tzinfos:
if callable(tzinfos):
tzdata = tzinfos(res.tzname, res.tzoffset)
else:
tzdata = tzinfos.get(res.tzname)
if isinstance(tzdata, datetime.tzinfo):
tzinfo = tzdata
elif isinstance(tzdata, basestring):
tzinfo = tz.tzstr(tzdata)
elif isinstance(tzdata, int):
tzinfo = tz.tzoffset(res.tzname, tzdata)
else:
raise ValueError, "offset must be tzinfo subclass, " \
"tz string, or int offset"
ret = ret.replace(tzinfo=tzinfo)
elif res.tzname and res.tzname in time.tzname:
ret = ret.replace(tzinfo=tz.tzlocal())
elif res.tzoffset == 0:
ret = ret.replace(tzinfo=tz.tzutc())
elif res.tzoffset:
ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
return ret
class _result(_resultbase):
__slots__ = ["year", "month", "day", "weekday",
"hour", "minute", "second", "microsecond",
"tzname", "tzoffset"]
def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False):
info = self.info
if dayfirst is None:
dayfirst = info.dayfirst
if yearfirst is None:
yearfirst = info.yearfirst
res = self._result()
l = _timelex.split(timestr)
try:
# year/month/day list
ymd = []
# Index of the month string in ymd
mstridx = -1
len_l = len(l)
i = 0
while i < len_l:
# Check if it's a number
try:
value_repr = l[i]
value = float(value_repr)
except ValueError:
value = None
if value is not None:
# Token is a number
len_li = len(l[i])
i += 1
if (len(ymd) == 3 and len_li in (2, 4)
and (i >= len_l or (l[i] != ':' and
info.hms(l[i]) is None))):
# 19990101T23[59]
s = l[i-1]
res.hour = int(s[:2])
if len_li == 4:
res.minute = int(s[2:])
elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
# YYMMDD or HHMMSS[.ss]
s = l[i-1]
if not ymd and l[i-1].find('.') == -1:
ymd.append(info.convertyear(int(s[:2])))
ymd.append(int(s[2:4]))
ymd.append(int(s[4:]))
else:
# 19990101T235959[.59]
res.hour = int(s[:2])
res.minute = int(s[2:4])
res.second, res.microsecond = _parsems(s[4:])
elif len_li == 8:
# YYYYMMDD
s = l[i-1]
ymd.append(int(s[:4]))
ymd.append(int(s[4:6]))
ymd.append(int(s[6:]))
elif len_li in (12, 14):
# YYYYMMDDhhmm[ss]
s = l[i-1]
ymd.append(int(s[:4]))
ymd.append(int(s[4:6]))
ymd.append(int(s[6:8]))
res.hour = int(s[8:10])
res.minute = int(s[10:12])
if len_li == 14:
res.second = int(s[12:])
elif ((i < len_l and info.hms(l[i]) is not None) or
(i+1 < len_l and l[i] == ' ' and
info.hms(l[i+1]) is not None)):
# HH[ ]h or MM[ ]m or SS[.ss][ ]s
if l[i] == ' ':
i += 1
idx = info.hms(l[i])
while True:
if idx == 0:
res.hour = int(value)
if value%1:
res.minute = int(60*(value%1))
elif idx == 1:
res.minute = int(value)
if value%1:
res.second = int(60*(value%1))
elif idx == 2:
res.second, res.microsecond = \
_parsems(value_repr)
i += 1
if i >= len_l or idx == 2:
break
# 12h00
try:
value_repr = l[i]
value = float(value_repr)
except ValueError:
break
else:
i += 1
idx += 1
if i < len_l:
newidx = info.hms(l[i])
if newidx is not None:
idx = newidx
elif i+1 < len_l and l[i] == ':':
# HH:MM[:SS[.ss]]
res.hour = int(value)
i += 1
value = float(l[i])
res.minute = int(value)
if value%1:
res.second = int(60*(value%1))
i += 1
if i < len_l and l[i] == ':':
res.second, res.microsecond = _parsems(l[i+1])
i += 2
elif i < len_l and l[i] in ('-', '/', '.'):
sep = l[i]
ymd.append(int(value))
i += 1
if i < len_l and not info.jump(l[i]):
try:
# 01-01[-01]
ymd.append(int(l[i]))
except ValueError:
# 01-Jan[-01]
value = info.month(l[i])
if value is not None:
ymd.append(value)
assert mstridx == -1
mstridx = len(ymd)-1
else:
return None
i += 1
if i < len_l and l[i] == sep:
# We have three members
i += 1
value = info.month(l[i])
if value is not None:
ymd.append(value)
mstridx = len(ymd)-1
assert mstridx == -1
else:
ymd.append(int(l[i]))
i += 1
elif i >= len_l or info.jump(l[i]):
if i+1 < len_l and info.ampm(l[i+1]) is not None:
# 12 am
res.hour = int(value)
if res.hour < 12 and info.ampm(l[i+1]) == 1:
res.hour += 12
elif res.hour == 12 and info.ampm(l[i+1]) == 0:
res.hour = 0
i += 1
else:
# Year, month or day
ymd.append(int(value))
i += 1
elif info.ampm(l[i]) is not None:
# 12am
res.hour = int(value)
if res.hour < 12 and info.ampm(l[i]) == 1:
res.hour += 12
elif res.hour == 12 and info.ampm(l[i]) == 0:
res.hour = 0
i += 1
elif not fuzzy:
return None
else:
i += 1
continue
# Check weekday
value = info.weekday(l[i])
if value is not None:
res.weekday = value
i += 1
continue
# Check month name
value = info.month(l[i])
if value is not None:
ymd.append(value)
assert mstridx == -1
mstridx = len(ymd)-1
i += 1
if i < len_l:
if l[i] in ('-', '/'):
# Jan-01[-99]
sep = l[i]
i += 1
ymd.append(int(l[i]))
i += 1
if i < len_l and l[i] == sep:
# Jan-01-99
i += 1
ymd.append(int(l[i]))
i += 1
elif (i+3 < len_l and l[i] == l[i+2] == ' '
and info.pertain(l[i+1])):
# Jan of 01
# In this case, 01 is clearly year
try:
value = int(l[i+3])
except ValueError:
# Wrong guess
pass
else:
# Convert it here to become unambiguous
ymd.append(info.convertyear(value))
i += 4
continue
# Check am/pm
value = info.ampm(l[i])
if value is not None:
if value == 1 and res.hour < 12:
res.hour += 12
elif value == 0 and res.hour == 12:
res.hour = 0
i += 1
continue
# Check for a timezone name
if (res.hour is not None and len(l[i]) <= 5 and
res.tzname is None and res.tzoffset is None and
not [x for x in l[i] if x not in string.ascii_uppercase]):
res.tzname = l[i]
res.tzoffset = info.tzoffset(res.tzname)
i += 1
# Check for something like GMT+3, or BRST+3. Notice
# that it doesn't mean "I am 3 hours after GMT", but
# "my time +3 is GMT". If found, we reverse the
# logic so that timezone parsing code will get it
# right.
if i < len_l and l[i] in ('+', '-'):
l[i] = ('+', '-')[l[i] == '+']
res.tzoffset = None
if info.utczone(res.tzname):
# With something like GMT+3, the timezone
# is *not* GMT.
res.tzname = None
continue
# Check for a numbered timezone
if res.hour is not None and l[i] in ('+', '-'):
signal = (-1,1)[l[i] == '+']
i += 1
len_li = len(l[i])
if len_li == 4:
# -0300
res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
elif i+1 < len_l and l[i+1] == ':':
# -03:00
res.tzoffset = int(l[i])*3600+int(l[i+2])*60
i += 2
elif len_li <= 2:
# -[0]3
res.tzoffset = int(l[i][:2])*3600
else:
return None
i += 1
res.tzoffset *= signal
# Look for a timezone name between parenthesis
if (i+3 < len_l and
info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
3 <= len(l[i+2]) <= 5 and
not [x for x in l[i+2]
if x not in string.ascii_uppercase]):
# -0300 (BRST)
res.tzname = l[i+2]
i += 4
continue
# Check jumps
if not (info.jump(l[i]) or fuzzy):
return None
i += 1
# Process year/month/day
len_ymd = len(ymd)
if len_ymd > 3:
# More than three members!?
return None
elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
# One member, or two members with a month string
if mstridx != -1:
res.month = ymd[mstridx]
del ymd[mstridx]
if len_ymd > 1 or mstridx == -1:
if ymd[0] > 31:
res.year = ymd[0]
else:
res.day = ymd[0]
elif len_ymd == 2:
# Two members with numbers
if ymd[0] > 31:
# 99-01
res.year, res.month = ymd
elif ymd[1] > 31:
# 01-99
res.month, res.year = ymd
elif dayfirst and ymd[1] <= 12:
# 13-01
res.day, res.month = ymd
else:
# 01-13
res.month, res.day = ymd
if len_ymd == 3:
# Three members
if mstridx == 0:
res.month, res.day, res.year = ymd
elif mstridx == 1:
if ymd[0] > 31 or (yearfirst and ymd[2] <= 31):
# 99-Jan-01
res.year, res.month, res.day = ymd
else:
# 01-Jan-01
# Give precendence to day-first, since
# two-digit years is usually hand-written.
res.day, res.month, res.year = ymd
elif mstridx == 2:
# WTF!?
if ymd[1] > 31:
# 01-99-Jan
res.day, res.year, res.month = ymd
else:
# 99-01-Jan
res.year, res.day, res.month = ymd
else:
if ymd[0] > 31 or \
(yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
# 99-01-01
res.year, res.month, res.day = ymd
elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12):
# 13-01-01
res.day, res.month, res.year = ymd
else:
# 01-13-01
res.month, res.day, res.year = ymd
except (IndexError, ValueError, AssertionError):
return None
if not info.validate(res):
return None
return res
DEFAULTPARSER = parser()
def parse(timestr, parserinfo=None, **kwargs):
if parserinfo:
return parser(parserinfo).parse(timestr, **kwargs)
else:
return DEFAULTPARSER.parse(timestr, **kwargs)
class _tzparser(object):
class _result(_resultbase):
__slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
"start", "end"]
class _attr(_resultbase):
__slots__ = ["month", "week", "weekday",
"yday", "jyday", "day", "time"]
def __repr__(self):
return self._repr("")
def __init__(self):
_resultbase.__init__(self)
self.start = self._attr()
self.end = self._attr()
def parse(self, tzstr):
res = self._result()
l = _timelex.split(tzstr)
try:
len_l = len(l)
i = 0
while i < len_l:
# BRST+3[BRDT[+2]]
j = i
while j < len_l and not [x for x in l[j]
if x in "0123456789:,-+"]:
j += 1
if j != i:
if not res.stdabbr:
offattr = "stdoffset"
res.stdabbr = "".join(l[i:j])
else:
offattr = "dstoffset"
res.dstabbr = "".join(l[i:j])
i = j
if (i < len_l and
(l[i] in ('+', '-') or l[i][0] in "0123456789")):
if l[i] in ('+', '-'):
# Yes, that's right. See the TZ variable
# documentation.
signal = (1,-1)[l[i] == '+']
i += 1
else:
signal = -1
len_li = len(l[i])
if len_li == 4:
# -0300
setattr(res, offattr,
(int(l[i][:2])*3600+int(l[i][2:])*60)*signal)
elif i+1 < len_l and l[i+1] == ':':
# -03:00
setattr(res, offattr,
(int(l[i])*3600+int(l[i+2])*60)*signal)
i += 2
elif len_li <= 2:
# -[0]3
setattr(res, offattr,
int(l[i][:2])*3600*signal)
else:
return None
i += 1
if res.dstabbr:
break
else:
break
if i < len_l:
for j in range(i, len_l):
if l[j] == ';': l[j] = ','
assert l[i] == ','
i += 1
if i >= len_l:
pass
elif (8 <= l.count(',') <= 9 and
not [y for x in l[i:] if x != ','
for y in x if y not in "0123456789"]):
# GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
for x in (res.start, res.end):
x.month = int(l[i])
i += 2
if l[i] == '-':
value = int(l[i+1])*-1
i += 1
else:
value = int(l[i])
i += 2
if value:
x.week = value
x.weekday = (int(l[i])-1)%7
else:
x.day = int(l[i])
i += 2
x.time = int(l[i])
i += 2
if i < len_l:
if l[i] in ('-','+'):
signal = (-1,1)[l[i] == "+"]
i += 1
else:
signal = 1
res.dstoffset = (res.stdoffset+int(l[i]))*signal
elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
not [y for x in l[i:] if x not in (',','/','J','M',
'.','-',':')
for y in x if y not in "0123456789"]):
for x in (res.start, res.end):
if l[i] == 'J':
# non-leap year day (1 based)
i += 1
x.jyday = int(l[i])
elif l[i] == 'M':
# month[-.]week[-.]weekday
i += 1
x.month = int(l[i])
i += 1
assert l[i] in ('-', '.')
i += 1
x.week = int(l[i])
if x.week == 5:
x.week = -1
i += 1
assert l[i] in ('-', '.')
i += 1
x.weekday = (int(l[i])-1)%7
else:
# year day (zero based)
x.yday = int(l[i])+1
i += 1
if i < len_l and l[i] == '/':
i += 1
# start time
len_li = len(l[i])
if len_li == 4:
# -0300
x.time = (int(l[i][:2])*3600+int(l[i][2:])*60)
elif i+1 < len_l and l[i+1] == ':':
# -03:00
x.time = int(l[i])*3600+int(l[i+2])*60
i += 2
if i+1 < len_l and l[i+1] == ':':
i += 2
x.time += int(l[i])
elif len_li <= 2:
# -[0]3
x.time = (int(l[i][:2])*3600)
else:
return None
i += 1
assert i == len_l or l[i] == ','
i += 1
assert i >= len_l
except (IndexError, ValueError, AssertionError):
return None
return res
DEFAULTTZPARSER = _tzparser()
def _parsetz(tzstr):
return DEFAULTTZPARSER.parse(tzstr)
def _parsems(value):
"""Parse a I[.F] seconds value into (seconds, microseconds)."""
if "." not in value:
return int(value), 0
else:
i, f = value.split(".")
return int(i), int(f.ljust(6, "0")[:6])
# vim:ts=4:sw=4:et

View File

@ -1,432 +0,0 @@
"""
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
This module offers extensions to the standard python 2.3+
datetime module.
"""
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
__license__ = "PSF License"
import datetime
import calendar
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
class weekday(object):
__slots__ = ["weekday", "n"]
def __init__(self, weekday, n=None):
self.weekday = weekday
self.n = n
def __call__(self, n):
if n == self.n:
return self
else:
return self.__class__(self.weekday, n)
def __eq__(self, other):
try:
if self.weekday != other.weekday or self.n != other.n:
return False
except AttributeError:
return False
return True
def __repr__(self):
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
if not self.n:
return s
else:
return "%s(%+d)" % (s, self.n)
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
class relativedelta:
"""
The relativedelta type is based on the specification of the excelent
work done by M.-A. Lemburg in his mx.DateTime extension. However,
notice that this type does *NOT* implement the same algorithm as
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
There's two different ways to build a relativedelta instance. The
first one is passing it two date/datetime classes:
relativedelta(datetime1, datetime2)
And the other way is to use the following keyword arguments:
year, month, day, hour, minute, second, microsecond:
Absolute information.
years, months, weeks, days, hours, minutes, seconds, microseconds:
Relative information, may be negative.
weekday:
One of the weekday instances (MO, TU, etc). These instances may
receive a parameter N, specifying the Nth weekday, which could
be positive or negative (like MO(+1) or MO(-2). Not specifying
it is the same as specifying +1. You can also use an integer,
where 0=MO.
leapdays:
Will add given days to the date found, if year is a leap
year, and the date found is post 28 of february.
yearday, nlyearday:
Set the yearday or the non-leap year day (jump leap days).
These are converted to day/month/leapdays information.
Here is the behavior of operations with relativedelta:
1) Calculate the absolute year, using the 'year' argument, or the
original datetime year, if the argument is not present.
2) Add the relative 'years' argument to the absolute year.
3) Do steps 1 and 2 for month/months.
4) Calculate the absolute day, using the 'day' argument, or the
original datetime day, if the argument is not present. Then,
subtract from the day until it fits in the year and month
found after their operations.
5) Add the relative 'days' argument to the absolute day. Notice
that the 'weeks' argument is multiplied by 7 and added to
'days'.
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
microsecond/microseconds.
7) If the 'weekday' argument is present, calculate the weekday,
with the given (wday, nth) tuple. wday is the index of the
weekday (0-6, 0=Mon), and nth is the number of weeks to add
forward or backward, depending on its signal. Notice that if
the calculated date is already Monday, for example, using
(0, 1) or (0, -1) won't change the day.
"""
def __init__(self, dt1=None, dt2=None,
years=0, months=0, days=0, leapdays=0, weeks=0,
hours=0, minutes=0, seconds=0, microseconds=0,
year=None, month=None, day=None, weekday=None,
yearday=None, nlyearday=None,
hour=None, minute=None, second=None, microsecond=None):
if dt1 and dt2:
if not isinstance(dt1, datetime.date) or \
not isinstance(dt2, datetime.date):
raise TypeError, "relativedelta only diffs datetime/date"
if type(dt1) is not type(dt2):
if not isinstance(dt1, datetime.datetime):
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
elif not isinstance(dt2, datetime.datetime):
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
self.years = 0
self.months = 0
self.days = 0
self.leapdays = 0
self.hours = 0
self.minutes = 0
self.seconds = 0
self.microseconds = 0
self.year = None
self.month = None
self.day = None
self.weekday = None
self.hour = None
self.minute = None
self.second = None
self.microsecond = None
self._has_time = 0
months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
self._set_months(months)
dtm = self.__radd__(dt2)
if dt1 < dt2:
while dt1 > dtm:
months += 1
self._set_months(months)
dtm = self.__radd__(dt2)
else:
while dt1 < dtm:
months -= 1
self._set_months(months)
dtm = self.__radd__(dt2)
delta = dt1 - dtm
self.seconds = delta.seconds+delta.days*86400
self.microseconds = delta.microseconds
else:
self.years = years
self.months = months
self.days = days+weeks*7
self.leapdays = leapdays
self.hours = hours
self.minutes = minutes
self.seconds = seconds
self.microseconds = microseconds
self.year = year
self.month = month
self.day = day
self.hour = hour
self.minute = minute
self.second = second
self.microsecond = microsecond
if type(weekday) is int:
self.weekday = weekdays[weekday]
else:
self.weekday = weekday
yday = 0
if nlyearday:
yday = nlyearday
elif yearday:
yday = yearday
if yearday > 59:
self.leapdays = -1
if yday:
ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366]
for idx, ydays in enumerate(ydayidx):
if yday <= ydays:
self.month = idx+1
if idx == 0:
self.day = ydays
else:
self.day = yday-ydayidx[idx-1]
break
else:
raise ValueError, "invalid year day (%d)" % yday
self._fix()
def _fix(self):
if abs(self.microseconds) > 999999:
s = self.microseconds//abs(self.microseconds)
div, mod = divmod(self.microseconds*s, 1000000)
self.microseconds = mod*s
self.seconds += div*s
if abs(self.seconds) > 59:
s = self.seconds//abs(self.seconds)
div, mod = divmod(self.seconds*s, 60)
self.seconds = mod*s
self.minutes += div*s
if abs(self.minutes) > 59:
s = self.minutes//abs(self.minutes)
div, mod = divmod(self.minutes*s, 60)
self.minutes = mod*s
self.hours += div*s
if abs(self.hours) > 23:
s = self.hours//abs(self.hours)
div, mod = divmod(self.hours*s, 24)
self.hours = mod*s
self.days += div*s
if abs(self.months) > 11:
s = self.months//abs(self.months)
div, mod = divmod(self.months*s, 12)
self.months = mod*s
self.years += div*s
if (self.hours or self.minutes or self.seconds or self.microseconds or
self.hour is not None or self.minute is not None or
self.second is not None or self.microsecond is not None):
self._has_time = 1
else:
self._has_time = 0
def _set_months(self, months):
self.months = months
if abs(self.months) > 11:
s = self.months//abs(self.months)
div, mod = divmod(self.months*s, 12)
self.months = mod*s
self.years = div*s
else:
self.years = 0
def __radd__(self, other):
if not isinstance(other, datetime.date):
raise TypeError, "unsupported type for add operation"
elif self._has_time and not isinstance(other, datetime.datetime):
other = datetime.datetime.fromordinal(other.toordinal())
year = (self.year or other.year)+self.years
month = self.month or other.month
if self.months:
assert 1 <= abs(self.months) <= 12
month += self.months
if month > 12:
year += 1
month -= 12
elif month < 1:
year -= 1
month += 12
day = min(calendar.monthrange(year, month)[1],
self.day or other.day)
repl = {"year": year, "month": month, "day": day}
for attr in ["hour", "minute", "second", "microsecond"]:
value = getattr(self, attr)
if value is not None:
repl[attr] = value
days = self.days
if self.leapdays and month > 2 and calendar.isleap(year):
days += self.leapdays
ret = (other.replace(**repl)
+ datetime.timedelta(days=days,
hours=self.hours,
minutes=self.minutes,
seconds=self.seconds,
microseconds=self.microseconds))
if self.weekday:
weekday, nth = self.weekday.weekday, self.weekday.n or 1
jumpdays = (abs(nth)-1)*7
if nth > 0:
jumpdays += (7-ret.weekday()+weekday)%7
else:
jumpdays += (ret.weekday()-weekday)%7
jumpdays *= -1
ret += datetime.timedelta(days=jumpdays)
return ret
def __rsub__(self, other):
return self.__neg__().__radd__(other)
def __add__(self, other):
if not isinstance(other, relativedelta):
raise TypeError, "unsupported type for add operation"
return relativedelta(years=other.years+self.years,
months=other.months+self.months,
days=other.days+self.days,
hours=other.hours+self.hours,
minutes=other.minutes+self.minutes,
seconds=other.seconds+self.seconds,
microseconds=other.microseconds+self.microseconds,
leapdays=other.leapdays or self.leapdays,
year=other.year or self.year,
month=other.month or self.month,
day=other.day or self.day,
weekday=other.weekday or self.weekday,
hour=other.hour or self.hour,
minute=other.minute or self.minute,
second=other.second or self.second,
microsecond=other.second or self.microsecond)
def __sub__(self, other):
if not isinstance(other, relativedelta):
raise TypeError, "unsupported type for sub operation"
return relativedelta(years=other.years-self.years,
months=other.months-self.months,
days=other.days-self.days,
hours=other.hours-self.hours,
minutes=other.minutes-self.minutes,
seconds=other.seconds-self.seconds,
microseconds=other.microseconds-self.microseconds,
leapdays=other.leapdays or self.leapdays,
year=other.year or self.year,
month=other.month or self.month,
day=other.day or self.day,
weekday=other.weekday or self.weekday,
hour=other.hour or self.hour,
minute=other.minute or self.minute,
second=other.second or self.second,
microsecond=other.second or self.microsecond)
def __neg__(self):
return relativedelta(years=-self.years,
months=-self.months,
days=-self.days,
hours=-self.hours,
minutes=-self.minutes,
seconds=-self.seconds,
microseconds=-self.microseconds,
leapdays=self.leapdays,
year=self.year,
month=self.month,
day=self.day,
weekday=self.weekday,
hour=self.hour,
minute=self.minute,
second=self.second,
microsecond=self.microsecond)
def __nonzero__(self):
return not (not self.years and
not self.months and
not self.days and
not self.hours and
not self.minutes and
not self.seconds and
not self.microseconds and
not self.leapdays and
self.year is None and
self.month is None and
self.day is None and
self.weekday is None and
self.hour is None and
self.minute is None and
self.second is None and
self.microsecond is None)
def __mul__(self, other):
f = float(other)
return relativedelta(years=self.years*f,
months=self.months*f,
days=self.days*f,
hours=self.hours*f,
minutes=self.minutes*f,
seconds=self.seconds*f,
microseconds=self.microseconds*f,
leapdays=self.leapdays,
year=self.year,
month=self.month,
day=self.day,
weekday=self.weekday,
hour=self.hour,
minute=self.minute,
second=self.second,
microsecond=self.microsecond)
def __eq__(self, other):
if not isinstance(other, relativedelta):
return False
if self.weekday or other.weekday:
if not self.weekday or not other.weekday:
return False
if self.weekday.weekday != other.weekday.weekday:
return False
n1, n2 = self.weekday.n, other.weekday.n
if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
return False
return (self.years == other.years and
self.months == other.months and
self.days == other.days and
self.hours == other.hours and
self.minutes == other.minutes and
self.seconds == other.seconds and
self.leapdays == other.leapdays and
self.year == other.year and
self.month == other.month and
self.day == other.day and
self.hour == other.hour and
self.minute == other.minute and
self.second == other.second and
self.microsecond == other.microsecond)
def __ne__(self, other):
return not self.__eq__(other)
def __div__(self, other):
return self.__mul__(1/float(other))
def __repr__(self):
l = []
for attr in ["years", "months", "days", "leapdays",
"hours", "minutes", "seconds", "microseconds"]:
value = getattr(self, attr)
if value:
l.append("%s=%+d" % (attr, value))
for attr in ["year", "month", "day", "weekday",
"hour", "minute", "second", "microsecond"]:
value = getattr(self, attr)
if value is not None:
l.append("%s=%s" % (attr, `value`))
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
# vim:ts=4:sw=4:et

File diff suppressed because it is too large Load Diff

View File

@ -1,951 +0,0 @@
"""
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
This module offers extensions to the standard python 2.3+
datetime module.
"""
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
__license__ = "PSF License"
import datetime
import struct
import time
import sys
import os
relativedelta = None
parser = None
rrule = None
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
try:
from dateutil.tzwin import tzwin, tzwinlocal
except (ImportError, OSError):
tzwin, tzwinlocal = None, None
ZERO = datetime.timedelta(0)
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
class tzutc(datetime.tzinfo):
def utcoffset(self, dt):
return ZERO
def dst(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def __eq__(self, other):
return (isinstance(other, tzutc) or
(isinstance(other, tzoffset) and other._offset == ZERO))
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "%s()" % self.__class__.__name__
__reduce__ = object.__reduce__
class tzoffset(datetime.tzinfo):
def __init__(self, name, offset):
self._name = name
self._offset = datetime.timedelta(seconds=offset)
def utcoffset(self, dt):
return self._offset
def dst(self, dt):
return ZERO
def tzname(self, dt):
return self._name
def __eq__(self, other):
return (isinstance(other, tzoffset) and
self._offset == other._offset)
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "%s(%s, %s)" % (self.__class__.__name__,
`self._name`,
self._offset.days*86400+self._offset.seconds)
__reduce__ = object.__reduce__
class tzlocal(datetime.tzinfo):
_std_offset = datetime.timedelta(seconds=-time.timezone)
if time.daylight:
_dst_offset = datetime.timedelta(seconds=-time.altzone)
else:
_dst_offset = _std_offset
def utcoffset(self, dt):
if self._isdst(dt):
return self._dst_offset
else:
return self._std_offset
def dst(self, dt):
if self._isdst(dt):
return self._dst_offset-self._std_offset
else:
return ZERO
def tzname(self, dt):
return time.tzname[self._isdst(dt)]
def _isdst(self, dt):
# We can't use mktime here. It is unstable when deciding if
# the hour near to a change is DST or not.
#
# timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
# dt.minute, dt.second, dt.weekday(), 0, -1))
# return time.localtime(timestamp).tm_isdst
#
# The code above yields the following result:
#
#>>> import tz, datetime
#>>> t = tz.tzlocal()
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
#'BRDT'
#>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
#'BRST'
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
#'BRST'
#>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
#'BRDT'
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
#'BRDT'
#
# Here is a more stable implementation:
#
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
+ dt.hour * 3600
+ dt.minute * 60
+ dt.second)
return time.localtime(timestamp+time.timezone).tm_isdst
def __eq__(self, other):
if not isinstance(other, tzlocal):
return False
return (self._std_offset == other._std_offset and
self._dst_offset == other._dst_offset)
return True
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "%s()" % self.__class__.__name__
__reduce__ = object.__reduce__
class _ttinfo(object):
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
def __init__(self):
for attr in self.__slots__:
setattr(self, attr, None)
def __repr__(self):
l = []
for attr in self.__slots__:
value = getattr(self, attr)
if value is not None:
l.append("%s=%s" % (attr, `value`))
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
def __eq__(self, other):
if not isinstance(other, _ttinfo):
return False
return (self.offset == other.offset and
self.delta == other.delta and
self.isdst == other.isdst and
self.abbr == other.abbr and
self.isstd == other.isstd and
self.isgmt == other.isgmt)
def __ne__(self, other):
return not self.__eq__(other)
def __getstate__(self):
state = {}
for name in self.__slots__:
state[name] = getattr(self, name, None)
return state
def __setstate__(self, state):
for name in self.__slots__:
if name in state:
setattr(self, name, state[name])
class tzfile(datetime.tzinfo):
# http://www.twinsun.com/tz/tz-link.htm
# ftp://elsie.nci.nih.gov/pub/tz*.tar.gz
def __init__(self, fileobj):
if isinstance(fileobj, basestring):
self._filename = fileobj
fileobj = open(fileobj)
elif hasattr(fileobj, "name"):
self._filename = fileobj.name
else:
self._filename = `fileobj`
# From tzfile(5):
#
# The time zone information files used by tzset(3)
# begin with the magic characters "TZif" to identify
# them as time zone information files, followed by
# sixteen bytes reserved for future use, followed by
# six four-byte values of type long, written in a
# ``standard'' byte order (the high-order byte
# of the value is written first).
if fileobj.read(4) != "TZif":
raise ValueError, "magic not found"
fileobj.read(16)
(
# The number of UTC/local indicators stored in the file.
ttisgmtcnt,
# The number of standard/wall indicators stored in the file.
ttisstdcnt,
# The number of leap seconds for which data is
# stored in the file.
leapcnt,
# The number of "transition times" for which data
# is stored in the file.
timecnt,
# The number of "local time types" for which data
# is stored in the file (must not be zero).
typecnt,
# The number of characters of "time zone
# abbreviation strings" stored in the file.
charcnt,
) = struct.unpack(">6l", fileobj.read(24))
# The above header is followed by tzh_timecnt four-byte
# values of type long, sorted in ascending order.
# These values are written in ``standard'' byte order.
# Each is used as a transition time (as returned by
# time(2)) at which the rules for computing local time
# change.
if timecnt:
self._trans_list = struct.unpack(">%dl" % timecnt,
fileobj.read(timecnt*4))
else:
self._trans_list = []
# Next come tzh_timecnt one-byte values of type unsigned
# char; each one tells which of the different types of
# ``local time'' types described in the file is associated
# with the same-indexed transition time. These values
# serve as indices into an array of ttinfo structures that
# appears next in the file.
if timecnt:
self._trans_idx = struct.unpack(">%dB" % timecnt,
fileobj.read(timecnt))
else:
self._trans_idx = []
# Each ttinfo structure is written as a four-byte value
# for tt_gmtoff of type long, in a standard byte
# order, followed by a one-byte value for tt_isdst
# and a one-byte value for tt_abbrind. In each
# structure, tt_gmtoff gives the number of
# seconds to be added to UTC, tt_isdst tells whether
# tm_isdst should be set by localtime(3), and
# tt_abbrind serves as an index into the array of
# time zone abbreviation characters that follow the
# ttinfo structure(s) in the file.
ttinfo = []
for i in range(typecnt):
ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
abbr = fileobj.read(charcnt)
# Then there are tzh_leapcnt pairs of four-byte
# values, written in standard byte order; the
# first value of each pair gives the time (as
# returned by time(2)) at which a leap second
# occurs; the second gives the total number of
# leap seconds to be applied after the given time.
# The pairs of values are sorted in ascending order
# by time.
# Not used, for now
if leapcnt:
leap = struct.unpack(">%dl" % (leapcnt*2),
fileobj.read(leapcnt*8))
# Then there are tzh_ttisstdcnt standard/wall
# indicators, each stored as a one-byte value;
# they tell whether the transition times associated
# with local time types were specified as standard
# time or wall clock time, and are used when
# a time zone file is used in handling POSIX-style
# time zone environment variables.
if ttisstdcnt:
isstd = struct.unpack(">%db" % ttisstdcnt,
fileobj.read(ttisstdcnt))
# Finally, there are tzh_ttisgmtcnt UTC/local
# indicators, each stored as a one-byte value;
# they tell whether the transition times associated
# with local time types were specified as UTC or
# local time, and are used when a time zone file
# is used in handling POSIX-style time zone envi-
# ronment variables.
if ttisgmtcnt:
isgmt = struct.unpack(">%db" % ttisgmtcnt,
fileobj.read(ttisgmtcnt))
# ** Everything has been read **
# Build ttinfo list
self._ttinfo_list = []
for i in range(typecnt):
gmtoff, isdst, abbrind = ttinfo[i]
# Round to full-minutes if that's not the case. Python's
# datetime doesn't accept sub-minute timezones. Check
# http://python.org/sf/1447945 for some information.
gmtoff = (gmtoff+30)//60*60
tti = _ttinfo()
tti.offset = gmtoff
tti.delta = datetime.timedelta(seconds=gmtoff)
tti.isdst = isdst
tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
self._ttinfo_list.append(tti)
# Replace ttinfo indexes for ttinfo objects.
trans_idx = []
for idx in self._trans_idx:
trans_idx.append(self._ttinfo_list[idx])
self._trans_idx = tuple(trans_idx)
# Set standard, dst, and before ttinfos. before will be
# used when a given time is before any transitions,
# and will be set to the first non-dst ttinfo, or to
# the first dst, if all of them are dst.
self._ttinfo_std = None
self._ttinfo_dst = None
self._ttinfo_before = None
if self._ttinfo_list:
if not self._trans_list:
self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0]
else:
for i in range(timecnt-1,-1,-1):
tti = self._trans_idx[i]
if not self._ttinfo_std and not tti.isdst:
self._ttinfo_std = tti
elif not self._ttinfo_dst and tti.isdst:
self._ttinfo_dst = tti
if self._ttinfo_std and self._ttinfo_dst:
break
else:
if self._ttinfo_dst and not self._ttinfo_std:
self._ttinfo_std = self._ttinfo_dst
for tti in self._ttinfo_list:
if not tti.isdst:
self._ttinfo_before = tti
break
else:
self._ttinfo_before = self._ttinfo_list[0]
# Now fix transition times to become relative to wall time.
#
# I'm not sure about this. In my tests, the tz source file
# is setup to wall time, and in the binary file isstd and
# isgmt are off, so it should be in wall time. OTOH, it's
# always in gmt time. Let me know if you have comments
# about this.
laststdoffset = 0
self._trans_list = list(self._trans_list)
for i in range(len(self._trans_list)):
tti = self._trans_idx[i]
if not tti.isdst:
# This is std time.
self._trans_list[i] += tti.offset
laststdoffset = tti.offset
else:
# This is dst time. Convert to std.
self._trans_list[i] += laststdoffset
self._trans_list = tuple(self._trans_list)
def _find_ttinfo(self, dt, laststd=0):
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
+ dt.hour * 3600
+ dt.minute * 60
+ dt.second)
idx = 0
for trans in self._trans_list:
if timestamp < trans:
break
idx += 1
else:
return self._ttinfo_std
if idx == 0:
return self._ttinfo_before
if laststd:
while idx > 0:
tti = self._trans_idx[idx-1]
if not tti.isdst:
return tti
idx -= 1
else:
return self._ttinfo_std
else:
return self._trans_idx[idx-1]
def utcoffset(self, dt):
if not self._ttinfo_std:
return ZERO
return self._find_ttinfo(dt).delta
def dst(self, dt):
if not self._ttinfo_dst:
return ZERO
tti = self._find_ttinfo(dt)
if not tti.isdst:
return ZERO
# The documentation says that utcoffset()-dst() must
# be constant for every dt.
return tti.delta-self._find_ttinfo(dt, laststd=1).delta
# An alternative for that would be:
#
# return self._ttinfo_dst.offset-self._ttinfo_std.offset
#
# However, this class stores historical changes in the
# dst offset, so I belive that this wouldn't be the right
# way to implement this.
def tzname(self, dt):
if not self._ttinfo_std:
return None
return self._find_ttinfo(dt).abbr
def __eq__(self, other):
if not isinstance(other, tzfile):
return False
return (self._trans_list == other._trans_list and
self._trans_idx == other._trans_idx and
self._ttinfo_list == other._ttinfo_list)
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, `self._filename`)
def __reduce__(self):
if not os.path.isfile(self._filename):
raise ValueError, "Unpickable %s class" % self.__class__.__name__
return (self.__class__, (self._filename,))
class tzrange(datetime.tzinfo):
def __init__(self, stdabbr, stdoffset=None,
dstabbr=None, dstoffset=None,
start=None, end=None):
global relativedelta
if not relativedelta:
from dateutil import relativedelta
self._std_abbr = stdabbr
self._dst_abbr = dstabbr
if stdoffset is not None:
self._std_offset = datetime.timedelta(seconds=stdoffset)
else:
self._std_offset = ZERO
if dstoffset is not None:
self._dst_offset = datetime.timedelta(seconds=dstoffset)
elif dstabbr and stdoffset is not None:
self._dst_offset = self._std_offset+datetime.timedelta(hours=+1)
else:
self._dst_offset = ZERO
if dstabbr and start is None:
self._start_delta = relativedelta.relativedelta(
hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
else:
self._start_delta = start
if dstabbr and end is None:
self._end_delta = relativedelta.relativedelta(
hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
else:
self._end_delta = end
def utcoffset(self, dt):
if self._isdst(dt):
return self._dst_offset
else:
return self._std_offset
def dst(self, dt):
if self._isdst(dt):
return self._dst_offset-self._std_offset
else:
return ZERO
def tzname(self, dt):
if self._isdst(dt):
return self._dst_abbr
else:
return self._std_abbr
def _isdst(self, dt):
if not self._start_delta:
return False
year = datetime.datetime(dt.year,1,1)
start = year+self._start_delta
end = year+self._end_delta
dt = dt.replace(tzinfo=None)
if start < end:
return dt >= start and dt < end
else:
return dt >= start or dt < end
def __eq__(self, other):
if not isinstance(other, tzrange):
return False
return (self._std_abbr == other._std_abbr and
self._dst_abbr == other._dst_abbr and
self._std_offset == other._std_offset and
self._dst_offset == other._dst_offset and
self._start_delta == other._start_delta and
self._end_delta == other._end_delta)
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "%s(...)" % self.__class__.__name__
__reduce__ = object.__reduce__
class tzstr(tzrange):
def __init__(self, s):
global parser
if not parser:
from dateutil import parser
self._s = s
res = parser._parsetz(s)
if res is None:
raise ValueError, "unknown string format"
# Here we break the compatibility with the TZ variable handling.
# GMT-3 actually *means* the timezone -3.
if res.stdabbr in ("GMT", "UTC"):
res.stdoffset *= -1
# We must initialize it first, since _delta() needs
# _std_offset and _dst_offset set. Use False in start/end
# to avoid building it two times.
tzrange.__init__(self, res.stdabbr, res.stdoffset,
res.dstabbr, res.dstoffset,
start=False, end=False)
if not res.dstabbr:
self._start_delta = None
self._end_delta = None
else:
self._start_delta = self._delta(res.start)
if self._start_delta:
self._end_delta = self._delta(res.end, isend=1)
def _delta(self, x, isend=0):
kwargs = {}
if x.month is not None:
kwargs["month"] = x.month
if x.weekday is not None:
kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
if x.week > 0:
kwargs["day"] = 1
else:
kwargs["day"] = 31
elif x.day:
kwargs["day"] = x.day
elif x.yday is not None:
kwargs["yearday"] = x.yday
elif x.jyday is not None:
kwargs["nlyearday"] = x.jyday
if not kwargs:
# Default is to start on first sunday of april, and end
# on last sunday of october.
if not isend:
kwargs["month"] = 4
kwargs["day"] = 1
kwargs["weekday"] = relativedelta.SU(+1)
else:
kwargs["month"] = 10
kwargs["day"] = 31
kwargs["weekday"] = relativedelta.SU(-1)
if x.time is not None:
kwargs["seconds"] = x.time
else:
# Default is 2AM.
kwargs["seconds"] = 7200
if isend:
# Convert to standard time, to follow the documented way
# of working with the extra hour. See the documentation
# of the tzinfo class.
delta = self._dst_offset-self._std_offset
kwargs["seconds"] -= delta.seconds+delta.days*86400
return relativedelta.relativedelta(**kwargs)
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, `self._s`)
class _tzicalvtzcomp:
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
tzname=None, rrule=None):
self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom
self.isdst = isdst
self.tzname = tzname
self.rrule = rrule
class _tzicalvtz(datetime.tzinfo):
def __init__(self, tzid, comps=[]):
self._tzid = tzid
self._comps = comps
self._cachedate = []
self._cachecomp = []
def _find_comp(self, dt):
if len(self._comps) == 1:
return self._comps[0]
dt = dt.replace(tzinfo=None)
try:
return self._cachecomp[self._cachedate.index(dt)]
except ValueError:
pass
lastcomp = None
lastcompdt = None
for comp in self._comps:
if not comp.isdst:
# Handle the extra hour in DST -> STD
compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True)
else:
compdt = comp.rrule.before(dt, inc=True)
if compdt and (not lastcompdt or lastcompdt < compdt):
lastcompdt = compdt
lastcomp = comp
if not lastcomp:
# RFC says nothing about what to do when a given
# time is before the first onset date. We'll look for the
# first standard component, or the first component, if
# none is found.
for comp in self._comps:
if not comp.isdst:
lastcomp = comp
break
else:
lastcomp = comp[0]
self._cachedate.insert(0, dt)
self._cachecomp.insert(0, lastcomp)
if len(self._cachedate) > 10:
self._cachedate.pop()
self._cachecomp.pop()
return lastcomp
def utcoffset(self, dt):
return self._find_comp(dt).tzoffsetto
def dst(self, dt):
comp = self._find_comp(dt)
if comp.isdst:
return comp.tzoffsetdiff
else:
return ZERO
def tzname(self, dt):
return self._find_comp(dt).tzname
def __repr__(self):
return "<tzicalvtz %s>" % `self._tzid`
__reduce__ = object.__reduce__
class tzical:
def __init__(self, fileobj):
global rrule
if not rrule:
from dateutil import rrule
if isinstance(fileobj, basestring):
self._s = fileobj
fileobj = open(fileobj)
elif hasattr(fileobj, "name"):
self._s = fileobj.name
else:
self._s = `fileobj`
self._vtz = {}
self._parse_rfc(fileobj.read())
def keys(self):
return self._vtz.keys()
def get(self, tzid=None):
if tzid is None:
keys = self._vtz.keys()
if len(keys) == 0:
raise ValueError, "no timezones defined"
elif len(keys) > 1:
raise ValueError, "more than one timezone available"
tzid = keys[0]
return self._vtz.get(tzid)
def _parse_offset(self, s):
s = s.strip()
if not s:
raise ValueError, "empty offset"
if s[0] in ('+', '-'):
signal = (-1,+1)[s[0]=='+']
s = s[1:]
else:
signal = +1
if len(s) == 4:
return (int(s[:2])*3600+int(s[2:])*60)*signal
elif len(s) == 6:
return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal
else:
raise ValueError, "invalid offset: "+s
def _parse_rfc(self, s):
lines = s.splitlines()
if not lines:
raise ValueError, "empty string"
# Unfold
i = 0
while i < len(lines):
line = lines[i].rstrip()
if not line:
del lines[i]
elif i > 0 and line[0] == " ":
lines[i-1] += line[1:]
del lines[i]
else:
i += 1
tzid = None
comps = []
invtz = False
comptype = None
for line in lines:
if not line:
continue
name, value = line.split(':', 1)
parms = name.split(';')
if not parms:
raise ValueError, "empty property name"
name = parms[0].upper()
parms = parms[1:]
if invtz:
if name == "BEGIN":
if value in ("STANDARD", "DAYLIGHT"):
# Process component
pass
else:
raise ValueError, "unknown component: "+value
comptype = value
founddtstart = False
tzoffsetfrom = None
tzoffsetto = None
rrulelines = []
tzname = None
elif name == "END":
if value == "VTIMEZONE":
if comptype:
raise ValueError, \
"component not closed: "+comptype
if not tzid:
raise ValueError, \
"mandatory TZID not found"
if not comps:
raise ValueError, \
"at least one component is needed"
# Process vtimezone
self._vtz[tzid] = _tzicalvtz(tzid, comps)
invtz = False
elif value == comptype:
if not founddtstart:
raise ValueError, \
"mandatory DTSTART not found"
if tzoffsetfrom is None:
raise ValueError, \
"mandatory TZOFFSETFROM not found"
if tzoffsetto is None:
raise ValueError, \
"mandatory TZOFFSETFROM not found"
# Process component
rr = None
if rrulelines:
rr = rrule.rrulestr("\n".join(rrulelines),
compatible=True,
ignoretz=True,
cache=True)
comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
(comptype == "DAYLIGHT"),
tzname, rr)
comps.append(comp)
comptype = None
else:
raise ValueError, \
"invalid component end: "+value
elif comptype:
if name == "DTSTART":
rrulelines.append(line)
founddtstart = True
elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
rrulelines.append(line)
elif name == "TZOFFSETFROM":
if parms:
raise ValueError, \
"unsupported %s parm: %s "%(name, parms[0])
tzoffsetfrom = self._parse_offset(value)
elif name == "TZOFFSETTO":
if parms:
raise ValueError, \
"unsupported TZOFFSETTO parm: "+parms[0]
tzoffsetto = self._parse_offset(value)
elif name == "TZNAME":
if parms:
raise ValueError, \
"unsupported TZNAME parm: "+parms[0]
tzname = value
elif name == "COMMENT":
pass
else:
raise ValueError, "unsupported property: "+name
else:
if name == "TZID":
if parms:
raise ValueError, \
"unsupported TZID parm: "+parms[0]
tzid = value
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
pass
else:
raise ValueError, "unsupported property: "+name
elif name == "BEGIN" and value == "VTIMEZONE":
tzid = None
comps = []
invtz = True
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, `self._s`)
if sys.platform != "win32":
TZFILES = ["/etc/localtime", "localtime"]
TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"]
else:
TZFILES = []
TZPATHS = []
def gettz(name=None):
tz = None
if not name:
try:
name = os.environ["TZ"]
except KeyError:
pass
if name is None or name == ":":
for filepath in TZFILES:
if not os.path.isabs(filepath):
filename = filepath
for path in TZPATHS:
filepath = os.path.join(path, filename)
if os.path.isfile(filepath):
break
else:
continue
if os.path.isfile(filepath):
try:
tz = tzfile(filepath)
break
except (IOError, OSError, ValueError):
pass
else:
tz = tzlocal()
else:
if name.startswith(":"):
name = name[:-1]
if os.path.isabs(name):
if os.path.isfile(name):
tz = tzfile(name)
else:
tz = None
else:
for path in TZPATHS:
filepath = os.path.join(path, name)
if not os.path.isfile(filepath):
filepath = filepath.replace(' ','_')
if not os.path.isfile(filepath):
continue
try:
tz = tzfile(filepath)
break
except (IOError, OSError, ValueError):
pass
else:
tz = None
if tzwin:
try:
tz = tzwin(name)
except OSError:
pass
if not tz:
from dateutil.zoneinfo import gettz
tz = gettz(name)
if not tz:
for c in name:
# name must have at least one offset to be a tzstr
if c in "0123456789":
try:
tz = tzstr(name)
except ValueError:
pass
break
else:
if name in ("GMT", "UTC"):
tz = tzutc()
elif name in time.tzname:
tz = tzlocal()
return tz
# vim:ts=4:sw=4:et

View File

@ -1,180 +0,0 @@
# This code was originally contributed by Jeffrey Harris.
import datetime
import struct
import _winreg
__author__ = "Jeffrey Harris & Gustavo Niemeyer <gustavo@niemeyer.net>"
__all__ = ["tzwin", "tzwinlocal"]
ONEWEEK = datetime.timedelta(7)
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
def _settzkeyname():
global TZKEYNAME
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
try:
_winreg.OpenKey(handle, TZKEYNAMENT).Close()
TZKEYNAME = TZKEYNAMENT
except WindowsError:
TZKEYNAME = TZKEYNAME9X
handle.Close()
_settzkeyname()
class tzwinbase(datetime.tzinfo):
"""tzinfo class based on win32's timezones available in the registry."""
def utcoffset(self, dt):
if self._isdst(dt):
return datetime.timedelta(minutes=self._dstoffset)
else:
return datetime.timedelta(minutes=self._stdoffset)
def dst(self, dt):
if self._isdst(dt):
minutes = self._dstoffset - self._stdoffset
return datetime.timedelta(minutes=minutes)
else:
return datetime.timedelta(0)
def tzname(self, dt):
if self._isdst(dt):
return self._dstname
else:
return self._stdname
def list():
"""Return a list of all time zones known to the system."""
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
tzkey = _winreg.OpenKey(handle, TZKEYNAME)
result = [_winreg.EnumKey(tzkey, i)
for i in range(_winreg.QueryInfoKey(tzkey)[0])]
tzkey.Close()
handle.Close()
return result
list = staticmethod(list)
def display(self):
return self._display
def _isdst(self, dt):
dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
self._dsthour, self._dstminute,
self._dstweeknumber)
dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek,
self._stdhour, self._stdminute,
self._stdweeknumber)
if dston < dstoff:
return dston <= dt.replace(tzinfo=None) < dstoff
else:
return not dstoff <= dt.replace(tzinfo=None) < dston
class tzwin(tzwinbase):
def __init__(self, name):
self._name = name
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
tzkey = _winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name))
keydict = valuestodict(tzkey)
tzkey.Close()
handle.Close()
self._stdname = keydict["Std"].encode("iso-8859-1")
self._dstname = keydict["Dlt"].encode("iso-8859-1")
self._display = keydict["Display"]
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
tup = struct.unpack("=3l16h", keydict["TZI"])
self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1
(self._stdmonth,
self._stddayofweek, # Sunday = 0
self._stdweeknumber, # Last = 5
self._stdhour,
self._stdminute) = tup[4:9]
(self._dstmonth,
self._dstdayofweek, # Sunday = 0
self._dstweeknumber, # Last = 5
self._dsthour,
self._dstminute) = tup[12:17]
def __repr__(self):
return "tzwin(%s)" % repr(self._name)
def __reduce__(self):
return (self.__class__, (self._name,))
class tzwinlocal(tzwinbase):
def __init__(self):
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
tzlocalkey = _winreg.OpenKey(handle, TZLOCALKEYNAME)
keydict = valuestodict(tzlocalkey)
tzlocalkey.Close()
self._stdname = keydict["StandardName"].encode("iso-8859-1")
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
try:
tzkey = _winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname))
_keydict = valuestodict(tzkey)
self._display = _keydict["Display"]
tzkey.Close()
except OSError:
self._display = None
handle.Close()
self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
self._dstoffset = self._stdoffset-keydict["DaylightBias"]
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
tup = struct.unpack("=8h", keydict["StandardStart"])
(self._stdmonth,
self._stddayofweek, # Sunday = 0
self._stdweeknumber, # Last = 5
self._stdhour,
self._stdminute) = tup[1:6]
tup = struct.unpack("=8h", keydict["DaylightStart"])
(self._dstmonth,
self._dstdayofweek, # Sunday = 0
self._dstweeknumber, # Last = 5
self._dsthour,
self._dstminute) = tup[1:6]
def __reduce__(self):
return (self.__class__, ())
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
"""dayofweek == 0 means Sunday, whichweek 5 means last instance"""
first = datetime.datetime(year, month, 1, hour, minute)
weekdayone = first.replace(day=((dayofweek-first.isoweekday())%7+1))
for n in xrange(whichweek):
dt = weekdayone+(whichweek-n)*ONEWEEK
if dt.month == month:
return dt
def valuestodict(key):
"""Convert a registry key's values to a dictionary."""
dict = {}
size = _winreg.QueryInfoKey(key)[1]
for i in range(size):
data = _winreg.EnumValue(key, i)
dict[data[0]] = data[1]
return dict

View File

@ -1,87 +0,0 @@
"""
Copyright (c) 2003-2005 Gustavo Niemeyer <gustavo@niemeyer.net>
This module offers extensions to the standard python 2.3+
datetime module.
"""
from dateutil.tz import tzfile
from tarfile import TarFile
import os
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
__license__ = "PSF License"
__all__ = ["setcachesize", "gettz", "rebuild"]
CACHE = []
CACHESIZE = 10
class tzfile(tzfile):
def __reduce__(self):
return (gettz, (self._filename,))
def getzoneinfofile():
filenames = os.listdir(os.path.join(os.path.dirname(__file__)))
filenames.sort()
filenames.reverse()
for entry in filenames:
if entry.startswith("zoneinfo") and ".tar." in entry:
return os.path.join(os.path.dirname(__file__), entry)
return None
ZONEINFOFILE = getzoneinfofile()
del getzoneinfofile
def setcachesize(size):
global CACHESIZE, CACHE
CACHESIZE = size
del CACHE[size:]
def gettz(name):
tzinfo = None
if ZONEINFOFILE:
for cachedname, tzinfo in CACHE:
if cachedname == name:
break
else:
tf = TarFile.open(ZONEINFOFILE)
try:
zonefile = tf.extractfile(name)
except KeyError:
tzinfo = None
else:
tzinfo = tzfile(zonefile)
tf.close()
CACHE.insert(0, (name, tzinfo))
del CACHE[CACHESIZE:]
return tzinfo
def rebuild(filename, tag=None, format="gz"):
import tempfile, shutil
tmpdir = tempfile.mkdtemp()
zonedir = os.path.join(tmpdir, "zoneinfo")
moduledir = os.path.dirname(__file__)
if tag: tag = "-"+tag
targetname = "zoneinfo%s.tar.%s" % (tag, format)
try:
tf = TarFile.open(filename)
for name in tf.getnames():
if not (name.endswith(".sh") or
name.endswith(".tab") or
name == "leapseconds"):
tf.extract(name, tmpdir)
filepath = os.path.join(tmpdir, name)
os.system("zic -d %s %s" % (zonedir, filepath))
tf.close()
target = os.path.join(moduledir, targetname)
for entry in os.listdir(moduledir):
if entry.startswith("zoneinfo") and ".tar." in entry:
os.unlink(os.path.join(moduledir, entry))
tf = TarFile.open(target, "w:%s" % format)
for entry in os.listdir(zonedir):
entrypath = os.path.join(zonedir, entry)
tf.add(entrypath, entry)
tf.close()
finally:
shutil.rmtree(tmpdir)

View File

@ -31,6 +31,8 @@ import time
TIME = time # For later use. TIME = time # For later use.
from datetime import datetime from datetime import datetime
from dateutil import parser
import supybot.conf as conf import supybot.conf as conf
import supybot.utils as utils import supybot.utils as utils
from supybot.commands import * from supybot.commands import *
@ -38,8 +40,6 @@ import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Time') _ = PluginInternationalization('Time')
from .local.dateutil import parser
def parse(s): def parse(s):
todo = [] todo = []
s = s.replace('noon', '12:00') s = s.replace('noon', '12:00')

View File

@ -238,9 +238,10 @@ class Todo(callbacks.Plugin):
criteria = [] criteria = []
for (option, arg) in optlist: for (option, arg) in optlist:
if option == 'regexp': if option == 'regexp':
criteria.append(lambda x: commands.regexp_wrapper(x, reobj=arg, criteria.append(lambda s:
timeout=0.1, plugin_name = self.name(), fcn_name='search')) regexp_wrapper(s, reobj=arg, timeout=0.1,
criteria.append(arg.search) plugin_name=self.name(),
fcn_name='search'))
for glob in globs: for glob in globs:
glob = utils.python.glob2re(glob) glob = utils.python.glob2re(glob)
criteria.append(re.compile(glob).search) criteria.append(re.compile(glob).search)

View File

@ -94,7 +94,7 @@ def getTopicNumber(irc, msg, args, state):
try: try:
topics[n] topics[n]
except IndexError: except IndexError:
error(str(n)) error(args[0])
del args[0] del args[0]
while n < 0: while n < 0:
n += len(topics) n += len(topics)
@ -415,9 +415,6 @@ class Topic(callbacks.Plugin):
index into the topics. <channel> is only necessary if the message index into the topics. <channel> is only necessary if the message
isn't sent in the channel itself. isn't sent in the channel itself.
""" """
if not self._checkManageCapabilities(irc, msg, channel):
capabilities = self.registryValue('requireManageCapability')
irc.errorNoCapability(capabilities, Raise=True)
topics = self._splitTopic(irc.state.getTopic(channel), channel) topics = self._splitTopic(irc.state.getTopic(channel), channel)
irc.reply(topics[number]) irc.reply(topics[number])
get = wrap(get, ['inChannel', 'topicNumber']) get = wrap(get, ['inChannel', 'topicNumber'])

View File

@ -68,4 +68,9 @@ conf.registerGlobalValue(Web.fetch, 'maximum',
registry.NonNegativeInteger(0, _("""Determines the maximum number of registry.NonNegativeInteger(0, _("""Determines the maximum number of
bytes the bot will download via the 'fetch' command in this plugin."""))) bytes the bot will download via the 'fetch' command in this plugin.""")))
conf.registerGlobalValue(Web.fetch, 'timeout',
registry.NonNegativeInteger(5, """Determines the maximum number of
seconds the bot will wait for the site to respond, when using the 'fetch'
command in this plugin. If 0, will use socket.defaulttimeout"""))
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -41,57 +41,8 @@ import os.path
import threading import threading
import collections import collections
import supybot.log as log from .. import callbacks, conf, dbi, ircdb, ircutils, log, utils, world
import supybot.dbi as dbi from ..commands import *
import supybot.conf as conf
import supybot.ircdb as ircdb
import supybot.utils as utils
import supybot.world as world
from supybot.commands import *
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
from supybot import commands
## i think we don't need any of this with sqlite3
#try:
## We need to sweep away all that mx.* crap because our code doesn't account
## for PySQLite's arbitrary use of it. Whoever decided to change sqlite's
## behavior based on whether or not that module is installed was a *CRACK*
## **FIEND**, plain and simple.
#mxCrap = {}
#for (name, module) in sys.modules.items():
#if name.startswith('mx'):
#mxCrap[name] = module
#sys.modules.pop(name)
## Now that the mx crap is gone, we can import sqlite.
#import sqlite3 as sqlite
## And now we'll put it back, even though it sucks.
#sys.modules.update(mxCrap)
## Just in case, we'll do this as well. It doesn't seem to work fine by
## itself, though, or else we'd just do this in the first place.
#sqlite.have_datetime = False
#Connection = sqlite.Connection
#class MyConnection(sqlite.Connection):
#def commit(self, *args, **kwargs):
#if self.autocommit:
#return
#else:
#Connection.commit(self, *args, **kwargs)
#def __del__(self):
#try:
#Connection.__del__(self)
#except AttributeError:
#pass
#except Exception, e:
#try:
#log.exception('Uncaught exception in __del__:')
#except:
#pass
#sqlite.Connection = MyConnection
##del Connection.__del__
#except ImportError:
#pass
try: try:
import sqlite3 import sqlite3
@ -451,9 +402,8 @@ class ChannelIdDatabasePlugin(callbacks.Plugin):
if opt == 'by': if opt == 'by':
predicates.append(lambda r, arg=arg: r.by == arg.id) predicates.append(lambda r, arg=arg: r.by == arg.id)
elif opt == 'regexp': elif opt == 'regexp':
predicates.append(lambda x: commands.regexp_wrapper(x.text, reobj=arg, predicates.append(lambda r: regexp_wrapper(r.text, reobj=arg,
timeout=0.1, plugin_name=self.name(), fcn_name='search')) timeout=0.1, plugin_name=self.name(), fcn_name='search'))
#predicates.append(lambda r, arg=arg: arg.search(r.text))
if glob: if glob:
def globP(r, glob=glob.lower()): def globP(r, glob=glob.lower()):
return fnmatch.fnmatch(r.text.lower(), glob) return fnmatch.fnmatch(r.text.lower(), glob)

View File

@ -211,14 +211,6 @@ class Debug(callbacks.Privmsg):
irc.reply(repr(os.environ)) irc.reply(repr(os.environ))
environ = wrap(environ) environ = wrap(environ)
def clearq(self, irc, msg, args):
"""takes no arguments
Clears the current send queue for this network.
"""
irc.queue.reset()
irc.replySuccess()
Class = Debug Class = Debug

View File

@ -122,22 +122,14 @@ if __name__ == '__main__':
debug('pidfile (%s) is not writable: %s' % (options.pidfile, e)) debug('pidfile (%s) is not writable: %s' % (options.pidfile, e))
sys.exit(-1) sys.exit(-1)
debug('Bot not found, starting.') debug('Bot not found, starting.')
home = os.environ['HOME'] cmdline = [options.supybot, '--daemon', options.conffile]
inst = subprocess.Popen('sh', close_fds=True, stderr=subprocess.STDOUT, inst = subprocess.Popen(cmdline, close_fds=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE) stderr=subprocess.STDOUT,
for filename in ('.login', '.bash_profile', '.profile', '.bashrc'): stdin=None, stdout=subprocess.PIPE)
filename = os.path.join(home, filename) debug('Output from supybot: %r' % inst.stdout.read())
if os.path.exists(filename):
debug('Found %s, sourcing.' % filename)
command = 'source %s' % filename + os.linesep
inst.stdin.write(command.encode('utf-8'))
cmdline = '%s --daemon %s' % (options.supybot, options.conffile)
debug('Sending cmdline to sh process.')
inst.stdin.write(cmdline.encode('utf-8') + os.linesep.encode('utf-8'))
inst.stdin.close()
debug('Received from sh process: %r' % inst.stdout.read())
ret = inst.wait() ret = inst.wait()
debug('Bot started, command line %r returned %s.' % (cmdline, ret)) debug('Bot started, command line %r returned %s.' % (' '.join(cmdline),
ret))
sys.exit(ret) sys.exit(ret)
else: else:
sys.exit(0) sys.exit(0)

View File

@ -69,44 +69,24 @@ if sys.version_info < (2, 6, 0):
sys.stderr.write(os.linesep) sys.stderr.write(os.linesep)
sys.exit(-1) sys.exit(-1)
import textwrap
clean = False
while '--clean' in sys.argv:
clean = True
sys.argv.remove('--clean')
import glob import glob
import shutil import shutil
import os import os
import textwrap
plugins = [s for s in os.listdir('plugins') if try:
os.path.exists(os.path.join('plugins', s, 'plugin.py'))] from distribute_setup import use_setuptools
except ImportError:
pass
else:
use_setuptools(version='0.6c9')
from setuptools import setup
def normalizeWhitespace(s): def normalizeWhitespace(s):
return ' '.join(s.split()) return ' '.join(s.split())
try:
from distutils.core import setup
from distutils.sysconfig import get_python_lib
except ImportError as e:
s = normalizeWhitespace("""Supybot requires the distutils package to
install. This package is normally included with Python, but for some
unfathomable reason, many distributions to take it out of standard Python
and put it in another package, usually caled 'python-dev' or python-devel'
or something similar. This is one of the dumbest things a distribution can
do, because it means that developers cannot rely on *STANDARD* Python
modules to be present on systems of that distribution. Complain to your
distribution, and loudly. If you how much of our time we've wasted telling
people to install what should be included by default with Python you'd
understand why we're unhappy about this. Anyway, to reiterate, install the
development package for Python that your distribution supplies.""")
sys.stderr.write(os.linesep*2)
sys.stderr.write(textwrap.fill(s))
sys.stderr.write(os.linesep*2)
sys.exit(-1)
try: try:
from distutils.command.build_py import build_py_2to3 from distutils.command.build_py import build_py_2to3
class build_py(build_py_2to3): class build_py(build_py_2to3):
@ -154,15 +134,9 @@ except ImportError:
from distutils.command.build_py import build_py from distutils.command.build_py import build_py
if clean:
previousInstall = os.path.join(get_python_lib(), 'supybot') plugins = [s for s in os.listdir('plugins') if
if os.path.exists(previousInstall): os.path.exists(os.path.join('plugins', s, 'plugin.py'))]
try:
print('Removing current installation.')
shutil.rmtree(previousInstall)
except Exception as e:
print('Couldn\'t remove former installation: %s' % e)
sys.exit(-1)
packages = ['supybot', packages = ['supybot',
'supybot.locales', 'supybot.locales',
@ -173,24 +147,15 @@ packages = ['supybot',
[ [
'supybot.plugins.Dict.local', 'supybot.plugins.Dict.local',
'supybot.plugins.Math.local', 'supybot.plugins.Math.local',
'supybot.plugins.Google.local',
'supybot.plugins.RSS.local',
'supybot.plugins.Time.local',
'supybot.plugins.Time.local.dateutil',
] ]
package_dir = {'supybot': 'src', package_dir = {'supybot': 'src',
'supybot.utils': 'src/utils', 'supybot.utils': 'src/utils',
'supybot.locales': 'locales',
'supybot.plugins': 'plugins', 'supybot.plugins': 'plugins',
'supybot.drivers': 'src/drivers', 'supybot.drivers': 'src/drivers',
'supybot.locales': 'locales',
'supybot.plugins.Google.local': 'plugins/Google/local',
'supybot.plugins.Dict.local': 'plugins/Dict/local', 'supybot.plugins.Dict.local': 'plugins/Dict/local',
'supybot.plugins.Math.local': 'plugins/Math/local', 'supybot.plugins.Math.local': 'plugins/Math/local',
'supybot.plugins.RSS.local': 'plugins/RSS/local',
'supybot.plugins.Time.local': 'plugins/Time/local',
'supybot.plugins.Time.local.dateutil':
'plugins/Time/local/dateutil',
} }
package_data = {'supybot.locales': [s for s in os.listdir('locales/')]} package_data = {'supybot.locales': [s for s in os.listdir('locales/')]}
@ -229,7 +194,8 @@ setup(
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Operating System :: POSIX', 'Operating System :: POSIX',
'Operating System :: Microsoft :: Windows', 'Operating System :: Microsoft :: Windows',
'Programming Language :: Python', 'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
], ],
cmdclass = {'build_py': build_py}, cmdclass = {'build_py': build_py},
@ -248,14 +214,12 @@ setup(
'scripts/supybot-plugin-doc', 'scripts/supybot-plugin-doc',
'scripts/supybot-plugin-create', 'scripts/supybot-plugin-create',
], ],
data_files=[('share/man/man1', ['docs/man/supybot.1']),
('share/man/man1', ['docs/man/supybot-test.1']), install_requires=[
('share/man/man1', ['docs/man/supybot-botchk.1']), # Time plugin
('share/man/man1', ['docs/man/supybot-wizard.1']), 'python-dateutil >=2.0',
('share/man/man1', ['docs/man/supybot-adduser.1']), 'feedparser',
('share/man/man1', ['docs/man/supybot-plugin-doc.1']), ],
('share/man/man1', ['docs/man/supybot-plugin-create.1']),
]
) )

View File

@ -32,12 +32,7 @@ import sys
import os.path import os.path
import dynamicScope import dynamicScope
try: from . import utils
import supybot.utils as utils
except ImportError: # We are running setup.py for the first time
import src
sys.modules['supybot'] = src
import src.utils as utils
(__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__)['format'] = utils.str.format (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__)['format'] = utils.str.format

View File

@ -33,8 +33,6 @@
This module contains the basic callbacks for handling PRIVMSGs. This module contains the basic callbacks for handling PRIVMSGs.
""" """
import supybot
import re import re
import sys import sys
import copy import copy
@ -53,17 +51,10 @@ if sys.version_info[0] < 3:
else: else:
from cStringIO import StringIO from cStringIO import StringIO
import supybot.log as log from . import (conf, ircdb, irclib, ircmsgs, ircutils, log, registry, utils,
import supybot.conf as conf world)
import supybot.utils as utils from .utils.iter import any, all
import supybot.world as world from .i18n import PluginInternationalization, internationalizeDocstring
import supybot.ircdb as ircdb
import supybot.irclib as irclib
import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils
import supybot.registry as registry
from supybot.utils.iter import any, all
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization() _ = PluginInternationalization()
def _addressed(nick, msg, prefixChars=None, nicks=None, def _addressed(nick, msg, prefixChars=None, nicks=None,
@ -237,6 +228,9 @@ def getHelp(method, name=None, doc=None):
if name is None: if name is None:
name = method.__name__ name = method.__name__
if doc is None: if doc is None:
if method.__doc__ is None:
doclines = ['This command has no help. Complain to the author.']
else:
doclines = method.__doc__.splitlines() doclines = method.__doc__.splitlines()
else: else:
doclines = doc.splitlines() doclines = doc.splitlines()
@ -644,7 +638,7 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
if maxNesting and self.nested > maxNesting: if maxNesting and self.nested > maxNesting:
log.warning('%s attempted more than %s levels of nesting.', log.warning('%s attempted more than %s levels of nesting.',
self.msg.prefix, maxNesting) self.msg.prefix, maxNesting)
return self.error(_('You\'ve attempted more nesting than is ' self.error(_('You\'ve attempted more nesting than is '
'currently allowed on this bot.')) 'currently allowed on this bot.'))
# The deepcopy here is necessary for Scheduler; it re-runs already # The deepcopy here is necessary for Scheduler; it re-runs already
# tokenized commands. There's a possibility a simple copy[:] would # tokenized commands. There's a possibility a simple copy[:] would

View File

@ -40,7 +40,7 @@ import struct
import os.path import os.path
import cPickle as pickle import cPickle as pickle
import supybot.utils as utils from . import utils
def hash(s): def hash(s):
"""DJB's hash function for CDB.""" """DJB's hash function for CDB."""
@ -184,6 +184,8 @@ class Reader(utils.IterableMap):
"""Class for reading from a CDB database.""" """Class for reading from a CDB database."""
def __init__(self, filename): def __init__(self, filename):
self.filename = filename self.filename = filename
import os
print(repr(os.getcwd()))
self.fd = open(filename, 'rb') self.fd = open(filename, 'rb')
self.loop = 0 self.loop = 0
self.khash = 0 self.khash = 0

View File

@ -33,6 +33,7 @@ Includes wrappers for commands.
""" """
import time import time
import Queue
import types import types
import getopt import getopt
import inspect import inspect
@ -45,18 +46,10 @@ try:
except ImportError: # Windows! except ImportError: # Windows!
resource = None resource = None
import supybot.log as log from . import callbacks, conf, ircdb, ircmsgs, ircutils, log, utils, world
import supybot.conf as conf from .i18n import PluginInternationalization, internationalizeDocstring
import supybot.utils as utils
import supybot.world as world
import supybot.ircdb as ircdb
import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization() _ = PluginInternationalization()
### ###
# Non-arg wrappers -- these just change the behavior of a command without # Non-arg wrappers -- these just change the behavior of a command without
# changing the arguments given to it. # changing the arguments given to it.
@ -251,7 +244,10 @@ def _int(s):
return int(s, base) return int(s, base)
except ValueError: except ValueError:
if base == 10: if base == 10:
try:
return int(float(s)) return int(float(s))
except OverflowError:
raise ValueError('I don\'t understand numbers that large.')
else: else:
raise raise
@ -1118,7 +1114,7 @@ __all__ = [
# Decorators. # Decorators.
'urlSnarfer', 'thread', 'urlSnarfer', 'thread',
# Functions. # Functions.
'wrap', 'wrap', 'process', 'regexp_wrapper',
# Stuff for testing. # Stuff for testing.
'Spec', 'Spec',
] ]

View File

@ -33,17 +33,11 @@ import sys
import time import time
import socket import socket
import supybot.utils as utils from . import ircutils, registry, utils
import supybot.registry as registry from .version import version
import supybot.ircutils as ircutils from .i18n import PluginInternationalization
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization() _ = PluginInternationalization()
###
# version: This should be pretty obvious.
###
from supybot.version import version
### ###
# *** The following variables are affected by command-line options. They are # *** The following variables are affected by command-line options. They are
# not registry variables for a specific reason. Do *not* change these to # not registry variables for a specific reason. Do *not* change these to
@ -303,12 +297,12 @@ def registerNetwork(name, password='', ssl=False, sasl_username='',
technically passwords are server-specific and not network-specific, technically passwords are server-specific and not network-specific,
but this is the best we can do right now.""") % name, private=True)) but this is the best we can do right now.""") % name, private=True))
registryServers = registerGlobalValue(network, 'servers', Servers([], registryServers = registerGlobalValue(network, 'servers', Servers([],
_("""Determines what servers the bot will connect to for %s. Each will _("""Space-separated list of servers the bot will connect to for %s.
be tried in order, wrapping back to the first when the cycle is Each will be tried in order, wrapping back to the first when the cycle
completed.""") % name)) is completed.""") % name))
registerGlobalValue(network, 'channels', SpaceSeparatedSetOfChannels([], registerGlobalValue(network, 'channels', SpaceSeparatedSetOfChannels([],
_("""Determines what channels the bot will join only on %s.""") % _("""Space-separated list of channels the bot will join only on %s.""")
name, private=True)) % name, private=True))
registerGlobalValue(network, 'ssl', registry.Boolean(ssl, registerGlobalValue(network, 'ssl', registry.Boolean(ssl,
_("""Determines whether the bot will attempt to connect with SSL _("""Determines whether the bot will attempt to connect with SSL
sockets to %s.""") % name)) sockets to %s.""") % name))
@ -940,7 +934,7 @@ registerChannelValue(supybot.databases.plugins.channelSpecific.link, 'allow',
class CDB(registry.Boolean): class CDB(registry.Boolean):
def connect(self, filename): def connect(self, filename):
import supybot.cdb as cdb from . import cdb
basename = os.path.basename(filename) basename = os.path.basename(filename)
journalName = supybot.directories.data.tmp.dirize(basename+'.journal') journalName = supybot.directories.data.tmp.dirize(basename+'.journal')
return cdb.open_db(filename, 'c', return cdb.open_db(filename, 'c',

View File

@ -35,9 +35,8 @@ import os
import csv import csv
import math import math
import supybot.cdb as cdb from . import cdb, utils
import supybot.utils as utils from .utils.iter import ilen
from supybot.utils.iter import ilen
class Error(Exception): class Error(Exception):
"""General error for this module.""" """General error for this module."""

View File

@ -1,6 +1,6 @@
## ##
# Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2002-2004, Jeremiah Fincher
# Copyright (c) 2010, James McCoy # Copyright (c) 2010, 2013, James McCoy
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -39,13 +39,9 @@ import time
import errno import errno
import select import select
import socket import socket
import supybot.log as log
import supybot.conf as conf from .. import (conf, drivers, log, schedule, utils, world)
import supybot.utils as utils from ..utils.iter import imap
import supybot.world as world
import supybot.drivers as drivers
import supybot.schedule as schedule
from itertools import imap
try: try:
from charade.universaldetector import UniversalDetector from charade.universaldetector import UniversalDetector
charadeLoaded = True charadeLoaded = True
@ -54,6 +50,7 @@ except:
'cannot guess character encoding if' 'cannot guess character encoding if'
'using Python3') 'using Python3')
charadeLoaded = False charadeLoaded = False
try: try:
import ssl import ssl
SSLError = ssl.SSLError SSLError = ssl.SSLError
@ -248,7 +245,7 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin):
def connect(self, **kwargs): def connect(self, **kwargs):
self.reconnect(reset=False, **kwargs) self.reconnect(reset=False, **kwargs)
def reconnect(self, reset=True): def reconnect(self, wait=False, reset=True):
self._attempt += 1 self._attempt += 1
self.nextReconnectTime = None self.nextReconnectTime = None
if self.connected: if self.connected:
@ -266,6 +263,9 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin):
self.irc.reset() self.irc.reset()
else: else:
drivers.log.debug('Not resetting %s.', self.irc) drivers.log.debug('Not resetting %s.', self.irc)
if wait:
self.scheduleReconnect()
return
server = self._getNextServer() server = self._getNextServer()
socks_proxy = getattr(conf.supybot.networks, self.irc.network) \ socks_proxy = getattr(conf.supybot.networks, self.irc.network) \
.socksproxy() .socksproxy()

View File

@ -28,10 +28,7 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import supybot.log as log from .. import conf, drivers, ircmsgs, log
import supybot.conf as conf
import supybot.drivers as drivers
import supybot.ircmsgs as ircmsgs
from twisted.names import client from twisted.names import client
from twisted.internet import reactor, error from twisted.internet import reactor, error

View File

@ -36,10 +36,7 @@ import sys
import time import time
import socket import socket
import supybot.conf as conf from .. import conf, ircmsgs, log as supylog, utils
import supybot.utils as utils
import supybot.log as supylog
import supybot.ircmsgs as ircmsgs
_drivers = {} _drivers = {}
_deadDrivers = [] _deadDrivers = []

View File

@ -29,20 +29,12 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
from __future__ import division
import os import os
import time import time
import operator import operator
import supybot.log as log from . import conf, ircutils, log, registry, unpreserve, utils, world
import supybot.conf as conf from .utils.iter import imap, ilen, ifilter
import supybot.utils as utils
import supybot.world as world
import supybot.ircutils as ircutils
import supybot.registry as registry
import supybot.unpreserve as unpreserve
from itertools import imap, ifilter
def isCapability(capability): def isCapability(capability):
return len(capability.split(None, 1)) == 1 return len(capability.split(None, 1)) == 1

View File

@ -33,17 +33,10 @@ import time
import random import random
import base64 import base64
import supybot.log as log from . import conf, ircdb, ircmsgs, ircutils, log, utils, world
import supybot.conf as conf from .utils.str import rsplit
import supybot.utils as utils from .utils.iter import imap, chain, cycle
import supybot.world as world from .utils.structures import queue, smallqueue, RingBuffer
import supybot.ircdb as ircdb
import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils
from utils.str import rsplit
from utils.iter import chain, cycle
from utils.structures import queue, smallqueue, RingBuffer
### ###
# The base class for a callback to be registered with an Irc object. Shows # The base class for a callback to be registered with an Irc object. Shows

View File

@ -40,10 +40,8 @@ import sys
import time import time
import functools import functools
import supybot.conf as conf from . import conf, ircutils, utils
import supybot.utils as utils from .utils.iter import all
from supybot.utils.iter import all
import supybot.ircutils as ircutils
### ###
# IrcMsg class -- used for representing IRC messages acquired from a network. # IrcMsg class -- used for representing IRC messages acquired from a network.

View File

@ -46,7 +46,7 @@ import textwrap
import functools import functools
from cStringIO import StringIO as sio from cStringIO import StringIO as sio
import supybot.utils as utils from . import utils
from itertools import imap from itertools import imap
def debug(s, *args): def debug(s, *args):

View File

@ -37,12 +37,7 @@ import operator
import textwrap import textwrap
import traceback import traceback
import supybot.ansi as ansi from . import ansi, conf, ircutils, registry, utils
import supybot.conf as conf
import supybot.utils as utils
import supybot.registry as registry
import supybot.ircutils as ircutils
deadlyExceptions = [KeyboardInterrupt, SystemExit] deadlyExceptions = [KeyboardInterrupt, SystemExit]

View File

@ -32,11 +32,9 @@ import sys
import imp import imp
import os.path import os.path
import linecache import linecache
import re
import supybot.log as log from . import callbacks, conf, log, registry
import supybot.conf as conf
import supybot.registry as registry
import supybot.callbacks as callbacks
installDir = os.path.dirname(sys.modules[__name__].__file__) installDir = os.path.dirname(sys.modules[__name__].__file__)
_pluginsDir = os.path.join(installDir, 'plugins') _pluginsDir = os.path.join(installDir, 'plugins')
@ -55,6 +53,13 @@ def loadPluginModule(name, ignoreDeprecation=False):
except EnvironmentError: # OSError, IOError superclass. except EnvironmentError: # OSError, IOError superclass.
log.warning('Invalid plugin directory: %s; removing.', dir) log.warning('Invalid plugin directory: %s; removing.', dir)
conf.supybot.directories.plugins().remove(dir) conf.supybot.directories.plugins().remove(dir)
if name not in files:
matched_names = filter(lambda x: re.search(r'(?i)^%s$' % (name,), x),
files)
if len(matched_names) == 1:
name = matched_names[0]
else:
raise ImportError, name
moduleInfo = imp.find_module(name, pluginDirs) moduleInfo = imp.find_module(name, pluginDirs)
try: try:
module = imp.load_module(name, *moduleInfo) module = imp.load_module(name, *moduleInfo)

View File

@ -35,8 +35,7 @@ import sys
import textwrap import textwrap
from getpass import getpass as getPass from getpass import getpass as getPass
import supybot.ansi as ansi from . import ansi, utils
import supybot.utils as utils
from supybot.i18n import PluginInternationalization, internationalizeDocstring from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization() _ = PluginInternationalization()

View File

@ -37,9 +37,7 @@ import codecs
import string import string
import textwrap import textwrap
import supybot.utils as utils from . import utils, i18n
import supybot.i18n as i18n
_ = i18n.PluginInternationalization() _ = i18n.PluginInternationalization()
def error(s): def error(s):

View File

@ -39,9 +39,7 @@ import heapq
import functools import functools
from threading import Lock from threading import Lock
import supybot.log as log from . import drivers, log, world
import supybot.world as world
import supybot.drivers as drivers
class mytuple(tuple): class mytuple(tuple):
def __cmp__(self, other): def __cmp__(self, other):

View File

@ -39,20 +39,8 @@ import httplib
import unittest import unittest
import threading import threading
import supybot.log as log from . import (callbacks, conf, drivers, httpserver, i18n, ircdb, irclib,
import supybot.i18n as i18n ircmsgs, ircutils, log, plugin, registry, utils, world)
import supybot.conf as conf
import supybot.utils as utils
import supybot.ircdb as ircdb
import supybot.world as world
import supybot.irclib as irclib
import supybot.plugin as plugin
import supybot.drivers as drivers
import supybot.ircmsgs as ircmsgs
import supybot.registry as registry
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.httpserver as httpserver
i18n.import_conf() i18n.import_conf()
network = True network = True

View File

@ -58,16 +58,7 @@ def force(x):
# These imports need to happen below the block above, so things get put into # These imports need to happen below the block above, so things get put into
# __builtins__ appropriately. # __builtins__ appropriately.
from gen import * from .gen import *
import net from . import crypt, error, file, iter, net, python, seq, str, transaction, web
import seq
import str
import web
import file
import iter
import crypt
import error
import python
import transaction
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -34,9 +34,8 @@ import random
import shutil import shutil
import os.path import os.path
from itertools import ifilter from . import crypt
from .iter import ifilter
import crypt
def contents(filename): def contents(filename):
return open(filename).read() return open(filename).read()

View File

@ -38,10 +38,10 @@ import traceback
import collections import collections
from itertools import imap from itertools import imap
from str import format from . import crypt
from file import mktemp from .str import format
from .file import mktemp
import crypt from .iter import imap
from supybot.i18n import PluginInternationalization from supybot.i18n import PluginInternationalization
_ = PluginInternationalization() _ = PluginInternationalization()

View File

@ -34,6 +34,12 @@ import random
from itertools import * from itertools import *
# For old plugins
ifilter = filter
def ifilterfalse(p, L):
return ifilter(lambda x:not p(x), L)
imap = map
def len(iterable): def len(iterable):
"""Returns the length of an iterator.""" """Returns the length of an iterator."""
i = 0 i = 0

View File

@ -1,6 +1,6 @@
### ###
# Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2011, James McCoy # Copyright (c) 2011, 2013, James McCoy
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -35,50 +35,10 @@ Simple utility modules.
import re import re
import socket import socket
class EmailRe: from .web import _ipAddr, _domain
"""Fake class used for backward compatibility."""
rfc822_specials = '()<>@,;:\\"[]' emailRe = re.compile(r"^(\w&.+-]+!)*[\w&.+-]+@(%s|%s)$" % (_domain, _ipAddr),
def match(self, addr): re.I)
# From http://www.secureprogramming.com/?action=view&feature=recipes&recipeid=1
# First we validate the name portion (name@domain)
c = 0
while c < len(addr):
if addr[c] == '"' and (not c or addr[c - 1] == '.' or addr[c - 1] == '"'):
c = c + 1
while c < len(addr):
if addr[c] == '"': break
if addr[c] == '\\' and addr[c + 1] == ' ':
c = c + 2
continue
if ord(addr[c]) < 32 or ord(addr[c]) >= 127: return 0
c = c + 1
else: return 0
if addr[c] == '@': break
if addr[c] != '.': return 0
c = c + 1
continue
if addr[c] == '@': break
if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0
if addr[c] in self.rfc822_specials: return 0
c = c + 1
if not c or addr[c - 1] == '.': return 0
# Next we validate the domain portion (name@domain)
domain = c = c + 1
if domain >= len(addr): return 0
count = 0
while c < len(addr):
if addr[c] == '.':
if c == domain or addr[c - 1] == '.': return 0
count = count + 1
if ord(addr[c]) <= 32 or ord(addr[c]) >= 127: return 0
if addr[c] in self.rfc822_specials: return 0
c = c + 1
return count >= 1
emailRe = EmailRe()
def getAddressFromHostname(host, attempt=0): def getAddressFromHostname(host, attempt=0):
addrinfo = socket.getaddrinfo(host, None) addrinfo = socket.getaddrinfo(host, None)

View File

@ -38,8 +38,8 @@ import sys
import string import string
import textwrap import textwrap
from iter import all, any from .iter import all, any
from structures import TwoWayDictionary from .structures import TwoWayDictionary
from supybot.i18n import PluginInternationalization from supybot.i18n import PluginInternationalization
_ = PluginInternationalization() _ = PluginInternationalization()
@ -117,7 +117,10 @@ class MultipleRemover:
_soundextrans = MultipleReplacer(dict(zip(string.ascii_uppercase, _soundextrans = MultipleReplacer(dict(zip(string.ascii_uppercase,
'01230120022455012623010202'))) '01230120022455012623010202')))
def soundex(s, length=4): def soundex(s, length=4):
"""Returns the soundex hash of a given string.""" """Returns the soundex hash of a given string.
length=0 doesn't truncate the hash.
"""
s = s.upper() # Make everything uppercase. s = s.upper() # Make everything uppercase.
s = ''.join([x for x in s if x in string.ascii_uppercase]) s = ''.join([x for x in s if x in string.ascii_uppercase])
if not s: if not s:
@ -129,9 +132,11 @@ def soundex(s, length=4):
for c in s: for c in s:
if c != L[-1]: if c != L[-1]:
L.append(c) L.append(c)
L = [c for c in L if c != '0'] + (['0']*(length-1)) L = [c for c in L if c != '0']
s = ''.join(L) s = ''.join(L)
return length and s[:length] or s.rstrip('0') if length:
s = s.ljust(length, '0')[:length]
return s
def dqrepr(s): def dqrepr(s):
"""Returns a repr() of s guaranteed to be in double quotes.""" """Returns a repr() of s guaranteed to be in double quotes."""

View File

@ -35,9 +35,7 @@ import os
import shutil import shutil
import os.path import os.path
import error from . import error, file as File, python
import python
import file as File
# 'txn' is used as an abbreviation for 'transaction' in the following source. # 'txn' is used as an abbreviation for 'transaction' in the following source.

View File

@ -45,7 +45,7 @@ try:
except AttributeError: except AttributeError:
pass pass
from str import normalizeWhitespace from .str import normalizeWhitespace
Request = urllib2.Request Request = urllib2.Request
urlquote = urllib.quote urlquote = urllib.quote
@ -62,10 +62,11 @@ _ipAddr = r'%s(?:\.%s){3}' % (_octet, _octet)
# Base domain regex off RFC 1034 and 1738 # Base domain regex off RFC 1034 and 1738
_label = r'[0-9a-z][-0-9a-z]*[0-9a-z]?' _label = r'[0-9a-z][-0-9a-z]*[0-9a-z]?'
_domain = r'%s(?:\.%s)*\.[0-9a-z][-0-9a-z]+' % (_label, _label) _domain = r'%s(?:\.%s)*\.[0-9a-z][-0-9a-z]+' % (_label, _label)
_urlRe = r'(\w+://(?:\S+@)?(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (_domain, _ipAddr) _urlRe = r'(\w+://(?:\S+@)?(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (_domain,
urlRe = re.compile(_urlRe, re.I)
_httpUrlRe = r'(https?://(?:\S+@)?(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (_domain,
_ipAddr) _ipAddr)
urlRe = re.compile(_urlRe, re.I)
_httpUrlRe = r'(https?://(?:\S+@)?(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % \
(_domain, _ipAddr)
httpUrlRe = re.compile(_httpUrlRe, re.I) httpUrlRe = re.compile(_httpUrlRe, re.I)
REFUSED = 'Connection refused.' REFUSED = 'Connection refused.'
@ -100,7 +101,7 @@ defaultHeaders = {
# application-specific function. Feel free to use a callable here. # application-specific function. Feel free to use a callable here.
proxy = None proxy = None
def getUrlFd(url, headers=None, data=None): def getUrlFd(url, headers=None, data=None, timeout=None):
"""getUrlFd(url, headers=None, data=None) """getUrlFd(url, headers=None, data=None)
Opens the given url and returns a file object. Headers and data are Opens the given url and returns a file object. Headers and data are
@ -109,28 +110,20 @@ def getUrlFd(url, headers=None, data=None):
headers = defaultHeaders headers = defaultHeaders
try: try:
if not isinstance(url, urllib2.Request): if not isinstance(url, urllib2.Request):
if '#' in url: (scheme, loc, path, query, frag) = urlparse.urlsplit(url)
url = url[:url.index('#')] (user, host) = urllib.splituser(loc)
if '@' in url: url = urlparse.urlunsplit((scheme, host, path, query, ''))
scheme, url = url.split('://', 2)
auth, url = url.split('@')
url = scheme + '://' + url
request = urllib2.Request(url, headers=headers, data=data) request = urllib2.Request(url, headers=headers, data=data)
if 'auth' in locals(): if user:
if sys.version_info[0] >= 3 and isinstance(auth, str):
auth = auth.encode()
auth = base64.b64encode(auth)
if sys.version_info[0] >= 3:
auth = auth.decode()
request.add_header('Authorization', request.add_header('Authorization',
'Basic ' + auth) 'Basic %s' % base64.b64encode(user))
else: else:
request = url request = url
request.add_data(data) request.add_data(data)
httpProxy = force(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, timeout=timeout)
return fd return fd
except socket.timeout, e: except socket.timeout, e:
raise Error, TIMED_OUT raise Error, TIMED_OUT

View File

@ -42,11 +42,7 @@ import multiprocessing
import re import re
import supybot.log as log from . import conf, drivers, ircutils, log, registry
import supybot.conf as conf
import supybot.drivers as drivers
import supybot.ircutils as ircutils
import supybot.registry as registry
startedAt = time.time() # Just in case it doesn't get set later. startedAt = time.time() # Just in case it doesn't get set later.

View File

@ -564,6 +564,9 @@ class SourceNestedPluginTestCase(PluginTestCase):
""" """
irc.reply('f') irc.reply('f')
def empty(self, irc, msg, args):
pass
class g(callbacks.Commands): class g(callbacks.Commands):
def h(self, irc, msg, args): def h(self, irc, msg, args):
"""takes no arguments """takes no arguments
@ -602,6 +605,7 @@ class SourceNestedPluginTestCase(PluginTestCase):
self.assertResponse('e g h', 'h') self.assertResponse('e g h', 'h')
self.assertResponse('e g i j', 'j') self.assertResponse('e g i j', 'j')
self.assertHelp('help f') self.assertHelp('help f')
self.assertHelp('help empty')
self.assertHelp('help same') self.assertHelp('help same')
self.assertHelp('help e g h') self.assertHelp('help e g h')
self.assertHelp('help e g i j') self.assertHelp('help e g i j')

View File

@ -74,6 +74,7 @@ class GeneralContextTestCase(CommandsTestCase):
def testSpecInt(self): def testSpecInt(self):
self.assertState(['int'], ['1'], [1]) self.assertState(['int'], ['1'], [1])
self.assertState(['int', 'int', 'int'], ['1', '2', '3'], [1, 2, 3]) self.assertState(['int', 'int', 'int'], ['1', '2', '3'], [1, 2, 3])
self.assertError(['int'], ['9e999'])
def testSpecNick(self): def testSpecNick(self):
strict = conf.supybot.protocols.irc.strictRfc() strict = conf.supybot.protocols.irc.strictRfc()