diff --git a/.gitignore b/.gitignore index a60ad1edd..7b1e282f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ backup build +dist +supybot.egg-info test-data test-conf test-logs diff --git a/RELNOTES b/RELNOTES index 1e030d6f9..fd4647142 100644 --- a/RELNOTES +++ b/RELNOTES @@ -10,6 +10,8 @@ Factoids' config variable supybot.plugins.Factoids.factoidPrefix has been replaced by supybot.plugins.Factoids.format, which allows the user to 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 diff --git a/distribute_setup.py b/distribute_setup.py new file mode 100644 index 000000000..8f5b0637b --- /dev/null +++ b/distribute_setup.py @@ -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:]) diff --git a/docs/USING_WRAP.rst b/docs/USING_WRAP.rst index 61fa66fbd..3a090e065 100644 --- a/docs/USING_WRAP.rst +++ b/docs/USING_WRAP.rst @@ -203,6 +203,10 @@ optional, the default value is shown. - Checks for a valid HTTP URL. +* email + + - Checks for a syntactically valid email address. + * long, type="long" - 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. -"checkChannelCapability", capability - Checks to make sure that the caller has the specified capability on the +* checkChannelCapability, capability + + - Checks to make sure that the caller has the specified capability on the 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 ============= What contexts are available for me to use? diff --git a/plugins/Admin/plugin.py b/plugins/Admin/plugin.py index 56049eefa..d5057475d 100644 --- a/plugins/Admin/plugin.py +++ b/plugins/Admin/plugin.py @@ -356,6 +356,15 @@ class Admin(callbacks.Plugin): irc.reply(_('I\'m not currently globally ignoring anyone.')) 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 def clearq(self, irc, msg, args): diff --git a/plugins/Filter/plugin.py b/plugins/Filter/plugin.py index 7e45538a8..0dc8732c6 100644 --- a/plugins/Filter/plugin.py +++ b/plugins/Filter/plugin.py @@ -165,19 +165,20 @@ class Filter(callbacks.Plugin): irc.reply(''.join(L)) binary = wrap(binary, ['text']) - @internationalizeDocstring def unbinary(self, irc, msg, args, text): """ Returns the character representation of binary . Assumes ASCII, 8 digits per character. """ - L = [chr(int(text[i:(i+8)], 2)) for i in xrange(0, len(text), 8)] - irc.reply(''.join(L)) + try: + L = [chr(int(text[i:(i+8)], 2)) for i in xrange(0, len(text), 8)] + irc.reply(''.join(L)) + except ValueError: + irc.errorInvalid('binary string', text) unbinary = wrap(unbinary, ['text']) _hex_encoder = staticmethod(codecs.getencoder('hex_codec')) - @internationalizeDocstring def hexlify(self, irc, msg, args, text): """ diff --git a/plugins/Filter/test.py b/plugins/Filter/test.py index fdea060db..449b13194 100644 --- a/plugins/Filter/test.py +++ b/plugins/Filter/test.py @@ -92,6 +92,10 @@ class FilterTest(ChannelPluginTestCase): def testUnbinary(self): self.assertResponse('unbinary 011011010110111101101111', 'moo') + def testUnbinary(self): + self.assertResponse('unbinary 011011010110111101101111', 'moo') + self.assertError('unbinary moo') + def testRot13(self): for s in map(str, range(1000, 1010)): self.assertResponse('rot13 [rot13 %s]' % s, s) diff --git a/plugins/Google/local/__init__.py b/plugins/Google/local/__init__.py deleted file mode 100644 index e86e97b86..000000000 --- a/plugins/Google/local/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Stub so local is a module, used for third-party modules diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 4d018ef7b..348cfbc87 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -31,6 +31,7 @@ import re import sys import cgi +import json import time import socket import urllib diff --git a/plugins/Math/plugin.py b/plugins/Math/plugin.py index 91dc12adb..f634d1b41 100644 --- a/plugins/Math/plugin.py +++ b/plugins/Math/plugin.py @@ -172,6 +172,13 @@ class Math(callbacks.Plugin): 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. """ + 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): irc.error(_('There\'s really no reason why you should have ' 'underscores or brackets in your mathematical ' diff --git a/plugins/Misc/plugin.py b/plugins/Misc/plugin.py index 5fbe201d7..662fe0edd 100644 --- a/plugins/Misc/plugin.py +++ b/plugins/Misc/plugin.py @@ -39,6 +39,7 @@ from itertools import ifilter import supybot import supybot.conf as conf +from supybot import commands import supybot.utils as utils from supybot.commands import * import supybot.ircdb as ircdb @@ -431,18 +432,11 @@ class Misc(callbacks.Plugin): return False if ircmsgs.isAction(m): m1 = ircmsgs.unAction(m) - #return arg.search(ircmsgs.unAction(m)) else: m1 = m.args[1] - #return arg.search(m.args[1]) - try: - # use a subprocess here, since specially crafted regexps can - # 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 + return regexp_wrapper(m1, reobj=arg, timeout=0.1, + plugin_name=self.name(), + fcn_name='last') predicates.setdefault('regexp', []).append(f) elif option == 'nolimit': nolimit = True diff --git a/plugins/MoobotFactoids/plugin.py b/plugins/MoobotFactoids/plugin.py index 0bb0a20cd..2208277d8 100755 --- a/plugins/MoobotFactoids/plugin.py +++ b/plugins/MoobotFactoids/plugin.py @@ -512,7 +512,7 @@ class MoobotFactoids(callbacks.Plugin): if not info: irc.error(format(_('No such factoid: %q'), key)) return - (created_by, _, _, _, _, _, _, locked_by, _) = info + (created_by, a, a, a, a, a, a, locked_by, a) = info # Don't perform redundant operations if locking and locked_by is not None: irc.error(format(_('Factoid %q is already locked.'), key)) diff --git a/plugins/Note/plugin.py b/plugins/Note/plugin.py index eb87dc1e9..31cebcc66 100644 --- a/plugins/Note/plugin.py +++ b/plugins/Note/plugin.py @@ -295,8 +295,10 @@ class Note(callbacks.Plugin): own = to for (option, arg) in optlist: if option == 'regexp': - criteria.append(lambda x: commands.regexp_wrapper(x, reobj=arg, - timeout=0.1, plugin_name = self.name(), fcn_name='search')) + criteria.append(lambda s: + regexp_wrapper(s, reobj=arg, timeout=0.1, + plugin_name=self.name(), + fcn_name='search')) elif option == 'sent': own = frm if glob: diff --git a/plugins/RSS/local/__init__.py b/plugins/RSS/local/__init__.py deleted file mode 100644 index e86e97b86..000000000 --- a/plugins/RSS/local/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Stub so local is a module, used for third-party modules diff --git a/plugins/RSS/plugin.py b/plugins/RSS/plugin.py index 065486507..6bf887a9a 100644 --- a/plugins/RSS/plugin.py +++ b/plugins/RSS/plugin.py @@ -34,6 +34,7 @@ import socket import threading import re import sys +import feedparser import supybot.conf as conf import supybot.utils as utils @@ -45,14 +46,6 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = 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 .' - def getFeedName(irc, msg, args, state): if not registry.isValidRegistryName(args[0]): state.errorInvalid('feed name', args[0], @@ -283,7 +276,7 @@ class RSS(callbacks.Plugin): # and DoS the website in question. self.acquireLock(url) if self.willGetNewFeed(url): - results = None + results = {} try: self.log.debug('Downloading new feed from %u', url) results = feedparser.parse(url) @@ -298,9 +291,7 @@ class RSS(callbacks.Plugin): # These seem mostly harmless. We'll need reports of a # kind that isn't. self.log.debug('Allowing bozo_exception %r through.', e) - if results is None: - self.log.error('Could not fetch feed %s' % url) - elif results.get('feed', {}): + if results.get('feed', {}) and self.getHeadlines(results): self.cachedFeeds[url] = results self.lastRequest[url] = time.time() else: diff --git a/plugins/RSS/test.py b/plugins/RSS/test.py index 5e9dea1c2..6dde2e9a2 100644 --- a/plugins/RSS/test.py +++ b/plugins/RSS/test.py @@ -77,7 +77,6 @@ class RSSTestCase(ChannelPluginTestCase): def testNonAsciiFeeds(self): 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') diff --git a/plugins/ShrinkUrl/config.py b/plugins/ShrinkUrl/config.py index a2343ec04..5bf421e79 100644 --- a/plugins/ShrinkUrl/config.py +++ b/plugins/ShrinkUrl/config.py @@ -42,7 +42,7 @@ def configure(advanced): conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) 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') class ShrinkCycle(registry.SpaceSeparatedListOfStrings): diff --git a/plugins/ShrinkUrl/plugin.py b/plugins/ShrinkUrl/plugin.py index ebd675519..3417c7ab6 100644 --- a/plugins/ShrinkUrl/plugin.py +++ b/plugins/ShrinkUrl/plugin.py @@ -258,14 +258,16 @@ class ShrinkUrl(callbacks.PluginRegexp): _gooApi = 'https://www.googleapis.com/urlshortener/v1/url' @retry def _getGooUrl(self, url): + url = utils.web.urlquote(url) try: return self.db.get('goo', url) except KeyError: - text = utils.web.getUrl(self._gooApi, - headers={'content-type':'application/json'}, - data=json.dumps({'longUrl': url}).encode()) - googl = json.loads(text.decode())['id'] - if len(googl) > 0 : + headers = utils.web.defaultHeaders.copy() + headers['content-type'] = 'application/json' + data = json.dumps({'longUrl': url}) + text = utils.web.getUrl(self._gooApi, data=data, headers=headers) + googl = json.loads(text)['id'] + if googl: self.db.set('goo', url, googl) return googl else: @@ -295,7 +297,7 @@ class ShrinkUrl(callbacks.PluginRegexp): parameters = utils.web.urlencode({'longurl': url}) response = utils.web.getUrl(self._ur1Api, data=parameters) ur1ca = self._ur1Regexp.search(response.decode()).group('url') - if len(ur1ca) > 0 : + if ur1ca: self.db.set('ur1', url, ur1ca) return ur1ca else: diff --git a/plugins/ShrinkUrl/test.py b/plugins/ShrinkUrl/test.py index 865c23d1b..948114007 100644 --- a/plugins/ShrinkUrl/test.py +++ b/plugins/ShrinkUrl/test.py @@ -41,12 +41,13 @@ class ShrinkUrlTestCase(ChannelPluginTestCase): (udUrl, r'http://tinyurl.com/u479')], 'ln': [(sfUrl, r'http://ln-s.net/\+PE-'), (udUrl, r'http://ln-s.net/2\$K')], - 'xrl': [(udUrl, r'http://xrl.us/bfnyji')], - 'goo': [(sfUrl, r'http://goo.gl/krnNC'), - (udUrl, r'http://goo.gl/1ejCD')], - 'ur1': [(sfUrl, r'http://ur1.ca/ceqh8'), + 'xrl': [(sfUrl, r'http://xrl.us/bfq8ik'), + (udUrl, r'http://xrl.us/bfnyji')], + 'goo': [(sfUrl, r'http://goo.gl/3c59N'), + (udUrl, r'http://goo.gl/ocTga')], + 'ur1': [(sfUrl, r'http://ur1.ca/9xl25'), (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')] } if network: diff --git a/plugins/Status/plugin.py b/plugins/Status/plugin.py index 0332a96d5..3b7a7f0d1 100644 --- a/plugins/Status/plugin.py +++ b/plugins/Status/plugin.py @@ -32,6 +32,7 @@ import os import sys import time import threading +import multiprocessing import subprocess import supybot.conf as conf @@ -98,7 +99,21 @@ class Status(callbacks.Plugin): irc.reply(s) 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): """takes no arguments diff --git a/plugins/Status/test.py b/plugins/Status/test.py index d6781dfab..a70947b76 100644 --- a/plugins/Status/test.py +++ b/plugins/Status/test.py @@ -72,6 +72,8 @@ class StatusTestCase(PluginTestCase): def testThreads(self): self.assertNotError('threads') + def testProcesses(self): + self.assertNotError('processes') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/String/config.py b/plugins/String/config.py index 6ebec26de..2fc8d805e 100644 --- a/plugins/String/config.py +++ b/plugins/String/config.py @@ -55,9 +55,9 @@ conf.registerGlobalValue(String.levenshtein, 'max', conf.registerGroup(String, 're') 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 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 - 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: diff --git a/plugins/String/plugin.py b/plugins/String/plugin.py index 51c244a66..8f001340c 100644 --- a/plugins/String/plugin.py +++ b/plugins/String/plugin.py @@ -38,6 +38,7 @@ import supybot.utils as utils from supybot.commands import * import supybot.commands as commands import supybot.plugins as plugins +import supybot.commands as commands import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring @@ -156,7 +157,7 @@ class String(callbacks.Plugin): 'it with some smaller inputs.')) else: irc.reply(str(utils.str.distance(s1, s2))) - levenshtein = wrap(levenshtein, ['something', 'text']) + levenshtein = thread(wrap(levenshtein, ['something', 'text'])) @internationalizeDocstring def soundex(self, irc, msg, args, text, length): @@ -200,14 +201,13 @@ class String(callbacks.Plugin): else: t = self.registryValue('re.timeout') 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) except commands.ProcessTimeoutError, e: irc.error("ProcessTimeoutError: %s" % (e,)) re = thread(wrap(re, [first('regexpMatcher', 'regexpReplacer'), 'text'])) - @internationalizeDocstring def xor(self, irc, msg, args, password, text): """ diff --git a/plugins/Time/local/__init__.py b/plugins/Time/local/__init__.py deleted file mode 100644 index e86e97b86..000000000 --- a/plugins/Time/local/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Stub so local is a module, used for third-party modules diff --git a/plugins/Time/local/dateutil/__init__.py b/plugins/Time/local/dateutil/__init__.py deleted file mode 100644 index 8b4ac7dc8..000000000 --- a/plugins/Time/local/dateutil/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Copyright (c) 2003-2007 Gustavo Niemeyer - -This module offers extensions to the standard python 2.3+ -datetime module. -""" -__author__ = "Gustavo Niemeyer " -__license__ = "PSF License" -__version__ = "1.4.1" diff --git a/plugins/Time/local/dateutil/easter.py b/plugins/Time/local/dateutil/easter.py deleted file mode 100644 index d7944104b..000000000 --- a/plugins/Time/local/dateutil/easter.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Copyright (c) 2003-2007 Gustavo Niemeyer - -This module offers extensions to the standard python 2.3+ -datetime module. -""" -__author__ = "Gustavo Niemeyer " -__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)) - diff --git a/plugins/Time/local/dateutil/parser.py b/plugins/Time/local/dateutil/parser.py deleted file mode 100644 index 5d824e411..000000000 --- a/plugins/Time/local/dateutil/parser.py +++ /dev/null @@ -1,886 +0,0 @@ -# -*- coding:iso-8859-1 -*- -""" -Copyright (c) 2003-2007 Gustavo Niemeyer - -This module offers extensions to the standard python 2.3+ -datetime module. -""" -__author__ = "Gustavo Niemeyer " -__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 diff --git a/plugins/Time/local/dateutil/relativedelta.py b/plugins/Time/local/dateutil/relativedelta.py deleted file mode 100644 index 562a7d3c4..000000000 --- a/plugins/Time/local/dateutil/relativedelta.py +++ /dev/null @@ -1,432 +0,0 @@ -""" -Copyright (c) 2003-2007 Gustavo Niemeyer - -This module offers extensions to the standard python 2.3+ -datetime module. -""" -__author__ = "Gustavo Niemeyer " -__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 diff --git a/plugins/Time/local/dateutil/rrule.py b/plugins/Time/local/dateutil/rrule.py deleted file mode 100644 index 4c21d2d1d..000000000 --- a/plugins/Time/local/dateutil/rrule.py +++ /dev/null @@ -1,1097 +0,0 @@ -""" -Copyright (c) 2003-2007 Gustavo Niemeyer - -This module offers extensions to the standard python 2.3+ -datetime module. -""" -__author__ = "Gustavo Niemeyer " -__license__ = "PSF License" - -import itertools -import datetime -import calendar -import thread -import sys - -__all__ = ["rrule", "rruleset", "rrulestr", - "YEARLY", "MONTHLY", "WEEKLY", "DAILY", - "HOURLY", "MINUTELY", "SECONDLY", - "MO", "TU", "WE", "TH", "FR", "SA", "SU"] - -# Every mask is 7 days longer to handle cross-year weekly periods. -M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30+ - [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7) -M365MASK = list(M366MASK) -M29, M30, M31 = range(1,30), range(1,31), range(1,32) -MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) -MDAY365MASK = list(MDAY366MASK) -M29, M30, M31 = range(-29,0), range(-30,0), range(-31,0) -NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) -NMDAY365MASK = list(NMDAY366MASK) -M366RANGE = (0,31,60,91,121,152,182,213,244,274,305,335,366) -M365RANGE = (0,31,59,90,120,151,181,212,243,273,304,334,365) -WDAYMASK = [0,1,2,3,4,5,6]*55 -del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31] -MDAY365MASK = tuple(MDAY365MASK) -M365MASK = tuple(M365MASK) - -(YEARLY, - MONTHLY, - WEEKLY, - DAILY, - HOURLY, - MINUTELY, - SECONDLY) = range(7) - -# Imported on demand. -easter = None -parser = None - -class weekday(object): - __slots__ = ["weekday", "n"] - - def __init__(self, weekday, n=None): - if n == 0: - raise ValueError, "Can't create weekday with n == 0" - 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 rrulebase: - def __init__(self, cache=False): - if cache: - self._cache = [] - self._cache_lock = thread.allocate_lock() - self._cache_gen = self._iter() - self._cache_complete = False - else: - self._cache = None - self._cache_complete = False - self._len = None - - def __iter__(self): - if self._cache_complete: - return iter(self._cache) - elif self._cache is None: - return self._iter() - else: - return self._iter_cached() - - def _iter_cached(self): - i = 0 - gen = self._cache_gen - cache = self._cache - acquire = self._cache_lock.acquire - release = self._cache_lock.release - while gen: - if i == len(cache): - acquire() - if self._cache_complete: - break - try: - for j in range(10): - cache.append(gen.next()) - except StopIteration: - self._cache_gen = gen = None - self._cache_complete = True - break - release() - yield cache[i] - i += 1 - while i < self._len: - yield cache[i] - i += 1 - - def __getitem__(self, item): - if self._cache_complete: - return self._cache[item] - elif isinstance(item, slice): - if item.step and item.step < 0: - return list(iter(self))[item] - else: - return list(itertools.islice(self, - item.start or 0, - item.stop or sys.maxint, - item.step or 1)) - elif item >= 0: - gen = iter(self) - try: - for i in range(item+1): - res = gen.next() - except StopIteration: - raise IndexError - return res - else: - return list(iter(self))[item] - - def __contains__(self, item): - if self._cache_complete: - return item in self._cache - else: - for i in self: - if i == item: - return True - elif i > item: - return False - return False - - # __len__() introduces a large performance penality. - def count(self): - if self._len is None: - for x in self: pass - return self._len - - def before(self, dt, inc=False): - if self._cache_complete: - gen = self._cache - else: - gen = self - last = None - if inc: - for i in gen: - if i > dt: - break - last = i - else: - for i in gen: - if i >= dt: - break - last = i - return last - - def after(self, dt, inc=False): - if self._cache_complete: - gen = self._cache - else: - gen = self - if inc: - for i in gen: - if i >= dt: - return i - else: - for i in gen: - if i > dt: - return i - return None - - def between(self, after, before, inc=False): - if self._cache_complete: - gen = self._cache - else: - gen = self - started = False - l = [] - if inc: - for i in gen: - if i > before: - break - elif not started: - if i >= after: - started = True - l.append(i) - else: - l.append(i) - else: - for i in gen: - if i >= before: - break - elif not started: - if i > after: - started = True - l.append(i) - else: - l.append(i) - return l - -class rrule(rrulebase): - def __init__(self, freq, dtstart=None, - interval=1, wkst=None, count=None, until=None, bysetpos=None, - bymonth=None, bymonthday=None, byyearday=None, byeaster=None, - byweekno=None, byweekday=None, - byhour=None, byminute=None, bysecond=None, - cache=False): - rrulebase.__init__(self, cache) - global easter - if not dtstart: - dtstart = datetime.datetime.now().replace(microsecond=0) - elif not isinstance(dtstart, datetime.datetime): - dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) - else: - dtstart = dtstart.replace(microsecond=0) - self._dtstart = dtstart - self._tzinfo = dtstart.tzinfo - self._freq = freq - self._interval = interval - self._count = count - if until and not isinstance(until, datetime.datetime): - until = datetime.datetime.fromordinal(until.toordinal()) - self._until = until - if wkst is None: - self._wkst = calendar.firstweekday() - elif type(wkst) is int: - self._wkst = wkst - else: - self._wkst = wkst.weekday - if bysetpos is None: - self._bysetpos = None - elif type(bysetpos) is int: - if bysetpos == 0 or not (-366 <= bysetpos <= 366): - raise ValueError("bysetpos must be between 1 and 366, " - "or between -366 and -1") - self._bysetpos = (bysetpos,) - else: - self._bysetpos = tuple(bysetpos) - for pos in self._bysetpos: - if pos == 0 or not (-366 <= pos <= 366): - raise ValueError("bysetpos must be between 1 and 366, " - "or between -366 and -1") - if not (byweekno or byyearday or bymonthday or - byweekday is not None or byeaster is not None): - if freq == YEARLY: - if not bymonth: - bymonth = dtstart.month - bymonthday = dtstart.day - elif freq == MONTHLY: - bymonthday = dtstart.day - elif freq == WEEKLY: - byweekday = dtstart.weekday() - # bymonth - if not bymonth: - self._bymonth = None - elif type(bymonth) is int: - self._bymonth = (bymonth,) - else: - self._bymonth = tuple(bymonth) - # byyearday - if not byyearday: - self._byyearday = None - elif type(byyearday) is int: - self._byyearday = (byyearday,) - else: - self._byyearday = tuple(byyearday) - # byeaster - if byeaster is not None: - if not easter: - from dateutil import easter - if type(byeaster) is int: - self._byeaster = (byeaster,) - else: - self._byeaster = tuple(byeaster) - else: - self._byeaster = None - # bymonthay - if not bymonthday: - self._bymonthday = () - self._bynmonthday = () - elif type(bymonthday) is int: - if bymonthday < 0: - self._bynmonthday = (bymonthday,) - self._bymonthday = () - else: - self._bymonthday = (bymonthday,) - self._bynmonthday = () - else: - self._bymonthday = tuple([x for x in bymonthday if x > 0]) - self._bynmonthday = tuple([x for x in bymonthday if x < 0]) - # byweekno - if byweekno is None: - self._byweekno = None - elif type(byweekno) is int: - self._byweekno = (byweekno,) - else: - self._byweekno = tuple(byweekno) - # byweekday / bynweekday - if byweekday is None: - self._byweekday = None - self._bynweekday = None - elif type(byweekday) is int: - self._byweekday = (byweekday,) - self._bynweekday = None - elif hasattr(byweekday, "n"): - if not byweekday.n or freq > MONTHLY: - self._byweekday = (byweekday.weekday,) - self._bynweekday = None - else: - self._bynweekday = ((byweekday.weekday, byweekday.n),) - self._byweekday = None - else: - self._byweekday = [] - self._bynweekday = [] - for wday in byweekday: - if type(wday) is int: - self._byweekday.append(wday) - elif not wday.n or freq > MONTHLY: - self._byweekday.append(wday.weekday) - else: - self._bynweekday.append((wday.weekday, wday.n)) - self._byweekday = tuple(self._byweekday) - self._bynweekday = tuple(self._bynweekday) - if not self._byweekday: - self._byweekday = None - elif not self._bynweekday: - self._bynweekday = None - # byhour - if byhour is None: - if freq < HOURLY: - self._byhour = (dtstart.hour,) - else: - self._byhour = None - elif type(byhour) is int: - self._byhour = (byhour,) - else: - self._byhour = tuple(byhour) - # byminute - if byminute is None: - if freq < MINUTELY: - self._byminute = (dtstart.minute,) - else: - self._byminute = None - elif type(byminute) is int: - self._byminute = (byminute,) - else: - self._byminute = tuple(byminute) - # bysecond - if bysecond is None: - if freq < SECONDLY: - self._bysecond = (dtstart.second,) - else: - self._bysecond = None - elif type(bysecond) is int: - self._bysecond = (bysecond,) - else: - self._bysecond = tuple(bysecond) - - if self._freq >= HOURLY: - self._timeset = None - else: - self._timeset = [] - for hour in self._byhour: - for minute in self._byminute: - for second in self._bysecond: - self._timeset.append( - datetime.time(hour, minute, second, - tzinfo=self._tzinfo)) - self._timeset.sort() - self._timeset = tuple(self._timeset) - - def _iter(self): - year, month, day, hour, minute, second, weekday, yearday, _ = \ - self._dtstart.timetuple() - - # Some local variables to speed things up a bit - freq = self._freq - interval = self._interval - wkst = self._wkst - until = self._until - bymonth = self._bymonth - byweekno = self._byweekno - byyearday = self._byyearday - byweekday = self._byweekday - byeaster = self._byeaster - bymonthday = self._bymonthday - bynmonthday = self._bynmonthday - bysetpos = self._bysetpos - byhour = self._byhour - byminute = self._byminute - bysecond = self._bysecond - - ii = _iterinfo(self) - ii.rebuild(year, month) - - getdayset = {YEARLY:ii.ydayset, - MONTHLY:ii.mdayset, - WEEKLY:ii.wdayset, - DAILY:ii.ddayset, - HOURLY:ii.ddayset, - MINUTELY:ii.ddayset, - SECONDLY:ii.ddayset}[freq] - - if freq < HOURLY: - timeset = self._timeset - else: - gettimeset = {HOURLY:ii.htimeset, - MINUTELY:ii.mtimeset, - SECONDLY:ii.stimeset}[freq] - if ((freq >= HOURLY and - self._byhour and hour not in self._byhour) or - (freq >= MINUTELY and - self._byminute and minute not in self._byminute) or - (freq >= SECONDLY and - self._bysecond and minute not in self._bysecond)): - timeset = () - else: - timeset = gettimeset(hour, minute, second) - - total = 0 - count = self._count - while True: - # Get dayset with the right frequency - dayset, start, end = getdayset(year, month, day) - - # Do the "hard" work ;-) - filtered = False - for i in dayset[start:end]: - if ((bymonth and ii.mmask[i] not in bymonth) or - (byweekno and not ii.wnomask[i]) or - (byweekday and ii.wdaymask[i] not in byweekday) or - (ii.nwdaymask and not ii.nwdaymask[i]) or - (byeaster and not ii.eastermask[i]) or - ((bymonthday or bynmonthday) and - ii.mdaymask[i] not in bymonthday and - ii.nmdaymask[i] not in bynmonthday) or - (byyearday and - ((i < ii.yearlen and i+1 not in byyearday - and -ii.yearlen+i not in byyearday) or - (i >= ii.yearlen and i+1-ii.yearlen not in byyearday - and -ii.nextyearlen+i-ii.yearlen - not in byyearday)))): - dayset[i] = None - filtered = True - - # Output results - if bysetpos and timeset: - poslist = [] - for pos in bysetpos: - if pos < 0: - daypos, timepos = divmod(pos, len(timeset)) - else: - daypos, timepos = divmod(pos-1, len(timeset)) - try: - i = [x for x in dayset[start:end] - if x is not None][daypos] - time = timeset[timepos] - except IndexError: - pass - else: - date = datetime.date.fromordinal(ii.yearordinal+i) - res = datetime.datetime.combine(date, time) - if res not in poslist: - poslist.append(res) - poslist.sort() - for res in poslist: - if until and res > until: - self._len = total - return - elif res >= self._dtstart: - total += 1 - yield res - if count: - count -= 1 - if not count: - self._len = total - return - else: - for i in dayset[start:end]: - if i is not None: - date = datetime.date.fromordinal(ii.yearordinal+i) - for time in timeset: - res = datetime.datetime.combine(date, time) - if until and res > until: - self._len = total - return - elif res >= self._dtstart: - total += 1 - yield res - if count: - count -= 1 - if not count: - self._len = total - return - - # Handle frequency and interval - fixday = False - if freq == YEARLY: - year += interval - if year > datetime.MAXYEAR: - self._len = total - return - ii.rebuild(year, month) - elif freq == MONTHLY: - month += interval - if month > 12: - div, mod = divmod(month, 12) - month = mod - year += div - if month == 0: - month = 12 - year -= 1 - if year > datetime.MAXYEAR: - self._len = total - return - ii.rebuild(year, month) - elif freq == WEEKLY: - if wkst > weekday: - day += -(weekday+1+(6-wkst))+self._interval*7 - else: - day += -(weekday-wkst)+self._interval*7 - weekday = wkst - fixday = True - elif freq == DAILY: - day += interval - fixday = True - elif freq == HOURLY: - if filtered: - # Jump to one iteration before next day - hour += ((23-hour)//interval)*interval - while True: - hour += interval - div, mod = divmod(hour, 24) - if div: - hour = mod - day += div - fixday = True - if not byhour or hour in byhour: - break - timeset = gettimeset(hour, minute, second) - elif freq == MINUTELY: - if filtered: - # Jump to one iteration before next day - minute += ((1439-(hour*60+minute))//interval)*interval - while True: - minute += interval - div, mod = divmod(minute, 60) - if div: - minute = mod - hour += div - div, mod = divmod(hour, 24) - if div: - hour = mod - day += div - fixday = True - filtered = False - if ((not byhour or hour in byhour) and - (not byminute or minute in byminute)): - break - timeset = gettimeset(hour, minute, second) - elif freq == SECONDLY: - if filtered: - # Jump to one iteration before next day - second += (((86399-(hour*3600+minute*60+second)) - //interval)*interval) - while True: - second += self._interval - div, mod = divmod(second, 60) - if div: - second = mod - minute += div - div, mod = divmod(minute, 60) - if div: - minute = mod - hour += div - div, mod = divmod(hour, 24) - if div: - hour = mod - day += div - fixday = True - if ((not byhour or hour in byhour) and - (not byminute or minute in byminute) and - (not bysecond or second in bysecond)): - break - timeset = gettimeset(hour, minute, second) - - if fixday and day > 28: - daysinmonth = calendar.monthrange(year, month)[1] - if day > daysinmonth: - while day > daysinmonth: - day -= daysinmonth - month += 1 - if month == 13: - month = 1 - year += 1 - if year > datetime.MAXYEAR: - self._len = total - return - daysinmonth = calendar.monthrange(year, month)[1] - ii.rebuild(year, month) - -class _iterinfo(object): - __slots__ = ["rrule", "lastyear", "lastmonth", - "yearlen", "nextyearlen", "yearordinal", "yearweekday", - "mmask", "mrange", "mdaymask", "nmdaymask", - "wdaymask", "wnomask", "nwdaymask", "eastermask"] - - def __init__(self, rrule): - for attr in self.__slots__: - setattr(self, attr, None) - self.rrule = rrule - - def rebuild(self, year, month): - # Every mask is 7 days longer to handle cross-year weekly periods. - rr = self.rrule - if year != self.lastyear: - self.yearlen = 365+calendar.isleap(year) - self.nextyearlen = 365+calendar.isleap(year+1) - firstyday = datetime.date(year, 1, 1) - self.yearordinal = firstyday.toordinal() - self.yearweekday = firstyday.weekday() - - wday = datetime.date(year, 1, 1).weekday() - if self.yearlen == 365: - self.mmask = M365MASK - self.mdaymask = MDAY365MASK - self.nmdaymask = NMDAY365MASK - self.wdaymask = WDAYMASK[wday:] - self.mrange = M365RANGE - else: - self.mmask = M366MASK - self.mdaymask = MDAY366MASK - self.nmdaymask = NMDAY366MASK - self.wdaymask = WDAYMASK[wday:] - self.mrange = M366RANGE - - if not rr._byweekno: - self.wnomask = None - else: - self.wnomask = [0]*(self.yearlen+7) - #no1wkst = firstwkst = self.wdaymask.index(rr._wkst) - no1wkst = firstwkst = (7-self.yearweekday+rr._wkst)%7 - if no1wkst >= 4: - no1wkst = 0 - # Number of days in the year, plus the days we got - # from last year. - wyearlen = self.yearlen+(self.yearweekday-rr._wkst)%7 - else: - # Number of days in the year, minus the days we - # left in last year. - wyearlen = self.yearlen-no1wkst - div, mod = divmod(wyearlen, 7) - numweeks = div+mod//4 - for n in rr._byweekno: - if n < 0: - n += numweeks+1 - if not (0 < n <= numweeks): - continue - if n > 1: - i = no1wkst+(n-1)*7 - if no1wkst != firstwkst: - i -= 7-firstwkst - else: - i = no1wkst - for j in range(7): - self.wnomask[i] = 1 - i += 1 - if self.wdaymask[i] == rr._wkst: - break - if 1 in rr._byweekno: - # Check week number 1 of next year as well - # TODO: Check -numweeks for next year. - i = no1wkst+numweeks*7 - if no1wkst != firstwkst: - i -= 7-firstwkst - if i < self.yearlen: - # If week starts in next year, we - # don't care about it. - for j in range(7): - self.wnomask[i] = 1 - i += 1 - if self.wdaymask[i] == rr._wkst: - break - if no1wkst: - # Check last week number of last year as - # well. If no1wkst is 0, either the year - # started on week start, or week number 1 - # got days from last year, so there are no - # days from last year's last week number in - # this year. - if -1 not in rr._byweekno: - lyearweekday = datetime.date(year-1,1,1).weekday() - lno1wkst = (7-lyearweekday+rr._wkst)%7 - lyearlen = 365+calendar.isleap(year-1) - if lno1wkst >= 4: - lno1wkst = 0 - lnumweeks = 52+(lyearlen+ - (lyearweekday-rr._wkst)%7)%7//4 - else: - lnumweeks = 52+(self.yearlen-no1wkst)%7//4 - else: - lnumweeks = -1 - if lnumweeks in rr._byweekno: - for i in range(no1wkst): - self.wnomask[i] = 1 - - if (rr._bynweekday and - (month != self.lastmonth or year != self.lastyear)): - ranges = [] - if rr._freq == YEARLY: - if rr._bymonth: - for month in rr._bymonth: - ranges.append(self.mrange[month-1:month+1]) - else: - ranges = [(0, self.yearlen)] - elif rr._freq == MONTHLY: - ranges = [self.mrange[month-1:month+1]] - if ranges: - # Weekly frequency won't get here, so we may not - # care about cross-year weekly periods. - self.nwdaymask = [0]*self.yearlen - for first, last in ranges: - last -= 1 - for wday, n in rr._bynweekday: - if n < 0: - i = last+(n+1)*7 - i -= (self.wdaymask[i]-wday)%7 - else: - i = first+(n-1)*7 - i += (7-self.wdaymask[i]+wday)%7 - if first <= i <= last: - self.nwdaymask[i] = 1 - - if rr._byeaster: - self.eastermask = [0]*(self.yearlen+7) - eyday = easter.easter(year).toordinal()-self.yearordinal - for offset in rr._byeaster: - self.eastermask[eyday+offset] = 1 - - self.lastyear = year - self.lastmonth = month - - def ydayset(self, year, month, day): - return range(self.yearlen), 0, self.yearlen - - def mdayset(self, year, month, day): - set = [None]*self.yearlen - start, end = self.mrange[month-1:month+1] - for i in range(start, end): - set[i] = i - return set, start, end - - def wdayset(self, year, month, day): - # We need to handle cross-year weeks here. - set = [None]*(self.yearlen+7) - i = datetime.date(year, month, day).toordinal()-self.yearordinal - start = i - for j in range(7): - set[i] = i - i += 1 - #if (not (0 <= i < self.yearlen) or - # self.wdaymask[i] == self.rrule._wkst): - # This will cross the year boundary, if necessary. - if self.wdaymask[i] == self.rrule._wkst: - break - return set, start, i - - def ddayset(self, year, month, day): - set = [None]*self.yearlen - i = datetime.date(year, month, day).toordinal()-self.yearordinal - set[i] = i - return set, i, i+1 - - def htimeset(self, hour, minute, second): - set = [] - rr = self.rrule - for minute in rr._byminute: - for second in rr._bysecond: - set.append(datetime.time(hour, minute, second, - tzinfo=rr._tzinfo)) - set.sort() - return set - - def mtimeset(self, hour, minute, second): - set = [] - rr = self.rrule - for second in rr._bysecond: - set.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) - set.sort() - return set - - def stimeset(self, hour, minute, second): - return (datetime.time(hour, minute, second, - tzinfo=self.rrule._tzinfo),) - - -class rruleset(rrulebase): - - class _genitem: - def __init__(self, genlist, gen): - try: - self.dt = gen() - genlist.append(self) - except StopIteration: - pass - self.genlist = genlist - self.gen = gen - - def next(self): - try: - self.dt = self.gen() - except StopIteration: - self.genlist.remove(self) - - def __cmp__(self, other): - return cmp(self.dt, other.dt) - - def __init__(self, cache=False): - rrulebase.__init__(self, cache) - self._rrule = [] - self._rdate = [] - self._exrule = [] - self._exdate = [] - - def rrule(self, rrule): - self._rrule.append(rrule) - - def rdate(self, rdate): - self._rdate.append(rdate) - - def exrule(self, exrule): - self._exrule.append(exrule) - - def exdate(self, exdate): - self._exdate.append(exdate) - - def _iter(self): - rlist = [] - self._rdate.sort() - self._genitem(rlist, iter(self._rdate).next) - for gen in [iter(x).next for x in self._rrule]: - self._genitem(rlist, gen) - rlist.sort() - exlist = [] - self._exdate.sort() - self._genitem(exlist, iter(self._exdate).next) - for gen in [iter(x).next for x in self._exrule]: - self._genitem(exlist, gen) - exlist.sort() - lastdt = None - total = 0 - while rlist: - ritem = rlist[0] - if not lastdt or lastdt != ritem.dt: - while exlist and exlist[0] < ritem: - exlist[0].next() - exlist.sort() - if not exlist or ritem != exlist[0]: - total += 1 - yield ritem.dt - lastdt = ritem.dt - ritem.next() - rlist.sort() - self._len = total - -class _rrulestr: - - _freq_map = {"YEARLY": YEARLY, - "MONTHLY": MONTHLY, - "WEEKLY": WEEKLY, - "DAILY": DAILY, - "HOURLY": HOURLY, - "MINUTELY": MINUTELY, - "SECONDLY": SECONDLY} - - _weekday_map = {"MO":0,"TU":1,"WE":2,"TH":3,"FR":4,"SA":5,"SU":6} - - def _handle_int(self, rrkwargs, name, value, **kwargs): - rrkwargs[name.lower()] = int(value) - - def _handle_int_list(self, rrkwargs, name, value, **kwargs): - rrkwargs[name.lower()] = [int(x) for x in value.split(',')] - - _handle_INTERVAL = _handle_int - _handle_COUNT = _handle_int - _handle_BYSETPOS = _handle_int_list - _handle_BYMONTH = _handle_int_list - _handle_BYMONTHDAY = _handle_int_list - _handle_BYYEARDAY = _handle_int_list - _handle_BYEASTER = _handle_int_list - _handle_BYWEEKNO = _handle_int_list - _handle_BYHOUR = _handle_int_list - _handle_BYMINUTE = _handle_int_list - _handle_BYSECOND = _handle_int_list - - def _handle_FREQ(self, rrkwargs, name, value, **kwargs): - rrkwargs["freq"] = self._freq_map[value] - - def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): - global parser - if not parser: - from dateutil import parser - try: - rrkwargs["until"] = parser.parse(value, - ignoretz=kwargs.get("ignoretz"), - tzinfos=kwargs.get("tzinfos")) - except ValueError: - raise ValueError, "invalid until date" - - def _handle_WKST(self, rrkwargs, name, value, **kwargs): - rrkwargs["wkst"] = self._weekday_map[value] - - def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwarsg): - l = [] - for wday in value.split(','): - for i in range(len(wday)): - if wday[i] not in '+-0123456789': - break - n = wday[:i] or None - w = wday[i:] - if n: n = int(n) - l.append(weekdays[self._weekday_map[w]](n)) - rrkwargs["byweekday"] = l - - _handle_BYDAY = _handle_BYWEEKDAY - - def _parse_rfc_rrule(self, line, - dtstart=None, - cache=False, - ignoretz=False, - tzinfos=None): - if line.find(':') != -1: - name, value = line.split(':') - if name != "RRULE": - raise ValueError, "unknown parameter name" - else: - value = line - rrkwargs = {} - for pair in value.split(';'): - name, value = pair.split('=') - name = name.upper() - value = value.upper() - try: - getattr(self, "_handle_"+name)(rrkwargs, name, value, - ignoretz=ignoretz, - tzinfos=tzinfos) - except AttributeError: - raise ValueError, "unknown parameter '%s'" % name - except (KeyError, ValueError): - raise ValueError, "invalid '%s': %s" % (name, value) - return rrule(dtstart=dtstart, cache=cache, **rrkwargs) - - def _parse_rfc(self, s, - dtstart=None, - cache=False, - unfold=False, - forceset=False, - compatible=False, - ignoretz=False, - tzinfos=None): - global parser - if compatible: - forceset = True - unfold = True - s = s.upper() - if not s.strip(): - raise ValueError, "empty string" - if unfold: - lines = s.splitlines() - 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 - else: - lines = s.split() - if (not forceset and len(lines) == 1 and - (s.find(':') == -1 or s.startswith('RRULE:'))): - return self._parse_rfc_rrule(lines[0], cache=cache, - dtstart=dtstart, ignoretz=ignoretz, - tzinfos=tzinfos) - else: - rrulevals = [] - rdatevals = [] - exrulevals = [] - exdatevals = [] - for line in lines: - if not line: - continue - if line.find(':') == -1: - name = "RRULE" - value = line - else: - name, value = line.split(':', 1) - parms = name.split(';') - if not parms: - raise ValueError, "empty property name" - name = parms[0] - parms = parms[1:] - if name == "RRULE": - for parm in parms: - raise ValueError, "unsupported RRULE parm: "+parm - rrulevals.append(value) - elif name == "RDATE": - for parm in parms: - if parm != "VALUE=DATE-TIME": - raise ValueError, "unsupported RDATE parm: "+parm - rdatevals.append(value) - elif name == "EXRULE": - for parm in parms: - raise ValueError, "unsupported EXRULE parm: "+parm - exrulevals.append(value) - elif name == "EXDATE": - for parm in parms: - if parm != "VALUE=DATE-TIME": - raise ValueError, "unsupported RDATE parm: "+parm - exdatevals.append(value) - elif name == "DTSTART": - for parm in parms: - raise ValueError, "unsupported DTSTART parm: "+parm - if not parser: - from dateutil import parser - dtstart = parser.parse(value, ignoretz=ignoretz, - tzinfos=tzinfos) - else: - raise ValueError, "unsupported property: "+name - if (forceset or len(rrulevals) > 1 or - rdatevals or exrulevals or exdatevals): - if not parser and (rdatevals or exdatevals): - from dateutil import parser - set = rruleset(cache=cache) - for value in rrulevals: - set.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, - ignoretz=ignoretz, - tzinfos=tzinfos)) - for value in rdatevals: - for datestr in value.split(','): - set.rdate(parser.parse(datestr, - ignoretz=ignoretz, - tzinfos=tzinfos)) - for value in exrulevals: - set.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, - ignoretz=ignoretz, - tzinfos=tzinfos)) - for value in exdatevals: - for datestr in value.split(','): - set.exdate(parser.parse(datestr, - ignoretz=ignoretz, - tzinfos=tzinfos)) - if compatible and dtstart: - set.rdate(dtstart) - return set - else: - return self._parse_rfc_rrule(rrulevals[0], - dtstart=dtstart, - cache=cache, - ignoretz=ignoretz, - tzinfos=tzinfos) - - def __call__(self, s, **kwargs): - return self._parse_rfc(s, **kwargs) - -rrulestr = _rrulestr() - -# vim:ts=4:sw=4:et diff --git a/plugins/Time/local/dateutil/tz.py b/plugins/Time/local/dateutil/tz.py deleted file mode 100644 index 0e28d6b33..000000000 --- a/plugins/Time/local/dateutil/tz.py +++ /dev/null @@ -1,951 +0,0 @@ -""" -Copyright (c) 2003-2007 Gustavo Niemeyer - -This module offers extensions to the standard python 2.3+ -datetime module. -""" -__author__ = "Gustavo Niemeyer " -__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 "" % `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 diff --git a/plugins/Time/local/dateutil/tzwin.py b/plugins/Time/local/dateutil/tzwin.py deleted file mode 100644 index 073e0ff68..000000000 --- a/plugins/Time/local/dateutil/tzwin.py +++ /dev/null @@ -1,180 +0,0 @@ -# This code was originally contributed by Jeffrey Harris. -import datetime -import struct -import _winreg - -__author__ = "Jeffrey Harris & Gustavo Niemeyer " - -__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 diff --git a/plugins/Time/local/dateutil/zoneinfo/__init__.py b/plugins/Time/local/dateutil/zoneinfo/__init__.py deleted file mode 100644 index 9bed6264c..000000000 --- a/plugins/Time/local/dateutil/zoneinfo/__init__.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Copyright (c) 2003-2005 Gustavo Niemeyer - -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 " -__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) diff --git a/plugins/Time/local/dateutil/zoneinfo/zoneinfo-2008e.tar.gz b/plugins/Time/local/dateutil/zoneinfo/zoneinfo-2008e.tar.gz deleted file mode 100644 index 65e4175f4..000000000 Binary files a/plugins/Time/local/dateutil/zoneinfo/zoneinfo-2008e.tar.gz and /dev/null differ diff --git a/plugins/Time/plugin.py b/plugins/Time/plugin.py index c6ea26f7e..c796ecb4d 100644 --- a/plugins/Time/plugin.py +++ b/plugins/Time/plugin.py @@ -31,6 +31,8 @@ import time TIME = time # For later use. from datetime import datetime +from dateutil import parser + import supybot.conf as conf import supybot.utils as utils from supybot.commands import * @@ -38,8 +40,6 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Time') -from .local.dateutil import parser - def parse(s): todo = [] s = s.replace('noon', '12:00') diff --git a/plugins/Todo/plugin.py b/plugins/Todo/plugin.py index 20372303b..9e1443ebb 100644 --- a/plugins/Todo/plugin.py +++ b/plugins/Todo/plugin.py @@ -238,9 +238,10 @@ class Todo(callbacks.Plugin): criteria = [] for (option, arg) in optlist: if option == 'regexp': - criteria.append(lambda x: commands.regexp_wrapper(x, reobj=arg, - timeout=0.1, plugin_name = self.name(), fcn_name='search')) - criteria.append(arg.search) + criteria.append(lambda s: + regexp_wrapper(s, reobj=arg, timeout=0.1, + plugin_name=self.name(), + fcn_name='search')) for glob in globs: glob = utils.python.glob2re(glob) criteria.append(re.compile(glob).search) diff --git a/plugins/Topic/plugin.py b/plugins/Topic/plugin.py index a11577792..d83593d92 100644 --- a/plugins/Topic/plugin.py +++ b/plugins/Topic/plugin.py @@ -94,7 +94,7 @@ def getTopicNumber(irc, msg, args, state): try: topics[n] except IndexError: - error(str(n)) + error(args[0]) del args[0] while n < 0: n += len(topics) @@ -415,9 +415,6 @@ class Topic(callbacks.Plugin): index into the topics. is only necessary if the message 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) irc.reply(topics[number]) get = wrap(get, ['inChannel', 'topicNumber']) diff --git a/plugins/Web/config.py b/plugins/Web/config.py index bc73d685b..2e1ef1847 100644 --- a/plugins/Web/config.py +++ b/plugins/Web/config.py @@ -68,4 +68,9 @@ conf.registerGlobalValue(Web.fetch, 'maximum', registry.NonNegativeInteger(0, _("""Determines the maximum number of 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: diff --git a/plugins/__init__.py b/plugins/__init__.py index 7e33cdb4c..cdda215dc 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -41,57 +41,8 @@ import os.path import threading import collections -import supybot.log as log -import supybot.dbi as dbi -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 +from .. import callbacks, conf, dbi, ircdb, ircutils, log, utils, world +from ..commands import * try: import sqlite3 @@ -451,9 +402,8 @@ class ChannelIdDatabasePlugin(callbacks.Plugin): if opt == 'by': predicates.append(lambda r, arg=arg: r.by == arg.id) elif opt == 'regexp': - predicates.append(lambda x: commands.regexp_wrapper(x.text, reobj=arg, - timeout=0.1, plugin_name = self.name(), fcn_name='search')) - #predicates.append(lambda r, arg=arg: arg.search(r.text)) + predicates.append(lambda r: regexp_wrapper(r.text, reobj=arg, + timeout=0.1, plugin_name=self.name(), fcn_name='search')) if glob: def globP(r, glob=glob.lower()): return fnmatch.fnmatch(r.text.lower(), glob) diff --git a/sandbox/Debug/plugin.py b/sandbox/Debug/plugin.py index 8a6ae15d7..672346e44 100644 --- a/sandbox/Debug/plugin.py +++ b/sandbox/Debug/plugin.py @@ -211,14 +211,6 @@ class Debug(callbacks.Privmsg): irc.reply(repr(os.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 diff --git a/scripts/supybot-botchk b/scripts/supybot-botchk index 430ac33b5..137bb4502 100644 --- a/scripts/supybot-botchk +++ b/scripts/supybot-botchk @@ -122,22 +122,14 @@ if __name__ == '__main__': debug('pidfile (%s) is not writable: %s' % (options.pidfile, e)) sys.exit(-1) debug('Bot not found, starting.') - home = os.environ['HOME'] - inst = subprocess.Popen('sh', close_fds=True, stderr=subprocess.STDOUT, - stdin=subprocess.PIPE, stdout=subprocess.PIPE) - for filename in ('.login', '.bash_profile', '.profile', '.bashrc'): - filename = os.path.join(home, filename) - 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()) + cmdline = [options.supybot, '--daemon', options.conffile] + inst = subprocess.Popen(cmdline, close_fds=True, + stderr=subprocess.STDOUT, + stdin=None, stdout=subprocess.PIPE) + debug('Output from supybot: %r' % inst.stdout.read()) 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) else: sys.exit(0) diff --git a/setup.py b/setup.py index 2e5d4a18f..908597d21 100644 --- a/setup.py +++ b/setup.py @@ -69,44 +69,24 @@ if sys.version_info < (2, 6, 0): sys.stderr.write(os.linesep) sys.exit(-1) - -import textwrap - -clean = False -while '--clean' in sys.argv: - clean = True - sys.argv.remove('--clean') - import glob import shutil import os +import textwrap -plugins = [s for s in os.listdir('plugins') if - os.path.exists(os.path.join('plugins', s, 'plugin.py'))] +try: + from distribute_setup import use_setuptools +except ImportError: + pass +else: + use_setuptools(version='0.6c9') + +from setuptools import setup def normalizeWhitespace(s): 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: from distutils.command.build_py import build_py_2to3 class build_py(build_py_2to3): @@ -154,15 +134,9 @@ except ImportError: from distutils.command.build_py import build_py -if clean: - previousInstall = os.path.join(get_python_lib(), 'supybot') - if os.path.exists(previousInstall): - try: - print('Removing current installation.') - shutil.rmtree(previousInstall) - except Exception as e: - print('Couldn\'t remove former installation: %s' % e) - sys.exit(-1) + +plugins = [s for s in os.listdir('plugins') if + os.path.exists(os.path.join('plugins', s, 'plugin.py'))] packages = ['supybot', 'supybot.locales', @@ -173,24 +147,15 @@ packages = ['supybot', [ 'supybot.plugins.Dict.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', 'supybot.utils': 'src/utils', + 'supybot.locales': 'locales', 'supybot.plugins': 'plugins', 'supybot.drivers': 'src/drivers', - 'supybot.locales': 'locales', - 'supybot.plugins.Google.local': 'plugins/Google/local', 'supybot.plugins.Dict.local': 'plugins/Dict/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/')]} @@ -229,7 +194,8 @@ setup( 'Operating System :: OS Independent', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', ], cmdclass = {'build_py': build_py}, @@ -248,14 +214,12 @@ setup( 'scripts/supybot-plugin-doc', 'scripts/supybot-plugin-create', ], - data_files=[('share/man/man1', ['docs/man/supybot.1']), - ('share/man/man1', ['docs/man/supybot-test.1']), - ('share/man/man1', ['docs/man/supybot-botchk.1']), - ('share/man/man1', ['docs/man/supybot-wizard.1']), - ('share/man/man1', ['docs/man/supybot-adduser.1']), - ('share/man/man1', ['docs/man/supybot-plugin-doc.1']), - ('share/man/man1', ['docs/man/supybot-plugin-create.1']), - ] + + install_requires=[ + # Time plugin + 'python-dateutil >=2.0', + 'feedparser', + ], ) diff --git a/src/__init__.py b/src/__init__.py index 8f811c7da..7e0950213 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -32,12 +32,7 @@ import sys import os.path import dynamicScope -try: - 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 +from . import utils (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__)['format'] = utils.str.format diff --git a/src/callbacks.py b/src/callbacks.py index a178e985d..502039c96 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -33,8 +33,6 @@ This module contains the basic callbacks for handling PRIVMSGs. """ -import supybot - import re import sys import copy @@ -53,17 +51,10 @@ if sys.version_info[0] < 3: else: from cStringIO import StringIO -import supybot.log as log -import supybot.conf as conf -import supybot.utils as utils -import supybot.world as world -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 +from . import (conf, ircdb, irclib, ircmsgs, ircutils, log, registry, utils, + world) +from .utils.iter import any, all +from .i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization() def _addressed(nick, msg, prefixChars=None, nicks=None, @@ -237,7 +228,10 @@ def getHelp(method, name=None, doc=None): if name is None: name = method.__name__ if doc is None: - doclines = method.__doc__.splitlines() + if method.__doc__ is None: + doclines = ['This command has no help. Complain to the author.'] + else: + doclines = method.__doc__.splitlines() else: doclines = doc.splitlines() s = '%s %s' % (name, doclines.pop(0)) @@ -644,7 +638,7 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): if maxNesting and self.nested > maxNesting: log.warning('%s attempted more than %s levels of nesting.', 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.')) # The deepcopy here is necessary for Scheduler; it re-runs already # tokenized commands. There's a possibility a simple copy[:] would @@ -1052,7 +1046,7 @@ class CommandProcess(world.SupyProcess): self.__parent = super(CommandProcess, self) self.__parent.__init__(target=target, name=procName, args=args, kwargs=kwargs) - + def run(self): self.__parent.run() diff --git a/src/cdb.py b/src/cdb.py index 84c926fda..150860239 100644 --- a/src/cdb.py +++ b/src/cdb.py @@ -40,7 +40,7 @@ import struct import os.path import cPickle as pickle -import supybot.utils as utils +from . import utils def hash(s): """DJB's hash function for CDB.""" @@ -184,6 +184,8 @@ class Reader(utils.IterableMap): """Class for reading from a CDB database.""" def __init__(self, filename): self.filename = filename + import os + print(repr(os.getcwd())) self.fd = open(filename, 'rb') self.loop = 0 self.khash = 0 diff --git a/src/commands.py b/src/commands.py index ac0e28622..42ee4f94a 100644 --- a/src/commands.py +++ b/src/commands.py @@ -33,6 +33,7 @@ Includes wrappers for commands. """ import time +import Queue import types import getopt import inspect @@ -45,18 +46,10 @@ try: except ImportError: # Windows! resource = None -import supybot.log as log -import supybot.conf as conf -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 +from . import callbacks, conf, ircdb, ircmsgs, ircutils, log, utils, world +from .i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization() - ### # Non-arg wrappers -- these just change the behavior of a command without # changing the arguments given to it. @@ -251,7 +244,10 @@ def _int(s): return int(s, base) except ValueError: if base == 10: - return int(float(s)) + try: + return int(float(s)) + except OverflowError: + raise ValueError('I don\'t understand numbers that large.') else: raise @@ -1118,7 +1114,7 @@ __all__ = [ # Decorators. 'urlSnarfer', 'thread', # Functions. - 'wrap', + 'wrap', 'process', 'regexp_wrapper', # Stuff for testing. 'Spec', ] diff --git a/src/conf.py b/src/conf.py index 746fb445d..80665199a 100644 --- a/src/conf.py +++ b/src/conf.py @@ -33,17 +33,11 @@ import sys import time import socket -import supybot.utils as utils -import supybot.registry as registry -import supybot.ircutils as ircutils -from supybot.i18n import PluginInternationalization +from . import ircutils, registry, utils +from .version import version +from .i18n import PluginInternationalization _ = PluginInternationalization() -### -# version: This should be pretty obvious. -### -from supybot.version import version - ### # *** The following variables are affected by command-line options. They are # 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, but this is the best we can do right now.""") % name, private=True)) registryServers = registerGlobalValue(network, 'servers', Servers([], - _("""Determines what servers the bot will connect to for %s. Each will - be tried in order, wrapping back to the first when the cycle is - completed.""") % name)) + _("""Space-separated list of servers the bot will connect to for %s. + Each will be tried in order, wrapping back to the first when the cycle + is completed.""") % name)) registerGlobalValue(network, 'channels', SpaceSeparatedSetOfChannels([], - _("""Determines what channels the bot will join only on %s.""") % - name, private=True)) + _("""Space-separated list of channels the bot will join only on %s.""") + % name, private=True)) registerGlobalValue(network, 'ssl', registry.Boolean(ssl, _("""Determines whether the bot will attempt to connect with SSL sockets to %s.""") % name)) @@ -940,7 +934,7 @@ registerChannelValue(supybot.databases.plugins.channelSpecific.link, 'allow', class CDB(registry.Boolean): def connect(self, filename): - import supybot.cdb as cdb + from . import cdb basename = os.path.basename(filename) journalName = supybot.directories.data.tmp.dirize(basename+'.journal') return cdb.open_db(filename, 'c', diff --git a/src/dbi.py b/src/dbi.py index 116344c8c..dee742297 100644 --- a/src/dbi.py +++ b/src/dbi.py @@ -35,9 +35,8 @@ import os import csv import math -import supybot.cdb as cdb -import supybot.utils as utils -from supybot.utils.iter import ilen +from . import cdb, utils +from .utils.iter import ilen class Error(Exception): """General error for this module.""" diff --git a/src/drivers/Socket.py b/src/drivers/Socket.py index 08619d15a..dc3cc2a32 100644 --- a/src/drivers/Socket.py +++ b/src/drivers/Socket.py @@ -1,6 +1,6 @@ ## # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2010, James McCoy +# Copyright (c) 2010, 2013, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -39,13 +39,9 @@ import time import errno import select import socket -import supybot.log as log -import supybot.conf as conf -import supybot.utils as utils -import supybot.world as world -import supybot.drivers as drivers -import supybot.schedule as schedule -from itertools import imap + +from .. import (conf, drivers, log, schedule, utils, world) +from ..utils.iter import imap try: from charade.universaldetector import UniversalDetector charadeLoaded = True @@ -54,6 +50,7 @@ except: 'cannot guess character encoding if' 'using Python3') charadeLoaded = False + try: import ssl SSLError = ssl.SSLError @@ -248,7 +245,7 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): def connect(self, **kwargs): self.reconnect(reset=False, **kwargs) - def reconnect(self, reset=True): + def reconnect(self, wait=False, reset=True): self._attempt += 1 self.nextReconnectTime = None if self.connected: @@ -266,6 +263,9 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): self.irc.reset() else: drivers.log.debug('Not resetting %s.', self.irc) + if wait: + self.scheduleReconnect() + return server = self._getNextServer() socks_proxy = getattr(conf.supybot.networks, self.irc.network) \ .socksproxy() diff --git a/src/drivers/Twisted.py b/src/drivers/Twisted.py index 6429022c4..3acabc07a 100644 --- a/src/drivers/Twisted.py +++ b/src/drivers/Twisted.py @@ -28,10 +28,7 @@ # POSSIBILITY OF SUCH DAMAGE. ### -import supybot.log as log -import supybot.conf as conf -import supybot.drivers as drivers -import supybot.ircmsgs as ircmsgs +from .. import conf, drivers, ircmsgs, log from twisted.names import client from twisted.internet import reactor, error diff --git a/src/drivers/__init__.py b/src/drivers/__init__.py index 302222ad7..b89d63fa4 100644 --- a/src/drivers/__init__.py +++ b/src/drivers/__init__.py @@ -36,10 +36,7 @@ import sys import time import socket -import supybot.conf as conf -import supybot.utils as utils -import supybot.log as supylog -import supybot.ircmsgs as ircmsgs +from .. import conf, ircmsgs, log as supylog, utils _drivers = {} _deadDrivers = [] diff --git a/src/ircdb.py b/src/ircdb.py index bb3c297af..0fcd7be83 100644 --- a/src/ircdb.py +++ b/src/ircdb.py @@ -29,20 +29,12 @@ # POSSIBILITY OF SUCH DAMAGE. ### -from __future__ import division - import os import time import operator -import supybot.log as log -import supybot.conf as conf -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 +from . import conf, ircutils, log, registry, unpreserve, utils, world +from .utils.iter import imap, ilen, ifilter def isCapability(capability): return len(capability.split(None, 1)) == 1 diff --git a/src/irclib.py b/src/irclib.py index fcecbf6e5..eed0fbbff 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -33,17 +33,10 @@ import time import random import base64 -import supybot.log as log -import supybot.conf as conf -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 - -from utils.str import rsplit -from utils.iter import chain, cycle -from utils.structures import queue, smallqueue, RingBuffer +from . import conf, ircdb, ircmsgs, ircutils, log, utils, world +from .utils.str import rsplit +from .utils.iter import imap, chain, cycle +from .utils.structures import queue, smallqueue, RingBuffer ### # The base class for a callback to be registered with an Irc object. Shows diff --git a/src/ircmsgs.py b/src/ircmsgs.py index 249ce6433..700b81576 100644 --- a/src/ircmsgs.py +++ b/src/ircmsgs.py @@ -40,10 +40,8 @@ import sys import time import functools -import supybot.conf as conf -import supybot.utils as utils -from supybot.utils.iter import all -import supybot.ircutils as ircutils +from . import conf, ircutils, utils +from .utils.iter import all ### # IrcMsg class -- used for representing IRC messages acquired from a network. diff --git a/src/ircutils.py b/src/ircutils.py index d9b916819..4e36af5c5 100644 --- a/src/ircutils.py +++ b/src/ircutils.py @@ -46,7 +46,7 @@ import textwrap import functools from cStringIO import StringIO as sio -import supybot.utils as utils +from . import utils from itertools import imap def debug(s, *args): diff --git a/src/log.py b/src/log.py index b8c335dc8..59203f0ce 100644 --- a/src/log.py +++ b/src/log.py @@ -37,12 +37,7 @@ import operator import textwrap import traceback -import supybot.ansi as ansi -import supybot.conf as conf -import supybot.utils as utils -import supybot.registry as registry - -import supybot.ircutils as ircutils +from . import ansi, conf, ircutils, registry, utils deadlyExceptions = [KeyboardInterrupt, SystemExit] diff --git a/src/plugin.py b/src/plugin.py index 36aa03d42..324a38792 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -32,11 +32,9 @@ import sys import imp import os.path import linecache +import re -import supybot.log as log -import supybot.conf as conf -import supybot.registry as registry -import supybot.callbacks as callbacks +from . import callbacks, conf, log, registry installDir = os.path.dirname(sys.modules[__name__].__file__) _pluginsDir = os.path.join(installDir, 'plugins') @@ -55,6 +53,13 @@ def loadPluginModule(name, ignoreDeprecation=False): except EnvironmentError: # OSError, IOError superclass. log.warning('Invalid plugin directory: %s; removing.', 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) try: module = imp.load_module(name, *moduleInfo) diff --git a/src/questions.py b/src/questions.py index 42ddbcd92..998a9b3e0 100644 --- a/src/questions.py +++ b/src/questions.py @@ -35,8 +35,7 @@ import sys import textwrap from getpass import getpass as getPass -import supybot.ansi as ansi -import supybot.utils as utils +from . import ansi, utils from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization() diff --git a/src/registry.py b/src/registry.py index 87fb1d3d2..8519a6f66 100644 --- a/src/registry.py +++ b/src/registry.py @@ -37,9 +37,7 @@ import codecs import string import textwrap -import supybot.utils as utils -import supybot.i18n as i18n - +from . import utils, i18n _ = i18n.PluginInternationalization() def error(s): diff --git a/src/schedule.py b/src/schedule.py index 620ee0b5a..be0fd05fd 100644 --- a/src/schedule.py +++ b/src/schedule.py @@ -39,9 +39,7 @@ import heapq import functools from threading import Lock -import supybot.log as log -import supybot.world as world -import supybot.drivers as drivers +from . import drivers, log, world class mytuple(tuple): def __cmp__(self, other): diff --git a/src/test.py b/src/test.py index e97b81e96..57f7ed25b 100644 --- a/src/test.py +++ b/src/test.py @@ -39,20 +39,8 @@ import httplib import unittest import threading -import supybot.log as log -import supybot.i18n as i18n -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 +from . import (callbacks, conf, drivers, httpserver, i18n, ircdb, irclib, + ircmsgs, ircutils, log, plugin, registry, utils, world) i18n.import_conf() network = True diff --git a/src/utils/__init__.py b/src/utils/__init__.py index 77f600e84..4e26f8d60 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -58,16 +58,7 @@ def force(x): # These imports need to happen below the block above, so things get put into # __builtins__ appropriately. -from gen import * -import net -import seq -import str -import web -import file -import iter -import crypt -import error -import python -import transaction +from .gen import * +from . import crypt, error, file, iter, net, python, seq, str, transaction, web # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/src/utils/file.py b/src/utils/file.py index e276c3acb..eb3901bad 100644 --- a/src/utils/file.py +++ b/src/utils/file.py @@ -34,9 +34,8 @@ import random import shutil import os.path -from itertools import ifilter - -import crypt +from . import crypt +from .iter import ifilter def contents(filename): return open(filename).read() diff --git a/src/utils/gen.py b/src/utils/gen.py index 049d05e90..42df662ea 100644 --- a/src/utils/gen.py +++ b/src/utils/gen.py @@ -38,10 +38,10 @@ import traceback import collections from itertools import imap -from str import format -from file import mktemp - -import crypt +from . import crypt +from .str import format +from .file import mktemp +from .iter import imap from supybot.i18n import PluginInternationalization _ = PluginInternationalization() diff --git a/src/utils/iter.py b/src/utils/iter.py index 074dc8e25..f4039341e 100644 --- a/src/utils/iter.py +++ b/src/utils/iter.py @@ -34,6 +34,12 @@ import random 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): """Returns the length of an iterator.""" i = 0 diff --git a/src/utils/net.py b/src/utils/net.py index db1fcc558..f65ead27a 100644 --- a/src/utils/net.py +++ b/src/utils/net.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher -# Copyright (c) 2011, James McCoy +# Copyright (c) 2011, 2013, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -35,50 +35,10 @@ Simple utility modules. import re import socket -class EmailRe: - """Fake class used for backward compatibility.""" +from .web import _ipAddr, _domain - rfc822_specials = '()<>@,;:\\"[]' - def match(self, addr): - # 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() +emailRe = re.compile(r"^(\w&.+-]+!)*[\w&.+-]+@(%s|%s)$" % (_domain, _ipAddr), + re.I) def getAddressFromHostname(host, attempt=0): addrinfo = socket.getaddrinfo(host, None) diff --git a/src/utils/str.py b/src/utils/str.py index 8e8418f31..0e9c18947 100644 --- a/src/utils/str.py +++ b/src/utils/str.py @@ -38,8 +38,8 @@ import sys import string import textwrap -from iter import all, any -from structures import TwoWayDictionary +from .iter import all, any +from .structures import TwoWayDictionary from supybot.i18n import PluginInternationalization _ = PluginInternationalization() @@ -117,7 +117,10 @@ class MultipleRemover: _soundextrans = MultipleReplacer(dict(zip(string.ascii_uppercase, '01230120022455012623010202'))) 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 = ''.join([x for x in s if x in string.ascii_uppercase]) if not s: @@ -129,9 +132,11 @@ def soundex(s, length=4): for c in s: if c != L[-1]: 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) - return length and s[:length] or s.rstrip('0') + if length: + s = s.ljust(length, '0')[:length] + return s def dqrepr(s): """Returns a repr() of s guaranteed to be in double quotes.""" diff --git a/src/utils/transaction.py b/src/utils/transaction.py index 82fea76bd..4f88f39b7 100644 --- a/src/utils/transaction.py +++ b/src/utils/transaction.py @@ -35,9 +35,7 @@ import os import shutil import os.path -import error -import python -import file as File +from . import error, file as File, python # 'txn' is used as an abbreviation for 'transaction' in the following source. diff --git a/src/utils/web.py b/src/utils/web.py index f47f1becb..b3c389cfc 100644 --- a/src/utils/web.py +++ b/src/utils/web.py @@ -45,7 +45,7 @@ try: except AttributeError: pass -from str import normalizeWhitespace +from .str import normalizeWhitespace Request = urllib2.Request urlquote = urllib.quote @@ -62,10 +62,11 @@ _ipAddr = r'%s(?:\.%s){3}' % (_octet, _octet) # Base domain regex off RFC 1034 and 1738 _label = r'[0-9a-z][-0-9a-z]*[0-9a-z]?' _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, + _ipAddr) urlRe = re.compile(_urlRe, re.I) -_httpUrlRe = r'(https?://(?:\S+@)?(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (_domain, - _ipAddr) +_httpUrlRe = r'(https?://(?:\S+@)?(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % \ + (_domain, _ipAddr) httpUrlRe = re.compile(_httpUrlRe, re.I) REFUSED = 'Connection refused.' @@ -100,7 +101,7 @@ defaultHeaders = { # application-specific function. Feel free to use a callable here. proxy = None -def getUrlFd(url, headers=None, data=None): +def getUrlFd(url, headers=None, data=None, timeout=None): """getUrlFd(url, headers=None, data=None) 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 try: if not isinstance(url, urllib2.Request): - if '#' in url: - url = url[:url.index('#')] - if '@' in url: - scheme, url = url.split('://', 2) - auth, url = url.split('@') - url = scheme + '://' + url + (scheme, loc, path, query, frag) = urlparse.urlsplit(url) + (user, host) = urllib.splituser(loc) + url = urlparse.urlunsplit((scheme, host, path, query, '')) request = urllib2.Request(url, headers=headers, data=data) - if 'auth' in locals(): - 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() + if user: request.add_header('Authorization', - 'Basic ' + auth) + 'Basic %s' % base64.b64encode(user)) else: request = url request.add_data(data) httpProxy = force(proxy) if httpProxy: request.set_proxy(httpProxy, 'http') - fd = urllib2.urlopen(request) + fd = urllib2.urlopen(request, timeout=timeout) return fd except socket.timeout, e: raise Error, TIMED_OUT diff --git a/src/world.py b/src/world.py index bba1ff575..3a6d53bd8 100644 --- a/src/world.py +++ b/src/world.py @@ -42,11 +42,7 @@ import multiprocessing import re -import supybot.log as log -import supybot.conf as conf -import supybot.drivers as drivers -import supybot.ircutils as ircutils -import supybot.registry as registry +from . import conf, drivers, ircutils, log, registry startedAt = time.time() # Just in case it doesn't get set later. diff --git a/test/test_callbacks.py b/test/test_callbacks.py index ba3e0a10c..8f11723e4 100644 --- a/test/test_callbacks.py +++ b/test/test_callbacks.py @@ -564,6 +564,9 @@ class SourceNestedPluginTestCase(PluginTestCase): """ irc.reply('f') + def empty(self, irc, msg, args): + pass + class g(callbacks.Commands): def h(self, irc, msg, args): """takes no arguments @@ -602,6 +605,7 @@ class SourceNestedPluginTestCase(PluginTestCase): self.assertResponse('e g h', 'h') self.assertResponse('e g i j', 'j') self.assertHelp('help f') + self.assertHelp('help empty') self.assertHelp('help same') self.assertHelp('help e g h') self.assertHelp('help e g i j') diff --git a/test/test_commands.py b/test/test_commands.py index 2a7bdc7b9..e19f7399b 100644 --- a/test/test_commands.py +++ b/test/test_commands.py @@ -74,6 +74,7 @@ class GeneralContextTestCase(CommandsTestCase): def testSpecInt(self): self.assertState(['int'], ['1'], [1]) self.assertState(['int', 'int', 'int'], ['1', '2', '3'], [1, 2, 3]) + self.assertError(['int'], ['9e999']) def testSpecNick(self): strict = conf.supybot.protocols.irc.strictRfc()