mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-23 11:12:47 +01:00
Merge remote-tracking branch 'supybot/master' into testing
Conflicts: plugins/Admin/plugin.py plugins/BadWords/config.py plugins/Filter/plugin.py plugins/Google/plugin.py plugins/Math/plugin.py plugins/Misc/plugin.py plugins/Note/plugin.py plugins/RSS/plugin.py plugins/Seen/test.py plugins/ShrinkUrl/config.py plugins/ShrinkUrl/plugin.py plugins/ShrinkUrl/test.py plugins/Status/plugin.py plugins/String/config.py plugins/String/plugin.py plugins/Time/plugin.py plugins/Todo/plugin.py plugins/Web/plugin.py plugins/__init__.py scripts/supybot-botchk setup.py src/__init__.py src/callbacks.py src/commands.py src/conf.py src/drivers/Socket.py src/ircdb.py src/irclib.py src/ircutils.py src/questions.py src/registry.py src/schedule.py src/test.py src/utils/file.py src/utils/gen.py src/utils/net.py src/utils/web.py src/world.py
This commit is contained in:
commit
40675ffdfa
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
||||
backup
|
||||
build
|
||||
dist
|
||||
supybot.egg-info
|
||||
test-data
|
||||
test-conf
|
||||
test-logs
|
||||
|
2
RELNOTES
2
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
|
||||
|
||||
|
515
distribute_setup.py
Normal file
515
distribute_setup.py
Normal file
@ -0,0 +1,515 @@
|
||||
#!python
|
||||
"""Bootstrap distribute installation
|
||||
|
||||
If you want to use setuptools in your package's setup.py, just include this
|
||||
file in the same directory with it, and add this to the top of your setup.py::
|
||||
|
||||
from distribute_setup import use_setuptools
|
||||
use_setuptools()
|
||||
|
||||
If you want to require a specific version of setuptools, set a download
|
||||
mirror, or use an alternate download directory, you can do so by supplying
|
||||
the appropriate options to ``use_setuptools()``.
|
||||
|
||||
This file can also be run as a script to install or upgrade setuptools.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import fnmatch
|
||||
import tempfile
|
||||
import tarfile
|
||||
from distutils import log
|
||||
|
||||
try:
|
||||
from site import USER_SITE
|
||||
except ImportError:
|
||||
USER_SITE = None
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
def _python_cmd(*args):
|
||||
args = (sys.executable,) + args
|
||||
return subprocess.call(args) == 0
|
||||
|
||||
except ImportError:
|
||||
# will be used for python 2.3
|
||||
def _python_cmd(*args):
|
||||
args = (sys.executable,) + args
|
||||
# quoting arguments if windows
|
||||
if sys.platform == 'win32':
|
||||
def quote(arg):
|
||||
if ' ' in arg:
|
||||
return '"%s"' % arg
|
||||
return arg
|
||||
args = [quote(arg) for arg in args]
|
||||
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
|
||||
|
||||
DEFAULT_VERSION = "0.6.28"
|
||||
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
|
||||
SETUPTOOLS_FAKED_VERSION = "0.6c11"
|
||||
|
||||
SETUPTOOLS_PKG_INFO = """\
|
||||
Metadata-Version: 1.0
|
||||
Name: setuptools
|
||||
Version: %s
|
||||
Summary: xxxx
|
||||
Home-page: xxx
|
||||
Author: xxx
|
||||
Author-email: xxx
|
||||
License: xxx
|
||||
Description: xxx
|
||||
""" % SETUPTOOLS_FAKED_VERSION
|
||||
|
||||
|
||||
def _install(tarball, install_args=()):
|
||||
# extracting the tarball
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
log.warn('Extracting in %s', tmpdir)
|
||||
old_wd = os.getcwd()
|
||||
try:
|
||||
os.chdir(tmpdir)
|
||||
tar = tarfile.open(tarball)
|
||||
_extractall(tar)
|
||||
tar.close()
|
||||
|
||||
# going in the directory
|
||||
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
|
||||
os.chdir(subdir)
|
||||
log.warn('Now working in %s', subdir)
|
||||
|
||||
# installing
|
||||
log.warn('Installing Distribute')
|
||||
if not _python_cmd('setup.py', 'install', *install_args):
|
||||
log.warn('Something went wrong during the installation.')
|
||||
log.warn('See the error message above.')
|
||||
finally:
|
||||
os.chdir(old_wd)
|
||||
|
||||
|
||||
def _build_egg(egg, tarball, to_dir):
|
||||
# extracting the tarball
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
log.warn('Extracting in %s', tmpdir)
|
||||
old_wd = os.getcwd()
|
||||
try:
|
||||
os.chdir(tmpdir)
|
||||
tar = tarfile.open(tarball)
|
||||
_extractall(tar)
|
||||
tar.close()
|
||||
|
||||
# going in the directory
|
||||
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
|
||||
os.chdir(subdir)
|
||||
log.warn('Now working in %s', subdir)
|
||||
|
||||
# building an egg
|
||||
log.warn('Building a Distribute egg in %s', to_dir)
|
||||
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
|
||||
|
||||
finally:
|
||||
os.chdir(old_wd)
|
||||
# returning the result
|
||||
log.warn(egg)
|
||||
if not os.path.exists(egg):
|
||||
raise IOError('Could not build the egg.')
|
||||
|
||||
|
||||
def _do_download(version, download_base, to_dir, download_delay):
|
||||
egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
|
||||
% (version, sys.version_info[0], sys.version_info[1]))
|
||||
if not os.path.exists(egg):
|
||||
tarball = download_setuptools(version, download_base,
|
||||
to_dir, download_delay)
|
||||
_build_egg(egg, tarball, to_dir)
|
||||
sys.path.insert(0, egg)
|
||||
import setuptools
|
||||
setuptools.bootstrap_install_from = egg
|
||||
|
||||
|
||||
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
||||
to_dir=os.curdir, download_delay=15, no_fake=True):
|
||||
# making sure we use the absolute path
|
||||
to_dir = os.path.abspath(to_dir)
|
||||
was_imported = 'pkg_resources' in sys.modules or \
|
||||
'setuptools' in sys.modules
|
||||
try:
|
||||
try:
|
||||
import pkg_resources
|
||||
if not hasattr(pkg_resources, '_distribute'):
|
||||
if not no_fake:
|
||||
_fake_setuptools()
|
||||
raise ImportError
|
||||
except ImportError:
|
||||
return _do_download(version, download_base, to_dir, download_delay)
|
||||
try:
|
||||
pkg_resources.require("distribute>=" + version)
|
||||
return
|
||||
except pkg_resources.VersionConflict:
|
||||
e = sys.exc_info()[1]
|
||||
if was_imported:
|
||||
sys.stderr.write(
|
||||
"The required version of distribute (>=%s) is not available,\n"
|
||||
"and can't be installed while this script is running. Please\n"
|
||||
"install a more recent version first, using\n"
|
||||
"'easy_install -U distribute'."
|
||||
"\n\n(Currently using %r)\n" % (version, e.args[0]))
|
||||
sys.exit(2)
|
||||
else:
|
||||
del pkg_resources, sys.modules['pkg_resources'] # reload ok
|
||||
return _do_download(version, download_base, to_dir,
|
||||
download_delay)
|
||||
except pkg_resources.DistributionNotFound:
|
||||
return _do_download(version, download_base, to_dir,
|
||||
download_delay)
|
||||
finally:
|
||||
if not no_fake:
|
||||
_create_fake_setuptools_pkg_info(to_dir)
|
||||
|
||||
|
||||
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
||||
to_dir=os.curdir, delay=15):
|
||||
"""Download distribute from a specified location and return its filename
|
||||
|
||||
`version` should be a valid distribute version number that is available
|
||||
as an egg for download under the `download_base` URL (which should end
|
||||
with a '/'). `to_dir` is the directory where the egg will be downloaded.
|
||||
`delay` is the number of seconds to pause before an actual download
|
||||
attempt.
|
||||
"""
|
||||
# making sure we use the absolute path
|
||||
to_dir = os.path.abspath(to_dir)
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except ImportError:
|
||||
from urllib2 import urlopen
|
||||
tgz_name = "distribute-%s.tar.gz" % version
|
||||
url = download_base + tgz_name
|
||||
saveto = os.path.join(to_dir, tgz_name)
|
||||
src = dst = None
|
||||
if not os.path.exists(saveto): # Avoid repeated downloads
|
||||
try:
|
||||
log.warn("Downloading %s", url)
|
||||
src = urlopen(url)
|
||||
# Read/write all in one block, so we don't create a corrupt file
|
||||
# if the download is interrupted.
|
||||
data = src.read()
|
||||
dst = open(saveto, "wb")
|
||||
dst.write(data)
|
||||
finally:
|
||||
if src:
|
||||
src.close()
|
||||
if dst:
|
||||
dst.close()
|
||||
return os.path.realpath(saveto)
|
||||
|
||||
|
||||
def _no_sandbox(function):
|
||||
def __no_sandbox(*args, **kw):
|
||||
try:
|
||||
from setuptools.sandbox import DirectorySandbox
|
||||
if not hasattr(DirectorySandbox, '_old'):
|
||||
def violation(*args):
|
||||
pass
|
||||
DirectorySandbox._old = DirectorySandbox._violation
|
||||
DirectorySandbox._violation = violation
|
||||
patched = True
|
||||
else:
|
||||
patched = False
|
||||
except ImportError:
|
||||
patched = False
|
||||
|
||||
try:
|
||||
return function(*args, **kw)
|
||||
finally:
|
||||
if patched:
|
||||
DirectorySandbox._violation = DirectorySandbox._old
|
||||
del DirectorySandbox._old
|
||||
|
||||
return __no_sandbox
|
||||
|
||||
|
||||
def _patch_file(path, content):
|
||||
"""Will backup the file then patch it"""
|
||||
existing_content = open(path).read()
|
||||
if existing_content == content:
|
||||
# already patched
|
||||
log.warn('Already patched.')
|
||||
return False
|
||||
log.warn('Patching...')
|
||||
_rename_path(path)
|
||||
f = open(path, 'w')
|
||||
try:
|
||||
f.write(content)
|
||||
finally:
|
||||
f.close()
|
||||
return True
|
||||
|
||||
_patch_file = _no_sandbox(_patch_file)
|
||||
|
||||
|
||||
def _same_content(path, content):
|
||||
return open(path).read() == content
|
||||
|
||||
|
||||
def _rename_path(path):
|
||||
new_name = path + '.OLD.%s' % time.time()
|
||||
log.warn('Renaming %s into %s', path, new_name)
|
||||
os.rename(path, new_name)
|
||||
return new_name
|
||||
|
||||
|
||||
def _remove_flat_installation(placeholder):
|
||||
if not os.path.isdir(placeholder):
|
||||
log.warn('Unkown installation at %s', placeholder)
|
||||
return False
|
||||
found = False
|
||||
for file in os.listdir(placeholder):
|
||||
if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
log.warn('Could not locate setuptools*.egg-info')
|
||||
return
|
||||
|
||||
log.warn('Removing elements out of the way...')
|
||||
pkg_info = os.path.join(placeholder, file)
|
||||
if os.path.isdir(pkg_info):
|
||||
patched = _patch_egg_dir(pkg_info)
|
||||
else:
|
||||
patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
|
||||
|
||||
if not patched:
|
||||
log.warn('%s already patched.', pkg_info)
|
||||
return False
|
||||
# now let's move the files out of the way
|
||||
for element in ('setuptools', 'pkg_resources.py', 'site.py'):
|
||||
element = os.path.join(placeholder, element)
|
||||
if os.path.exists(element):
|
||||
_rename_path(element)
|
||||
else:
|
||||
log.warn('Could not find the %s element of the '
|
||||
'Setuptools distribution', element)
|
||||
return True
|
||||
|
||||
_remove_flat_installation = _no_sandbox(_remove_flat_installation)
|
||||
|
||||
|
||||
def _after_install(dist):
|
||||
log.warn('After install bootstrap.')
|
||||
placeholder = dist.get_command_obj('install').install_purelib
|
||||
_create_fake_setuptools_pkg_info(placeholder)
|
||||
|
||||
|
||||
def _create_fake_setuptools_pkg_info(placeholder):
|
||||
if not placeholder or not os.path.exists(placeholder):
|
||||
log.warn('Could not find the install location')
|
||||
return
|
||||
pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
|
||||
setuptools_file = 'setuptools-%s-py%s.egg-info' % \
|
||||
(SETUPTOOLS_FAKED_VERSION, pyver)
|
||||
pkg_info = os.path.join(placeholder, setuptools_file)
|
||||
if os.path.exists(pkg_info):
|
||||
log.warn('%s already exists', pkg_info)
|
||||
return
|
||||
|
||||
if not os.access(pkg_info, os.W_OK):
|
||||
log.warn("Don't have permissions to write %s, skipping", pkg_info)
|
||||
|
||||
log.warn('Creating %s', pkg_info)
|
||||
f = open(pkg_info, 'w')
|
||||
try:
|
||||
f.write(SETUPTOOLS_PKG_INFO)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
pth_file = os.path.join(placeholder, 'setuptools.pth')
|
||||
log.warn('Creating %s', pth_file)
|
||||
f = open(pth_file, 'w')
|
||||
try:
|
||||
f.write(os.path.join(os.curdir, setuptools_file))
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
_create_fake_setuptools_pkg_info = _no_sandbox(
|
||||
_create_fake_setuptools_pkg_info
|
||||
)
|
||||
|
||||
|
||||
def _patch_egg_dir(path):
|
||||
# let's check if it's already patched
|
||||
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
|
||||
if os.path.exists(pkg_info):
|
||||
if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
|
||||
log.warn('%s already patched.', pkg_info)
|
||||
return False
|
||||
_rename_path(path)
|
||||
os.mkdir(path)
|
||||
os.mkdir(os.path.join(path, 'EGG-INFO'))
|
||||
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
|
||||
f = open(pkg_info, 'w')
|
||||
try:
|
||||
f.write(SETUPTOOLS_PKG_INFO)
|
||||
finally:
|
||||
f.close()
|
||||
return True
|
||||
|
||||
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
|
||||
|
||||
|
||||
def _before_install():
|
||||
log.warn('Before install bootstrap.')
|
||||
_fake_setuptools()
|
||||
|
||||
|
||||
def _under_prefix(location):
|
||||
if 'install' not in sys.argv:
|
||||
return True
|
||||
args = sys.argv[sys.argv.index('install') + 1:]
|
||||
for index, arg in enumerate(args):
|
||||
for option in ('--root', '--prefix'):
|
||||
if arg.startswith('%s=' % option):
|
||||
top_dir = arg.split('root=')[-1]
|
||||
return location.startswith(top_dir)
|
||||
elif arg == option:
|
||||
if len(args) > index:
|
||||
top_dir = args[index + 1]
|
||||
return location.startswith(top_dir)
|
||||
if arg == '--user' and USER_SITE is not None:
|
||||
return location.startswith(USER_SITE)
|
||||
return True
|
||||
|
||||
|
||||
def _fake_setuptools():
|
||||
log.warn('Scanning installed packages')
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
# we're cool
|
||||
log.warn('Setuptools or Distribute does not seem to be installed.')
|
||||
return
|
||||
ws = pkg_resources.working_set
|
||||
try:
|
||||
setuptools_dist = ws.find(
|
||||
pkg_resources.Requirement.parse('setuptools', replacement=False)
|
||||
)
|
||||
except TypeError:
|
||||
# old distribute API
|
||||
setuptools_dist = ws.find(
|
||||
pkg_resources.Requirement.parse('setuptools')
|
||||
)
|
||||
|
||||
if setuptools_dist is None:
|
||||
log.warn('No setuptools distribution found')
|
||||
return
|
||||
# detecting if it was already faked
|
||||
setuptools_location = setuptools_dist.location
|
||||
log.warn('Setuptools installation detected at %s', setuptools_location)
|
||||
|
||||
# if --root or --preix was provided, and if
|
||||
# setuptools is not located in them, we don't patch it
|
||||
if not _under_prefix(setuptools_location):
|
||||
log.warn('Not patching, --root or --prefix is installing Distribute'
|
||||
' in another location')
|
||||
return
|
||||
|
||||
# let's see if its an egg
|
||||
if not setuptools_location.endswith('.egg'):
|
||||
log.warn('Non-egg installation')
|
||||
res = _remove_flat_installation(setuptools_location)
|
||||
if not res:
|
||||
return
|
||||
else:
|
||||
log.warn('Egg installation')
|
||||
pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
|
||||
if (os.path.exists(pkg_info) and
|
||||
_same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
|
||||
log.warn('Already patched.')
|
||||
return
|
||||
log.warn('Patching...')
|
||||
# let's create a fake egg replacing setuptools one
|
||||
res = _patch_egg_dir(setuptools_location)
|
||||
if not res:
|
||||
return
|
||||
log.warn('Patched done.')
|
||||
_relaunch()
|
||||
|
||||
|
||||
def _relaunch():
|
||||
log.warn('Relaunching...')
|
||||
# we have to relaunch the process
|
||||
# pip marker to avoid a relaunch bug
|
||||
_cmd = ['-c', 'install', '--single-version-externally-managed']
|
||||
if sys.argv[:3] == _cmd:
|
||||
sys.argv[0] = 'setup.py'
|
||||
args = [sys.executable] + sys.argv
|
||||
sys.exit(subprocess.call(args))
|
||||
|
||||
|
||||
def _extractall(self, path=".", members=None):
|
||||
"""Extract all members from the archive to the current working
|
||||
directory and set owner, modification time and permissions on
|
||||
directories afterwards. `path' specifies a different directory
|
||||
to extract to. `members' is optional and must be a subset of the
|
||||
list returned by getmembers().
|
||||
"""
|
||||
import copy
|
||||
import operator
|
||||
from tarfile import ExtractError
|
||||
directories = []
|
||||
|
||||
if members is None:
|
||||
members = self
|
||||
|
||||
for tarinfo in members:
|
||||
if tarinfo.isdir():
|
||||
# Extract directories with a safe mode.
|
||||
directories.append(tarinfo)
|
||||
tarinfo = copy.copy(tarinfo)
|
||||
tarinfo.mode = 448 # decimal for oct 0700
|
||||
self.extract(tarinfo, path)
|
||||
|
||||
# Reverse sort directories.
|
||||
if sys.version_info < (2, 4):
|
||||
def sorter(dir1, dir2):
|
||||
return cmp(dir1.name, dir2.name)
|
||||
directories.sort(sorter)
|
||||
directories.reverse()
|
||||
else:
|
||||
directories.sort(key=operator.attrgetter('name'), reverse=True)
|
||||
|
||||
# Set correct owner, mtime and filemode on directories.
|
||||
for tarinfo in directories:
|
||||
dirpath = os.path.join(path, tarinfo.name)
|
||||
try:
|
||||
self.chown(tarinfo, dirpath)
|
||||
self.utime(tarinfo, dirpath)
|
||||
self.chmod(tarinfo, dirpath)
|
||||
except ExtractError:
|
||||
e = sys.exc_info()[1]
|
||||
if self.errorlevel > 1:
|
||||
raise
|
||||
else:
|
||||
self._dbg(1, "tarfile: %s" % e)
|
||||
|
||||
|
||||
def _build_install_args(argv):
|
||||
install_args = []
|
||||
user_install = '--user' in argv
|
||||
if user_install and sys.version_info < (2, 6):
|
||||
log.warn("--user requires Python 2.6 or later")
|
||||
raise SystemExit(1)
|
||||
if user_install:
|
||||
install_args.append('--user')
|
||||
return install_args
|
||||
|
||||
|
||||
def main(argv, version=DEFAULT_VERSION):
|
||||
"""Install or upgrade setuptools and EasyInstall"""
|
||||
tarball = download_setuptools()
|
||||
_install(tarball, _build_install_args(argv))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
@ -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?
|
||||
|
@ -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):
|
||||
|
@ -165,19 +165,20 @@ class Filter(callbacks.Plugin):
|
||||
irc.reply(''.join(L))
|
||||
binary = wrap(binary, ['text'])
|
||||
|
||||
@internationalizeDocstring
|
||||
def unbinary(self, irc, msg, args, text):
|
||||
"""<text>
|
||||
|
||||
Returns the character representation of binary <text>.
|
||||
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):
|
||||
"""<text>
|
||||
|
||||
|
@ -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)
|
||||
|
@ -1 +0,0 @@
|
||||
# Stub so local is a module, used for third-party modules
|
@ -31,6 +31,7 @@
|
||||
import re
|
||||
import sys
|
||||
import cgi
|
||||
import json
|
||||
import time
|
||||
import socket
|
||||
import urllib
|
||||
|
@ -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 '
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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:
|
||||
|
@ -1 +0,0 @@
|
||||
# Stub so local is a module, used for third-party modules
|
@ -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 <http://feedparser.org/>.'
|
||||
|
||||
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:
|
||||
|
@ -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')
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
"""<password> <text>
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
# Stub so local is a module, used for third-party modules
|
@ -1,9 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
__version__ = "1.4.1"
|
@ -1,92 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
|
||||
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
|
||||
|
||||
EASTER_JULIAN = 1
|
||||
EASTER_ORTHODOX = 2
|
||||
EASTER_WESTERN = 3
|
||||
|
||||
def easter(year, method=EASTER_WESTERN):
|
||||
"""
|
||||
This method was ported from the work done by GM Arts,
|
||||
on top of the algorithm by Claus Tondering, which was
|
||||
based in part on the algorithm of Ouding (1940), as
|
||||
quoted in "Explanatory Supplement to the Astronomical
|
||||
Almanac", P. Kenneth Seidelmann, editor.
|
||||
|
||||
This algorithm implements three different easter
|
||||
calculation methods:
|
||||
|
||||
1 - Original calculation in Julian calendar, valid in
|
||||
dates after 326 AD
|
||||
2 - Original method, with date converted to Gregorian
|
||||
calendar, valid in years 1583 to 4099
|
||||
3 - Revised method, in Gregorian calendar, valid in
|
||||
years 1583 to 4099 as well
|
||||
|
||||
These methods are represented by the constants:
|
||||
|
||||
EASTER_JULIAN = 1
|
||||
EASTER_ORTHODOX = 2
|
||||
EASTER_WESTERN = 3
|
||||
|
||||
The default method is method 3.
|
||||
|
||||
More about the algorithm may be found at:
|
||||
|
||||
http://users.chariot.net.au/~gmarts/eastalg.htm
|
||||
|
||||
and
|
||||
|
||||
http://www.tondering.dk/claus/calendar.html
|
||||
|
||||
"""
|
||||
|
||||
if not (1 <= method <= 3):
|
||||
raise ValueError, "invalid method"
|
||||
|
||||
# g - Golden year - 1
|
||||
# c - Century
|
||||
# h - (23 - Epact) mod 30
|
||||
# i - Number of days from March 21 to Paschal Full Moon
|
||||
# j - Weekday for PFM (0=Sunday, etc)
|
||||
# p - Number of days from March 21 to Sunday on or before PFM
|
||||
# (-6 to 28 methods 1 & 3, to 56 for method 2)
|
||||
# e - Extra days to add for method 2 (converting Julian
|
||||
# date to Gregorian date)
|
||||
|
||||
y = year
|
||||
g = y % 19
|
||||
e = 0
|
||||
if method < 3:
|
||||
# Old method
|
||||
i = (19*g+15)%30
|
||||
j = (y+y//4+i)%7
|
||||
if method == 2:
|
||||
# Extra dates to convert Julian to Gregorian date
|
||||
e = 10
|
||||
if y > 1600:
|
||||
e = e+y//100-16-(y//100-16)//4
|
||||
else:
|
||||
# New method
|
||||
c = y//100
|
||||
h = (c-c//4-(8*c+13)//25+19*g+15)%30
|
||||
i = h-(h//28)*(1-(h//28)*(29//(h+1))*((21-g)//11))
|
||||
j = (y+y//4+i+2-c+c//4)%7
|
||||
|
||||
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
|
||||
# (later dates apply to method 2, although 23 May never actually occurs)
|
||||
p = i-j+e
|
||||
d = 1+(p+27+(p+6)//40)%31
|
||||
m = 3+(p+26)//30
|
||||
return datetime.date(int(y),int(m),int(d))
|
||||
|
@ -1,886 +0,0 @@
|
||||
# -*- coding:iso-8859-1 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
import string
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
import relativedelta
|
||||
import tz
|
||||
|
||||
|
||||
__all__ = ["parse", "parserinfo"]
|
||||
|
||||
|
||||
# Some pointers:
|
||||
#
|
||||
# http://www.cl.cam.ac.uk/~mgk25/iso-time.html
|
||||
# http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html
|
||||
# http://www.w3.org/TR/NOTE-datetime
|
||||
# http://ringmaster.arc.nasa.gov/tools/time_formats.html
|
||||
# http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.pm
|
||||
# http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html
|
||||
|
||||
|
||||
class _timelex(object):
|
||||
|
||||
def __init__(self, instream):
|
||||
if isinstance(instream, basestring):
|
||||
instream = StringIO(instream)
|
||||
self.instream = instream
|
||||
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
||||
'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
|
||||
'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
|
||||
self.numchars = '0123456789'
|
||||
self.whitespace = ' \t\r\n'
|
||||
self.charstack = []
|
||||
self.tokenstack = []
|
||||
self.eof = False
|
||||
|
||||
def get_token(self):
|
||||
if self.tokenstack:
|
||||
return self.tokenstack.pop(0)
|
||||
seenletters = False
|
||||
token = None
|
||||
state = None
|
||||
wordchars = self.wordchars
|
||||
numchars = self.numchars
|
||||
whitespace = self.whitespace
|
||||
while not self.eof:
|
||||
if self.charstack:
|
||||
nextchar = self.charstack.pop(0)
|
||||
else:
|
||||
nextchar = self.instream.read(1)
|
||||
while nextchar == '\x00':
|
||||
nextchar = self.instream.read(1)
|
||||
if not nextchar:
|
||||
self.eof = True
|
||||
break
|
||||
elif not state:
|
||||
token = nextchar
|
||||
if nextchar in wordchars:
|
||||
state = 'a'
|
||||
elif nextchar in numchars:
|
||||
state = '0'
|
||||
elif nextchar in whitespace:
|
||||
token = ' '
|
||||
break # emit token
|
||||
else:
|
||||
break # emit token
|
||||
elif state == 'a':
|
||||
seenletters = True
|
||||
if nextchar in wordchars:
|
||||
token += nextchar
|
||||
elif nextchar == '.':
|
||||
token += nextchar
|
||||
state = 'a.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == '0':
|
||||
if nextchar in numchars:
|
||||
token += nextchar
|
||||
elif nextchar == '.':
|
||||
token += nextchar
|
||||
state = '0.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == 'a.':
|
||||
seenletters = True
|
||||
if nextchar == '.' or nextchar in wordchars:
|
||||
token += nextchar
|
||||
elif nextchar in numchars and token[-1] == '.':
|
||||
token += nextchar
|
||||
state = '0.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == '0.':
|
||||
if nextchar == '.' or nextchar in numchars:
|
||||
token += nextchar
|
||||
elif nextchar in wordchars and token[-1] == '.':
|
||||
token += nextchar
|
||||
state = 'a.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
if (state in ('a.', '0.') and
|
||||
(seenletters or token.count('.') > 1 or token[-1] == '.')):
|
||||
l = token.split('.')
|
||||
token = l[0]
|
||||
for tok in l[1:]:
|
||||
self.tokenstack.append('.')
|
||||
if tok:
|
||||
self.tokenstack.append(tok)
|
||||
return token
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
token = self.get_token()
|
||||
if token is None:
|
||||
raise StopIteration
|
||||
return token
|
||||
|
||||
def split(cls, s):
|
||||
return list(cls(s))
|
||||
split = classmethod(split)
|
||||
|
||||
|
||||
class _resultbase(object):
|
||||
|
||||
def __init__(self):
|
||||
for attr in self.__slots__:
|
||||
setattr(self, attr, None)
|
||||
|
||||
def _repr(self, classname):
|
||||
l = []
|
||||
for attr in self.__slots__:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("%s=%s" % (attr, `value`))
|
||||
return "%s(%s)" % (classname, ", ".join(l))
|
||||
|
||||
def __repr__(self):
|
||||
return self._repr(self.__class__.__name__)
|
||||
|
||||
|
||||
class parserinfo(object):
|
||||
|
||||
# m from a.m/p.m, t from ISO T separator
|
||||
JUMP = [" ", ".", ",", ";", "-", "/", "'",
|
||||
"at", "on", "and", "ad", "m", "t", "of",
|
||||
"st", "nd", "rd", "th"]
|
||||
|
||||
WEEKDAYS = [("Mon", "Monday"),
|
||||
("Tue", "Tuesday"),
|
||||
("Wed", "Wednesday"),
|
||||
("Thu", "Thursday"),
|
||||
("Fri", "Friday"),
|
||||
("Sat", "Saturday"),
|
||||
("Sun", "Sunday")]
|
||||
MONTHS = [("Jan", "January"),
|
||||
("Feb", "February"),
|
||||
("Mar", "March"),
|
||||
("Apr", "April"),
|
||||
("May", "May"),
|
||||
("Jun", "June"),
|
||||
("Jul", "July"),
|
||||
("Aug", "August"),
|
||||
("Sep", "September"),
|
||||
("Oct", "October"),
|
||||
("Nov", "November"),
|
||||
("Dec", "December")]
|
||||
HMS = [("h", "hour", "hours"),
|
||||
("m", "minute", "minutes"),
|
||||
("s", "second", "seconds")]
|
||||
AMPM = [("am", "a"),
|
||||
("pm", "p")]
|
||||
UTCZONE = ["UTC", "GMT", "Z"]
|
||||
PERTAIN = ["of"]
|
||||
TZOFFSET = {}
|
||||
|
||||
def __init__(self, dayfirst=False, yearfirst=False):
|
||||
self._jump = self._convert(self.JUMP)
|
||||
self._weekdays = self._convert(self.WEEKDAYS)
|
||||
self._months = self._convert(self.MONTHS)
|
||||
self._hms = self._convert(self.HMS)
|
||||
self._ampm = self._convert(self.AMPM)
|
||||
self._utczone = self._convert(self.UTCZONE)
|
||||
self._pertain = self._convert(self.PERTAIN)
|
||||
|
||||
self.dayfirst = dayfirst
|
||||
self.yearfirst = yearfirst
|
||||
|
||||
self._year = time.localtime().tm_year
|
||||
self._century = self._year//100*100
|
||||
|
||||
def _convert(self, lst):
|
||||
dct = {}
|
||||
for i in range(len(lst)):
|
||||
v = lst[i]
|
||||
if isinstance(v, tuple):
|
||||
for v in v:
|
||||
dct[v.lower()] = i
|
||||
else:
|
||||
dct[v.lower()] = i
|
||||
return dct
|
||||
|
||||
def jump(self, name):
|
||||
return name.lower() in self._jump
|
||||
|
||||
def weekday(self, name):
|
||||
if len(name) >= 3:
|
||||
try:
|
||||
return self._weekdays[name.lower()]
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def month(self, name):
|
||||
if len(name) >= 3:
|
||||
try:
|
||||
return self._months[name.lower()]+1
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def hms(self, name):
|
||||
try:
|
||||
return self._hms[name.lower()]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def ampm(self, name):
|
||||
try:
|
||||
return self._ampm[name.lower()]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def pertain(self, name):
|
||||
return name.lower() in self._pertain
|
||||
|
||||
def utczone(self, name):
|
||||
return name.lower() in self._utczone
|
||||
|
||||
def tzoffset(self, name):
|
||||
if name in self._utczone:
|
||||
return 0
|
||||
return self.TZOFFSET.get(name)
|
||||
|
||||
def convertyear(self, year):
|
||||
if year < 100:
|
||||
year += self._century
|
||||
if abs(year-self._year) >= 50:
|
||||
if year < self._year:
|
||||
year += 100
|
||||
else:
|
||||
year -= 100
|
||||
return year
|
||||
|
||||
def validate(self, res):
|
||||
# move to info
|
||||
if res.year is not None:
|
||||
res.year = self.convertyear(res.year)
|
||||
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
|
||||
res.tzname = "UTC"
|
||||
res.tzoffset = 0
|
||||
elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
|
||||
res.tzoffset = 0
|
||||
return True
|
||||
|
||||
|
||||
class parser(object):
|
||||
|
||||
def __init__(self, info=None):
|
||||
self.info = info or parserinfo()
|
||||
|
||||
def parse(self, timestr, default=None,
|
||||
ignoretz=False, tzinfos=None,
|
||||
**kwargs):
|
||||
if not default:
|
||||
default = datetime.datetime.now().replace(hour=0, minute=0,
|
||||
second=0, microsecond=0)
|
||||
res = self._parse(timestr, **kwargs)
|
||||
if res is None:
|
||||
raise ValueError, "unknown string format"
|
||||
repl = {}
|
||||
for attr in ["year", "month", "day", "hour",
|
||||
"minute", "second", "microsecond"]:
|
||||
value = getattr(res, attr)
|
||||
if value is not None:
|
||||
repl[attr] = value
|
||||
ret = default.replace(**repl)
|
||||
if res.weekday is not None and not res.day:
|
||||
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
|
||||
if not ignoretz:
|
||||
if callable(tzinfos) or tzinfos and res.tzname in tzinfos:
|
||||
if callable(tzinfos):
|
||||
tzdata = tzinfos(res.tzname, res.tzoffset)
|
||||
else:
|
||||
tzdata = tzinfos.get(res.tzname)
|
||||
if isinstance(tzdata, datetime.tzinfo):
|
||||
tzinfo = tzdata
|
||||
elif isinstance(tzdata, basestring):
|
||||
tzinfo = tz.tzstr(tzdata)
|
||||
elif isinstance(tzdata, int):
|
||||
tzinfo = tz.tzoffset(res.tzname, tzdata)
|
||||
else:
|
||||
raise ValueError, "offset must be tzinfo subclass, " \
|
||||
"tz string, or int offset"
|
||||
ret = ret.replace(tzinfo=tzinfo)
|
||||
elif res.tzname and res.tzname in time.tzname:
|
||||
ret = ret.replace(tzinfo=tz.tzlocal())
|
||||
elif res.tzoffset == 0:
|
||||
ret = ret.replace(tzinfo=tz.tzutc())
|
||||
elif res.tzoffset:
|
||||
ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
|
||||
return ret
|
||||
|
||||
class _result(_resultbase):
|
||||
__slots__ = ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond",
|
||||
"tzname", "tzoffset"]
|
||||
|
||||
def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False):
|
||||
info = self.info
|
||||
if dayfirst is None:
|
||||
dayfirst = info.dayfirst
|
||||
if yearfirst is None:
|
||||
yearfirst = info.yearfirst
|
||||
res = self._result()
|
||||
l = _timelex.split(timestr)
|
||||
try:
|
||||
|
||||
# year/month/day list
|
||||
ymd = []
|
||||
|
||||
# Index of the month string in ymd
|
||||
mstridx = -1
|
||||
|
||||
len_l = len(l)
|
||||
i = 0
|
||||
while i < len_l:
|
||||
|
||||
# Check if it's a number
|
||||
try:
|
||||
value_repr = l[i]
|
||||
value = float(value_repr)
|
||||
except ValueError:
|
||||
value = None
|
||||
|
||||
if value is not None:
|
||||
# Token is a number
|
||||
len_li = len(l[i])
|
||||
i += 1
|
||||
if (len(ymd) == 3 and len_li in (2, 4)
|
||||
and (i >= len_l or (l[i] != ':' and
|
||||
info.hms(l[i]) is None))):
|
||||
# 19990101T23[59]
|
||||
s = l[i-1]
|
||||
res.hour = int(s[:2])
|
||||
if len_li == 4:
|
||||
res.minute = int(s[2:])
|
||||
elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
|
||||
# YYMMDD or HHMMSS[.ss]
|
||||
s = l[i-1]
|
||||
if not ymd and l[i-1].find('.') == -1:
|
||||
ymd.append(info.convertyear(int(s[:2])))
|
||||
ymd.append(int(s[2:4]))
|
||||
ymd.append(int(s[4:]))
|
||||
else:
|
||||
# 19990101T235959[.59]
|
||||
res.hour = int(s[:2])
|
||||
res.minute = int(s[2:4])
|
||||
res.second, res.microsecond = _parsems(s[4:])
|
||||
elif len_li == 8:
|
||||
# YYYYMMDD
|
||||
s = l[i-1]
|
||||
ymd.append(int(s[:4]))
|
||||
ymd.append(int(s[4:6]))
|
||||
ymd.append(int(s[6:]))
|
||||
elif len_li in (12, 14):
|
||||
# YYYYMMDDhhmm[ss]
|
||||
s = l[i-1]
|
||||
ymd.append(int(s[:4]))
|
||||
ymd.append(int(s[4:6]))
|
||||
ymd.append(int(s[6:8]))
|
||||
res.hour = int(s[8:10])
|
||||
res.minute = int(s[10:12])
|
||||
if len_li == 14:
|
||||
res.second = int(s[12:])
|
||||
elif ((i < len_l and info.hms(l[i]) is not None) or
|
||||
(i+1 < len_l and l[i] == ' ' and
|
||||
info.hms(l[i+1]) is not None)):
|
||||
# HH[ ]h or MM[ ]m or SS[.ss][ ]s
|
||||
if l[i] == ' ':
|
||||
i += 1
|
||||
idx = info.hms(l[i])
|
||||
while True:
|
||||
if idx == 0:
|
||||
res.hour = int(value)
|
||||
if value%1:
|
||||
res.minute = int(60*(value%1))
|
||||
elif idx == 1:
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
elif idx == 2:
|
||||
res.second, res.microsecond = \
|
||||
_parsems(value_repr)
|
||||
i += 1
|
||||
if i >= len_l or idx == 2:
|
||||
break
|
||||
# 12h00
|
||||
try:
|
||||
value_repr = l[i]
|
||||
value = float(value_repr)
|
||||
except ValueError:
|
||||
break
|
||||
else:
|
||||
i += 1
|
||||
idx += 1
|
||||
if i < len_l:
|
||||
newidx = info.hms(l[i])
|
||||
if newidx is not None:
|
||||
idx = newidx
|
||||
elif i+1 < len_l and l[i] == ':':
|
||||
# HH:MM[:SS[.ss]]
|
||||
res.hour = int(value)
|
||||
i += 1
|
||||
value = float(l[i])
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
i += 1
|
||||
if i < len_l and l[i] == ':':
|
||||
res.second, res.microsecond = _parsems(l[i+1])
|
||||
i += 2
|
||||
elif i < len_l and l[i] in ('-', '/', '.'):
|
||||
sep = l[i]
|
||||
ymd.append(int(value))
|
||||
i += 1
|
||||
if i < len_l and not info.jump(l[i]):
|
||||
try:
|
||||
# 01-01[-01]
|
||||
ymd.append(int(l[i]))
|
||||
except ValueError:
|
||||
# 01-Jan[-01]
|
||||
value = info.month(l[i])
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
assert mstridx == -1
|
||||
mstridx = len(ymd)-1
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
if i < len_l and l[i] == sep:
|
||||
# We have three members
|
||||
i += 1
|
||||
value = info.month(l[i])
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
mstridx = len(ymd)-1
|
||||
assert mstridx == -1
|
||||
else:
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
elif i >= len_l or info.jump(l[i]):
|
||||
if i+1 < len_l and info.ampm(l[i+1]) is not None:
|
||||
# 12 am
|
||||
res.hour = int(value)
|
||||
if res.hour < 12 and info.ampm(l[i+1]) == 1:
|
||||
res.hour += 12
|
||||
elif res.hour == 12 and info.ampm(l[i+1]) == 0:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
else:
|
||||
# Year, month or day
|
||||
ymd.append(int(value))
|
||||
i += 1
|
||||
elif info.ampm(l[i]) is not None:
|
||||
# 12am
|
||||
res.hour = int(value)
|
||||
if res.hour < 12 and info.ampm(l[i]) == 1:
|
||||
res.hour += 12
|
||||
elif res.hour == 12 and info.ampm(l[i]) == 0:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
elif not fuzzy:
|
||||
return None
|
||||
else:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check weekday
|
||||
value = info.weekday(l[i])
|
||||
if value is not None:
|
||||
res.weekday = value
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check month name
|
||||
value = info.month(l[i])
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
assert mstridx == -1
|
||||
mstridx = len(ymd)-1
|
||||
i += 1
|
||||
if i < len_l:
|
||||
if l[i] in ('-', '/'):
|
||||
# Jan-01[-99]
|
||||
sep = l[i]
|
||||
i += 1
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
if i < len_l and l[i] == sep:
|
||||
# Jan-01-99
|
||||
i += 1
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
elif (i+3 < len_l and l[i] == l[i+2] == ' '
|
||||
and info.pertain(l[i+1])):
|
||||
# Jan of 01
|
||||
# In this case, 01 is clearly year
|
||||
try:
|
||||
value = int(l[i+3])
|
||||
except ValueError:
|
||||
# Wrong guess
|
||||
pass
|
||||
else:
|
||||
# Convert it here to become unambiguous
|
||||
ymd.append(info.convertyear(value))
|
||||
i += 4
|
||||
continue
|
||||
|
||||
# Check am/pm
|
||||
value = info.ampm(l[i])
|
||||
if value is not None:
|
||||
if value == 1 and res.hour < 12:
|
||||
res.hour += 12
|
||||
elif value == 0 and res.hour == 12:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check for a timezone name
|
||||
if (res.hour is not None and len(l[i]) <= 5 and
|
||||
res.tzname is None and res.tzoffset is None and
|
||||
not [x for x in l[i] if x not in string.ascii_uppercase]):
|
||||
res.tzname = l[i]
|
||||
res.tzoffset = info.tzoffset(res.tzname)
|
||||
i += 1
|
||||
|
||||
# Check for something like GMT+3, or BRST+3. Notice
|
||||
# that it doesn't mean "I am 3 hours after GMT", but
|
||||
# "my time +3 is GMT". If found, we reverse the
|
||||
# logic so that timezone parsing code will get it
|
||||
# right.
|
||||
if i < len_l and l[i] in ('+', '-'):
|
||||
l[i] = ('+', '-')[l[i] == '+']
|
||||
res.tzoffset = None
|
||||
if info.utczone(res.tzname):
|
||||
# With something like GMT+3, the timezone
|
||||
# is *not* GMT.
|
||||
res.tzname = None
|
||||
|
||||
continue
|
||||
|
||||
# Check for a numbered timezone
|
||||
if res.hour is not None and l[i] in ('+', '-'):
|
||||
signal = (-1,1)[l[i] == '+']
|
||||
i += 1
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
res.tzoffset = int(l[i])*3600+int(l[i+2])*60
|
||||
i += 2
|
||||
elif len_li <= 2:
|
||||
# -[0]3
|
||||
res.tzoffset = int(l[i][:2])*3600
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
res.tzoffset *= signal
|
||||
|
||||
# Look for a timezone name between parenthesis
|
||||
if (i+3 < len_l and
|
||||
info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
|
||||
3 <= len(l[i+2]) <= 5 and
|
||||
not [x for x in l[i+2]
|
||||
if x not in string.ascii_uppercase]):
|
||||
# -0300 (BRST)
|
||||
res.tzname = l[i+2]
|
||||
i += 4
|
||||
continue
|
||||
|
||||
# Check jumps
|
||||
if not (info.jump(l[i]) or fuzzy):
|
||||
return None
|
||||
|
||||
i += 1
|
||||
|
||||
# Process year/month/day
|
||||
len_ymd = len(ymd)
|
||||
if len_ymd > 3:
|
||||
# More than three members!?
|
||||
return None
|
||||
elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
|
||||
# One member, or two members with a month string
|
||||
if mstridx != -1:
|
||||
res.month = ymd[mstridx]
|
||||
del ymd[mstridx]
|
||||
if len_ymd > 1 or mstridx == -1:
|
||||
if ymd[0] > 31:
|
||||
res.year = ymd[0]
|
||||
else:
|
||||
res.day = ymd[0]
|
||||
elif len_ymd == 2:
|
||||
# Two members with numbers
|
||||
if ymd[0] > 31:
|
||||
# 99-01
|
||||
res.year, res.month = ymd
|
||||
elif ymd[1] > 31:
|
||||
# 01-99
|
||||
res.month, res.year = ymd
|
||||
elif dayfirst and ymd[1] <= 12:
|
||||
# 13-01
|
||||
res.day, res.month = ymd
|
||||
else:
|
||||
# 01-13
|
||||
res.month, res.day = ymd
|
||||
if len_ymd == 3:
|
||||
# Three members
|
||||
if mstridx == 0:
|
||||
res.month, res.day, res.year = ymd
|
||||
elif mstridx == 1:
|
||||
if ymd[0] > 31 or (yearfirst and ymd[2] <= 31):
|
||||
# 99-Jan-01
|
||||
res.year, res.month, res.day = ymd
|
||||
else:
|
||||
# 01-Jan-01
|
||||
# Give precendence to day-first, since
|
||||
# two-digit years is usually hand-written.
|
||||
res.day, res.month, res.year = ymd
|
||||
elif mstridx == 2:
|
||||
# WTF!?
|
||||
if ymd[1] > 31:
|
||||
# 01-99-Jan
|
||||
res.day, res.year, res.month = ymd
|
||||
else:
|
||||
# 99-01-Jan
|
||||
res.year, res.day, res.month = ymd
|
||||
else:
|
||||
if ymd[0] > 31 or \
|
||||
(yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
|
||||
# 99-01-01
|
||||
res.year, res.month, res.day = ymd
|
||||
elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12):
|
||||
# 13-01-01
|
||||
res.day, res.month, res.year = ymd
|
||||
else:
|
||||
# 01-13-01
|
||||
res.month, res.day, res.year = ymd
|
||||
|
||||
except (IndexError, ValueError, AssertionError):
|
||||
return None
|
||||
|
||||
if not info.validate(res):
|
||||
return None
|
||||
return res
|
||||
|
||||
DEFAULTPARSER = parser()
|
||||
def parse(timestr, parserinfo=None, **kwargs):
|
||||
if parserinfo:
|
||||
return parser(parserinfo).parse(timestr, **kwargs)
|
||||
else:
|
||||
return DEFAULTPARSER.parse(timestr, **kwargs)
|
||||
|
||||
|
||||
class _tzparser(object):
|
||||
|
||||
class _result(_resultbase):
|
||||
|
||||
__slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
|
||||
"start", "end"]
|
||||
|
||||
class _attr(_resultbase):
|
||||
__slots__ = ["month", "week", "weekday",
|
||||
"yday", "jyday", "day", "time"]
|
||||
|
||||
def __repr__(self):
|
||||
return self._repr("")
|
||||
|
||||
def __init__(self):
|
||||
_resultbase.__init__(self)
|
||||
self.start = self._attr()
|
||||
self.end = self._attr()
|
||||
|
||||
def parse(self, tzstr):
|
||||
res = self._result()
|
||||
l = _timelex.split(tzstr)
|
||||
try:
|
||||
|
||||
len_l = len(l)
|
||||
|
||||
i = 0
|
||||
while i < len_l:
|
||||
# BRST+3[BRDT[+2]]
|
||||
j = i
|
||||
while j < len_l and not [x for x in l[j]
|
||||
if x in "0123456789:,-+"]:
|
||||
j += 1
|
||||
if j != i:
|
||||
if not res.stdabbr:
|
||||
offattr = "stdoffset"
|
||||
res.stdabbr = "".join(l[i:j])
|
||||
else:
|
||||
offattr = "dstoffset"
|
||||
res.dstabbr = "".join(l[i:j])
|
||||
i = j
|
||||
if (i < len_l and
|
||||
(l[i] in ('+', '-') or l[i][0] in "0123456789")):
|
||||
if l[i] in ('+', '-'):
|
||||
# Yes, that's right. See the TZ variable
|
||||
# documentation.
|
||||
signal = (1,-1)[l[i] == '+']
|
||||
i += 1
|
||||
else:
|
||||
signal = -1
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
setattr(res, offattr,
|
||||
(int(l[i][:2])*3600+int(l[i][2:])*60)*signal)
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
setattr(res, offattr,
|
||||
(int(l[i])*3600+int(l[i+2])*60)*signal)
|
||||
i += 2
|
||||
elif len_li <= 2:
|
||||
# -[0]3
|
||||
setattr(res, offattr,
|
||||
int(l[i][:2])*3600*signal)
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
if res.dstabbr:
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
if i < len_l:
|
||||
for j in range(i, len_l):
|
||||
if l[j] == ';': l[j] = ','
|
||||
|
||||
assert l[i] == ','
|
||||
|
||||
i += 1
|
||||
|
||||
if i >= len_l:
|
||||
pass
|
||||
elif (8 <= l.count(',') <= 9 and
|
||||
not [y for x in l[i:] if x != ','
|
||||
for y in x if y not in "0123456789"]):
|
||||
# GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
|
||||
for x in (res.start, res.end):
|
||||
x.month = int(l[i])
|
||||
i += 2
|
||||
if l[i] == '-':
|
||||
value = int(l[i+1])*-1
|
||||
i += 1
|
||||
else:
|
||||
value = int(l[i])
|
||||
i += 2
|
||||
if value:
|
||||
x.week = value
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
else:
|
||||
x.day = int(l[i])
|
||||
i += 2
|
||||
x.time = int(l[i])
|
||||
i += 2
|
||||
if i < len_l:
|
||||
if l[i] in ('-','+'):
|
||||
signal = (-1,1)[l[i] == "+"]
|
||||
i += 1
|
||||
else:
|
||||
signal = 1
|
||||
res.dstoffset = (res.stdoffset+int(l[i]))*signal
|
||||
elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
|
||||
not [y for x in l[i:] if x not in (',','/','J','M',
|
||||
'.','-',':')
|
||||
for y in x if y not in "0123456789"]):
|
||||
for x in (res.start, res.end):
|
||||
if l[i] == 'J':
|
||||
# non-leap year day (1 based)
|
||||
i += 1
|
||||
x.jyday = int(l[i])
|
||||
elif l[i] == 'M':
|
||||
# month[-.]week[-.]weekday
|
||||
i += 1
|
||||
x.month = int(l[i])
|
||||
i += 1
|
||||
assert l[i] in ('-', '.')
|
||||
i += 1
|
||||
x.week = int(l[i])
|
||||
if x.week == 5:
|
||||
x.week = -1
|
||||
i += 1
|
||||
assert l[i] in ('-', '.')
|
||||
i += 1
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
else:
|
||||
# year day (zero based)
|
||||
x.yday = int(l[i])+1
|
||||
|
||||
i += 1
|
||||
|
||||
if i < len_l and l[i] == '/':
|
||||
i += 1
|
||||
# start time
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
x.time = (int(l[i][:2])*3600+int(l[i][2:])*60)
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
x.time = int(l[i])*3600+int(l[i+2])*60
|
||||
i += 2
|
||||
if i+1 < len_l and l[i+1] == ':':
|
||||
i += 2
|
||||
x.time += int(l[i])
|
||||
elif len_li <= 2:
|
||||
# -[0]3
|
||||
x.time = (int(l[i][:2])*3600)
|
||||
else:
|
||||
return None
|
||||
i += 1
|
||||
|
||||
assert i == len_l or l[i] == ','
|
||||
|
||||
i += 1
|
||||
|
||||
assert i >= len_l
|
||||
|
||||
except (IndexError, ValueError, AssertionError):
|
||||
return None
|
||||
|
||||
return res
|
||||
|
||||
|
||||
DEFAULTTZPARSER = _tzparser()
|
||||
def _parsetz(tzstr):
|
||||
return DEFAULTTZPARSER.parse(tzstr)
|
||||
|
||||
|
||||
def _parsems(value):
|
||||
"""Parse a I[.F] seconds value into (seconds, microseconds)."""
|
||||
if "." not in value:
|
||||
return int(value), 0
|
||||
else:
|
||||
i, f = value.split(".")
|
||||
return int(i), int(f.ljust(6, "0")[:6])
|
||||
|
||||
|
||||
# vim:ts=4:sw=4:et
|
@ -1,432 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
import calendar
|
||||
|
||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||
|
||||
class weekday(object):
|
||||
__slots__ = ["weekday", "n"]
|
||||
|
||||
def __init__(self, weekday, n=None):
|
||||
self.weekday = weekday
|
||||
self.n = n
|
||||
|
||||
def __call__(self, n):
|
||||
if n == self.n:
|
||||
return self
|
||||
else:
|
||||
return self.__class__(self.weekday, n)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
if self.weekday != other.weekday or self.n != other.n:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
||||
if not self.n:
|
||||
return s
|
||||
else:
|
||||
return "%s(%+d)" % (s, self.n)
|
||||
|
||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
||||
|
||||
class relativedelta:
|
||||
"""
|
||||
The relativedelta type is based on the specification of the excelent
|
||||
work done by M.-A. Lemburg in his mx.DateTime extension. However,
|
||||
notice that this type does *NOT* implement the same algorithm as
|
||||
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||
|
||||
There's two different ways to build a relativedelta instance. The
|
||||
first one is passing it two date/datetime classes:
|
||||
|
||||
relativedelta(datetime1, datetime2)
|
||||
|
||||
And the other way is to use the following keyword arguments:
|
||||
|
||||
year, month, day, hour, minute, second, microsecond:
|
||||
Absolute information.
|
||||
|
||||
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||
Relative information, may be negative.
|
||||
|
||||
weekday:
|
||||
One of the weekday instances (MO, TU, etc). These instances may
|
||||
receive a parameter N, specifying the Nth weekday, which could
|
||||
be positive or negative (like MO(+1) or MO(-2). Not specifying
|
||||
it is the same as specifying +1. You can also use an integer,
|
||||
where 0=MO.
|
||||
|
||||
leapdays:
|
||||
Will add given days to the date found, if year is a leap
|
||||
year, and the date found is post 28 of february.
|
||||
|
||||
yearday, nlyearday:
|
||||
Set the yearday or the non-leap year day (jump leap days).
|
||||
These are converted to day/month/leapdays information.
|
||||
|
||||
Here is the behavior of operations with relativedelta:
|
||||
|
||||
1) Calculate the absolute year, using the 'year' argument, or the
|
||||
original datetime year, if the argument is not present.
|
||||
|
||||
2) Add the relative 'years' argument to the absolute year.
|
||||
|
||||
3) Do steps 1 and 2 for month/months.
|
||||
|
||||
4) Calculate the absolute day, using the 'day' argument, or the
|
||||
original datetime day, if the argument is not present. Then,
|
||||
subtract from the day until it fits in the year and month
|
||||
found after their operations.
|
||||
|
||||
5) Add the relative 'days' argument to the absolute day. Notice
|
||||
that the 'weeks' argument is multiplied by 7 and added to
|
||||
'days'.
|
||||
|
||||
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
||||
microsecond/microseconds.
|
||||
|
||||
7) If the 'weekday' argument is present, calculate the weekday,
|
||||
with the given (wday, nth) tuple. wday is the index of the
|
||||
weekday (0-6, 0=Mon), and nth is the number of weeks to add
|
||||
forward or backward, depending on its signal. Notice that if
|
||||
the calculated date is already Monday, for example, using
|
||||
(0, 1) or (0, -1) won't change the day.
|
||||
"""
|
||||
|
||||
def __init__(self, dt1=None, dt2=None,
|
||||
years=0, months=0, days=0, leapdays=0, weeks=0,
|
||||
hours=0, minutes=0, seconds=0, microseconds=0,
|
||||
year=None, month=None, day=None, weekday=None,
|
||||
yearday=None, nlyearday=None,
|
||||
hour=None, minute=None, second=None, microsecond=None):
|
||||
if dt1 and dt2:
|
||||
if not isinstance(dt1, datetime.date) or \
|
||||
not isinstance(dt2, datetime.date):
|
||||
raise TypeError, "relativedelta only diffs datetime/date"
|
||||
if type(dt1) is not type(dt2):
|
||||
if not isinstance(dt1, datetime.datetime):
|
||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||
elif not isinstance(dt2, datetime.datetime):
|
||||
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
||||
self.years = 0
|
||||
self.months = 0
|
||||
self.days = 0
|
||||
self.leapdays = 0
|
||||
self.hours = 0
|
||||
self.minutes = 0
|
||||
self.seconds = 0
|
||||
self.microseconds = 0
|
||||
self.year = None
|
||||
self.month = None
|
||||
self.day = None
|
||||
self.weekday = None
|
||||
self.hour = None
|
||||
self.minute = None
|
||||
self.second = None
|
||||
self.microsecond = None
|
||||
self._has_time = 0
|
||||
|
||||
months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
if dt1 < dt2:
|
||||
while dt1 > dtm:
|
||||
months += 1
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
else:
|
||||
while dt1 < dtm:
|
||||
months -= 1
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
delta = dt1 - dtm
|
||||
self.seconds = delta.seconds+delta.days*86400
|
||||
self.microseconds = delta.microseconds
|
||||
else:
|
||||
self.years = years
|
||||
self.months = months
|
||||
self.days = days+weeks*7
|
||||
self.leapdays = leapdays
|
||||
self.hours = hours
|
||||
self.minutes = minutes
|
||||
self.seconds = seconds
|
||||
self.microseconds = microseconds
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
self.hour = hour
|
||||
self.minute = minute
|
||||
self.second = second
|
||||
self.microsecond = microsecond
|
||||
|
||||
if type(weekday) is int:
|
||||
self.weekday = weekdays[weekday]
|
||||
else:
|
||||
self.weekday = weekday
|
||||
|
||||
yday = 0
|
||||
if nlyearday:
|
||||
yday = nlyearday
|
||||
elif yearday:
|
||||
yday = yearday
|
||||
if yearday > 59:
|
||||
self.leapdays = -1
|
||||
if yday:
|
||||
ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366]
|
||||
for idx, ydays in enumerate(ydayidx):
|
||||
if yday <= ydays:
|
||||
self.month = idx+1
|
||||
if idx == 0:
|
||||
self.day = ydays
|
||||
else:
|
||||
self.day = yday-ydayidx[idx-1]
|
||||
break
|
||||
else:
|
||||
raise ValueError, "invalid year day (%d)" % yday
|
||||
|
||||
self._fix()
|
||||
|
||||
def _fix(self):
|
||||
if abs(self.microseconds) > 999999:
|
||||
s = self.microseconds//abs(self.microseconds)
|
||||
div, mod = divmod(self.microseconds*s, 1000000)
|
||||
self.microseconds = mod*s
|
||||
self.seconds += div*s
|
||||
if abs(self.seconds) > 59:
|
||||
s = self.seconds//abs(self.seconds)
|
||||
div, mod = divmod(self.seconds*s, 60)
|
||||
self.seconds = mod*s
|
||||
self.minutes += div*s
|
||||
if abs(self.minutes) > 59:
|
||||
s = self.minutes//abs(self.minutes)
|
||||
div, mod = divmod(self.minutes*s, 60)
|
||||
self.minutes = mod*s
|
||||
self.hours += div*s
|
||||
if abs(self.hours) > 23:
|
||||
s = self.hours//abs(self.hours)
|
||||
div, mod = divmod(self.hours*s, 24)
|
||||
self.hours = mod*s
|
||||
self.days += div*s
|
||||
if abs(self.months) > 11:
|
||||
s = self.months//abs(self.months)
|
||||
div, mod = divmod(self.months*s, 12)
|
||||
self.months = mod*s
|
||||
self.years += div*s
|
||||
if (self.hours or self.minutes or self.seconds or self.microseconds or
|
||||
self.hour is not None or self.minute is not None or
|
||||
self.second is not None or self.microsecond is not None):
|
||||
self._has_time = 1
|
||||
else:
|
||||
self._has_time = 0
|
||||
|
||||
def _set_months(self, months):
|
||||
self.months = months
|
||||
if abs(self.months) > 11:
|
||||
s = self.months//abs(self.months)
|
||||
div, mod = divmod(self.months*s, 12)
|
||||
self.months = mod*s
|
||||
self.years = div*s
|
||||
else:
|
||||
self.years = 0
|
||||
|
||||
def __radd__(self, other):
|
||||
if not isinstance(other, datetime.date):
|
||||
raise TypeError, "unsupported type for add operation"
|
||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||
other = datetime.datetime.fromordinal(other.toordinal())
|
||||
year = (self.year or other.year)+self.years
|
||||
month = self.month or other.month
|
||||
if self.months:
|
||||
assert 1 <= abs(self.months) <= 12
|
||||
month += self.months
|
||||
if month > 12:
|
||||
year += 1
|
||||
month -= 12
|
||||
elif month < 1:
|
||||
year -= 1
|
||||
month += 12
|
||||
day = min(calendar.monthrange(year, month)[1],
|
||||
self.day or other.day)
|
||||
repl = {"year": year, "month": month, "day": day}
|
||||
for attr in ["hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
repl[attr] = value
|
||||
days = self.days
|
||||
if self.leapdays and month > 2 and calendar.isleap(year):
|
||||
days += self.leapdays
|
||||
ret = (other.replace(**repl)
|
||||
+ datetime.timedelta(days=days,
|
||||
hours=self.hours,
|
||||
minutes=self.minutes,
|
||||
seconds=self.seconds,
|
||||
microseconds=self.microseconds))
|
||||
if self.weekday:
|
||||
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||
jumpdays = (abs(nth)-1)*7
|
||||
if nth > 0:
|
||||
jumpdays += (7-ret.weekday()+weekday)%7
|
||||
else:
|
||||
jumpdays += (ret.weekday()-weekday)%7
|
||||
jumpdays *= -1
|
||||
ret += datetime.timedelta(days=jumpdays)
|
||||
return ret
|
||||
|
||||
def __rsub__(self, other):
|
||||
return self.__neg__().__radd__(other)
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
raise TypeError, "unsupported type for add operation"
|
||||
return relativedelta(years=other.years+self.years,
|
||||
months=other.months+self.months,
|
||||
days=other.days+self.days,
|
||||
hours=other.hours+self.hours,
|
||||
minutes=other.minutes+self.minutes,
|
||||
seconds=other.seconds+self.seconds,
|
||||
microseconds=other.microseconds+self.microseconds,
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=other.year or self.year,
|
||||
month=other.month or self.month,
|
||||
day=other.day or self.day,
|
||||
weekday=other.weekday or self.weekday,
|
||||
hour=other.hour or self.hour,
|
||||
minute=other.minute or self.minute,
|
||||
second=other.second or self.second,
|
||||
microsecond=other.second or self.microsecond)
|
||||
|
||||
def __sub__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
raise TypeError, "unsupported type for sub operation"
|
||||
return relativedelta(years=other.years-self.years,
|
||||
months=other.months-self.months,
|
||||
days=other.days-self.days,
|
||||
hours=other.hours-self.hours,
|
||||
minutes=other.minutes-self.minutes,
|
||||
seconds=other.seconds-self.seconds,
|
||||
microseconds=other.microseconds-self.microseconds,
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=other.year or self.year,
|
||||
month=other.month or self.month,
|
||||
day=other.day or self.day,
|
||||
weekday=other.weekday or self.weekday,
|
||||
hour=other.hour or self.hour,
|
||||
minute=other.minute or self.minute,
|
||||
second=other.second or self.second,
|
||||
microsecond=other.second or self.microsecond)
|
||||
|
||||
def __neg__(self):
|
||||
return relativedelta(years=-self.years,
|
||||
months=-self.months,
|
||||
days=-self.days,
|
||||
hours=-self.hours,
|
||||
minutes=-self.minutes,
|
||||
seconds=-self.seconds,
|
||||
microseconds=-self.microseconds,
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __nonzero__(self):
|
||||
return not (not self.years and
|
||||
not self.months and
|
||||
not self.days and
|
||||
not self.hours and
|
||||
not self.minutes and
|
||||
not self.seconds and
|
||||
not self.microseconds and
|
||||
not self.leapdays and
|
||||
self.year is None and
|
||||
self.month is None and
|
||||
self.day is None and
|
||||
self.weekday is None and
|
||||
self.hour is None and
|
||||
self.minute is None and
|
||||
self.second is None and
|
||||
self.microsecond is None)
|
||||
|
||||
def __mul__(self, other):
|
||||
f = float(other)
|
||||
return relativedelta(years=self.years*f,
|
||||
months=self.months*f,
|
||||
days=self.days*f,
|
||||
hours=self.hours*f,
|
||||
minutes=self.minutes*f,
|
||||
seconds=self.seconds*f,
|
||||
microseconds=self.microseconds*f,
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
return False
|
||||
if self.weekday or other.weekday:
|
||||
if not self.weekday or not other.weekday:
|
||||
return False
|
||||
if self.weekday.weekday != other.weekday.weekday:
|
||||
return False
|
||||
n1, n2 = self.weekday.n, other.weekday.n
|
||||
if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
|
||||
return False
|
||||
return (self.years == other.years and
|
||||
self.months == other.months and
|
||||
self.days == other.days and
|
||||
self.hours == other.hours and
|
||||
self.minutes == other.minutes and
|
||||
self.seconds == other.seconds and
|
||||
self.leapdays == other.leapdays and
|
||||
self.year == other.year and
|
||||
self.month == other.month and
|
||||
self.day == other.day and
|
||||
self.hour == other.hour and
|
||||
self.minute == other.minute and
|
||||
self.second == other.second and
|
||||
self.microsecond == other.microsecond)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __div__(self, other):
|
||||
return self.__mul__(1/float(other))
|
||||
|
||||
def __repr__(self):
|
||||
l = []
|
||||
for attr in ["years", "months", "days", "leapdays",
|
||||
"hours", "minutes", "seconds", "microseconds"]:
|
||||
value = getattr(self, attr)
|
||||
if value:
|
||||
l.append("%s=%+d" % (attr, value))
|
||||
for attr in ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("%s=%s" % (attr, `value`))
|
||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
||||
|
||||
# vim:ts=4:sw=4:et
|
File diff suppressed because it is too large
Load Diff
@ -1,951 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
relativedelta = None
|
||||
parser = None
|
||||
rrule = None
|
||||
|
||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
|
||||
|
||||
try:
|
||||
from dateutil.tzwin import tzwin, tzwinlocal
|
||||
except (ImportError, OSError):
|
||||
tzwin, tzwinlocal = None, None
|
||||
|
||||
ZERO = datetime.timedelta(0)
|
||||
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
||||
|
||||
class tzutc(datetime.tzinfo):
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return ZERO
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return "UTC"
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, tzutc) or
|
||||
(isinstance(other, tzoffset) and other._offset == ZERO))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s()" % self.__class__.__name__
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class tzoffset(datetime.tzinfo):
|
||||
|
||||
def __init__(self, name, offset):
|
||||
self._name = name
|
||||
self._offset = datetime.timedelta(seconds=offset)
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self._offset
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return self._name
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, tzoffset) and
|
||||
self._offset == other._offset)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s, %s)" % (self.__class__.__name__,
|
||||
`self._name`,
|
||||
self._offset.days*86400+self._offset.seconds)
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class tzlocal(datetime.tzinfo):
|
||||
|
||||
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
||||
if time.daylight:
|
||||
_dst_offset = datetime.timedelta(seconds=-time.altzone)
|
||||
else:
|
||||
_dst_offset = _std_offset
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset
|
||||
else:
|
||||
return self._std_offset
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset-self._std_offset
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
# We can't use mktime here. It is unstable when deciding if
|
||||
# the hour near to a change is DST or not.
|
||||
#
|
||||
# timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
|
||||
# dt.minute, dt.second, dt.weekday(), 0, -1))
|
||||
# return time.localtime(timestamp).tm_isdst
|
||||
#
|
||||
# The code above yields the following result:
|
||||
#
|
||||
#>>> import tz, datetime
|
||||
#>>> t = tz.tzlocal()
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#
|
||||
# Here is a more stable implementation:
|
||||
#
|
||||
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
||||
+ dt.hour * 3600
|
||||
+ dt.minute * 60
|
||||
+ dt.second)
|
||||
return time.localtime(timestamp+time.timezone).tm_isdst
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tzlocal):
|
||||
return False
|
||||
return (self._std_offset == other._std_offset and
|
||||
self._dst_offset == other._dst_offset)
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s()" % self.__class__.__name__
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class _ttinfo(object):
|
||||
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
||||
|
||||
def __init__(self):
|
||||
for attr in self.__slots__:
|
||||
setattr(self, attr, None)
|
||||
|
||||
def __repr__(self):
|
||||
l = []
|
||||
for attr in self.__slots__:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("%s=%s" % (attr, `value`))
|
||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, _ttinfo):
|
||||
return False
|
||||
return (self.offset == other.offset and
|
||||
self.delta == other.delta and
|
||||
self.isdst == other.isdst and
|
||||
self.abbr == other.abbr and
|
||||
self.isstd == other.isstd and
|
||||
self.isgmt == other.isgmt)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __getstate__(self):
|
||||
state = {}
|
||||
for name in self.__slots__:
|
||||
state[name] = getattr(self, name, None)
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
for name in self.__slots__:
|
||||
if name in state:
|
||||
setattr(self, name, state[name])
|
||||
|
||||
class tzfile(datetime.tzinfo):
|
||||
|
||||
# http://www.twinsun.com/tz/tz-link.htm
|
||||
# ftp://elsie.nci.nih.gov/pub/tz*.tar.gz
|
||||
|
||||
def __init__(self, fileobj):
|
||||
if isinstance(fileobj, basestring):
|
||||
self._filename = fileobj
|
||||
fileobj = open(fileobj)
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._filename = fileobj.name
|
||||
else:
|
||||
self._filename = `fileobj`
|
||||
|
||||
# From tzfile(5):
|
||||
#
|
||||
# The time zone information files used by tzset(3)
|
||||
# begin with the magic characters "TZif" to identify
|
||||
# them as time zone information files, followed by
|
||||
# sixteen bytes reserved for future use, followed by
|
||||
# six four-byte values of type long, written in a
|
||||
# ``standard'' byte order (the high-order byte
|
||||
# of the value is written first).
|
||||
|
||||
if fileobj.read(4) != "TZif":
|
||||
raise ValueError, "magic not found"
|
||||
|
||||
fileobj.read(16)
|
||||
|
||||
(
|
||||
# The number of UTC/local indicators stored in the file.
|
||||
ttisgmtcnt,
|
||||
|
||||
# The number of standard/wall indicators stored in the file.
|
||||
ttisstdcnt,
|
||||
|
||||
# The number of leap seconds for which data is
|
||||
# stored in the file.
|
||||
leapcnt,
|
||||
|
||||
# The number of "transition times" for which data
|
||||
# is stored in the file.
|
||||
timecnt,
|
||||
|
||||
# The number of "local time types" for which data
|
||||
# is stored in the file (must not be zero).
|
||||
typecnt,
|
||||
|
||||
# The number of characters of "time zone
|
||||
# abbreviation strings" stored in the file.
|
||||
charcnt,
|
||||
|
||||
) = struct.unpack(">6l", fileobj.read(24))
|
||||
|
||||
# The above header is followed by tzh_timecnt four-byte
|
||||
# values of type long, sorted in ascending order.
|
||||
# These values are written in ``standard'' byte order.
|
||||
# Each is used as a transition time (as returned by
|
||||
# time(2)) at which the rules for computing local time
|
||||
# change.
|
||||
|
||||
if timecnt:
|
||||
self._trans_list = struct.unpack(">%dl" % timecnt,
|
||||
fileobj.read(timecnt*4))
|
||||
else:
|
||||
self._trans_list = []
|
||||
|
||||
# Next come tzh_timecnt one-byte values of type unsigned
|
||||
# char; each one tells which of the different types of
|
||||
# ``local time'' types described in the file is associated
|
||||
# with the same-indexed transition time. These values
|
||||
# serve as indices into an array of ttinfo structures that
|
||||
# appears next in the file.
|
||||
|
||||
if timecnt:
|
||||
self._trans_idx = struct.unpack(">%dB" % timecnt,
|
||||
fileobj.read(timecnt))
|
||||
else:
|
||||
self._trans_idx = []
|
||||
|
||||
# Each ttinfo structure is written as a four-byte value
|
||||
# for tt_gmtoff of type long, in a standard byte
|
||||
# order, followed by a one-byte value for tt_isdst
|
||||
# and a one-byte value for tt_abbrind. In each
|
||||
# structure, tt_gmtoff gives the number of
|
||||
# seconds to be added to UTC, tt_isdst tells whether
|
||||
# tm_isdst should be set by localtime(3), and
|
||||
# tt_abbrind serves as an index into the array of
|
||||
# time zone abbreviation characters that follow the
|
||||
# ttinfo structure(s) in the file.
|
||||
|
||||
ttinfo = []
|
||||
|
||||
for i in range(typecnt):
|
||||
ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
|
||||
|
||||
abbr = fileobj.read(charcnt)
|
||||
|
||||
# Then there are tzh_leapcnt pairs of four-byte
|
||||
# values, written in standard byte order; the
|
||||
# first value of each pair gives the time (as
|
||||
# returned by time(2)) at which a leap second
|
||||
# occurs; the second gives the total number of
|
||||
# leap seconds to be applied after the given time.
|
||||
# The pairs of values are sorted in ascending order
|
||||
# by time.
|
||||
|
||||
# Not used, for now
|
||||
if leapcnt:
|
||||
leap = struct.unpack(">%dl" % (leapcnt*2),
|
||||
fileobj.read(leapcnt*8))
|
||||
|
||||
# Then there are tzh_ttisstdcnt standard/wall
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as standard
|
||||
# time or wall clock time, and are used when
|
||||
# a time zone file is used in handling POSIX-style
|
||||
# time zone environment variables.
|
||||
|
||||
if ttisstdcnt:
|
||||
isstd = struct.unpack(">%db" % ttisstdcnt,
|
||||
fileobj.read(ttisstdcnt))
|
||||
|
||||
# Finally, there are tzh_ttisgmtcnt UTC/local
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as UTC or
|
||||
# local time, and are used when a time zone file
|
||||
# is used in handling POSIX-style time zone envi-
|
||||
# ronment variables.
|
||||
|
||||
if ttisgmtcnt:
|
||||
isgmt = struct.unpack(">%db" % ttisgmtcnt,
|
||||
fileobj.read(ttisgmtcnt))
|
||||
|
||||
# ** Everything has been read **
|
||||
|
||||
# Build ttinfo list
|
||||
self._ttinfo_list = []
|
||||
for i in range(typecnt):
|
||||
gmtoff, isdst, abbrind = ttinfo[i]
|
||||
# Round to full-minutes if that's not the case. Python's
|
||||
# datetime doesn't accept sub-minute timezones. Check
|
||||
# http://python.org/sf/1447945 for some information.
|
||||
gmtoff = (gmtoff+30)//60*60
|
||||
tti = _ttinfo()
|
||||
tti.offset = gmtoff
|
||||
tti.delta = datetime.timedelta(seconds=gmtoff)
|
||||
tti.isdst = isdst
|
||||
tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
|
||||
tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
|
||||
tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
|
||||
self._ttinfo_list.append(tti)
|
||||
|
||||
# Replace ttinfo indexes for ttinfo objects.
|
||||
trans_idx = []
|
||||
for idx in self._trans_idx:
|
||||
trans_idx.append(self._ttinfo_list[idx])
|
||||
self._trans_idx = tuple(trans_idx)
|
||||
|
||||
# Set standard, dst, and before ttinfos. before will be
|
||||
# used when a given time is before any transitions,
|
||||
# and will be set to the first non-dst ttinfo, or to
|
||||
# the first dst, if all of them are dst.
|
||||
self._ttinfo_std = None
|
||||
self._ttinfo_dst = None
|
||||
self._ttinfo_before = None
|
||||
if self._ttinfo_list:
|
||||
if not self._trans_list:
|
||||
self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0]
|
||||
else:
|
||||
for i in range(timecnt-1,-1,-1):
|
||||
tti = self._trans_idx[i]
|
||||
if not self._ttinfo_std and not tti.isdst:
|
||||
self._ttinfo_std = tti
|
||||
elif not self._ttinfo_dst and tti.isdst:
|
||||
self._ttinfo_dst = tti
|
||||
if self._ttinfo_std and self._ttinfo_dst:
|
||||
break
|
||||
else:
|
||||
if self._ttinfo_dst and not self._ttinfo_std:
|
||||
self._ttinfo_std = self._ttinfo_dst
|
||||
|
||||
for tti in self._ttinfo_list:
|
||||
if not tti.isdst:
|
||||
self._ttinfo_before = tti
|
||||
break
|
||||
else:
|
||||
self._ttinfo_before = self._ttinfo_list[0]
|
||||
|
||||
# Now fix transition times to become relative to wall time.
|
||||
#
|
||||
# I'm not sure about this. In my tests, the tz source file
|
||||
# is setup to wall time, and in the binary file isstd and
|
||||
# isgmt are off, so it should be in wall time. OTOH, it's
|
||||
# always in gmt time. Let me know if you have comments
|
||||
# about this.
|
||||
laststdoffset = 0
|
||||
self._trans_list = list(self._trans_list)
|
||||
for i in range(len(self._trans_list)):
|
||||
tti = self._trans_idx[i]
|
||||
if not tti.isdst:
|
||||
# This is std time.
|
||||
self._trans_list[i] += tti.offset
|
||||
laststdoffset = tti.offset
|
||||
else:
|
||||
# This is dst time. Convert to std.
|
||||
self._trans_list[i] += laststdoffset
|
||||
self._trans_list = tuple(self._trans_list)
|
||||
|
||||
def _find_ttinfo(self, dt, laststd=0):
|
||||
timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400
|
||||
+ dt.hour * 3600
|
||||
+ dt.minute * 60
|
||||
+ dt.second)
|
||||
idx = 0
|
||||
for trans in self._trans_list:
|
||||
if timestamp < trans:
|
||||
break
|
||||
idx += 1
|
||||
else:
|
||||
return self._ttinfo_std
|
||||
if idx == 0:
|
||||
return self._ttinfo_before
|
||||
if laststd:
|
||||
while idx > 0:
|
||||
tti = self._trans_idx[idx-1]
|
||||
if not tti.isdst:
|
||||
return tti
|
||||
idx -= 1
|
||||
else:
|
||||
return self._ttinfo_std
|
||||
else:
|
||||
return self._trans_idx[idx-1]
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if not self._ttinfo_std:
|
||||
return ZERO
|
||||
return self._find_ttinfo(dt).delta
|
||||
|
||||
def dst(self, dt):
|
||||
if not self._ttinfo_dst:
|
||||
return ZERO
|
||||
tti = self._find_ttinfo(dt)
|
||||
if not tti.isdst:
|
||||
return ZERO
|
||||
|
||||
# The documentation says that utcoffset()-dst() must
|
||||
# be constant for every dt.
|
||||
return tti.delta-self._find_ttinfo(dt, laststd=1).delta
|
||||
|
||||
# An alternative for that would be:
|
||||
#
|
||||
# return self._ttinfo_dst.offset-self._ttinfo_std.offset
|
||||
#
|
||||
# However, this class stores historical changes in the
|
||||
# dst offset, so I belive that this wouldn't be the right
|
||||
# way to implement this.
|
||||
|
||||
def tzname(self, dt):
|
||||
if not self._ttinfo_std:
|
||||
return None
|
||||
return self._find_ttinfo(dt).abbr
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tzfile):
|
||||
return False
|
||||
return (self._trans_list == other._trans_list and
|
||||
self._trans_idx == other._trans_idx and
|
||||
self._ttinfo_list == other._ttinfo_list)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, `self._filename`)
|
||||
|
||||
def __reduce__(self):
|
||||
if not os.path.isfile(self._filename):
|
||||
raise ValueError, "Unpickable %s class" % self.__class__.__name__
|
||||
return (self.__class__, (self._filename,))
|
||||
|
||||
class tzrange(datetime.tzinfo):
|
||||
|
||||
def __init__(self, stdabbr, stdoffset=None,
|
||||
dstabbr=None, dstoffset=None,
|
||||
start=None, end=None):
|
||||
global relativedelta
|
||||
if not relativedelta:
|
||||
from dateutil import relativedelta
|
||||
self._std_abbr = stdabbr
|
||||
self._dst_abbr = dstabbr
|
||||
if stdoffset is not None:
|
||||
self._std_offset = datetime.timedelta(seconds=stdoffset)
|
||||
else:
|
||||
self._std_offset = ZERO
|
||||
if dstoffset is not None:
|
||||
self._dst_offset = datetime.timedelta(seconds=dstoffset)
|
||||
elif dstabbr and stdoffset is not None:
|
||||
self._dst_offset = self._std_offset+datetime.timedelta(hours=+1)
|
||||
else:
|
||||
self._dst_offset = ZERO
|
||||
if dstabbr and start is None:
|
||||
self._start_delta = relativedelta.relativedelta(
|
||||
hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
|
||||
else:
|
||||
self._start_delta = start
|
||||
if dstabbr and end is None:
|
||||
self._end_delta = relativedelta.relativedelta(
|
||||
hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
|
||||
else:
|
||||
self._end_delta = end
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset
|
||||
else:
|
||||
return self._std_offset
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_offset-self._std_offset
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_abbr
|
||||
else:
|
||||
return self._std_abbr
|
||||
|
||||
def _isdst(self, dt):
|
||||
if not self._start_delta:
|
||||
return False
|
||||
year = datetime.datetime(dt.year,1,1)
|
||||
start = year+self._start_delta
|
||||
end = year+self._end_delta
|
||||
dt = dt.replace(tzinfo=None)
|
||||
if start < end:
|
||||
return dt >= start and dt < end
|
||||
else:
|
||||
return dt >= start or dt < end
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, tzrange):
|
||||
return False
|
||||
return (self._std_abbr == other._std_abbr and
|
||||
self._dst_abbr == other._dst_abbr and
|
||||
self._std_offset == other._std_offset and
|
||||
self._dst_offset == other._dst_offset and
|
||||
self._start_delta == other._start_delta and
|
||||
self._end_delta == other._end_delta)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(...)" % self.__class__.__name__
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class tzstr(tzrange):
|
||||
|
||||
def __init__(self, s):
|
||||
global parser
|
||||
if not parser:
|
||||
from dateutil import parser
|
||||
self._s = s
|
||||
|
||||
res = parser._parsetz(s)
|
||||
if res is None:
|
||||
raise ValueError, "unknown string format"
|
||||
|
||||
# Here we break the compatibility with the TZ variable handling.
|
||||
# GMT-3 actually *means* the timezone -3.
|
||||
if res.stdabbr in ("GMT", "UTC"):
|
||||
res.stdoffset *= -1
|
||||
|
||||
# We must initialize it first, since _delta() needs
|
||||
# _std_offset and _dst_offset set. Use False in start/end
|
||||
# to avoid building it two times.
|
||||
tzrange.__init__(self, res.stdabbr, res.stdoffset,
|
||||
res.dstabbr, res.dstoffset,
|
||||
start=False, end=False)
|
||||
|
||||
if not res.dstabbr:
|
||||
self._start_delta = None
|
||||
self._end_delta = None
|
||||
else:
|
||||
self._start_delta = self._delta(res.start)
|
||||
if self._start_delta:
|
||||
self._end_delta = self._delta(res.end, isend=1)
|
||||
|
||||
def _delta(self, x, isend=0):
|
||||
kwargs = {}
|
||||
if x.month is not None:
|
||||
kwargs["month"] = x.month
|
||||
if x.weekday is not None:
|
||||
kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
|
||||
if x.week > 0:
|
||||
kwargs["day"] = 1
|
||||
else:
|
||||
kwargs["day"] = 31
|
||||
elif x.day:
|
||||
kwargs["day"] = x.day
|
||||
elif x.yday is not None:
|
||||
kwargs["yearday"] = x.yday
|
||||
elif x.jyday is not None:
|
||||
kwargs["nlyearday"] = x.jyday
|
||||
if not kwargs:
|
||||
# Default is to start on first sunday of april, and end
|
||||
# on last sunday of october.
|
||||
if not isend:
|
||||
kwargs["month"] = 4
|
||||
kwargs["day"] = 1
|
||||
kwargs["weekday"] = relativedelta.SU(+1)
|
||||
else:
|
||||
kwargs["month"] = 10
|
||||
kwargs["day"] = 31
|
||||
kwargs["weekday"] = relativedelta.SU(-1)
|
||||
if x.time is not None:
|
||||
kwargs["seconds"] = x.time
|
||||
else:
|
||||
# Default is 2AM.
|
||||
kwargs["seconds"] = 7200
|
||||
if isend:
|
||||
# Convert to standard time, to follow the documented way
|
||||
# of working with the extra hour. See the documentation
|
||||
# of the tzinfo class.
|
||||
delta = self._dst_offset-self._std_offset
|
||||
kwargs["seconds"] -= delta.seconds+delta.days*86400
|
||||
return relativedelta.relativedelta(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||
|
||||
class _tzicalvtzcomp:
|
||||
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
||||
tzname=None, rrule=None):
|
||||
self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
|
||||
self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
|
||||
self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom
|
||||
self.isdst = isdst
|
||||
self.tzname = tzname
|
||||
self.rrule = rrule
|
||||
|
||||
class _tzicalvtz(datetime.tzinfo):
|
||||
def __init__(self, tzid, comps=[]):
|
||||
self._tzid = tzid
|
||||
self._comps = comps
|
||||
self._cachedate = []
|
||||
self._cachecomp = []
|
||||
|
||||
def _find_comp(self, dt):
|
||||
if len(self._comps) == 1:
|
||||
return self._comps[0]
|
||||
dt = dt.replace(tzinfo=None)
|
||||
try:
|
||||
return self._cachecomp[self._cachedate.index(dt)]
|
||||
except ValueError:
|
||||
pass
|
||||
lastcomp = None
|
||||
lastcompdt = None
|
||||
for comp in self._comps:
|
||||
if not comp.isdst:
|
||||
# Handle the extra hour in DST -> STD
|
||||
compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True)
|
||||
else:
|
||||
compdt = comp.rrule.before(dt, inc=True)
|
||||
if compdt and (not lastcompdt or lastcompdt < compdt):
|
||||
lastcompdt = compdt
|
||||
lastcomp = comp
|
||||
if not lastcomp:
|
||||
# RFC says nothing about what to do when a given
|
||||
# time is before the first onset date. We'll look for the
|
||||
# first standard component, or the first component, if
|
||||
# none is found.
|
||||
for comp in self._comps:
|
||||
if not comp.isdst:
|
||||
lastcomp = comp
|
||||
break
|
||||
else:
|
||||
lastcomp = comp[0]
|
||||
self._cachedate.insert(0, dt)
|
||||
self._cachecomp.insert(0, lastcomp)
|
||||
if len(self._cachedate) > 10:
|
||||
self._cachedate.pop()
|
||||
self._cachecomp.pop()
|
||||
return lastcomp
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self._find_comp(dt).tzoffsetto
|
||||
|
||||
def dst(self, dt):
|
||||
comp = self._find_comp(dt)
|
||||
if comp.isdst:
|
||||
return comp.tzoffsetdiff
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return self._find_comp(dt).tzname
|
||||
|
||||
def __repr__(self):
|
||||
return "<tzicalvtz %s>" % `self._tzid`
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
class tzical:
|
||||
def __init__(self, fileobj):
|
||||
global rrule
|
||||
if not rrule:
|
||||
from dateutil import rrule
|
||||
|
||||
if isinstance(fileobj, basestring):
|
||||
self._s = fileobj
|
||||
fileobj = open(fileobj)
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._s = fileobj.name
|
||||
else:
|
||||
self._s = `fileobj`
|
||||
|
||||
self._vtz = {}
|
||||
|
||||
self._parse_rfc(fileobj.read())
|
||||
|
||||
def keys(self):
|
||||
return self._vtz.keys()
|
||||
|
||||
def get(self, tzid=None):
|
||||
if tzid is None:
|
||||
keys = self._vtz.keys()
|
||||
if len(keys) == 0:
|
||||
raise ValueError, "no timezones defined"
|
||||
elif len(keys) > 1:
|
||||
raise ValueError, "more than one timezone available"
|
||||
tzid = keys[0]
|
||||
return self._vtz.get(tzid)
|
||||
|
||||
def _parse_offset(self, s):
|
||||
s = s.strip()
|
||||
if not s:
|
||||
raise ValueError, "empty offset"
|
||||
if s[0] in ('+', '-'):
|
||||
signal = (-1,+1)[s[0]=='+']
|
||||
s = s[1:]
|
||||
else:
|
||||
signal = +1
|
||||
if len(s) == 4:
|
||||
return (int(s[:2])*3600+int(s[2:])*60)*signal
|
||||
elif len(s) == 6:
|
||||
return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal
|
||||
else:
|
||||
raise ValueError, "invalid offset: "+s
|
||||
|
||||
def _parse_rfc(self, s):
|
||||
lines = s.splitlines()
|
||||
if not lines:
|
||||
raise ValueError, "empty string"
|
||||
|
||||
# Unfold
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].rstrip()
|
||||
if not line:
|
||||
del lines[i]
|
||||
elif i > 0 and line[0] == " ":
|
||||
lines[i-1] += line[1:]
|
||||
del lines[i]
|
||||
else:
|
||||
i += 1
|
||||
|
||||
tzid = None
|
||||
comps = []
|
||||
invtz = False
|
||||
comptype = None
|
||||
for line in lines:
|
||||
if not line:
|
||||
continue
|
||||
name, value = line.split(':', 1)
|
||||
parms = name.split(';')
|
||||
if not parms:
|
||||
raise ValueError, "empty property name"
|
||||
name = parms[0].upper()
|
||||
parms = parms[1:]
|
||||
if invtz:
|
||||
if name == "BEGIN":
|
||||
if value in ("STANDARD", "DAYLIGHT"):
|
||||
# Process component
|
||||
pass
|
||||
else:
|
||||
raise ValueError, "unknown component: "+value
|
||||
comptype = value
|
||||
founddtstart = False
|
||||
tzoffsetfrom = None
|
||||
tzoffsetto = None
|
||||
rrulelines = []
|
||||
tzname = None
|
||||
elif name == "END":
|
||||
if value == "VTIMEZONE":
|
||||
if comptype:
|
||||
raise ValueError, \
|
||||
"component not closed: "+comptype
|
||||
if not tzid:
|
||||
raise ValueError, \
|
||||
"mandatory TZID not found"
|
||||
if not comps:
|
||||
raise ValueError, \
|
||||
"at least one component is needed"
|
||||
# Process vtimezone
|
||||
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
||||
invtz = False
|
||||
elif value == comptype:
|
||||
if not founddtstart:
|
||||
raise ValueError, \
|
||||
"mandatory DTSTART not found"
|
||||
if tzoffsetfrom is None:
|
||||
raise ValueError, \
|
||||
"mandatory TZOFFSETFROM not found"
|
||||
if tzoffsetto is None:
|
||||
raise ValueError, \
|
||||
"mandatory TZOFFSETFROM not found"
|
||||
# Process component
|
||||
rr = None
|
||||
if rrulelines:
|
||||
rr = rrule.rrulestr("\n".join(rrulelines),
|
||||
compatible=True,
|
||||
ignoretz=True,
|
||||
cache=True)
|
||||
comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
|
||||
(comptype == "DAYLIGHT"),
|
||||
tzname, rr)
|
||||
comps.append(comp)
|
||||
comptype = None
|
||||
else:
|
||||
raise ValueError, \
|
||||
"invalid component end: "+value
|
||||
elif comptype:
|
||||
if name == "DTSTART":
|
||||
rrulelines.append(line)
|
||||
founddtstart = True
|
||||
elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
|
||||
rrulelines.append(line)
|
||||
elif name == "TZOFFSETFROM":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported %s parm: %s "%(name, parms[0])
|
||||
tzoffsetfrom = self._parse_offset(value)
|
||||
elif name == "TZOFFSETTO":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported TZOFFSETTO parm: "+parms[0]
|
||||
tzoffsetto = self._parse_offset(value)
|
||||
elif name == "TZNAME":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported TZNAME parm: "+parms[0]
|
||||
tzname = value
|
||||
elif name == "COMMENT":
|
||||
pass
|
||||
else:
|
||||
raise ValueError, "unsupported property: "+name
|
||||
else:
|
||||
if name == "TZID":
|
||||
if parms:
|
||||
raise ValueError, \
|
||||
"unsupported TZID parm: "+parms[0]
|
||||
tzid = value
|
||||
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
||||
pass
|
||||
else:
|
||||
raise ValueError, "unsupported property: "+name
|
||||
elif name == "BEGIN" and value == "VTIMEZONE":
|
||||
tzid = None
|
||||
comps = []
|
||||
invtz = True
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, `self._s`)
|
||||
|
||||
if sys.platform != "win32":
|
||||
TZFILES = ["/etc/localtime", "localtime"]
|
||||
TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"]
|
||||
else:
|
||||
TZFILES = []
|
||||
TZPATHS = []
|
||||
|
||||
def gettz(name=None):
|
||||
tz = None
|
||||
if not name:
|
||||
try:
|
||||
name = os.environ["TZ"]
|
||||
except KeyError:
|
||||
pass
|
||||
if name is None or name == ":":
|
||||
for filepath in TZFILES:
|
||||
if not os.path.isabs(filepath):
|
||||
filename = filepath
|
||||
for path in TZPATHS:
|
||||
filepath = os.path.join(path, filename)
|
||||
if os.path.isfile(filepath):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if os.path.isfile(filepath):
|
||||
try:
|
||||
tz = tzfile(filepath)
|
||||
break
|
||||
except (IOError, OSError, ValueError):
|
||||
pass
|
||||
else:
|
||||
tz = tzlocal()
|
||||
else:
|
||||
if name.startswith(":"):
|
||||
name = name[:-1]
|
||||
if os.path.isabs(name):
|
||||
if os.path.isfile(name):
|
||||
tz = tzfile(name)
|
||||
else:
|
||||
tz = None
|
||||
else:
|
||||
for path in TZPATHS:
|
||||
filepath = os.path.join(path, name)
|
||||
if not os.path.isfile(filepath):
|
||||
filepath = filepath.replace(' ','_')
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
try:
|
||||
tz = tzfile(filepath)
|
||||
break
|
||||
except (IOError, OSError, ValueError):
|
||||
pass
|
||||
else:
|
||||
tz = None
|
||||
if tzwin:
|
||||
try:
|
||||
tz = tzwin(name)
|
||||
except OSError:
|
||||
pass
|
||||
if not tz:
|
||||
from dateutil.zoneinfo import gettz
|
||||
tz = gettz(name)
|
||||
if not tz:
|
||||
for c in name:
|
||||
# name must have at least one offset to be a tzstr
|
||||
if c in "0123456789":
|
||||
try:
|
||||
tz = tzstr(name)
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
else:
|
||||
if name in ("GMT", "UTC"):
|
||||
tz = tzutc()
|
||||
elif name in time.tzname:
|
||||
tz = tzlocal()
|
||||
return tz
|
||||
|
||||
# vim:ts=4:sw=4:et
|
@ -1,180 +0,0 @@
|
||||
# This code was originally contributed by Jeffrey Harris.
|
||||
import datetime
|
||||
import struct
|
||||
import _winreg
|
||||
|
||||
__author__ = "Jeffrey Harris & Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
|
||||
__all__ = ["tzwin", "tzwinlocal"]
|
||||
|
||||
ONEWEEK = datetime.timedelta(7)
|
||||
|
||||
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
||||
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
||||
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
||||
|
||||
def _settzkeyname():
|
||||
global TZKEYNAME
|
||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
||||
try:
|
||||
_winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
||||
TZKEYNAME = TZKEYNAMENT
|
||||
except WindowsError:
|
||||
TZKEYNAME = TZKEYNAME9X
|
||||
handle.Close()
|
||||
|
||||
_settzkeyname()
|
||||
|
||||
class tzwinbase(datetime.tzinfo):
|
||||
"""tzinfo class based on win32's timezones available in the registry."""
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
return datetime.timedelta(minutes=self._dstoffset)
|
||||
else:
|
||||
return datetime.timedelta(minutes=self._stdoffset)
|
||||
|
||||
def dst(self, dt):
|
||||
if self._isdst(dt):
|
||||
minutes = self._dstoffset - self._stdoffset
|
||||
return datetime.timedelta(minutes=minutes)
|
||||
else:
|
||||
return datetime.timedelta(0)
|
||||
|
||||
def tzname(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dstname
|
||||
else:
|
||||
return self._stdname
|
||||
|
||||
def list():
|
||||
"""Return a list of all time zones known to the system."""
|
||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
||||
tzkey = _winreg.OpenKey(handle, TZKEYNAME)
|
||||
result = [_winreg.EnumKey(tzkey, i)
|
||||
for i in range(_winreg.QueryInfoKey(tzkey)[0])]
|
||||
tzkey.Close()
|
||||
handle.Close()
|
||||
return result
|
||||
list = staticmethod(list)
|
||||
|
||||
def display(self):
|
||||
return self._display
|
||||
|
||||
def _isdst(self, dt):
|
||||
dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
|
||||
self._dsthour, self._dstminute,
|
||||
self._dstweeknumber)
|
||||
dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek,
|
||||
self._stdhour, self._stdminute,
|
||||
self._stdweeknumber)
|
||||
if dston < dstoff:
|
||||
return dston <= dt.replace(tzinfo=None) < dstoff
|
||||
else:
|
||||
return not dstoff <= dt.replace(tzinfo=None) < dston
|
||||
|
||||
|
||||
class tzwin(tzwinbase):
|
||||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
||||
tzkey = _winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name))
|
||||
keydict = valuestodict(tzkey)
|
||||
tzkey.Close()
|
||||
handle.Close()
|
||||
|
||||
self._stdname = keydict["Std"].encode("iso-8859-1")
|
||||
self._dstname = keydict["Dlt"].encode("iso-8859-1")
|
||||
|
||||
self._display = keydict["Display"]
|
||||
|
||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||
tup = struct.unpack("=3l16h", keydict["TZI"])
|
||||
self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1
|
||||
|
||||
(self._stdmonth,
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[4:9]
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[12:17]
|
||||
|
||||
def __repr__(self):
|
||||
return "tzwin(%s)" % repr(self._name)
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, (self._name,))
|
||||
|
||||
|
||||
class tzwinlocal(tzwinbase):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
|
||||
|
||||
tzlocalkey = _winreg.OpenKey(handle, TZLOCALKEYNAME)
|
||||
keydict = valuestodict(tzlocalkey)
|
||||
tzlocalkey.Close()
|
||||
|
||||
self._stdname = keydict["StandardName"].encode("iso-8859-1")
|
||||
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
|
||||
|
||||
try:
|
||||
tzkey = _winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname))
|
||||
_keydict = valuestodict(tzkey)
|
||||
self._display = _keydict["Display"]
|
||||
tzkey.Close()
|
||||
except OSError:
|
||||
self._display = None
|
||||
|
||||
handle.Close()
|
||||
|
||||
self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
||||
self._dstoffset = self._stdoffset-keydict["DaylightBias"]
|
||||
|
||||
|
||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||
tup = struct.unpack("=8h", keydict["StandardStart"])
|
||||
|
||||
(self._stdmonth,
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[1:6]
|
||||
|
||||
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[1:6]
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, ())
|
||||
|
||||
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
||||
"""dayofweek == 0 means Sunday, whichweek 5 means last instance"""
|
||||
first = datetime.datetime(year, month, 1, hour, minute)
|
||||
weekdayone = first.replace(day=((dayofweek-first.isoweekday())%7+1))
|
||||
for n in xrange(whichweek):
|
||||
dt = weekdayone+(whichweek-n)*ONEWEEK
|
||||
if dt.month == month:
|
||||
return dt
|
||||
|
||||
def valuestodict(key):
|
||||
"""Convert a registry key's values to a dictionary."""
|
||||
dict = {}
|
||||
size = _winreg.QueryInfoKey(key)[1]
|
||||
for i in range(size):
|
||||
data = _winreg.EnumValue(key, i)
|
||||
dict[data[0]] = data[1]
|
||||
return dict
|
@ -1,87 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2003-2005 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard python 2.3+
|
||||
datetime module.
|
||||
"""
|
||||
from dateutil.tz import tzfile
|
||||
from tarfile import TarFile
|
||||
import os
|
||||
|
||||
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
|
||||
__license__ = "PSF License"
|
||||
|
||||
__all__ = ["setcachesize", "gettz", "rebuild"]
|
||||
|
||||
CACHE = []
|
||||
CACHESIZE = 10
|
||||
|
||||
class tzfile(tzfile):
|
||||
def __reduce__(self):
|
||||
return (gettz, (self._filename,))
|
||||
|
||||
def getzoneinfofile():
|
||||
filenames = os.listdir(os.path.join(os.path.dirname(__file__)))
|
||||
filenames.sort()
|
||||
filenames.reverse()
|
||||
for entry in filenames:
|
||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
||||
return os.path.join(os.path.dirname(__file__), entry)
|
||||
return None
|
||||
|
||||
ZONEINFOFILE = getzoneinfofile()
|
||||
|
||||
del getzoneinfofile
|
||||
|
||||
def setcachesize(size):
|
||||
global CACHESIZE, CACHE
|
||||
CACHESIZE = size
|
||||
del CACHE[size:]
|
||||
|
||||
def gettz(name):
|
||||
tzinfo = None
|
||||
if ZONEINFOFILE:
|
||||
for cachedname, tzinfo in CACHE:
|
||||
if cachedname == name:
|
||||
break
|
||||
else:
|
||||
tf = TarFile.open(ZONEINFOFILE)
|
||||
try:
|
||||
zonefile = tf.extractfile(name)
|
||||
except KeyError:
|
||||
tzinfo = None
|
||||
else:
|
||||
tzinfo = tzfile(zonefile)
|
||||
tf.close()
|
||||
CACHE.insert(0, (name, tzinfo))
|
||||
del CACHE[CACHESIZE:]
|
||||
return tzinfo
|
||||
|
||||
def rebuild(filename, tag=None, format="gz"):
|
||||
import tempfile, shutil
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||
moduledir = os.path.dirname(__file__)
|
||||
if tag: tag = "-"+tag
|
||||
targetname = "zoneinfo%s.tar.%s" % (tag, format)
|
||||
try:
|
||||
tf = TarFile.open(filename)
|
||||
for name in tf.getnames():
|
||||
if not (name.endswith(".sh") or
|
||||
name.endswith(".tab") or
|
||||
name == "leapseconds"):
|
||||
tf.extract(name, tmpdir)
|
||||
filepath = os.path.join(tmpdir, name)
|
||||
os.system("zic -d %s %s" % (zonedir, filepath))
|
||||
tf.close()
|
||||
target = os.path.join(moduledir, targetname)
|
||||
for entry in os.listdir(moduledir):
|
||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
||||
os.unlink(os.path.join(moduledir, entry))
|
||||
tf = TarFile.open(target, "w:%s" % format)
|
||||
for entry in os.listdir(zonedir):
|
||||
entrypath = os.path.join(zonedir, entry)
|
||||
tf.add(entrypath, entry)
|
||||
tf.close()
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
Binary file not shown.
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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. <channel> 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'])
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
78
setup.py
78
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',
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
]
|
||||
|
24
src/conf.py
24
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',
|
||||
|
@ -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."""
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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 = []
|
||||
|
12
src/ircdb.py
12
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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
16
src/test.py
16
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
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user