summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2015-04-22 02:33:32 +0200
committerVictor Stinner <victor.stinner@gmail.com>2015-04-22 02:33:32 +0200
commit916a56715c777e39461627ea564bc0625712c733 (patch)
treea58fb49cd65f9b7ff24029aeb4dfaccd657128dd
parentd705a0524606c7b9b11f935de5a69d173c61ec0d (diff)
parent6c4ef4b3876812e6bbbb3e4748793997067aa85c (diff)
downloadpaste-git-916a56715c777e39461627ea564bc0625712c733.tar.gz
Merged in mfrobben/paste (pull request #21)
Fix bad reference to iterator variable
-rw-r--r--.hgignore1
-rw-r--r--MANIFEST.in2
-rw-r--r--docs/news.txt10
-rw-r--r--paste/__init__.py2
-rw-r--r--paste/auth/basic.py4
-rw-r--r--paste/auth/cookie.py19
-rw-r--r--paste/auth/digest.py29
-rw-r--r--paste/auth/form.py2
-rw-r--r--paste/auth/grantip.py14
-rw-r--r--paste/auth/open_id.py10
-rw-r--r--paste/cascade.py6
-rw-r--r--paste/cgiapp.py31
-rw-r--r--paste/cgitb_catcher.py8
-rw-r--r--paste/cowbell/__init__.py2
-rwxr-xr-xpaste/debug/debugapp.py2
-rwxr-xr-xpaste/debug/doctest_webapp.py13
-rw-r--r--paste/debug/fsdiff.py17
-rw-r--r--paste/debug/prints.py2
-rw-r--r--paste/debug/profile.py8
-rwxr-xr-xpaste/debug/testserver.py6
-rw-r--r--paste/debug/watchthreads.py8
-rw-r--r--paste/debug/wdg_validate.py5
-rw-r--r--paste/errordocument.py16
-rw-r--r--paste/evalexception/middleware.py11
-rw-r--r--paste/exceptions/collector.py12
-rw-r--r--paste/exceptions/errormiddleware.py36
-rw-r--r--paste/exceptions/formatter.py6
-rw-r--r--paste/exceptions/serial_number_generator.py2
-rw-r--r--paste/fileapp.py12
-rw-r--r--paste/fixture.py72
-rw-r--r--paste/flup_session.py4
-rw-r--r--paste/gzipper.py5
-rw-r--r--paste/httpexceptions.py7
-rw-r--r--paste/httpheaders.py39
-rwxr-xr-xpaste/httpserver.py3
-rw-r--r--paste/lint.py22
-rw-r--r--paste/modpython.py60
-rw-r--r--paste/proxy.py56
-rw-r--r--paste/recursive.py8
-rw-r--r--paste/registry.py16
-rw-r--r--paste/reloader.py4
-rw-r--r--paste/request.py13
-rw-r--r--paste/session.py5
-rw-r--r--paste/url.py33
-rw-r--r--paste/util/PySourceColor.py4204
-rw-r--r--paste/util/UserDict24.py167
-rw-r--r--paste/util/dateinterval.py6
-rw-r--r--paste/util/datetimeutil.py720
-rw-r--r--paste/util/doctest24.py2665
-rw-r--r--paste/util/filemixin.py2
-rw-r--r--paste/util/finddata.py2
-rw-r--r--paste/util/findpackage.py2
-rw-r--r--paste/util/intset.py36
-rw-r--r--paste/util/ip4.py11
-rw-r--r--paste/util/looper.py11
-rw-r--r--paste/util/mimeparse.py4
-rw-r--r--paste/util/multidict.py39
-rw-r--r--paste/util/quoting.py16
-rw-r--r--paste/util/scgiserver.py8
-rw-r--r--paste/util/string24.py531
-rw-r--r--paste/util/subprocess24.py1152
-rw-r--r--paste/util/template.py22
-rw-r--r--paste/util/threadedprint.py6
-rw-r--r--paste/wsgilib.py34
-rw-r--r--paste/wsgiwrappers.py51
-rw-r--r--setup.cfg2
-rw-r--r--setup.py23
-rwxr-xr-xtests/cgiapp_data/error.cgi2
-rwxr-xr-xtests/cgiapp_data/form.cgi10
-rwxr-xr-xtests/cgiapp_data/ok.cgi9
-rwxr-xr-xtests/cgiapp_data/stderr.cgi12
-rw-r--r--tests/test_auth/test_auth_cookie.py15
-rw-r--r--tests/test_auth/test_auth_digest.py9
-rw-r--r--tests/test_cgiapp.py6
-rw-r--r--tests/test_cgitb_catcher.py15
-rw-r--r--tests/test_config.py76
-rw-r--r--tests/test_doctests.py2
-rw-r--r--tests/test_errordocument.py12
-rw-r--r--tests/test_exceptions/test_error_middleware.py20
-rw-r--r--tests/test_exceptions/test_formatter.py3
-rw-r--r--tests/test_exceptions/test_httpexceptions.py21
-rw-r--r--tests/test_fileapp.py71
-rw-r--r--tests/test_grantip.py19
-rw-r--r--tests/test_gzipper.py10
-rw-r--r--tests/test_import_string.py4
-rw-r--r--tests/test_multidict.py88
-rw-r--r--tests/test_proxy.py2
-rw-r--r--tests/test_recursive.py22
-rw-r--r--tests/test_registry.py46
-rw-r--r--tests/test_request.py14
-rw-r--r--tests/test_request_form.py9
-rw-r--r--tests/test_response.py2
-rw-r--r--tests/test_session.py28
-rw-r--r--tests/test_urlmap.py10
-rw-r--r--tests/test_urlparser.py12
-rw-r--r--tests/test_util/test_datetimeutil.py270
-rw-r--r--tests/test_util/test_mimeparse.py472
-rw-r--r--tests/test_wsgiwrappers.py21
-rw-r--r--tests/urlparser_data/hook/app.py8
-rw-r--r--tests/urlparser_data/hook/index.py7
-rw-r--r--tests/urlparser_data/not_found/simple/__init__.py2
-rw-r--r--tests/urlparser_data/not_found/user/list.py7
-rw-r--r--tests/urlparser_data/python/simpleapp.py3
-rw-r--r--tests/urlparser_data/python/stream.py6
-rw-r--r--tests/urlparser_data/python/sub/simpleapp.py4
-rw-r--r--tox.ini10
106 files changed, 3706 insertions, 8010 deletions
diff --git a/.hgignore b/.hgignore
index 09b0959..36cfdf7 100644
--- a/.hgignore
+++ b/.hgignore
@@ -11,3 +11,4 @@ syntax: glob
dist/
build/
docs/_build
+.tox
diff --git a/MANIFEST.in b/MANIFEST.in
index d310570..cc3f4ba 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,6 +2,8 @@ recursive-include docs *.txt *.css *.js
include docs/_templates/*.html
include docs/conf.py
include docs/test_server.ini
+include regen-docs
+include tox.ini
recursive-exclude docs/_build/_sources *
recursive-include docs/_build *.html
recursive-include tests *.txt *.py *.cgi *.html
diff --git a/docs/news.txt b/docs/news.txt
index 1239c87..9f90a39 100644
--- a/docs/news.txt
+++ b/docs/news.txt
@@ -3,15 +3,15 @@ News
.. contents::
-hg tip
-------
+2.0
+---
+
+* Experimental Python 3 support.
* paste now requires the six module.
* Drop support of Python 2.5 and older.
-* Experimental Python 3 support.
-
* Fixed ``egg:Paste#cgi``
* In ``paste.httpserver``: give a 100 Continue response even when the
@@ -23,6 +23,8 @@ hg tip
* Fixed parsing of paths beginning with multiple forward slashes.
+* Add tox.ini to run tests with tox on Python 2.6, 2.7 and 3.4.
+
1.7.5.1
-------
diff --git a/paste/__init__.py b/paste/__init__.py
index ba66606..4e2d638 100644
--- a/paste/__init__.py
+++ b/paste/__init__.py
@@ -6,7 +6,7 @@ try:
except ImportError:
# don't prevent use of paste if pkg_resources isn't installed
from pkgutil import extend_path
- __path__ = extend_path(__path__, __name__)
+ __path__ = extend_path(__path__, __name__)
try:
import modulefinder
diff --git a/paste/auth/basic.py b/paste/auth/basic.py
index 69db128..24d1731 100644
--- a/paste/auth/basic.py
+++ b/paste/auth/basic.py
@@ -108,14 +108,14 @@ def make_basic(app, global_conf, realm, authfunc, **kw):
use = egg:Paste#auth_basic
realm=myrealm
authfunc=somepackage.somemodule:somefunction
-
+
"""
from paste.util.import_string import eval_import
import types
authfunc = eval_import(authfunc)
assert isinstance(authfunc, types.FunctionType), "authfunc must resolve to a function"
return AuthBasicHandler(app, realm, authfunc)
-
+
if "__main__" == __name__:
import doctest
diff --git a/paste/auth/cookie.py b/paste/auth/cookie.py
index c636824..8f11d1b 100644
--- a/paste/auth/cookie.py
+++ b/paste/auth/cookie.py
@@ -74,7 +74,10 @@ class CookieTooLarge(RuntimeError):
_all_chars = ''.join([chr(x) for x in range(0, 255)])
def new_secret():
""" returns a 64 byte secret """
- return ''.join(random.sample(_all_chars, 64))
+ secret = ''.join(random.sample(_all_chars, 64))
+ if six.PY3:
+ secret = secret.encode('utf8')
+ return secret
class AuthCookieSigner(object):
"""
@@ -137,12 +140,16 @@ class AuthCookieSigner(object):
need to be escaped and quoted). The expiration of this
cookie is handled server-side in the auth() function.
"""
+ timestamp = make_time(time.time() + 60*self.timeout)
+ if six.PY3:
+ content = content.encode('utf8')
+ timestamp = timestamp.encode('utf8')
cookie = base64.encodestring(
hmac.new(self.secret, content, sha1).digest() +
- make_time(time.time() + 60*self.timeout) +
+ timestamp +
content)
- cookie = cookie.replace("/", "_").replace("=", "~")
- cookie = cookie.replace('\n', '').replace('\r', '')
+ cookie = cookie.replace(b"/", b"_").replace(b"=", b"~")
+ cookie = cookie.replace(b'\n', b'').replace(b'\r', b'')
if len(cookie) > self.maxlen:
raise CookieTooLarge(content, cookie)
return cookie
@@ -298,6 +305,8 @@ class AuthCookieHandler(object):
if content:
content = ";".join(content)
content = self.signer.sign(content)
+ if six.PY3:
+ content = content.decode('utf8')
cookie = '%s=%s; Path=/;' % (self.cookie_name, content)
if 'https' == environ['wsgi.url_scheme']:
cookie += ' secure;'
@@ -368,7 +377,7 @@ def make_auth_cookie(
The maximum length of the cookie that is sent (default 4k,
which is a typical browser maximum)
-
+
"""
if isinstance(scanlist, six.string_types):
scanlist = scanlist.split()
diff --git a/paste/auth/digest.py b/paste/auth/digest.py
index 798f447..85e0362 100644
--- a/paste/auth/digest.py
+++ b/paste/auth/digest.py
@@ -37,6 +37,7 @@ except ImportError:
from md5 import md5
import time, random
from six.moves.urllib.parse import quote as url_quote
+import six
def _split_auth_string(auth_string):
""" split a digest auth string into individual key=value strings """
@@ -68,7 +69,10 @@ def _auth_to_kv_pairs(auth_string):
def digest_password(realm, username, password):
""" construct the appropriate hashcode needed for HTTP digest """
- return md5("%s:%s:%s" % (username, realm, password)).hexdigest()
+ content = "%s:%s:%s" % (username, realm, password)
+ if six.PY3:
+ content = content.encode('utf8')
+ return md5(content).hexdigest()
class AuthDigestAuthenticator(object):
""" implementation of RFC 2617 - HTTP Digest Authentication """
@@ -79,10 +83,16 @@ class AuthDigestAuthenticator(object):
def build_authentication(self, stale = ''):
""" builds the authentication error """
- nonce = md5(
- "%s:%s" % (time.time(), random.random())).hexdigest()
- opaque = md5(
- "%s:%s" % (time.time(), random.random())).hexdigest()
+ content = "%s:%s" % (time.time(), random.random())
+ if six.PY3:
+ content = content.encode('utf-8')
+ nonce = md5(content).hexdigest()
+
+ content = "%s:%s" % (time.time(), random.random())
+ if six.PY3:
+ content = content.encode('utf-8')
+ opaque = md5(content).hexdigest()
+
self.nonce[nonce] = None
parts = {'realm': self.realm, 'qop': 'auth',
'nonce': nonce, 'opaque': opaque }
@@ -97,17 +107,22 @@ class AuthDigestAuthenticator(object):
""" computes the authentication, raises error if unsuccessful """
if not ha1:
return self.build_authentication()
- ha2 = md5('%s:%s' % (method, path)).hexdigest()
+ content = '%s:%s' % (method, path)
+ if six.PY3:
+ content = content.encode('utf8')
+ ha2 = md5(content).hexdigest()
if qop:
chk = "%s:%s:%s:%s:%s:%s" % (ha1, nonce, nc, cnonce, qop, ha2)
else:
chk = "%s:%s:%s" % (ha1, nonce, ha2)
+ if six.PY3:
+ chk = chk.encode('utf8')
if response != md5(chk).hexdigest():
if nonce in self.nonce:
del self.nonce[nonce]
return self.build_authentication()
pnc = self.nonce.get(nonce,'00000000')
- if nc <= pnc:
+ if pnc is not None and nc <= pnc:
if nonce in self.nonce:
del self.nonce[nonce]
return self.build_authentication(stale = True)
diff --git a/paste/auth/form.py b/paste/auth/form.py
index 4e6aa49..9be82a2 100644
--- a/paste/auth/form.py
+++ b/paste/auth/form.py
@@ -131,7 +131,7 @@ def make_form(app, global_conf, realm, authfunc, **kw):
use = egg:Paste#auth_form
realm=myrealm
authfunc=somepackage.somemodule:somefunction
-
+
"""
from paste.util.import_string import eval_import
import types
diff --git a/paste/auth/grantip.py b/paste/auth/grantip.py
index 4ea6df5..3fe6e1c 100644
--- a/paste/auth/grantip.py
+++ b/paste/auth/grantip.py
@@ -38,7 +38,7 @@ class GrantIPMiddleware(object):
if roles and isinstance(roles, six.string_types):
roles = roles.split(',')
return (username, roles)
-
+
def __call__(self, environ, start_response):
addr = ip4.ip2int(environ['REMOTE_ADDR'], False)
remove_user = False
@@ -62,7 +62,7 @@ class GrantIPMiddleware(object):
def _set_roles(self, environ, roles):
cur_roles = environ.get('REMOTE_USER_TOKENS', '').split(',')
# Get rid of empty roles:
- cur_roles = filter(None, cur_roles)
+ cur_roles = list(filter(None, cur_roles))
remove_roles = []
for role in roles:
if role.startswith('-'):
@@ -74,8 +74,8 @@ class GrantIPMiddleware(object):
if role in cur_roles:
cur_roles.remove(role)
environ['REMOTE_USER_TOKENS'] = ','.join(cur_roles)
-
-
+
+
def make_grantip(app, global_conf, clobber_username=False, **kw):
"""
Grant roles or usernames based on IP addresses.
@@ -93,7 +93,7 @@ def make_grantip(app, global_conf, clobber_username=False, **kw):
192.168.0.7 = joe
# And one IP is should not be logged in:
192.168.0.10 = __remove__:-editor
-
+
"""
from paste.deploy.converters import asbool
clobber_username = asbool(clobber_username)
@@ -110,5 +110,5 @@ def make_grantip(app, global_conf, clobber_username=False, **kw):
role = ''
ip_map[key] = value
return GrantIPMiddleware(app, ip_map, clobber_username)
-
-
+
+
diff --git a/paste/auth/open_id.py b/paste/auth/open_id.py
index 967e699..f79f7f8 100644
--- a/paste/auth/open_id.py
+++ b/paste/auth/open_id.py
@@ -91,20 +91,20 @@ class AuthOpenIDHandler(object):
``app``
Your WSGI app to call
-
+
``data_store_path``
Directory to store crypto data in for use with OpenID servers.
-
+
``auth_prefix``
Location for authentication process/verification
-
+
``login_redirect``
Location to load after successful process of login
-
+
``catch_401``
If true, then any 401 responses will turn into open ID login
requirements.
-
+
``url_to_username``
A function called like ``url_to_username(environ, url)``, which should
return a string username. If not given, the URL will be the username.
diff --git a/paste/cascade.py b/paste/cascade.py
index 424794e..8207ae3 100644
--- a/paste/cascade.py
+++ b/paste/cascade.py
@@ -15,7 +15,7 @@ __all__ = ['Cascade']
def make_cascade(loader, global_conf, catch='404', **local_conf):
"""
Entry point for Paste Deploy configuration
-
+
Expects configuration like::
[composit:cascade]
@@ -39,7 +39,7 @@ def make_cascade(loader, global_conf, catch='404', **local_conf):
apps.sort()
apps = [app for name, app in apps]
return Cascade(apps, catch=catch)
-
+
class Cascade(object):
"""
@@ -70,7 +70,7 @@ class Cascade(object):
self.catch_codes[code] = exc
self.catch_exceptions.append(exc)
self.catch_exceptions = tuple(self.catch_exceptions)
-
+
def __call__(self, environ, start_response):
"""
WSGI application interface
diff --git a/paste/cgiapp.py b/paste/cgiapp.py
index c37ba4c..e5a62f4 100644
--- a/paste/cgiapp.py
+++ b/paste/cgiapp.py
@@ -12,6 +12,7 @@ try:
import select
except ImportError:
select = None
+import six
from paste.util import converters
@@ -119,18 +120,18 @@ class CGIWriter(object):
self.headers = []
self.headers_finished = False
self.writer = None
- self.buffer = ''
+ self.buffer = b''
def write(self, data):
if self.headers_finished:
self.writer(data)
return
self.buffer += data
- while '\n' in self.buffer:
- if '\r\n' in self.buffer and self.buffer.find('\r\n') < self.buffer.find('\n'):
- line1, self.buffer = self.buffer.split('\r\n', 1)
+ while b'\n' in self.buffer:
+ if b'\r\n' in self.buffer and self.buffer.find(b'\r\n') < self.buffer.find(b'\n'):
+ line1, self.buffer = self.buffer.split(b'\r\n', 1)
else:
- line1, self.buffer = self.buffer.split('\n', 1)
+ line1, self.buffer = self.buffer.split(b'\n', 1)
if not line1:
self.headers_finished = True
self.writer = self.start_response(
@@ -140,13 +141,16 @@ class CGIWriter(object):
del self.headers
del self.status
break
- elif ':' not in line1:
+ elif b':' not in line1:
raise CGIError(
"Bad header line: %r" % line1)
else:
- name, value = line1.split(':', 1)
+ name, value = line1.split(b':', 1)
value = value.lstrip()
name = name.strip()
+ if six.PY3:
+ name = name.decode('utf8')
+ value = value.decode('utf8')
if name.lower() == 'status':
if ' ' not in value:
# WSGI requires this space, sometimes CGI scripts don't set it:
@@ -161,6 +165,7 @@ class StdinReader(object):
self.stdin = stdin
self.content_length = content_length
+ @classmethod
def from_environ(cls, environ):
length = environ.get('CONTENT_LENGTH')
if length:
@@ -169,11 +174,9 @@ class StdinReader(object):
length = 0
return cls(environ['wsgi.input'], length)
- from_environ = classmethod(from_environ)
-
def read(self, size=None):
if not self.content_length:
- return ''
+ return b''
if size is None:
text = self.stdin.read(self.content_length)
else:
@@ -193,7 +196,7 @@ def proc_communicate(proc, stdin=None, stdout=None, stderr=None):
"""
read_set = []
write_set = []
- input_buffer = ''
+ input_buffer = b''
trans_nl = proc.universal_newlines and hasattr(open, 'newlines')
if proc.stdin:
@@ -222,7 +225,7 @@ def proc_communicate(proc, stdin=None, stdout=None, stderr=None):
# When select has indicated that the file is writable,
# we can write up to PIPE_BUF bytes without risk
# blocking. POSIX defines PIPE_BUF >= 512
- next, input_buffer = input_buffer, ''
+ next, input_buffer = input_buffer, b''
next_len = 512-len(next)
if next_len:
next += stdin.read(next_len)
@@ -236,7 +239,7 @@ def proc_communicate(proc, stdin=None, stdout=None, stderr=None):
if proc.stdout in rlist:
data = os.read(proc.stdout.fileno(), 1024)
- if data == "":
+ if data == b"":
proc.stdout.close()
read_set.remove(proc.stdout)
if trans_nl:
@@ -245,7 +248,7 @@ def proc_communicate(proc, stdin=None, stdout=None, stderr=None):
if proc.stderr in rlist:
data = os.read(proc.stderr.fileno(), 1024)
- if data == "":
+ if data == b"":
proc.stderr.close()
read_set.remove(proc.stderr)
if trans_nl:
diff --git a/paste/cgitb_catcher.py b/paste/cgitb_catcher.py
index 55a346f..f88ffb8 100644
--- a/paste/cgitb_catcher.py
+++ b/paste/cgitb_catcher.py
@@ -49,6 +49,8 @@ class CgitbMiddleware(object):
[('content-type', 'text/html')],
exc_info)
response = self.exception_handler(exc_info, environ)
+ if six.PY3:
+ response = response.encode('utf8')
return [response]
def catching_iter(self, app_iter, environ):
@@ -72,6 +74,8 @@ class CgitbMiddleware(object):
response += (
'<hr noshade>Error in .close():<br>%s'
% close_response)
+ if six.PY3:
+ response = response.encode('utf8')
yield response
def exception_handler(self, exc_info, environ):
@@ -83,7 +87,7 @@ class CgitbMiddleware(object):
format=self.format)
hook(*exc_info)
return dummy_file.getvalue()
-
+
def make_cgitb_middleware(app, global_conf,
display=NoDefault,
logdir=None,
@@ -92,7 +96,7 @@ def make_cgitb_middleware(app, global_conf,
"""
Wraps the application in the ``cgitb`` (standard library)
error catcher.
-
+
display:
If true (or debug is set in the global configuration)
then the traceback will be displayed in the browser
diff --git a/paste/cowbell/__init__.py b/paste/cowbell/__init__.py
index 43b7097..5a0d22d 100644
--- a/paste/cowbell/__init__.py
+++ b/paste/cowbell/__init__.py
@@ -45,7 +45,7 @@ function showSomewhere() {
var sec, el;
if (cowbellState == 'hidden') {
el = document.getElementById('cowbell-ascending');
- lastCowbellPosition = [parseInt(Math.random()*(window.innerWidth-200)),
+ lastCowbellPosition = [parseInt(Math.random()*(window.innerWidth-200)),
parseInt(Math.random()*(window.innerHeight-200))];
el.style.left = lastCowbellPosition[0] + 'px';
el.style.top = lastCowbellPosition[1] + 'px';
diff --git a/paste/debug/debugapp.py b/paste/debug/debugapp.py
index 8c7c7c2..f752c36 100755
--- a/paste/debug/debugapp.py
+++ b/paste/debug/debugapp.py
@@ -17,7 +17,7 @@ class SimpleApplication(object):
Produces a simple web page
"""
def __call__(self, environ, start_response):
- body = "<html><body>simple</body></html>"
+ body = b"<html><body>simple</body></html>"
start_response("200 OK", [('Content-Type', 'text/html'),
('Content-Length', str(len(body)))])
return [body]
diff --git a/paste/debug/doctest_webapp.py b/paste/debug/doctest_webapp.py
index f399ac3..ffcfaa7 100755
--- a/paste/debug/doctest_webapp.py
+++ b/paste/debug/doctest_webapp.py
@@ -8,10 +8,7 @@
These are functions for use when doctest-testing a document.
"""
-try:
- import subprocess
-except ImportError:
- from paste.util import subprocess24 as subprocess
+import subprocess
import doctest
import os
import sys
@@ -214,7 +211,7 @@ def show_file(path, version, description=None, data=None):
data = f.read()
f.close()
if ext == '.py':
- html = ('<div class="source-code">%s</div>'
+ html = ('<div class="source-code">%s</div>'
% PySourceColor.str2html(data, PySourceColor.dark))
else:
html = '<pre class="source-code">%s</pre>' % cgi.escape(data, 1)
@@ -241,7 +238,7 @@ def write_data(path, data):
f = open(path, 'wb')
f.write(data)
f.close()
-
+
def change_file(path, changes):
f = open(os.path.abspath(path), 'rb')
@@ -282,7 +279,7 @@ class LongFormDocTestParser(doctest.DocTestParser):
(?![ ]*>>>) # Not a line starting with PS1
.*$\n? # But any other line
)*))
- |
+ |
(?: # This is for longer commands that are prefixed with a reST
# comment like '.. run:' (two colons makes that a directive).
# These commands cannot have any output.
@@ -331,7 +328,7 @@ class LongFormDocTestParser(doctest.DocTestParser):
# Get the example's indentation level.
runner = m.group('run') or ''
indent = len(m.group('%sindent' % runner))
-
+
# Divide source into lines; check that they're properly
# indented; and then strip their indentation & prompts.
source_lines = m.group('%ssource' % runner).split('\n')
diff --git a/paste/debug/fsdiff.py b/paste/debug/fsdiff.py
index 156a2e4..6f9ec2d 100644
--- a/paste/debug/fsdiff.py
+++ b/paste/debug/fsdiff.py
@@ -17,12 +17,8 @@ try:
# Python 3
import collections.UserDict as IterableUserDict
except ImportError:
- try:
- # Python 2.5-2.7
- from UserDict import IterableUserDict
- except ImportError:
- # Python <= 2.4
- from paste.util.UserDict24 import IterableUserDict
+ # Python 2.5-2.7
+ from UserDict import IterableUserDict
import operator
import re
@@ -131,13 +127,6 @@ class Snapshot(IterableUserDict):
return True
return False
- def _ignore_file(self, fn):
- if fn in self.ignore_paths:
- return True
- if self.ignore_hidden and os.path.basename(fn).startswith('.'):
- return True
- return False
-
def _find_traverse(self, path, result):
full = os.path.join(self.base_path, path)
if os.path.isdir(full):
@@ -298,7 +287,7 @@ class Dir(File):
"Directory %r doesn't have content" % self)
bytes = property(bytes__get)
-
+
def _space_prefix(pref, full, sep=None, indent=None, include_sep=True):
"""
diff --git a/paste/debug/prints.py b/paste/debug/prints.py
index 6cc3f7d..b660bfa 100644
--- a/paste/debug/prints.py
+++ b/paste/debug/prints.py
@@ -132,7 +132,7 @@ class PrintDebugMiddleware(object):
_body_re = re.compile(r'<body[^>]*>', re.I)
_explicit_re = re.compile(r'<pre\s*[^>]*id="paste-debug-prints".*?>',
re.I+re.S)
-
+
def add_log(self, html, log):
if not log:
return html
diff --git a/paste/debug/profile.py b/paste/debug/profile.py
index 036c805..470a54a 100644
--- a/paste/debug/profile.py
+++ b/paste/debug/profile.py
@@ -100,7 +100,7 @@ def profile_decorator(**options):
"""
Profile a single function call.
-
+
Used around a function, like::
@profile_decorator(options...)
@@ -203,14 +203,14 @@ class DecoratedProfile(object):
# We captured an exception earlier, now we re-raise it
six.reraise(exc_info[0], exc_info[1], exc_info[2])
return result
-
+
def format_function(self, func, *args, **kw):
args = map(repr, args)
args.extend(
['%s=%r' % (k, v) for k, v in kw.items()])
return '%s(%s)' % (func.__name__, ', '.join(args))
-
-
+
+
def make_profile_middleware(
app, global_conf,
log_filename='profile.log.tmp',
diff --git a/paste/debug/testserver.py b/paste/debug/testserver.py
index 4817161..8044c7c 100755
--- a/paste/debug/testserver.py
+++ b/paste/debug/testserver.py
@@ -28,7 +28,7 @@ class WSGIRegressionServer(WSGIServer):
self.pending = []
self.timeout = self.defaulttimeout
# this is a local connection, be quick
- self.socket.settimeout(2)
+ self.socket.settimeout(2)
def serve_forever(self):
from threading import Thread
thread = Thread(target=self.serve_pending)
@@ -75,13 +75,13 @@ if __name__ == '__main__':
def fetch(path):
# tell the server to humor exactly one more request
server.accept(1)
- # not needed; but this is what you do if the server
+ # not needed; but this is what you do if the server
# may not respond in a resonable time period
import socket
socket.setdefaulttimeout(5)
# build a uri, fetch and return
return urlopen(baseuri + path).read()
-
+
assert "PATH_INFO: /foo" in fetch("/foo")
assert "PATH_INFO: /womble" in fetch("/womble")
diff --git a/paste/debug/watchthreads.py b/paste/debug/watchthreads.py
index c877942..b06ccea 100644
--- a/paste/debug/watchthreads.py
+++ b/paste/debug/watchthreads.py
@@ -123,7 +123,7 @@ page_template = HTMLTemplate('''
}
return false
">&#9656; Show environ</a>
-
+
<div id="environ-{{thread.thread_id}}" style="display: none">
{{if thread.environ:}}
<table class="environ">
@@ -221,7 +221,7 @@ class WatchThreads(object):
thread.uri_short = shorten(thread.uri)
thread.environ = worker_environ
thread.traceback = traceback_thread(thread_id)
-
+
page = page_template.substitute(
title="Thread Pool Worker Tracker",
nworkers=nworkers,
@@ -255,7 +255,7 @@ class WatchThreads(object):
exc = httpexceptions.HTTPFound(
headers=[('Location', script_name+'?kill=%s' % thread_id)])
return exc(environ, start_response)
-
+
def traceback_thread(thread_id):
"""
Returns a plain-text traceback of the given thread, or None if it
@@ -296,7 +296,7 @@ def format_environ(environ):
key=cgi.escape(str(key)),
value='Error in <code>repr()</code>: %s' % e))
return ''.join(environ_rows)
-
+
def format_time(time_length):
if time_length >= 60*60:
# More than an hour
diff --git a/paste/debug/wdg_validate.py b/paste/debug/wdg_validate.py
index d3678fb..225baf9 100644
--- a/paste/debug/wdg_validate.py
+++ b/paste/debug/wdg_validate.py
@@ -6,10 +6,7 @@ Middleware that tests the validity of all generated HTML using the
"""
from cStringIO import StringIO
-try:
- import subprocess
-except ImportError:
- from paste.util import subprocess24 as subprocess
+import subprocess
from paste.response import header_value
import re
import cgi
diff --git a/paste/errordocument.py b/paste/errordocument.py
index 62224b3..34f2d4a 100644
--- a/paste/errordocument.py
+++ b/paste/errordocument.py
@@ -15,6 +15,7 @@ from six.moves.urllib import parse as urlparse
from paste.recursive import ForwardRequestException, RecursiveMiddleware, RecursionLoop
from paste.util import converters
from paste.response import replace_header
+import six
def forward(app, codes):
"""
@@ -85,10 +86,16 @@ class StatusKeeper(object):
try:
return self.app(environ, keep_status_start_response)
except RecursionLoop as e:
- environ['wsgi.errors'].write('Recursion error getting error page: %s\n' % e)
+ line = 'Recursion error getting error page: %s\n' % e
+ if six.PY3:
+ line = line.encode('utf8')
+ environ['wsgi.errors'].write(line)
keep_status_start_response('500 Server Error', [('Content-type', 'text/plain')], sys.exc_info())
- return ['Error: %s. (Error page could not be fetched)'
- % self.status]
+ body = ('Error: %s. (Error page could not be fetched)'
+ % self.status)
+ if six.PY3:
+ body = body.encode('utf8')
+ return [body]
class StatusBasedForward(object):
@@ -161,7 +168,6 @@ class StatusBasedForward(object):
def __call__(self, environ, start_response):
url = []
- writer = []
def change_response(status, headers, exc_info=None):
status_code = status.split(' ')
@@ -363,7 +369,7 @@ class _StatusBasedRedirect(object):
forward.start_response = eat_start_response
try:
app_iter = forward(url_, new_environ)
- except InvalidForward as e:
+ except InvalidForward:
code, message = code_message[0]
environ['wsgi.errors'].write(
'Error occurred in '
diff --git a/paste/evalexception/middleware.py b/paste/evalexception/middleware.py
index 481d498..da7876d 100644
--- a/paste/evalexception/middleware.py
+++ b/paste/evalexception/middleware.py
@@ -332,7 +332,10 @@ class EvalException(object):
start_response('500 Internal Server Error',
headers,
exc_info)
- environ['wsgi.errors'].write('Debug at: %s\n' % view_uri)
+ msg = 'Debug at: %s\n' % view_uri
+ if six.PY3:
+ msg = msg.encode('utf8')
+ environ['wsgi.errors'].write(msg)
exc_data = collector.collect_exception(*exc_info)
debug_info = DebugInfo(count, exc_info, exc_data, base_path,
@@ -341,7 +344,7 @@ class EvalException(object):
self.debug_infos[count] = debug_info
if self.xmlhttp_key:
- get_vars = wsgilib.parse_querystring(environ)
+ get_vars = request.parse_querystring(environ)
if dict(get_vars).get(self.xmlhttp_key):
exc_data = collector.collect_exception(*exc_info)
html = formatter.format_html(
@@ -355,7 +358,7 @@ class EvalException(object):
def exception_handler(self, exc_info, environ):
simple_html_error = False
if self.xmlhttp_key:
- get_vars = wsgilib.parse_querystring(environ)
+ get_vars = request.parse_querystring(environ)
if dict(get_vars).get(self.xmlhttp_key):
simple_html_error = True
return errormiddleware.handle_exception(
@@ -417,6 +420,8 @@ class DebugInfo(object):
'repost_button': repost_button or '',
'head_html': head_html,
'body': html}
+ if six.PY3:
+ page = page.encode('utf8')
return [page]
def eval_javascript(self):
diff --git a/paste/exceptions/collector.py b/paste/exceptions/collector.py
index d6a30db..8867bf7 100644
--- a/paste/exceptions/collector.py
+++ b/paste/exceptions/collector.py
@@ -92,7 +92,7 @@ class ExceptionCollector(object):
The actually interpretation of these values is largely up to the
reporters and formatters.
-
+
``collect_exception(*sys.exc_info())`` will return an object with
several attributes:
@@ -113,7 +113,7 @@ class ExceptionCollector(object):
can refer to the exception later. (@@: should it include a
portion that allows identification of the specific instance
of the exception as well?)
-
+
The list of frames goes innermost first. Each frame has these
attributes; some values may be None if they could not be
determined.
@@ -140,7 +140,7 @@ class ExceptionCollector(object):
the value of any ``__traceback_hide__`` variable
``traceback_log``:
the value of any ``__traceback_log__`` variable
-
+
``__traceback_supplement__`` is thrown away, but a fixed
set of attributes are captured; each of these attributes is
@@ -189,7 +189,7 @@ class ExceptionCollector(object):
hide frames that are part of the 'framework' or underlying system.
There are a variety of rules about special values for this
variables that formatters should be aware of.
-
+
TODO:
More attributes in __traceback_supplement__? Maybe an attribute
@@ -503,7 +503,7 @@ class ExceptionFrame(Bunch):
for lineno in range(self.lineno-context, self.lineno+context+1):
lines.append(linecache.getline(self.filename, lineno))
return ''.join(lines)
-
+
if hasattr(sys, 'tracebacklimit'):
limit = min(limit, sys.tracebacklimit)
@@ -512,7 +512,7 @@ col = ExceptionCollector()
def collect_exception(t, v, tb, limit=None):
"""
Collection an exception from ``sys.exc_info()``.
-
+
Use like::
try:
diff --git a/paste/exceptions/errormiddleware.py b/paste/exceptions/errormiddleware.py
index 7a0918a..95c1261 100644
--- a/paste/exceptions/errormiddleware.py
+++ b/paste/exceptions/errormiddleware.py
@@ -11,6 +11,7 @@ from six.moves import cStringIO as StringIO
from paste.exceptions import formatter, collector, reporter
from paste import wsgilib
from paste import request
+import six
__all__ = ['ErrorMiddleware', 'handle_exception']
@@ -23,7 +24,7 @@ class ErrorMiddleware(object):
"""
Error handling middleware
-
+
Usage::
error_catching_wsgi_app = ErrorMiddleware(wsgi_app)
@@ -34,14 +35,14 @@ class ErrorMiddleware(object):
If true, then tracebacks will be shown in the browser.
``error_email``:
- an email address (or list of addresses) to send exception
+ an email address (or list of addresses) to send exception
reports to
``error_log``:
a filename to append tracebacks to
``show_exceptions_in_wsgi_errors``:
- If true, then errors will be printed to ``wsgi.errors``
+ If true, then errors will be printed to ``wsgi.errors``
(frequently a server error log, or stderr).
``from_address``, ``smtp_server``, ``error_subject_prefix``, ``smtp_username``, ``smtp_password``, ``smtp_use_tls``:
@@ -57,7 +58,7 @@ class ErrorMiddleware(object):
HTML page.
Environment Configuration:
-
+
``paste.throw_errors``:
If this setting in the request environment is true, then this
middleware is disabled. This can be useful in a testing situation
@@ -65,10 +66,10 @@ class ErrorMiddleware(object):
``paste.expected_exceptions``:
When this middleware encounters an exception listed in this
- environment variable and when the ``start_response`` has not
+ environment variable and when the ``start_response`` has not
yet occurred, the exception will be re-raised instead of being
- caught. This should generally be set by middleware that may
- (but probably shouldn't be) installed above this middleware,
+ caught. This should generally be set by middleware that may
+ (but probably shouldn't be) installed above this middleware,
and wants to get certain exceptions. Exceptions raised after
``start_response`` have been called are always caught since
by definition they are no longer expected.
@@ -123,7 +124,7 @@ class ErrorMiddleware(object):
if xmlhttp_key is None:
xmlhttp_key = global_conf.get('xmlhttp_key', '_')
self.xmlhttp_key = xmlhttp_key
-
+
def __call__(self, environ, start_response):
"""
The WSGI application interface.
@@ -151,6 +152,8 @@ class ErrorMiddleware(object):
exc_info)
# @@: it would be nice to deal with bad content types here
response = self.exception_handler(exc_info, environ)
+ if six.PY3:
+ response = response.encode('utf8')
return [response]
finally:
# clean up locals...
@@ -165,7 +168,7 @@ class ErrorMiddleware(object):
def exception_handler(self, exc_info, environ):
simple_html_error = False
if self.xmlhttp_key:
- get_vars = wsgilib.parse_querystring(environ)
+ get_vars = request.parse_querystring(environ)
if dict(get_vars).get(self.xmlhttp_key):
simple_html_error = True
return handle_exception(
@@ -242,6 +245,8 @@ class CatchingIter(object):
[('content-type', 'text/html')],
exc_info)
+ if six.PY3:
+ response = response.encode('utf8')
return response
__next__ = next
@@ -313,7 +318,7 @@ class Supplement(object):
(1, 0, 1): 'CGI',
(1, 1, 1): 'Multi thread/process CGI (?)',
}
-
+
def handle_exception(exc_info, error_stream, html=True,
debug_mode=False,
error_email=None,
@@ -321,8 +326,8 @@ def handle_exception(exc_info, error_stream, html=True,
show_exceptions_in_wsgi_errors=False,
error_email_from='errors@localhost',
smtp_server='localhost',
- smtp_username=None,
- smtp_password=None,
+ smtp_username=None,
+ smtp_password=None,
smtp_use_tls=False,
error_subject_prefix='',
error_message=None,
@@ -379,8 +384,11 @@ def handle_exception(exc_info, error_stream, html=True,
else:
reported = True
else:
- error_stream.write('Error - %s: %s\n' % (
- exc_data.exception_type, exc_data.exception_value))
+ line = ('Error - %s: %s\n'
+ % (exc_data.exception_type, exc_data.exception_value))
+ if six.PY3:
+ line = line.encode('utf8')
+ error_stream.write(line)
if html:
if debug_mode and simple_html_error:
return_error = formatter.format_html(
diff --git a/paste/exceptions/formatter.py b/paste/exceptions/formatter.py
index 7fa5e7d..c83ab50 100644
--- a/paste/exceptions/formatter.py
+++ b/paste/exceptions/formatter.py
@@ -196,7 +196,7 @@ class TextFormatter(AbstractFormatter):
'%s: %s' % (self.quote(etype), self.quote(evalue)))
def format_traceback_info(self, info):
return info
-
+
def format_combine(self, data_by_importance, lines, exc_info):
lines[:0] = [value for n, value in data_by_importance['important']]
lines.append(exc_info)
@@ -380,7 +380,7 @@ function switch_source(el, hide_type) {
}
</script>'''
-
+
error_css = """
<style type="text/css">
@@ -464,7 +464,7 @@ def format_html(exc_data, include_hidden_frames=False, **ops):
<textarea style="width: 100%%" rows=10 cols=60>%s</textarea>
</div>
""" % (short_er, long_er, cgi.escape(text_er))
-
+
def format_text(exc_data, **ops):
return TextFormatter(**ops).format_collected_data(exc_data)
diff --git a/paste/exceptions/serial_number_generator.py b/paste/exceptions/serial_number_generator.py
index d4f6235..5315b7c 100644
--- a/paste/exceptions/serial_number_generator.py
+++ b/paste/exceptions/serial_number_generator.py
@@ -122,4 +122,4 @@ __test__ = {
if __name__ == '__main__':
import doctest
doctest.testmod()
-
+
diff --git a/paste/fileapp.py b/paste/fileapp.py
index 3825386..e18281a 100644
--- a/paste/fileapp.py
+++ b/paste/fileapp.py
@@ -122,7 +122,7 @@ class DataApp(object):
for head in list_headers(entity=True):
head.delete(headers)
start_response('304 Not Modified', headers)
- return ['']
+ return [b'']
except HTTPBadRequest as exce:
return exce.wsgi_application(environ, start_response)
@@ -133,12 +133,13 @@ class DataApp(object):
if not client_etags:
try:
client_clock = IF_MODIFIED_SINCE.parse(environ)
- if client_clock >= int(self.last_modified):
+ if (client_clock is not None
+ and client_clock >= int(self.last_modified)):
# horribly inefficient, n^2 performance, yuck!
for head in list_headers(entity=True):
head.delete(headers)
start_response('304 Not Modified', headers)
- return [''] # empty body
+ return [b''] # empty body
except HTTPBadRequest as exce:
return exce.wsgi_application(environ, start_response)
@@ -224,11 +225,11 @@ class FileApp(DataApp):
if isinstance(retval, list):
# cached content, exception, or not-modified
if is_head:
- return ['']
+ return [b'']
return retval
(lower, content_length) = retval
if is_head:
- return ['']
+ return [b'']
file.seek(lower)
file_wrapper = environ.get('wsgi.file_wrapper', None)
if file_wrapper:
@@ -256,6 +257,7 @@ class _FileIter(object):
if not data:
raise StopIteration
return data
+ __next__ = next
def close(self):
self.file.close()
diff --git a/paste/fixture.py b/paste/fixture.py
index 1b97c35..5d6944c 100644
--- a/paste/fixture.py
+++ b/paste/fixture.py
@@ -211,7 +211,7 @@ class TestApp(object):
req = TestRequest(url, environ, expect_errors)
return self.do_request(req, status=status)
- def _gen_request(self, method, url, params='', headers=None, extra_environ=None,
+ def _gen_request(self, method, url, params=b'', headers=None, extra_environ=None,
status=None, upload_files=None, expect_errors=False):
"""
Do a generic request.
@@ -227,6 +227,8 @@ class TestApp(object):
if hasattr(params, 'items'):
# Some other multi-dict like format
params = urlencode(params.items())
+ if six.PY3:
+ params = params.encode('utf8')
if upload_files:
params = cgi.parse_qsl(params, keep_blank_values=True)
content_type, params = self.encode_multipart(
@@ -240,13 +242,13 @@ class TestApp(object):
environ['QUERY_STRING'] = ''
environ['CONTENT_LENGTH'] = str(len(params))
environ['REQUEST_METHOD'] = method
- environ['wsgi.input'] = StringIO(params)
+ environ['wsgi.input'] = six.BytesIO(params)
self._set_headers(headers, environ)
environ.update(extra_environ)
req = TestRequest(url, environ, expect_errors)
return self.do_request(req, status=status)
- def post(self, url, params='', headers=None, extra_environ=None,
+ def post(self, url, params=b'', headers=None, extra_environ=None,
status=None, upload_files=None, expect_errors=False):
"""
Do a POST request. Very like the ``.get()`` method.
@@ -265,7 +267,7 @@ class TestApp(object):
upload_files=upload_files,
expect_errors=expect_errors)
- def put(self, url, params='', headers=None, extra_environ=None,
+ def put(self, url, params=b'', headers=None, extra_environ=None,
status=None, upload_files=None, expect_errors=False):
"""
Do a PUT request. Very like the ``.get()`` method.
@@ -284,7 +286,7 @@ class TestApp(object):
upload_files=upload_files,
expect_errors=expect_errors)
- def delete(self, url, params='', headers=None, extra_environ=None,
+ def delete(self, url, params=b'', headers=None, extra_environ=None,
status=None, expect_errors=False):
"""
Do a DELETE request. Very like the ``.get()`` method.
@@ -322,26 +324,41 @@ class TestApp(object):
typical POST body, returning the (content_type, body).
"""
boundary = '----------a_BoUnDaRy%s$' % random.random()
+ content_type = 'multipart/form-data; boundary=%s' % boundary
+ if six.PY3:
+ boundary = boundary.encode('ascii')
+
lines = []
for key, value in params:
- lines.append('--'+boundary)
- lines.append('Content-Disposition: form-data; name="%s"' % key)
- lines.append('')
- lines.append(value)
+ lines.append(b'--'+boundary)
+ line = 'Content-Disposition: form-data; name="%s"' % key
+ if six.PY3:
+ line = line.encode('utf8')
+ lines.append(line)
+ lines.append(b'')
+ line = value
+ if six.PY3 and isinstance(line, six.text_type):
+ line = line.encode('utf8')
+ lines.append(line)
for file_info in files:
key, filename, value = self._get_file_info(file_info)
- lines.append('--'+boundary)
- lines.append('Content-Disposition: form-data; name="%s"; filename="%s"'
+ lines.append(b'--'+boundary)
+ line = ('Content-Disposition: form-data; name="%s"; filename="%s"'
% (key, filename))
+ if six.PY3:
+ line = line.encode('utf8')
+ lines.append(line)
fcontent = mimetypes.guess_type(filename)[0]
- lines.append('Content-Type: %s' %
- fcontent or 'application/octet-stream')
- lines.append('')
+ line = ('Content-Type: %s'
+ % (fcontent or 'application/octet-stream'))
+ if six.PY3:
+ line = line.encode('utf8')
+ lines.append(line)
+ lines.append(b'')
lines.append(value)
- lines.append('--' + boundary + '--')
- lines.append('')
- body = '\r\n'.join(lines)
- content_type = 'multipart/form-data; boundary=%s' % boundary
+ lines.append(b'--' + boundary + b'--')
+ lines.append(b'')
+ body = b'\r\n'.join(lines)
return content_type, body
def _get_file_info(self, file_info):
@@ -437,10 +454,13 @@ class TestApp(object):
if status is None:
if res.status >= 200 and res.status < 400:
return
+ body = res.body
+ if six.PY3:
+ body = body.decode('utf8', 'xmlcharrefreplace')
raise AppError(
"Bad response: %s (not 200 OK or 3xx redirect for %s)\n%s"
% (res.full_status, res.request.url,
- res.body))
+ body))
if status != res.status:
raise AppError(
"Bad response: %s (not %s)" % (res.full_status, status))
@@ -776,12 +796,12 @@ class TestResponse(object):
method = self.test_app.post
return method(href, **args)
- _normal_body_regex = re.compile(r'[ \n\r\t]+')
+ _normal_body_regex = re.compile(br'[ \n\r\t]+')
def normal_body__get(self):
if self._normal_body is None:
self._normal_body = self._normal_body_regex.sub(
- ' ', self.body)
+ b' ', self.body)
return self._normal_body
normal_body = property(normal_body__get,
@@ -836,11 +856,17 @@ class TestResponse(object):
"Body contains string %r" % s)
def __repr__(self):
- return '<Response %s %r>' % (self.full_status, self.body[:20])
+ body = self.body
+ if six.PY3:
+ body = body.decode('utf8', 'xmlcharrefreplace')
+ body = body[:20]
+ return '<Response %s %r>' % (self.full_status, body)
def __str__(self):
- simple_body = '\n'.join([l for l in self.body.splitlines()
+ simple_body = b'\n'.join([l for l in self.body.splitlines()
if l.strip()])
+ if six.PY3:
+ simple_body = simple_body.decode('utf8', 'xmlcharrefreplace')
return 'Response: %s\n%s\n%s' % (
self.status,
'\n'.join(['%s: %s' % (n, v) for n, v in self.headers]),
diff --git a/paste/flup_session.py b/paste/flup_session.py
index 6903c6f..6f5c750 100644
--- a/paste/flup_session.py
+++ b/paste/flup_session.py
@@ -68,7 +68,7 @@ class SessionMiddleware(object):
if cookie_name is NoDefault:
cookie_name = global_conf.get('session_cookie', '_SID_')
self.cookie_name = cookie_name
-
+
def __call__(self, environ, start_response):
service = flup_session.SessionService(
self.store, environ, cookieName=self.cookie_name,
@@ -92,7 +92,7 @@ class SessionMiddleware(object):
raise
return wsgilib.add_close(app_iter, service.close)
-
+
def make_session_middleware(app, global_conf,
session_type=NoDefault,
cookie_name=NoDefault,
diff --git a/paste/gzipper.py b/paste/gzipper.py
index 21b4164..eca8775 100644
--- a/paste/gzipper.py
+++ b/paste/gzipper.py
@@ -13,8 +13,7 @@ Gzip-encodes the response.
import gzip
from paste.response import header_value, remove_header
from paste.httpheaders import CONTENT_LENGTH
-
-from six.moves import cStringIO as StringIO
+import six
class GzipOutput(object):
pass
@@ -43,7 +42,7 @@ class GzipResponse(object):
def __init__(self, start_response, compress_level):
self.start_response = start_response
self.compress_level = compress_level
- self.buffer = StringIO()
+ self.buffer = six.BytesIO()
self.compressible = False
self.content_length = None
diff --git a/paste/httpexceptions.py b/paste/httpexceptions.py
index 492f558..6b8d5c5 100644
--- a/paste/httpexceptions.py
+++ b/paste/httpexceptions.py
@@ -205,9 +205,10 @@ class HTTPException(Exception):
if self.headers:
for (k, v) in self.headers:
args[k.lower()] = escfunc(v)
- for key, value in args.items():
- if isinstance(value, six.text_type):
- args[key] = value.encode('utf8', 'xmlcharrefreplace')
+ if six.PY2:
+ for key, value in args.items():
+ if isinstance(value, six.text_type):
+ args[key] = value.encode('utf8', 'xmlcharrefreplace')
return template % args
def plain(self, environ):
diff --git a/paste/httpheaders.py b/paste/httpheaders.py
index 702e596..5457138 100644
--- a/paste/httpheaders.py
+++ b/paste/httpheaders.py
@@ -135,7 +135,6 @@ dashes to give CamelCase style names.
"""
import mimetypes
-import re
import six
from time import time as now
try:
@@ -590,13 +589,10 @@ def normalize_headers(response_headers, strict=True):
continue
response_headers[idx] = (str(head), val)
category[str(head)] = head.sort_order
- def compare(a, b):
- ac = category[a[0]]
- bc = category[b[0]]
- if ac == bc:
- return cmp(a[0], b[0])
- return cmp(ac, bc)
- response_headers.sort(compare)
+ def key_func(item):
+ value = item[0]
+ return (category[value], value)
+ response_headers.sort(key=key_func)
class _DateHeader(_SingleValueHeader):
"""
@@ -975,7 +971,7 @@ class _AcceptLanguage(_MultiValueHeader):
if lvalue == "q":
q = float(rvalue)
qs.append((lang, q))
- qs.sort(lambda a, b: -cmp(a[1], b[1]))
+ qs.sort(key=lambda query: query[1], reverse=True)
return [lang for (lang, q) in qs]
_AcceptLanguage('Accept-Language', 'request', 'RFC 2616, 14.4')
@@ -1020,13 +1016,28 @@ class _Authorization(_SingleValueHeader):
(token, challenge) = challenge.split(' ', 1)
chal = parse_keqv_list(parse_http_list(challenge))
class FakeRequest(object):
- def get_full_url(self):
- return path
- def has_data(self):
- return False
+ if six.PY3:
+ @property
+ def full_url(self):
+ return path
+
+ selector = full_url
+
+ @property
+ def data(self):
+ return None
+ else:
+ def get_full_url(self):
+ return path
+
+ get_selector = get_full_url
+
+ def has_data(self):
+ return False
+
def get_method(self):
return method or "GET"
- get_selector = get_full_url
+
retval = "Digest %s" % auth.get_authorization(FakeRequest(), chal)
return (retval,)
_Authorization('Authorization', 'request', 'RFC 2617')
diff --git a/paste/httpserver.py b/paste/httpserver.py
index 64da8fe..6b4fea7 100755
--- a/paste/httpserver.py
+++ b/paste/httpserver.py
@@ -395,7 +395,7 @@ else:
return (conn, info)
def _auto_ssl_context():
- import OpenSSL, time, random
+ import OpenSSL, random
pkey = OpenSSL.crypto.PKey()
pkey.generate_key(OpenSSL.crypto.TYPE_RSA, 768)
@@ -945,7 +945,6 @@ class ThreadPool(object):
self.kill_worker(worker.thread_id)
self.logger.info('Workers killed forcefully')
if force_quit_timeout:
- hung = []
timed_out = False
need_force_quit = bool(zombies)
for worker in self.workers:
diff --git a/paste/lint.py b/paste/lint.py
index 0eedfa2..d781686 100644
--- a/paste/lint.py
+++ b/paste/lint.py
@@ -133,7 +133,7 @@ def middleware(application, global_conf=None):
will be printed to stderr -- there's no way to throw an exception
at that point).
"""
-
+
def lint_app(*args, **kw):
assert len(args) == 2, "Two arguments required"
assert not kw, "No keyword arguments allowed"
@@ -185,22 +185,22 @@ class InputWrapper(object):
def read(self, *args):
assert len(args) <= 1
v = self.input.read(*args)
- assert type(v) is type("")
+ assert isinstance(v, six.binary_type)
return v
def readline(self, *args):
v = self.input.readline(*args)
- assert type(v) is type("")
+ assert isinstance(v, six.binary_type)
return v
def readlines(self, *args):
assert len(args) <= 1
lines = self.input.readlines(*args)
- assert type(lines) is type([])
+ assert isinstance(lines, list)
for line in lines:
- assert type(line) is type("")
+ assert isinstance(line, six.binary_type)
return lines
-
+
def __iter__(self):
while 1:
line = self.readline()
@@ -217,7 +217,7 @@ class ErrorWrapper(object):
self.errors = wsgi_errors
def write(self, s):
- assert type(s) is type("")
+ assert isinstance(s, bytes)
self.errors.write(s)
def flush(self):
@@ -236,7 +236,7 @@ class WriteWrapper(object):
self.writer = wsgi_writer
def __call__(self, s):
- assert type(s) is type("")
+ assert isinstance(s, six.binary_type)
self.writer(s)
class PartialIteratorWrapper(object):
@@ -270,7 +270,7 @@ class IteratorWrapper(object):
return v
__next__ = next
-
+
def close(self):
self.closed = True
if hasattr(self.original_iterator, 'close'):
@@ -287,7 +287,7 @@ def check_environ(environ):
assert isinstance(environ,dict), (
"Environment is not of the right type: %r (environment: %r)"
% (type(environ), environ))
-
+
for key in ['REQUEST_METHOD', 'SERVER_NAME', 'SERVER_PORT',
'wsgi.version', 'wsgi.input', 'wsgi.errors',
'wsgi.multithread', 'wsgi.multiprocess',
@@ -314,7 +314,7 @@ def check_environ(environ):
assert isinstance(environ[key], str), (
"Environmental variable %s is not a string: %r (value: %r)"
% (key, type(environ[key]), environ[key]))
-
+
assert isinstance(environ['wsgi.version'], tuple), (
"wsgi.version should be a tuple (%r)" % environ['wsgi.version'])
assert environ['wsgi.url_scheme'] in ('http', 'https'), (
diff --git a/paste/modpython.py b/paste/modpython.py
index 692a94f..d20a588 100644
--- a/paste/modpython.py
+++ b/paste/modpython.py
@@ -8,7 +8,7 @@ Example httpd.conf section for a Paste app with an ini file::
PythonHandler paste.modpython
PythonOption paste.ini /some/location/your/pasteconfig.ini
</Location>
-
+
Or if you want to load a WSGI application under /your/homedir in the module
``startup`` and the WSGI app is ``app``::
@@ -59,22 +59,22 @@ except:
from paste.deploy import loadapp
class InputWrapper(object):
-
+
def __init__(self, req):
self.req = req
-
+
def close(self):
pass
-
+
def read(self, size=-1):
return self.req.read(size)
-
+
def readline(self, size=-1):
return self.req.readline(size)
-
+
def readlines(self, hint=-1):
return self.req.readlines(hint)
-
+
def __iter__(self):
line = self.readline()
while line:
@@ -85,16 +85,16 @@ class InputWrapper(object):
class ErrorWrapper(object):
-
+
def __init__(self, req):
self.req = req
-
+
def flush(self):
pass
-
+
def write(self, msg):
self.req.log_error(msg)
-
+
def writelines(self, seq):
self.write(''.join(seq))
@@ -104,12 +104,12 @@ bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', "
class Handler(object):
-
+
def __init__(self, req):
self.started = False
-
+
options = req.get_options()
-
+
# Threading and forking
try:
q = apache.mpm_query
@@ -123,7 +123,7 @@ class Handler(object):
threaded = False
else:
raise ValueError(bad_value % "multithread")
-
+
forked = options.get('multiprocess', '').lower()
if forked == 'on':
forked = True
@@ -131,9 +131,9 @@ class Handler(object):
forked = False
else:
raise ValueError(bad_value % "multiprocess")
-
+
env = self.environ = dict(apache.build_cgi_env(req))
-
+
if 'SCRIPT_NAME' in options:
# Override SCRIPT_NAME and PATH_INFO if requested.
env['SCRIPT_NAME'] = options['SCRIPT_NAME']
@@ -141,7 +141,7 @@ class Handler(object):
else:
env['SCRIPT_NAME'] = ''
env['PATH_INFO'] = req.uri
-
+
env['wsgi.input'] = InputWrapper(req)
env['wsgi.errors'] = ErrorWrapper(req)
env['wsgi.version'] = (1, 0)
@@ -152,9 +152,9 @@ class Handler(object):
env['wsgi.url_scheme'] = 'http'
env['wsgi.multithread'] = threaded
env['wsgi.multiprocess'] = forked
-
+
self.request = req
-
+
def run(self, application):
try:
result = application(self.environ, self.start_response)
@@ -172,7 +172,7 @@ class Handler(object):
data = "A server error occurred. Please contact the administrator."
self.request.set_content_length(len(data))
self.request.write(data)
-
+
def start_response(self, status, headers, exc_info=None):
if exc_info:
try:
@@ -180,9 +180,9 @@ class Handler(object):
six.reraise(exc_info[0], exc_info[1], exc_info[2])
finally:
exc_info = None
-
+
self.request.status = int(status[:3])
-
+
for key, val in headers:
if key.lower() == 'content-length':
self.request.set_content_length(int(val))
@@ -190,9 +190,9 @@ class Handler(object):
self.request.content_type = val
else:
self.request.headers_out.add(key, val)
-
+
return self.write
-
+
def write(self, data):
if not self.started:
self.started = True
@@ -214,7 +214,7 @@ def handler(req):
module = __import__(module_name, globals(), locals(), [''])
startup = apache.resolve_object(module, object_str)
startup(req)
-
+
# Register a cleanup function if requested.
global cleanup
if 'wsgi.cleanup' in options and not cleanup:
@@ -230,7 +230,7 @@ def handler(req):
apache.register_cleanup(cleaner)
except AttributeError:
req.server.register_cleanup(req, cleaner)
-
+
# Import the wsgi 'application' callable and pass it to Handler.run
global wsgiapps
appini = options.get('paste.ini')
@@ -239,15 +239,15 @@ def handler(req):
if appini not in wsgiapps:
wsgiapps[appini] = loadapp("config:%s" % appini)
app = wsgiapps[appini]
-
+
# Import the wsgi 'application' callable and pass it to Handler.run
appwsgi = options.get('wsgi.application')
if appwsgi and not appini:
modname, objname = appwsgi.split('::', 1)
module = __import__(modname, globals(), locals(), [''])
app = getattr(module, objname)
-
+
Handler(req).run(app)
-
+
# status was set in Handler; always return apache.OK
return apache.OK
diff --git a/paste/proxy.py b/paste/proxy.py
index a33efbc..b315265 100644
--- a/paste/proxy.py
+++ b/paste/proxy.py
@@ -21,12 +21,12 @@ TODO:
* Rewriting body? (Probably not on this one -- that can be done with
a different middleware that wraps this middleware)
-* Example::
-
+* Example::
+
use = egg:Paste#proxy
address = http://server3:8680/exist/rest/db/orgs/sch/config/
allowed_request_methods = GET
-
+
"""
from six.moves import http_client as httplib
@@ -38,7 +38,7 @@ from paste.util.converters import aslist
# Remove these headers from response (specify lower case header
# names):
-filtered_headers = (
+filtered_headers = (
'transfer-encoding',
'connection',
'keep-alive',
@@ -60,12 +60,12 @@ class Proxy(object):
self.path = self.parsed[2]
self.allowed_request_methods = [
x.lower() for x in allowed_request_methods if x]
-
+
self.suppress_http_headers = [
x.lower() for x in suppress_http_headers if x]
def __call__(self, environ, start_response):
- if (self.allowed_request_methods and
+ if (self.allowed_request_methods and
environ['REQUEST_METHOD'].lower() not in self.allowed_request_methods):
return httpexceptions.HTTPBadRequest("Disallowed")(environ, start_response)
@@ -95,30 +95,30 @@ class Proxy(object):
body = environ['wsgi.input'].read(-1)
headers['content-length'] = str(len(body))
else:
- headers['content-length'] = environ['CONTENT_LENGTH']
+ headers['content-length'] = environ['CONTENT_LENGTH']
length = int(environ['CONTENT_LENGTH'])
body = environ['wsgi.input'].read(length)
else:
body = ''
-
+
path_info = quote(environ['PATH_INFO'])
- if self.path:
+ if self.path:
request_path = path_info
if request_path and request_path[0] == '/':
request_path = request_path[1:]
-
+
path = urlparse.urljoin(self.path, request_path)
else:
path = path_info
if environ.get('QUERY_STRING'):
path += '?' + environ['QUERY_STRING']
-
+
conn.request(environ['REQUEST_METHOD'],
path,
body, headers)
res = conn.getresponse()
headers_out = parse_headers(res.msg)
-
+
status = '%s %s' % (res.status, res.reason)
start_response(status, headers_out)
# @@: Default?
@@ -134,13 +134,13 @@ def make_proxy(global_conf, address, allowed_request_methods="",
suppress_http_headers=""):
"""
Make a WSGI application that proxies to another address:
-
+
``address``
the full URL ending with a trailing ``/``
-
+
``allowed_request_methods``:
a space seperated list of request methods (e.g., ``GET POST``)
-
+
``suppress_http_headers``
a space seperated list of http headers (lower case, without
the leading ``http_``) that should not be passed on to target
@@ -224,7 +224,7 @@ class TransparentProxy(object):
else:
body = ''
length = 0
-
+
path = (environ.get('SCRIPT_NAME', '')
+ environ.get('PATH_INFO', ''))
path = quote(path)
@@ -234,7 +234,7 @@ class TransparentProxy(object):
path, body, headers)
res = conn.getresponse()
headers_out = parse_headers(res.msg)
-
+
status = '%s %s' % (res.status, res.reason)
start_response(status, headers_out)
# @@: Default?
@@ -250,27 +250,27 @@ def parse_headers(message):
"""
Turn a Message object into a list of WSGI-style headers.
"""
- headers_out = []
+ headers_out = []
for full_header in message.headers:
- if not full_header:
+ if not full_header:
# Shouldn't happen, but we'll just ignore
- continue
+ continue
if full_header[0].isspace():
# Continuation line, add to the last header
- if not headers_out:
+ if not headers_out:
raise ValueError(
"First header starts with a space (%r)" % full_header)
- last_header, last_value = headers_out.pop()
+ last_header, last_value = headers_out.pop()
value = last_value + ' ' + full_header.strip()
- headers_out.append((last_header, value))
- continue
- try:
+ headers_out.append((last_header, value))
+ continue
+ try:
header, value = full_header.split(':', 1)
- except:
+ except:
raise ValueError("Invalid header: %r" % full_header)
- value = value.strip()
+ value = value.strip()
if header.lower() not in filtered_headers:
- headers_out.append((header, value))
+ headers_out.append((header, value))
return headers_out
def make_transparent_proxy(
diff --git a/paste/recursive.py b/paste/recursive.py
index 8f28fc3..0bef920 100644
--- a/paste/recursive.py
+++ b/paste/recursive.py
@@ -115,10 +115,10 @@ class ForwardRequestException(Exception):
def app(environ, start_response):
if environ['PATH_INFO'] == '/hello':
start_response("200 OK", [('Content-type', 'text/plain')])
- return ['Hello World!']
+ return [b'Hello World!']
elif environ['PATH_INFO'] == '/error':
start_response("404 Not Found", [('Content-type', 'text/plain')])
- return ['Page not found']
+ return [b'Page not found']
else:
raise ForwardRequestException('/error')
@@ -158,10 +158,10 @@ class ForwardRequestException(Exception):
def app(environ, start_response):
if environ['PATH_INFO'] == '/hello':
start_response("200 OK", [('Content-type', 'text/plain')])
- return ['Hello World!']
+ return [b'Hello World!']
elif environ['PATH_INFO'] == '/error':
start_response("404 Not Found", [('Content-type', 'text/plain')])
- return ['Page not found']
+ return [b'Page not found']
else:
def factory(app):
return StatusKeeper(app, status='404 Not Found', url='/error')
diff --git a/paste/registry.py b/paste/registry.py
index 1148632..908bc0d 100644
--- a/paste/registry.py
+++ b/paste/registry.py
@@ -90,7 +90,6 @@ quick way to work around it is documented.
"""
import six
-import sys
import paste.util.threadinglocal as threadinglocal
__all__ = ['StackedObjectProxy', 'RegistryManager', 'StackedObjectRestorer',
@@ -373,18 +372,11 @@ class RegistryManager(object):
app_iter = None
reg = environ.setdefault('paste.registry', Registry())
reg.prepare()
- #if self.streaming:
- # return self.streaming_iter(reg, environ, start_response)
+ if self.streaming:
+ return self.streaming_iter(reg, environ, start_response)
try:
app_iter = self.application(environ, start_response)
- #print("REG ", type(app_iter))
- if isinstance(app_iter, (list, tuple)):
- #print("DIRECT")
- return app_iter
- #print("STREAMING")
- return self.streaming_iter(app_iter, reg, environ)
-
except Exception as e:
# Regardless of if the content is an iterable, generator, list
# or tuple, we clean-up right now. If its an iterable/generator
@@ -412,9 +404,9 @@ class RegistryManager(object):
return app_iter
- def streaming_iter(self, app_iter, reg, environ):
+ def streaming_iter(self, reg, environ, start_response):
try:
- for item in app_iter:
+ for item in self.application(environ, start_response):
yield item
except Exception as e:
# Regardless of if the content is an iterable, generator, list
diff --git a/paste/reloader.py b/paste/reloader.py
index 29b1891..15c4d1c 100644
--- a/paste/reloader.py
+++ b/paste/reloader.py
@@ -26,7 +26,7 @@ or is run from this .bat file (if you use Windows)::
if %errorlevel% == 3 goto repeat
or run a monitoring process in Python (``paster serve --reload`` does
-this).
+this).
Use the ``watch_file(filename)`` function to cause a reload/restart for
other other non-Python files (e.g., configuration files). If you have
@@ -100,7 +100,7 @@ class Monitor(object):
for module in sys.modules.values():
try:
filename = module.__file__
- except (AttributeError, ImportError) as exc:
+ except (AttributeError, ImportError):
continue
if filename is not None:
filenames.append(filename)
diff --git a/paste/request.py b/paste/request.py
index 28b1f67..2e7280b 100644
--- a/paste/request.py
+++ b/paste/request.py
@@ -18,13 +18,12 @@ environment to solve common requirements.
"""
import cgi
-from six.moves import StringIO
from six.moves.urllib import parse as urlparse
from six.moves.urllib.parse import quote
try:
# Python 3
from http.cookies import SimpleCookie, CookieError
-except ImportError:
+except ImportError:
# Python 2
from Cookie import SimpleCookie, CookieError
@@ -32,6 +31,7 @@ try:
from UserDict import DictMixin
except ImportError:
from collections import MutableMapping as DictMixin
+import six
from paste.util.multidict import MultiDict
@@ -175,11 +175,11 @@ def parse_formvars(environ, include_get_vars=True):
old_query_string = environ.get('QUERY_STRING','')
environ['QUERY_STRING'] = ''
if fake_out_cgi:
- input = StringIO('')
+ input = six.BytesIO(b'')
old_content_type = environ.get('CONTENT_TYPE')
old_content_length = environ.get('CONTENT_LENGTH')
environ['CONTENT_LENGTH'] = '0'
- environ['CONTENT_TYPE'] = ''
+ environ['CONTENT_TYPE'] = ''
else:
input = environ['wsgi.input']
fs = cgi.FieldStorage(fp=input,
@@ -375,7 +375,10 @@ class EnvironHeaders(DictMixin):
return key[5:].replace('_', '-').title()
else:
return None
-
+
+ def __len__(self):
+ return len(self.environ)
+
def __getitem__(self, item):
return self.environ[self._trans_name(item)]
diff --git a/paste/session.py b/paste/session.py
index 133cad6..ae208e7 100644
--- a/paste/session.py
+++ b/paste/session.py
@@ -148,7 +148,10 @@ class SessionFactory(object):
r.append(os.times())
if for_object is not None:
r.append(id(for_object))
- md5_hash = md5(str(r))
+ content = str(r)
+ if six.PY3:
+ content = content.encode('utf8')
+ md5_hash = md5(content)
try:
return md5_hash.hexdigest()
except AttributeError:
diff --git a/paste/url.py b/paste/url.py
index 87a6e8a..7273d51 100644
--- a/paste/url.py
+++ b/paste/url.py
@@ -7,6 +7,8 @@ This module implements a class for handling URLs.
from six.moves.urllib.parse import quote, unquote, urlencode
import cgi
from paste import request
+import six
+
# Imported lazily from FormEncode:
variabledecode = None
@@ -136,10 +138,10 @@ class URLResource(object):
vars = variabledecode.variable_encode(vars)
return vars
-
+
def var(self, **kw):
kw = self.coerce_vars(kw)
- new_vars = self.vars + kw.items()
+ new_vars = self.vars + list(kw.items())
return self.__class__(self.url, vars=new_vars,
attrs=self.attrs,
params=self.original_params)
@@ -181,14 +183,17 @@ class URLResource(object):
attrs=u.attrs,
params=u.original_params)
return u
-
- __div__ = addpath
+
+ if six.PY3:
+ __truediv__ = addpath
+ else:
+ __div__ = addpath
def become(self, OtherClass):
return OtherClass(self.url, vars=self.vars,
attrs=self.attrs,
params=self.original_params)
-
+
def href__get(self):
s = self.url
if self.vars:
@@ -217,7 +222,7 @@ class URLResource(object):
', '.join(['%s=%r' % (n, v)
for n, v in self.attrs.items()]))
return base + '>'
-
+
def html__get(self):
if not self.params.get('tag'):
raise ValueError(
@@ -250,7 +255,7 @@ class URLResource(object):
for an empty tag (like ``<img />``)
"""
raise NotImplementedError
-
+
def _add_vars(self, vars):
raise NotImplementedError
@@ -307,7 +312,7 @@ class URL(URLResource):
return self.addpath(*args)
def _html_attrs(self):
- attrs = self.attrs.items()
+ attrs = list(self.attrs.items())
attrs.insert(0, ('href', self.href))
if self.params.get('confirm'):
attrs.append(('onclick', 'return confirm(%s)'
@@ -328,7 +333,7 @@ class URL(URLResource):
return self.become(JSPopup)
js_popup = property(js_popup__get)
-
+
class Image(URLResource):
r"""
@@ -341,7 +346,7 @@ class Image(URLResource):
>>> i.href
'/images/foo.png'
"""
-
+
default_params = {'tag': 'img'}
def __str__(self):
@@ -357,7 +362,7 @@ class Image(URLResource):
return self.addpath(*args)
def _html_attrs(self):
- attrs = self.attrs.items()
+ attrs = list(self.attrs.items())
attrs.insert(0, ('src', self.href))
return attrs
@@ -396,7 +401,7 @@ class Button(URLResource):
return self.addpath(*args)
def _html_attrs(self):
- attrs = self.attrs.items()
+ attrs = list(self.attrs.items())
onclick = 'location.href=%s' % js_repr(self.href)
if self.params.get('confirm'):
onclick = 'if (confirm(%s)) {%s}' % (
@@ -449,7 +454,7 @@ class JSPopup(URLResource):
return ', '.join(map(js_repr, args))
def _html_attrs(self):
- attrs = self.attrs.items()
+ attrs = list(self.attrs.items())
onclick = ('window.open(%s); return false'
% self._window_args())
attrs.insert(0, ('target', self.params['target']))
@@ -470,4 +475,4 @@ class JSPopup(URLResource):
if __name__ == '__main__':
import doctest
doctest.testmod()
-
+
diff --git a/paste/util/PySourceColor.py b/paste/util/PySourceColor.py
index 1c11041..c576ead 100644
--- a/paste/util/PySourceColor.py
+++ b/paste/util/PySourceColor.py
@@ -1,2102 +1,2102 @@
-# -*- coding: Latin-1 -*-
-"""
-PySourceColor: color Python source code
-"""
-
-"""
- PySourceColor.py
-
-----------------------------------------------------------------------------
-
- A python source to colorized html/css/xhtml converter.
- Hacked by M.E.Farmer Jr. 2004, 2005
- Python license
-
-----------------------------------------------------------------------------
-
- - HTML markup does not create w3c valid html, but it works on every
- browser i've tried so far.(I.E.,Mozilla/Firefox,Opera,Konqueror,wxHTML).
- - CSS markup is w3c validated html 4.01 strict,
- but will not render correctly on all browsers.
- - XHTML markup is w3c validated xhtml 1.0 strict,
- like html 4.01, will not render correctly on all browsers.
-
-----------------------------------------------------------------------------
-
-Features:
-
- -Three types of markup:
- html (default)
- css/html 4.01 strict
- xhtml 1.0 strict
-
- -Can tokenize and colorize:
- 12 types of strings
- 2 comment types
- numbers
- operators
- brackets
- math operators
- class / name
- def / name
- decorator / name
- keywords
- arguments class/def/decorator
- linenumbers
- names
- text
-
- -Eight colorschemes built-in:
- null
- mono
- lite (default)
- dark
- dark2
- idle
- viewcvs
- pythonwin
-
- -Header and footer
- set to '' for builtin header / footer.
- give path to a file containing the html
- you want added as header or footer.
-
- -Arbitrary text and html
- html markup converts all to raw (TEXT token)
- #@# for raw -> send raw text.
- #$# for span -> inline html and text.
- #%# for div -> block level html and text.
-
- -Linenumbers
- Supports all styles. New token is called LINENUMBER.
- Defaults to NAME if not defined.
-
- Style options
-
- -ALL markups support these text styles:
- b = bold
- i = italic
- u = underline
- -CSS and XHTML has limited support for borders:
- HTML markup functions will ignore these.
- Optional: Border color in RGB hex
- Defaults to the text forecolor.
- #rrggbb = border color
- Border size:
- l = thick
- m = medium
- t = thin
- Border type:
- - = dashed
- . = dotted
- s = solid
- d = double
- g = groove
- r = ridge
- n = inset
- o = outset
- You can specify multiple sides,
- they will all use the same style.
- Optional: Default is full border.
- v = bottom
- < = left
- > = right
- ^ = top
- NOTE: Specify the styles you want.
- The markups will ignore unsupported styles
- Also note not all browsers can show these options
-
- -All tokens default to NAME if not defined
- so the only absolutely critical ones to define are:
- NAME, ERRORTOKEN, PAGEBACKGROUND
-
-----------------------------------------------------------------------------
-
-Example usage::
-
- # import
- import PySourceColor as psc
- psc.convert('c:/Python22/PySourceColor.py', colors=psc.idle, show=1)
-
- # from module import *
- from PySourceColor import *
- convert('c:/Python22/Lib', colors=lite, markup="css",
- header='#$#<b>This is a simpe heading</b><hr/>')
-
- # How to use a custom colorscheme, and most of the 'features'
- from PySourceColor import *
- new = {
- ERRORTOKEN: ('bui','#FF8080',''),
- DECORATOR_NAME: ('s','#AACBBC',''),
- DECORATOR: ('n','#333333',''),
- NAME: ('t.<v','#1133AA','#DDFF22'),
- NUMBER: ('','#236676','#FF5555'),
- OPERATOR: ('b','#454567','#BBBB11'),
- MATH_OPERATOR: ('','#935623','#423afb'),
- BRACKETS: ('b','#ac34bf','#6457a5'),
- COMMENT: ('t-#0022FF','#545366','#AABBFF'),
- DOUBLECOMMENT: ('<l#553455','#553455','#FF00FF'),
- CLASS_NAME: ('m^v-','#000000','#FFFFFF'),
- DEF_NAME: ('l=<v','#897845','#000022'),
- KEYWORD: ('.b','#345345','#FFFF22'),
- SINGLEQUOTE: ('mn','#223344','#AADDCC'),
- SINGLEQUOTE_R: ('','#344522',''),
- SINGLEQUOTE_U: ('','#234234',''),
- DOUBLEQUOTE: ('m#0022FF','#334421',''),
- DOUBLEQUOTE_R: ('','#345345',''),
- DOUBLEQUOTE_U: ('','#678673',''),
- TRIPLESINGLEQUOTE: ('tv','#FFFFFF','#000000'),
- TRIPLESINGLEQUOTE_R: ('tbu','#443256','#DDFFDA'),
- TRIPLESINGLEQUOTE_U: ('','#423454','#DDFFDA'),
- TRIPLEDOUBLEQUOTE: ('li#236fd3b<>','#000000','#FFFFFF'),
- TRIPLEDOUBLEQUOTE_R: ('tub','#000000','#FFFFFF'),
- TRIPLEDOUBLEQUOTE_U: ('-', '#CCAABB','#FFFAFF'),
- LINENUMBER: ('ib-','#ff66aa','#7733FF'),]
- TEXT: ('','#546634',''),
- PAGEBACKGROUND: '#FFFAAA',
- }
- if __name__ == '__main__':
- import sys
- convert(sys.argv[1], './xhtml.html', colors=new, markup='xhtml', show=1,
- linenumbers=1)
- convert(sys.argv[1], './html.html', colors=new, markup='html', show=1,
- linenumbers=1)
-
-"""
-
-__all__ = ['ERRORTOKEN','DECORATOR_NAME', 'DECORATOR', 'ARGS', 'EXTRASPACE',
- 'NAME', 'NUMBER', 'OPERATOR', 'COMMENT', 'MATH_OPERATOR',
- 'DOUBLECOMMENT', 'CLASS_NAME', 'DEF_NAME', 'KEYWORD', 'BRACKETS',
- 'SINGLEQUOTE','SINGLEQUOTE_R','SINGLEQUOTE_U','DOUBLEQUOTE',
- 'DOUBLEQUOTE_R', 'DOUBLEQUOTE_U', 'TRIPLESINGLEQUOTE', 'TEXT',
- 'TRIPLESINGLEQUOTE_R', 'TRIPLESINGLEQUOTE_U', 'TRIPLEDOUBLEQUOTE',
- 'TRIPLEDOUBLEQUOTE_R', 'TRIPLEDOUBLEQUOTE_U', 'PAGEBACKGROUND',
- 'LINENUMBER', 'CODESTART', 'CODEEND', 'PY', 'TOKEN_NAMES', 'CSSHOOK',
- 'null', 'mono', 'lite', 'dark','dark2', 'pythonwin','idle',
- 'viewcvs', 'Usage', 'cli', 'str2stdout', 'path2stdout', 'Parser',
- 'str2file', 'str2html', 'str2css', 'str2markup', 'path2file',
- 'path2html', 'convert', 'walkdir', 'defaultColors', 'showpage',
- 'pageconvert','tagreplace', 'MARKUPDICT']
-__title__ = 'PySourceColor'
-__version__ = "2.1a"
-__date__ = '25 April 2005'
-__author__ = "M.E.Farmer Jr."
-__credits__ = '''This was originally based on a python recipe
-submitted by Jrgen Hermann to ASPN. Now based on the voices in my head.
-M.E.Farmer 2004, 2005
-Python license
-'''
-import os
-import sys
-import time
-import glob
-import getopt
-import keyword
-import token
-import tokenize
-import traceback
-from six.moves import cStringIO as StringIO
-# Do not edit
-NAME = token.NAME
-NUMBER = token.NUMBER
-COMMENT = tokenize.COMMENT
-OPERATOR = token.OP
-ERRORTOKEN = token.ERRORTOKEN
-ARGS = token.NT_OFFSET + 1
-DOUBLECOMMENT = token.NT_OFFSET + 2
-CLASS_NAME = token.NT_OFFSET + 3
-DEF_NAME = token.NT_OFFSET + 4
-KEYWORD = token.NT_OFFSET + 5
-SINGLEQUOTE = token.NT_OFFSET + 6
-SINGLEQUOTE_R = token.NT_OFFSET + 7
-SINGLEQUOTE_U = token.NT_OFFSET + 8
-DOUBLEQUOTE = token.NT_OFFSET + 9
-DOUBLEQUOTE_R = token.NT_OFFSET + 10
-DOUBLEQUOTE_U = token.NT_OFFSET + 11
-TRIPLESINGLEQUOTE = token.NT_OFFSET + 12
-TRIPLESINGLEQUOTE_R = token.NT_OFFSET + 13
-TRIPLESINGLEQUOTE_U = token.NT_OFFSET + 14
-TRIPLEDOUBLEQUOTE = token.NT_OFFSET + 15
-TRIPLEDOUBLEQUOTE_R = token.NT_OFFSET + 16
-TRIPLEDOUBLEQUOTE_U = token.NT_OFFSET + 17
-PAGEBACKGROUND = token.NT_OFFSET + 18
-DECORATOR = token.NT_OFFSET + 19
-DECORATOR_NAME = token.NT_OFFSET + 20
-BRACKETS = token.NT_OFFSET + 21
-MATH_OPERATOR = token.NT_OFFSET + 22
-LINENUMBER = token.NT_OFFSET + 23
-TEXT = token.NT_OFFSET + 24
-PY = token.NT_OFFSET + 25
-CODESTART = token.NT_OFFSET + 26
-CODEEND = token.NT_OFFSET + 27
-CSSHOOK = token.NT_OFFSET + 28
-EXTRASPACE = token.NT_OFFSET + 29
-
-# markup classname lookup
-MARKUPDICT = {
- ERRORTOKEN: 'py_err',
- DECORATOR_NAME: 'py_decn',
- DECORATOR: 'py_dec',
- ARGS: 'py_args',
- NAME: 'py_name',
- NUMBER: 'py_num',
- OPERATOR: 'py_op',
- COMMENT: 'py_com',
- DOUBLECOMMENT: 'py_dcom',
- CLASS_NAME: 'py_clsn',
- DEF_NAME: 'py_defn',
- KEYWORD: 'py_key',
- SINGLEQUOTE: 'py_sq',
- SINGLEQUOTE_R: 'py_sqr',
- SINGLEQUOTE_U: 'py_squ',
- DOUBLEQUOTE: 'py_dq',
- DOUBLEQUOTE_R: 'py_dqr',
- DOUBLEQUOTE_U: 'py_dqu',
- TRIPLESINGLEQUOTE: 'py_tsq',
- TRIPLESINGLEQUOTE_R: 'py_tsqr',
- TRIPLESINGLEQUOTE_U: 'py_tsqu',
- TRIPLEDOUBLEQUOTE: 'py_tdq',
- TRIPLEDOUBLEQUOTE_R: 'py_tdqr',
- TRIPLEDOUBLEQUOTE_U: 'py_tdqu',
- BRACKETS: 'py_bra',
- MATH_OPERATOR: 'py_mop',
- LINENUMBER: 'py_lnum',
- TEXT: 'py_text',
- }
-# might help users that want to create custom schemes
-TOKEN_NAMES= {
- ERRORTOKEN:'ERRORTOKEN',
- DECORATOR_NAME:'DECORATOR_NAME',
- DECORATOR:'DECORATOR',
- ARGS:'ARGS',
- NAME:'NAME',
- NUMBER:'NUMBER',
- OPERATOR:'OPERATOR',
- COMMENT:'COMMENT',
- DOUBLECOMMENT:'DOUBLECOMMENT',
- CLASS_NAME:'CLASS_NAME',
- DEF_NAME:'DEF_NAME',
- KEYWORD:'KEYWORD',
- SINGLEQUOTE:'SINGLEQUOTE',
- SINGLEQUOTE_R:'SINGLEQUOTE_R',
- SINGLEQUOTE_U:'SINGLEQUOTE_U',
- DOUBLEQUOTE:'DOUBLEQUOTE',
- DOUBLEQUOTE_R:'DOUBLEQUOTE_R',
- DOUBLEQUOTE_U:'DOUBLEQUOTE_U',
- TRIPLESINGLEQUOTE:'TRIPLESINGLEQUOTE',
- TRIPLESINGLEQUOTE_R:'TRIPLESINGLEQUOTE_R',
- TRIPLESINGLEQUOTE_U:'TRIPLESINGLEQUOTE_U',
- TRIPLEDOUBLEQUOTE:'TRIPLEDOUBLEQUOTE',
- TRIPLEDOUBLEQUOTE_R:'TRIPLEDOUBLEQUOTE_R',
- TRIPLEDOUBLEQUOTE_U:'TRIPLEDOUBLEQUOTE_U',
- BRACKETS:'BRACKETS',
- MATH_OPERATOR:'MATH_OPERATOR',
- LINENUMBER:'LINENUMBER',
- TEXT:'TEXT',
- PAGEBACKGROUND:'PAGEBACKGROUND',
- }
-
-######################################################################
-# Edit colors and styles to taste
-# Create your own scheme, just copy one below , rename and edit.
-# Custom styles must at least define NAME, ERRORTOKEN, PAGEBACKGROUND,
-# all missing elements will default to NAME.
-# See module docstring for details on style attributes.
-######################################################################
-# Copy null and use it as a starter colorscheme.
-null = {# tokentype: ('tags border_color', 'textforecolor', 'textbackcolor')
- ERRORTOKEN: ('','#000000',''),# Error token
- DECORATOR_NAME: ('','#000000',''),# Decorator name
- DECORATOR: ('','#000000',''),# @ symbol
- ARGS: ('','#000000',''),# class,def,deco arguments
- NAME: ('','#000000',''),# All other python text
- NUMBER: ('','#000000',''),# 0->10
- OPERATOR: ('','#000000',''),# ':','<=',';',',','.','==', etc
- MATH_OPERATOR: ('','#000000',''),# '+','-','=','','**',etc
- BRACKETS: ('','#000000',''),# '[',']','(',')','{','}'
- COMMENT: ('','#000000',''),# Single comment
- DOUBLECOMMENT: ('','#000000',''),## Double comment
- CLASS_NAME: ('','#000000',''),# Class name
- DEF_NAME: ('','#000000',''),# Def name
- KEYWORD: ('','#000000',''),# Python keywords
- SINGLEQUOTE: ('','#000000',''),# 'SINGLEQUOTE'
- SINGLEQUOTE_R: ('','#000000',''),# r'SINGLEQUOTE'
- SINGLEQUOTE_U: ('','#000000',''),# u'SINGLEQUOTE'
- DOUBLEQUOTE: ('','#000000',''),# "DOUBLEQUOTE"
- DOUBLEQUOTE_R: ('','#000000',''),# r"DOUBLEQUOTE"
- DOUBLEQUOTE_U: ('','#000000',''),# u"DOUBLEQUOTE"
- TRIPLESINGLEQUOTE: ('','#000000',''),# '''TRIPLESINGLEQUOTE'''
- TRIPLESINGLEQUOTE_R: ('','#000000',''),# r'''TRIPLESINGLEQUOTE'''
- TRIPLESINGLEQUOTE_U: ('','#000000',''),# u'''TRIPLESINGLEQUOTE'''
- TRIPLEDOUBLEQUOTE: ('','#000000',''),# """TRIPLEDOUBLEQUOTE"""
- TRIPLEDOUBLEQUOTE_R: ('','#000000',''),# r"""TRIPLEDOUBLEQUOTE"""
- TRIPLEDOUBLEQUOTE_U: ('','#000000',''),# u"""TRIPLEDOUBLEQUOTE"""
- TEXT: ('','#000000',''),# non python text
- LINENUMBER: ('>ti#555555','#000000',''),# Linenumbers
- PAGEBACKGROUND: '#FFFFFF'# set the page background
- }
-
-mono = {
- ERRORTOKEN: ('s#FF0000','#FF8080',''),
- DECORATOR_NAME: ('bu','#000000',''),
- DECORATOR: ('b','#000000',''),
- ARGS: ('b','#555555',''),
- NAME: ('','#000000',''),
- NUMBER: ('b','#000000',''),
- OPERATOR: ('b','#000000',''),
- MATH_OPERATOR: ('b','#000000',''),
- BRACKETS: ('b','#000000',''),
- COMMENT: ('i','#999999',''),
- DOUBLECOMMENT: ('b','#999999',''),
- CLASS_NAME: ('bu','#000000',''),
- DEF_NAME: ('b','#000000',''),
- KEYWORD: ('b','#000000',''),
- SINGLEQUOTE: ('','#000000',''),
- SINGLEQUOTE_R: ('','#000000',''),
- SINGLEQUOTE_U: ('','#000000',''),
- DOUBLEQUOTE: ('','#000000',''),
- DOUBLEQUOTE_R: ('','#000000',''),
- DOUBLEQUOTE_U: ('','#000000',''),
- TRIPLESINGLEQUOTE: ('','#000000',''),
- TRIPLESINGLEQUOTE_R: ('','#000000',''),
- TRIPLESINGLEQUOTE_U: ('','#000000',''),
- TRIPLEDOUBLEQUOTE: ('i','#000000',''),
- TRIPLEDOUBLEQUOTE_R: ('i','#000000',''),
- TRIPLEDOUBLEQUOTE_U: ('i','#000000',''),
- TEXT: ('','#000000',''),
- LINENUMBER: ('>ti#555555','#000000',''),
- PAGEBACKGROUND: '#FFFFFF'
- }
-
-dark = {
- ERRORTOKEN: ('s#FF0000','#FF8080',''),
- DECORATOR_NAME: ('b','#FFBBAA',''),
- DECORATOR: ('b','#CC5511',''),
- ARGS: ('b','#DDDDFF',''),
- NAME: ('','#DDDDDD',''),
- NUMBER: ('','#FF0000',''),
- OPERATOR: ('b','#FAF785',''),
- MATH_OPERATOR: ('b','#FAF785',''),
- BRACKETS: ('b','#FAF785',''),
- COMMENT: ('','#45FCA0',''),
- DOUBLECOMMENT: ('i','#A7C7A9',''),
- CLASS_NAME: ('b','#B666FD',''),
- DEF_NAME: ('b','#EBAE5C',''),
- KEYWORD: ('b','#8680FF',''),
- SINGLEQUOTE: ('','#F8BAFE',''),
- SINGLEQUOTE_R: ('','#F8BAFE',''),
- SINGLEQUOTE_U: ('','#F8BAFE',''),
- DOUBLEQUOTE: ('','#FF80C0',''),
- DOUBLEQUOTE_R: ('','#FF80C0',''),
- DOUBLEQUOTE_U: ('','#FF80C0',''),
- TRIPLESINGLEQUOTE: ('','#FF9595',''),
- TRIPLESINGLEQUOTE_R: ('','#FF9595',''),
- TRIPLESINGLEQUOTE_U: ('','#FF9595',''),
- TRIPLEDOUBLEQUOTE: ('','#B3FFFF',''),
- TRIPLEDOUBLEQUOTE_R: ('','#B3FFFF',''),
- TRIPLEDOUBLEQUOTE_U: ('','#B3FFFF',''),
- TEXT: ('','#FFFFFF',''),
- LINENUMBER: ('>mi#555555','#bbccbb','#333333'),
- PAGEBACKGROUND: '#000000'
- }
-
-dark2 = {
- ERRORTOKEN: ('','#FF0000',''),
- DECORATOR_NAME: ('b','#FFBBAA',''),
- DECORATOR: ('b','#CC5511',''),
- ARGS: ('b','#DDDDDD',''),
- NAME: ('','#C0C0C0',''),
- NUMBER: ('b','#00FF00',''),
- OPERATOR: ('b','#FF090F',''),
- MATH_OPERATOR: ('b','#EE7020',''),
- BRACKETS: ('b','#FFB90F',''),
- COMMENT: ('i','#D0D000','#522000'),#'#88AA88','#11111F'),
- DOUBLECOMMENT: ('i','#D0D000','#522000'),#'#77BB77','#11111F'),
- CLASS_NAME: ('b','#DD4080',''),
- DEF_NAME: ('b','#FF8040',''),
- KEYWORD: ('b','#4726d1',''),
- SINGLEQUOTE: ('','#8080C0',''),
- SINGLEQUOTE_R: ('','#8080C0',''),
- SINGLEQUOTE_U: ('','#8080C0',''),
- DOUBLEQUOTE: ('','#ADB9F1',''),
- DOUBLEQUOTE_R: ('','#ADB9F1',''),
- DOUBLEQUOTE_U: ('','#ADB9F1',''),
- TRIPLESINGLEQUOTE: ('','#00C1C1',''),#A050C0
- TRIPLESINGLEQUOTE_R: ('','#00C1C1',''),#A050C0
- TRIPLESINGLEQUOTE_U: ('','#00C1C1',''),#A050C0
- TRIPLEDOUBLEQUOTE: ('','#33E3E3',''),#B090E0
- TRIPLEDOUBLEQUOTE_R: ('','#33E3E3',''),#B090E0
- TRIPLEDOUBLEQUOTE_U: ('','#33E3E3',''),#B090E0
- TEXT: ('','#C0C0C0',''),
- LINENUMBER: ('>mi#555555','#bbccbb','#333333'),
- PAGEBACKGROUND: '#000000'
- }
-
-lite = {
- ERRORTOKEN: ('s#FF0000','#FF8080',''),
- DECORATOR_NAME: ('b','#BB4422',''),
- DECORATOR: ('b','#3333AF',''),
- ARGS: ('b','#000000',''),
- NAME: ('','#333333',''),
- NUMBER: ('b','#DD2200',''),
- OPERATOR: ('b','#000000',''),
- MATH_OPERATOR: ('b','#000000',''),
- BRACKETS: ('b','#000000',''),
- COMMENT: ('','#007F00',''),
- DOUBLECOMMENT: ('','#608060',''),
- CLASS_NAME: ('b','#0000DF',''),
- DEF_NAME: ('b','#9C7A00',''),#f09030
- KEYWORD: ('b','#0000AF',''),
- SINGLEQUOTE: ('','#600080',''),
- SINGLEQUOTE_R: ('','#600080',''),
- SINGLEQUOTE_U: ('','#600080',''),
- DOUBLEQUOTE: ('','#A0008A',''),
- DOUBLEQUOTE_R: ('','#A0008A',''),
- DOUBLEQUOTE_U: ('','#A0008A',''),
- TRIPLESINGLEQUOTE: ('','#337799',''),
- TRIPLESINGLEQUOTE_R: ('','#337799',''),
- TRIPLESINGLEQUOTE_U: ('','#337799',''),
- TRIPLEDOUBLEQUOTE: ('','#1166AA',''),
- TRIPLEDOUBLEQUOTE_R: ('','#1166AA',''),
- TRIPLEDOUBLEQUOTE_U: ('','#1166AA',''),
- TEXT: ('','#000000',''),
- LINENUMBER: ('>ti#555555','#000000',''),
- PAGEBACKGROUND: '#FFFFFF'
- }
-
-idle = {
- ERRORTOKEN: ('s#FF0000','#FF8080',''),
- DECORATOR_NAME: ('','#900090',''),
- DECORATOR: ('','#FF7700',''),
- NAME: ('','#000000',''),
- NUMBER: ('','#000000',''),
- OPERATOR: ('','#000000',''),
- MATH_OPERATOR: ('','#000000',''),
- BRACKETS: ('','#000000',''),
- COMMENT: ('','#DD0000',''),
- DOUBLECOMMENT: ('','#DD0000',''),
- CLASS_NAME: ('','#0000FF',''),
- DEF_NAME: ('','#0000FF',''),
- KEYWORD: ('','#FF7700',''),
- SINGLEQUOTE: ('','#00AA00',''),
- SINGLEQUOTE_R: ('','#00AA00',''),
- SINGLEQUOTE_U: ('','#00AA00',''),
- DOUBLEQUOTE: ('','#00AA00',''),
- DOUBLEQUOTE_R: ('','#00AA00',''),
- DOUBLEQUOTE_U: ('','#00AA00',''),
- TRIPLESINGLEQUOTE: ('','#00AA00',''),
- TRIPLESINGLEQUOTE_R: ('','#00AA00',''),
- TRIPLESINGLEQUOTE_U: ('','#00AA00',''),
- TRIPLEDOUBLEQUOTE: ('','#00AA00',''),
- TRIPLEDOUBLEQUOTE_R: ('','#00AA00',''),
- TRIPLEDOUBLEQUOTE_U: ('','#00AA00',''),
- TEXT: ('','#000000',''),
- LINENUMBER: ('>ti#555555','#000000',''),
- PAGEBACKGROUND: '#FFFFFF'
- }
-
-pythonwin = {
- ERRORTOKEN: ('s#FF0000','#FF8080',''),
- DECORATOR_NAME: ('b','#DD0080',''),
- DECORATOR: ('b','#000080',''),
- ARGS: ('','#000000',''),
- NAME: ('','#303030',''),
- NUMBER: ('','#008080',''),
- OPERATOR: ('','#000000',''),
- MATH_OPERATOR: ('','#000000',''),
- BRACKETS: ('','#000000',''),
- COMMENT: ('','#007F00',''),
- DOUBLECOMMENT: ('','#7F7F7F',''),
- CLASS_NAME: ('b','#0000FF',''),
- DEF_NAME: ('b','#007F7F',''),
- KEYWORD: ('b','#000080',''),
- SINGLEQUOTE: ('','#808000',''),
- SINGLEQUOTE_R: ('','#808000',''),
- SINGLEQUOTE_U: ('','#808000',''),
- DOUBLEQUOTE: ('','#808000',''),
- DOUBLEQUOTE_R: ('','#808000',''),
- DOUBLEQUOTE_U: ('','#808000',''),
- TRIPLESINGLEQUOTE: ('','#808000',''),
- TRIPLESINGLEQUOTE_R: ('','#808000',''),
- TRIPLESINGLEQUOTE_U: ('','#808000',''),
- TRIPLEDOUBLEQUOTE: ('','#808000',''),
- TRIPLEDOUBLEQUOTE_R: ('','#808000',''),
- TRIPLEDOUBLEQUOTE_U: ('','#808000',''),
- TEXT: ('','#303030',''),
- LINENUMBER: ('>ti#555555','#000000',''),
- PAGEBACKGROUND: '#FFFFFF'
- }
-
-viewcvs = {
- ERRORTOKEN: ('s#FF0000','#FF8080',''),
- DECORATOR_NAME: ('','#000000',''),
- DECORATOR: ('','#000000',''),
- ARGS: ('','#000000',''),
- NAME: ('','#000000',''),
- NUMBER: ('','#000000',''),
- OPERATOR: ('','#000000',''),
- MATH_OPERATOR: ('','#000000',''),
- BRACKETS: ('','#000000',''),
- COMMENT: ('i','#b22222',''),
- DOUBLECOMMENT: ('i','#b22222',''),
- CLASS_NAME: ('','#000000',''),
- DEF_NAME: ('b','#0000ff',''),
- KEYWORD: ('b','#a020f0',''),
- SINGLEQUOTE: ('b','#bc8f8f',''),
- SINGLEQUOTE_R: ('b','#bc8f8f',''),
- SINGLEQUOTE_U: ('b','#bc8f8f',''),
- DOUBLEQUOTE: ('b','#bc8f8f',''),
- DOUBLEQUOTE_R: ('b','#bc8f8f',''),
- DOUBLEQUOTE_U: ('b','#bc8f8f',''),
- TRIPLESINGLEQUOTE: ('b','#bc8f8f',''),
- TRIPLESINGLEQUOTE_R: ('b','#bc8f8f',''),
- TRIPLESINGLEQUOTE_U: ('b','#bc8f8f',''),
- TRIPLEDOUBLEQUOTE: ('b','#bc8f8f',''),
- TRIPLEDOUBLEQUOTE_R: ('b','#bc8f8f',''),
- TRIPLEDOUBLEQUOTE_U: ('b','#bc8f8f',''),
- TEXT: ('','#000000',''),
- LINENUMBER: ('>ti#555555','#000000',''),
- PAGEBACKGROUND: '#FFFFFF'
- }
-
-defaultColors = lite
-
-def Usage():
- doc = """
- -----------------------------------------------------------------------------
- PySourceColor.py ver: %s
- -----------------------------------------------------------------------------
- Module summary:
- This module is designed to colorize python source code.
- Input--->python source
- Output-->colorized (html, html4.01/css, xhtml1.0)
- Standalone:
- This module will work from the command line with options.
- This module will work with redirected stdio.
- Imported:
- This module can be imported and used directly in your code.
- -----------------------------------------------------------------------------
- Command line options:
- -h, --help
- Optional-> Display this help message.
- -t, --test
- Optional-> Will ignore all others flags but --profile
- test all schemes and markup combinations
- -p, --profile
- Optional-> Works only with --test or -t
- runs profile.py and makes the test work in quiet mode.
- -i, --in, --input
- Optional-> If you give input on stdin.
- Use any of these for the current dir (.,cwd)
- Input can be file or dir.
- Input from stdin use one of the following (-,stdin)
- If stdin is used as input stdout is output unless specified.
- -o, --out, --output
- Optional-> output dir for the colorized source.
- default: output dir is the input dir.
- To output html to stdout use one of the following (-,stdout)
- Stdout can be used without stdin if you give a file as input.
- -c, --color
- Optional-> null, mono, dark, dark2, lite, idle, pythonwin, viewcvs
- default: dark
- -s, --show
- Optional-> Show page after creation.
- default: no show
- -m, --markup
- Optional-> html, css, xhtml
- css, xhtml also support external stylesheets (-e,--external)
- default: HTML
- -e, --external
- Optional-> use with css, xhtml
- Writes an style sheet instead of embedding it in the page
- saves it as pystyle.css in the same directory.
- html markup will silently ignore this flag.
- -H, --header
- Opional-> add a page header to the top of the output
- -H
- Builtin header (name,date,hrule)
- --header
- You must specify a filename.
- The header file must be valid html
- and must handle its own font colors.
- ex. --header c:/tmp/header.txt
- -F, --footer
- Opional-> add a page footer to the bottom of the output
- -F
- Builtin footer (hrule,name,date)
- --footer
- You must specify a filename.
- The footer file must be valid html
- and must handle its own font colors.
- ex. --footer c:/tmp/footer.txt
- -l, --linenumbers
- Optional-> default is no linenumbers
- Adds line numbers to the start of each line in the code.
- --convertpage
- Given a webpage that has code embedded in tags it will
- convert embedded code to colorized html.
- (see pageconvert for details)
- -----------------------------------------------------------------------------
- Option usage:
- # Test and show pages
- python PySourceColor.py -t -s
- # Test and only show profile results
- python PySourceColor.py -t -p
- # Colorize all .py,.pyw files in cwdir you can also use: (.,cwd)
- python PySourceColor.py -i .
- # Using long options w/ =
- python PySourceColor.py --in=c:/myDir/my.py --color=lite --show
- # Using short options w/out =
- python PySourceColor.py -i c:/myDir/ -c idle -m css -e
- # Using any mix
- python PySourceColor.py --in . -o=c:/myDir --show
- # Place a custom header on your files
- python PySourceColor.py -i . -o c:/tmp -m xhtml --header c:/header.txt
- -----------------------------------------------------------------------------
- Stdio usage:
- # Stdio using no options
- python PySourceColor.py < c:/MyFile.py > c:/tmp/MyFile.html
- # Using stdin alone automatically uses stdout for output: (stdin,-)
- python PySourceColor.py -i- < c:/MyFile.py > c:/tmp/myfile.html
- # Stdout can also be written to directly from a file instead of stdin
- python PySourceColor.py -i c:/MyFile.py -m css -o- > c:/tmp/myfile.html
- # Stdin can be used as input , but output can still be specified
- python PySourceColor.py -i- -o c:/pydoc.py.html -s < c:/Python22/my.py
- _____________________________________________________________________________
- """
- print(doc % (__version__))
- sys.exit(1)
-
-###################################################### Command line interface
-
-def cli():
- """Handle command line args and redirections"""
- try:
- # try to get command line args
- opts, args = getopt.getopt(sys.argv[1:],
- "hseqtplHFi:o:c:m:h:f:",["help", "show", "quiet",
- "test", "external", "linenumbers", "convertpage", "profile",
- "input=", "output=", "color=", "markup=","header=", "footer="])
- except getopt.GetoptError:
- # on error print help information and exit:
- Usage()
- # init some names
- input = None
- output = None
- colorscheme = None
- markup = 'html'
- header = None
- footer = None
- linenumbers = 0
- show = 0
- quiet = 0
- test = 0
- profile = 0
- convertpage = 0
- form = None
- # if we have args then process them
- for o, a in opts:
- if o in ["-h", "--help"]:
- Usage()
- sys.exit()
- if o in ["-o", "--output", "--out"]:
- output = a
- if o in ["-i", "--input", "--in"]:
- input = a
- if input in [".", "cwd"]:
- input = os.getcwd()
- if o in ["-s", "--show"]:
- show = 1
- if o in ["-q", "--quiet"]:
- quiet = 1
- if o in ["-t", "--test"]:
- test = 1
- if o in ["--convertpage"]:
- convertpage = 1
- if o in ["-p", "--profile"]:
- profile = 1
- if o in ["-e", "--external"]:
- form = 'external'
- if o in ["-m", "--markup"]:
- markup = str(a)
- if o in ["-l", "--linenumbers"]:
- linenumbers = 1
- if o in ["--header"]:
- header = str(a)
- elif o == "-H":
- header = ''
- if o in ["--footer"]:
- footer = str(a)
- elif o == "-F":
- footer = ''
- if o in ["-c", "--color"]:
- try:
- colorscheme = globals().get(a.lower())
- except:
- traceback.print_exc()
- Usage()
- if test:
- if profile:
- import profile
- profile.run('_test(show=%s, quiet=%s)'%(show,quiet))
- else:
- # Parse this script in every possible colorscheme and markup
- _test(show,quiet)
- elif input in [None, "-", "stdin"] or output in ["-", "stdout"]:
- # determine if we are going to use stdio
- if input not in [None, "-", "stdin"]:
- if os.path.isfile(input) :
- path2stdout(input, colors=colorscheme, markup=markup,
- linenumbers=linenumbers, header=header,
- footer=footer, form=form)
- else:
- raise PathError('File does not exists!')
- else:
- try:
- if sys.stdin.isatty():
- raise InputError('Please check input!')
- else:
- if output in [None,"-","stdout"]:
- str2stdout(sys.stdin.read(), colors=colorscheme,
- markup=markup, header=header,
- footer=footer, linenumbers=linenumbers,
- form=form)
- else:
- str2file(sys.stdin.read(), outfile=output, show=show,
- markup=markup, header=header, footer=footer,
- linenumbers=linenumbers, form=form)
- except:
- traceback.print_exc()
- Usage()
- else:
- if os.path.exists(input):
- if convertpage:
- # if there was at least an input given we can proceed
- pageconvert(input, out=output, colors=colorscheme,
- show=show, markup=markup,linenumbers=linenumbers)
- else:
- # if there was at least an input given we can proceed
- convert(source=input, outdir=output, colors=colorscheme,
- show=show, markup=markup, quiet=quiet, header=header,
- footer=footer, linenumbers=linenumbers, form=form)
- else:
- raise PathError('File does not exists!')
- Usage()
-
-######################################################### Simple markup tests
-
-def _test(show=0, quiet=0):
- """Test the parser and most of the functions.
-
- There are 19 test total(eight colorschemes in three diffrent markups,
- and a str2file test. Most functions are tested by this.
- """
- fi = sys.argv[0]
- if not fi.endswith('.exe'):# Do not test if frozen as an archive
- # this is a collection of test, most things are covered.
- path2file(fi, '/tmp/null.html', null, show=show, quiet=quiet)
- path2file(fi, '/tmp/null_css.html', null, show=show,
- markup='css', quiet=quiet)
- path2file(fi, '/tmp/mono.html', mono, show=show, quiet=quiet)
- path2file(fi, '/tmp/mono_css.html', mono, show=show,
- markup='css', quiet=quiet)
- path2file(fi, '/tmp/lite.html', lite, show=show, quiet=quiet)
- path2file(fi, '/tmp/lite_css.html', lite, show=show,
- markup='css', quiet=quiet, header='', footer='',
- linenumbers=1)
- path2file(fi, '/tmp/lite_xhtml.html', lite, show=show,
- markup='xhtml', quiet=quiet)
- path2file(fi, '/tmp/dark.html', dark, show=show, quiet=quiet)
- path2file(fi, '/tmp/dark_css.html', dark, show=show,
- markup='css', quiet=quiet, linenumbers=1)
- path2file(fi, '/tmp/dark2.html', dark2, show=show, quiet=quiet)
- path2file(fi, '/tmp/dark2_css.html', dark2, show=show,
- markup='css', quiet=quiet)
- path2file(fi, '/tmp/dark2_xhtml.html', dark2, show=show,
- markup='xhtml', quiet=quiet, header='', footer='',
- linenumbers=1, form='external')
- path2file(fi, '/tmp/idle.html', idle, show=show, quiet=quiet)
- path2file(fi, '/tmp/idle_css.html', idle, show=show,
- markup='css', quiet=quiet)
- path2file(fi, '/tmp/viewcvs.html', viewcvs, show=show,
- quiet=quiet, linenumbers=1)
- path2file(fi, '/tmp/viewcvs_css.html', viewcvs, show=show,
- markup='css', linenumbers=1, quiet=quiet)
- path2file(fi, '/tmp/pythonwin.html', pythonwin, show=show,
- quiet=quiet)
- path2file(fi, '/tmp/pythonwin_css.html', pythonwin, show=show,
- markup='css', quiet=quiet)
- teststr=r'''"""This is a test of decorators and other things"""
-# This should be line 421...
-@whatever(arg,arg2)
-@A @B(arghh) @C
-def LlamaSaysNi(arg='Ni!',arg2="RALPH"):
- """This docstring is deeply disturbed by all the llama references"""
- print('%s The Wonder Llama says %s'% (arg2,arg))
-# So I was like duh!, and he was like ya know?!,
-# and so we were both like huh...wtf!? RTFM!! LOL!!;)
-@staticmethod## Double comments are KewL.
-def LlamasRLumpy():
- """This docstring is too sexy to be here.
- """
- u"""
-=============================
-A Mse once bit my sister...
-=============================
- """
- ## Relax, this won't hurt a bit, just a simple, painless procedure,
- ## hold still while I get the anesthetizing hammer.
- m = {'three':'1','won':'2','too':'3'}
- o = r'fishy\fishy\fishy/fish\oh/where/is\my/little\..'
- python = uR"""
- No realli! She was Karving her initials n the mse with the sharpened end
- of an interspace tthbrush given her by Svenge - her brother-in-law -an Oslo
- dentist and star of many Norwegian mvies: "The Ht Hands of an Oslo
- Dentist", "Fillings of Passion", "The Huge Mlars of Horst Nordfink"..."""
- RU"""142 MEXICAN WHOOPING LLAMAS"""#<-Can you fit 142 llamas in a red box?
- n = u' HERMSGERVRDENBRTBRDA ' + """ YUTTE """
- t = """SAMALLNIATNUOMNAIRODAUCE"""+"DENIARTYLLAICEPS04"
- ## We apologise for the fault in the
- ## comments. Those responsible have been
- ## sacked.
- y = '14 NORTH CHILEAN GUANACOS \
-(CLOSELY RELATED TO THE LLAMA)'
- rules = [0,1,2,3,4,5]
- print y'''
- htmlPath = os.path.abspath('/tmp/strtest_lines.html')
- str2file(teststr, htmlPath, colors=dark, markup='xhtml',
- linenumbers=420, show=show)
- _printinfo(" wrote %s" % htmlPath, quiet)
- htmlPath = os.path.abspath('/tmp/strtest_nolines.html')
- str2file(teststr, htmlPath, colors=dark, markup='xhtml',
- show=show)
- _printinfo(" wrote %s" % htmlPath, quiet)
- else:
- Usage()
- return
-
-# emacs wants this: '
-
-####################################################### User funtctions
-
-def str2stdout(sourcestring, colors=None, title='', markup='html',
- header=None, footer=None,
- linenumbers=0, form=None):
- """Converts a code(string) to colorized HTML. Writes to stdout.
-
- form='code',or'snip' (for "<pre>yourcode</pre>" only)
- colors=null,mono,lite,dark,dark2,idle,or pythonwin
- """
- Parser(sourcestring, colors=colors, title=title, markup=markup,
- header=header, footer=footer,
- linenumbers=linenumbers).format(form)
-
-def path2stdout(sourcepath, title='', colors=None, markup='html',
- header=None, footer=None,
- linenumbers=0, form=None):
- """Converts code(file) to colorized HTML. Writes to stdout.
-
- form='code',or'snip' (for "<pre>yourcode</pre>" only)
- colors=null,mono,lite,dark,dark2,idle,or pythonwin
- """
- sourcestring = open(sourcepath).read()
- Parser(sourcestring, colors=colors, title=sourcepath,
- markup=markup, header=header, footer=footer,
- linenumbers=linenumbers).format(form)
-
-def str2html(sourcestring, colors=None, title='',
- markup='html', header=None, footer=None,
- linenumbers=0, form=None):
- """Converts a code(string) to colorized HTML. Returns an HTML string.
-
- form='code',or'snip' (for "<pre>yourcode</pre>" only)
- colors=null,mono,lite,dark,dark2,idle,or pythonwin
- """
- stringIO = StringIO.StringIO()
- Parser(sourcestring, colors=colors, title=title, out=stringIO,
- markup=markup, header=header, footer=footer,
- linenumbers=linenumbers).format(form)
- stringIO.seek(0)
- return stringIO.read()
-
-def str2css(sourcestring, colors=None, title='',
- markup='css', header=None, footer=None,
- linenumbers=0, form=None):
- """Converts a code string to colorized CSS/HTML. Returns CSS/HTML string
-
- If form != None then this will return (stylesheet_str, code_str)
- colors=null,mono,lite,dark,dark2,idle,or pythonwin
- """
- if markup.lower() not in ['css' ,'xhtml']:
- markup = 'css'
- stringIO = StringIO.StringIO()
- parse = Parser(sourcestring, colors=colors, title=title,
- out=stringIO, markup=markup,
- header=header, footer=footer,
- linenumbers=linenumbers)
- parse.format(form)
- stringIO.seek(0)
- if form != None:
- return parse._sendCSSStyle(external=1), stringIO.read()
- else:
- return None, stringIO.read()
-
-def str2markup(sourcestring, colors=None, title = '',
- markup='xhtml', header=None, footer=None,
- linenumbers=0, form=None):
- """ Convert code strings into ([stylesheet or None], colorized string) """
- if markup.lower() == 'html':
- return None, str2html(sourcestring, colors=colors, title=title,
- header=header, footer=footer, markup=markup,
- linenumbers=linenumbers, form=form)
- else:
- return str2css(sourcestring, colors=colors, title=title,
- header=header, footer=footer, markup=markup,
- linenumbers=linenumbers, form=form)
-
-def str2file(sourcestring, outfile, colors=None, title='',
- markup='html', header=None, footer=None,
- linenumbers=0, show=0, dosheet=1, form=None):
- """Converts a code string to a file.
-
- makes no attempt at correcting bad pathnames
- """
- css , html = str2markup(sourcestring, colors=colors, title='',
- markup=markup, header=header, footer=footer,
- linenumbers=linenumbers, form=form)
- # write html
- f = open(outfile,'wt')
- f.writelines(html)
- f.close()
- #write css
- if css != None and dosheet:
- dir = os.path.dirname(outfile)
- outcss = os.path.join(dir,'pystyle.css')
- f = open(outcss,'wt')
- f.writelines(css)
- f.close()
- if show:
- showpage(outfile)
-
-def path2html(sourcepath, colors=None, markup='html',
- header=None, footer=None,
- linenumbers=0, form=None):
- """Converts code(file) to colorized HTML. Returns an HTML string.
-
- form='code',or'snip' (for "<pre>yourcode</pre>" only)
- colors=null,mono,lite,dark,dark2,idle,or pythonwin
- """
- stringIO = StringIO.StringIO()
- sourcestring = open(sourcepath).read()
- Parser(sourcestring, colors, title=sourcepath, out=stringIO,
- markup=markup, header=header, footer=footer,
- linenumbers=linenumbers).format(form)
- stringIO.seek(0)
- return stringIO.read()
-
-def convert(source, outdir=None, colors=None,
- show=0, markup='html', quiet=0,
- header=None, footer=None, linenumbers=0, form=None):
- """Takes a file or dir as input and places the html in the outdir.
-
- If outdir is none it defaults to the input dir
- """
- count=0
- # If it is a filename then path2file
- if not os.path.isdir(source):
- if os.path.isfile(source):
- count+=1
- path2file(source, outdir, colors, show, markup,
- quiet, form, header, footer, linenumbers, count)
- else:
- raise PathError('File does not exist!')
- # If we pass in a dir we need to walkdir for files.
- # Then we need to colorize them with path2file
- else:
- fileList = walkdir(source)
- if fileList != None:
- # make sure outdir is a dir
- if outdir != None:
- if os.path.splitext(outdir)[1] != '':
- outdir = os.path.split(outdir)[0]
- for item in fileList:
- count+=1
- path2file(item, outdir, colors, show, markup,
- quiet, form, header, footer, linenumbers, count)
- _printinfo('Completed colorizing %s files.'%str(count), quiet)
- else:
- _printinfo("No files to convert in dir.", quiet)
-
-def path2file(sourcePath, out=None, colors=None, show=0,
- markup='html', quiet=0, form=None,
- header=None, footer=None, linenumbers=0, count=1):
- """ Converts python source to html file"""
- # If no outdir is given we use the sourcePath
- if out == None:#this is a guess
- htmlPath = sourcePath + '.html'
- else:
- # If we do give an out_dir, and it does
- # not exist , it will be created.
- if os.path.splitext(out)[1] == '':
- if not os.path.isdir(out):
- os.makedirs(out)
- sourceName = os.path.basename(sourcePath)
- htmlPath = os.path.join(out,sourceName)+'.html'
- # If we do give an out_name, and its dir does
- # not exist , it will be created.
- else:
- outdir = os.path.split(out)[0]
- if not os.path.isdir(outdir):
- os.makedirs(outdir)
- htmlPath = out
- htmlPath = os.path.abspath(htmlPath)
- # Open the text and do the parsing.
- source = open(sourcePath).read()
- parse = Parser(source, colors, sourcePath, open(htmlPath, 'wt'),
- markup, header, footer, linenumbers)
- parse.format(form)
- _printinfo(" wrote %s" % htmlPath, quiet)
- # html markup will ignore the external flag, but
- # we need to stop the blank file from being written.
- if form == 'external' and count == 1 and markup != 'html':
- cssSheet = parse._sendCSSStyle(external=1)
- cssPath = os.path.join(os.path.dirname(htmlPath),'pystyle.css')
- css = open(cssPath, 'wt')
- css.write(cssSheet)
- css.close()
- _printinfo(" wrote %s" % cssPath, quiet)
- if show:
- # load HTML page into the default web browser.
- showpage(htmlPath)
- return htmlPath
-
-def tagreplace(sourcestr, colors=lite, markup='xhtml',
- linenumbers=0, dosheet=1, tagstart='<PY>'.lower(),
- tagend='</PY>'.lower(), stylesheet='pystyle.css'):
- """This is a helper function for pageconvert. Returns css, page.
- """
- if markup.lower() != 'html':
- link = '<link rel="stylesheet" href="%s" type="text/css"/></head>'
- css = link%stylesheet
- if sourcestr.find(css) == -1:
- sourcestr = sourcestr.replace('</head>', css, 1)
- starttags = sourcestr.count(tagstart)
- endtags = sourcestr.count(tagend)
- if starttags:
- if starttags == endtags:
- for _ in range(starttags):
- datastart = sourcestr.find(tagstart)
- dataend = sourcestr.find(tagend)
- data = sourcestr[datastart+len(tagstart):dataend]
- data = unescape(data)
- css , data = str2markup(data, colors=colors,
- linenumbers=linenumbers, markup=markup, form='embed')
- start = sourcestr[:datastart]
- end = sourcestr[dataend+len(tagend):]
- sourcestr = ''.join([start,data,end])
- else:
- raise InputError('Tag mismatch!\nCheck %s,%s tags'%tagstart,tagend)
- if not dosheet:
- css = None
- return css, sourcestr
-
-def pageconvert(path, out=None, colors=lite, markup='xhtml', linenumbers=0,
- dosheet=1, tagstart='<PY>'.lower(), tagend='</PY>'.lower(),
- stylesheet='pystyle', show=1, returnstr=0):
- """This function can colorize Python source
-
- that is written in a webpage enclosed in tags.
- """
- if out == None:
- out = os.path.dirname(path)
- infile = open(path, 'r').read()
- css,page = tagreplace(sourcestr=infile,colors=colors,
- markup=markup, linenumbers=linenumbers, dosheet=dosheet,
- tagstart=tagstart, tagend=tagend, stylesheet=stylesheet)
- if not returnstr:
- newpath = os.path.abspath(os.path.join(
- out,'tmp', os.path.basename(path)))
- if not os.path.exists(newpath):
- try:
- os.makedirs(os.path.dirname(newpath))
- except:
- pass#traceback.print_exc()
- #Usage()
- y = open(newpath, 'w')
- y.write(page)
- y.close()
- if css:
- csspath = os.path.abspath(os.path.join(
- out,'tmp','%s.css'%stylesheet))
- x = open(csspath,'w')
- x.write(css)
- x.close()
- if show:
- try:
- os.startfile(newpath)
- except:
- traceback.print_exc()
- return newpath
- else:
- return css, page
-
-##################################################################### helpers
-
-def walkdir(dir):
- """Return a list of .py and .pyw files from a given directory.
-
- This function can be written as a generator Python 2.3, or a genexp
- in Python 2.4. But 2.2 and 2.1 would be left out....
- """
- # Get a list of files that match *.py*
- GLOB_PATTERN = os.path.join(dir, "*.[p][y]*")
- pathlist = glob.glob(GLOB_PATTERN)
- # Now filter out all but py and pyw
- filterlist = [x for x in pathlist
- if x.endswith('.py')
- or x.endswith('.pyw')]
- if filterlist != []:
- # if we have a list send it
- return filterlist
- else:
- return None
-
-def showpage(path):
- """Helper function to open webpages"""
- try:
- import webbrowser
- webbrowser.open_new(os.path.abspath(path))
- except:
- traceback.print_exc()
-
-def _printinfo(message, quiet):
- """Helper to print messages"""
- if not quiet:
- print(message)
-
-def escape(text):
- """escape text for html. similar to cgi.escape"""
- text = text.replace("&", "&amp;")
- text = text.replace("<", "&lt;")
- text = text.replace(">", "&gt;")
- return text
-
-def unescape(text):
- """unsecape escaped text"""
- text = text.replace("&quot;", '"')
- text = text.replace("&gt;", ">")
- text = text.replace("&lt;", "<")
- text = text.replace("&amp;", "&")
- return text
-
-########################################################### Custom Exceptions
-
-class PySourceColorError(Exception):
- # Base for custom errors
- def __init__(self, msg=''):
- self._msg = msg
- Exception.__init__(self, msg)
- def __repr__(self):
- return self._msg
- __str__ = __repr__
-
-class PathError(PySourceColorError):
- def __init__(self, msg):
- PySourceColorError.__init__(self,
- 'Path error! : %s'% msg)
-
-class InputError(PySourceColorError):
- def __init__(self, msg):
- PySourceColorError.__init__(self,
- 'Input error! : %s'% msg)
-
-########################################################## Python code parser
-
-class Parser(object):
-
- """MoinMoin python parser heavily chopped :)"""
-
- def __init__(self, raw, colors=None, title='', out=sys.stdout,
- markup='html', header=None, footer=None, linenumbers=0):
- """Store the source text & set some flags"""
- if colors == None:
- colors = defaultColors
- self.raw = raw.expandtabs().rstrip()
- self.title = os.path.basename(title)
- self.out = out
- self.line = ''
- self.lasttext = ''
- self.argFlag = 0
- self.classFlag = 0
- self.defFlag = 0
- self.decoratorFlag = 0
- self.external = 0
- self.markup = markup.upper()
- self.colors = colors
- self.header = header
- self.footer = footer
- self.doArgs = 1 # overrides the new tokens
- self.doNames = 1 # overrides the new tokens
- self.doMathOps = 1 # overrides the new tokens
- self.doBrackets = 1 # overrides the new tokens
- self.doURL = 1 # override url conversion
- self.LINENUMHOLDER = "___line___".upper()
- self.LINESTART = "___start___".upper()
- self.skip = 0
- # add space left side of code for padding.Override in color dict.
- self.extraspace = self.colors.get(EXTRASPACE, '')
- # Linenumbers less then zero also have numberlinks
- self.dolinenums = self.linenum = abs(linenumbers)
- if linenumbers < 0:
- self.numberlinks = 1
- else:
- self.numberlinks = 0
-
- def format(self, form=None):
- """Parse and send the colorized source"""
- if form in ('snip','code'):
- self.addEnds = 0
- elif form == 'embed':
- self.addEnds = 0
- self.external = 1
- else:
- if form == 'external':
- self.external = 1
- self.addEnds = 1
-
- # Store line offsets in self.lines
- self.lines = [0, 0]
- pos = 0
-
- # Add linenumbers
- if self.dolinenums:
- start=self.LINENUMHOLDER+' '+self.extraspace
- else:
- start=''+self.extraspace
- newlines = []
- lines = self.raw.splitlines(0)
- for l in lines:
- # span and div escape for customizing and embedding raw text
- if (l.startswith('#$#')
- or l.startswith('#%#')
- or l.startswith('#@#')):
- newlines.append(l)
- else:
- # kludge for line spans in css,xhtml
- if self.markup in ['XHTML','CSS']:
- newlines.append(self.LINESTART+' '+start+l)
- else:
- newlines.append(start+l)
- self.raw = "\n".join(newlines)+'\n'# plus an extra newline at the end
-
- # Gather lines
- while 1:
- pos = self.raw.find('\n', pos) + 1
- if not pos: break
- self.lines.append(pos)
- self.lines.append(len(self.raw))
-
- # Wrap text in a filelike object
- self.pos = 0
- text = StringIO.StringIO(self.raw)
-
- # Markup start
- if self.addEnds:
- self._doPageStart()
- else:
- self._doSnippetStart()
-
- ## Tokenize calls the __call__
- ## function for each token till done.
- # Parse the source and write out the results.
- try:
- tokenize.tokenize(text.readline, self)
- except tokenize.TokenError as ex:
- msg = ex[0]
- line = ex[1][0]
- self.out.write("<h3>ERROR: %s</h3>%s\n"%
- (msg, self.raw[self.lines[line]:]))
- #traceback.print_exc()
-
- # Markup end
- if self.addEnds:
- self._doPageEnd()
- else:
- self._doSnippetEnd()
-
- def __call__(self, toktype, toktext, srow_col, erow_col, line):
- """Token handler. Order is important do not rearrange."""
- self.line = line
- srow, scol = srow_col
- erow, ecol = erow_col
- # Calculate new positions
- oldpos = self.pos
- newpos = self.lines[srow] + scol
- self.pos = newpos + len(toktext)
- # Handle newlines
- if toktype in (token.NEWLINE, tokenize.NL):
- self.decoratorFlag = self.argFlag = 0
- # kludge for line spans in css,xhtml
- if self.markup in ['XHTML','CSS']:
- self.out.write('</span>')
- self.out.write('\n')
- return
-
- # Send the original whitespace, and tokenize backslashes if present.
- # Tokenizer.py just sends continued line backslashes with whitespace.
- # This is a hack to tokenize continued line slashes as operators.
- # Should continued line backslashes be treated as operators
- # or some other token?
-
- if newpos > oldpos:
- if self.raw[oldpos:newpos].isspace():
- # consume a single space after linestarts and linenumbers
- # had to have them so tokenizer could seperate them.
- # multiline strings are handled by do_Text functions
- if self.lasttext != self.LINESTART \
- and self.lasttext != self.LINENUMHOLDER:
- self.out.write(self.raw[oldpos:newpos])
- else:
- self.out.write(self.raw[oldpos+1:newpos])
- else:
- slash = self.raw[oldpos:newpos].find('\\')+oldpos
- self.out.write(self.raw[oldpos:slash])
- getattr(self, '_send%sText'%(self.markup))(OPERATOR, '\\')
- self.linenum+=1
- # kludge for line spans in css,xhtml
- if self.markup in ['XHTML','CSS']:
- self.out.write('</span>')
- self.out.write(self.raw[slash+1:newpos])
-
- # Skip indenting tokens
- if toktype in (token.INDENT, token.DEDENT):
- self.pos = newpos
- return
-
- # Look for operators
- if token.LPAR <= toktype and toktype <= token.OP:
- # Trap decorators py2.4 >
- if toktext == '@':
- toktype = DECORATOR
- # Set a flag if this was the decorator start so
- # the decorator name and arguments can be identified
- self.decoratorFlag = self.argFlag = 1
- else:
- if self.doArgs:
- # Find the start for arguments
- if toktext == '(' and self.argFlag:
- self.argFlag = 2
- # Find the end for arguments
- elif toktext == ':':
- self.argFlag = 0
- ## Seperate the diffrent operator types
- # Brackets
- if self.doBrackets and toktext in ['[',']','(',')','{','}']:
- toktype = BRACKETS
- # Math operators
- elif self.doMathOps and toktext in ['*=','**=','-=','+=','|=',
- '%=','>>=','<<=','=','^=',
- '/=', '+','-','**','*','/','%']:
- toktype = MATH_OPERATOR
- # Operator
- else:
- toktype = OPERATOR
- # example how flags should work.
- # def fun(arg=argvalue,arg2=argvalue2):
- # 0 1 2 A 1 N 2 A 1 N 0
- if toktext == "=" and self.argFlag == 2:
- self.argFlag = 1
- elif toktext == "," and self.argFlag == 1:
- self.argFlag = 2
- # Look for keywords
- elif toktype == NAME and keyword.iskeyword(toktext):
- toktype = KEYWORD
- # Set a flag if this was the class / def start so
- # the class / def name and arguments can be identified
- if toktext in ['class', 'def']:
- if toktext =='class' and \
- not line[:line.find('class')].endswith('.'):
- self.classFlag = self.argFlag = 1
- elif toktext == 'def' and \
- not line[:line.find('def')].endswith('.'):
- self.defFlag = self.argFlag = 1
- else:
- # must have used a keyword as a name i.e. self.class
- toktype = ERRORTOKEN
-
- # Look for class, def, decorator name
- elif (self.classFlag or self.defFlag or self.decoratorFlag) \
- and self.doNames:
- if self.classFlag:
- self.classFlag = 0
- toktype = CLASS_NAME
- elif self.defFlag:
- self.defFlag = 0
- toktype = DEF_NAME
- elif self.decoratorFlag:
- self.decoratorFlag = 0
- toktype = DECORATOR_NAME
-
- # Look for strings
- # Order of evaluation is important do not change.
- elif toktype == token.STRING:
- text = toktext.lower()
- # TRIPLE DOUBLE QUOTE's
- if (text[:3] == '"""'):
- toktype = TRIPLEDOUBLEQUOTE
- elif (text[:4] == 'r"""'):
- toktype = TRIPLEDOUBLEQUOTE_R
- elif (text[:4] == 'u"""' or
- text[:5] == 'ur"""'):
- toktype = TRIPLEDOUBLEQUOTE_U
- # DOUBLE QUOTE's
- elif (text[:1] == '"'):
- toktype = DOUBLEQUOTE
- elif (text[:2] == 'r"'):
- toktype = DOUBLEQUOTE_R
- elif (text[:2] == 'u"' or
- text[:3] == 'ur"'):
- toktype = DOUBLEQUOTE_U
- # TRIPLE SINGLE QUOTE's
- elif (text[:3] == "'''"):
- toktype = TRIPLESINGLEQUOTE
- elif (text[:4] == "r'''"):
- toktype = TRIPLESINGLEQUOTE_R
- elif (text[:4] == "u'''" or
- text[:5] == "ur'''"):
- toktype = TRIPLESINGLEQUOTE_U
- # SINGLE QUOTE's
- elif (text[:1] == "'"):
- toktype = SINGLEQUOTE
- elif (text[:2] == "r'"):
- toktype = SINGLEQUOTE_R
- elif (text[:2] == "u'" or
- text[:3] == "ur'"):
- toktype = SINGLEQUOTE_U
-
- # test for invalid string declaration
- if self.lasttext.lower() == 'ru':
- toktype = ERRORTOKEN
-
- # Look for comments
- elif toktype == COMMENT:
- if toktext[:2] == "##":
- toktype = DOUBLECOMMENT
- elif toktext[:3] == '#$#':
- toktype = TEXT
- self.textFlag = 'SPAN'
- toktext = toktext[3:]
- elif toktext[:3] == '#%#':
- toktype = TEXT
- self.textFlag = 'DIV'
- toktext = toktext[3:]
- elif toktext[:3] == '#@#':
- toktype = TEXT
- self.textFlag = 'RAW'
- toktext = toktext[3:]
- if self.doURL:
- # this is a 'fake helper function'
- # url(URI,Alias_name) or url(URI)
- url_pos = toktext.find('url(')
- if url_pos != -1:
- before = toktext[:url_pos]
- url = toktext[url_pos+4:]
- splitpoint = url.find(',')
- endpoint = url.find(')')
- after = url[endpoint+1:]
- url = url[:endpoint]
- if splitpoint != -1:
- urlparts = url.split(',',1)
- toktext = '%s<a href="%s">%s</a>%s'%(
- before,urlparts[0],urlparts[1].lstrip(),after)
- else:
- toktext = '%s<a href="%s">%s</a>%s'%(before,url,url,after)
-
- # Seperate errors from decorators
- elif toktype == ERRORTOKEN:
- # Bug fix for < py2.4
- # space between decorators
- if self.argFlag and toktext.isspace():
- #toktype = NAME
- self.out.write(toktext)
- return
- # Bug fix for py2.2 linenumbers with decorators
- elif toktext.isspace():
- # What if we have a decorator after a >>> or ...
- #p = line.find('@')
- #if p >= 0 and not line[:p].isspace():
- #self.out.write(toktext)
- #return
- if self.skip:
- self.skip=0
- return
- else:
- self.out.write(toktext)
- return
- # trap decorators < py2.4
- elif toktext == '@':
- toktype = DECORATOR
- # Set a flag if this was the decorator start so
- # the decorator name and arguments can be identified
- self.decoratorFlag = self.argFlag = 1
-
- # Seperate args from names
- elif (self.argFlag == 2 and
- toktype == NAME and
- toktext != 'None' and
- self.doArgs):
- toktype = ARGS
-
- # Look for line numbers
- # The conversion code for them is in the send_text functions.
- if toktext in [self.LINENUMHOLDER,self.LINESTART]:
- toktype = LINENUMBER
- # if we don't have linenumbers set flag
- # to skip the trailing space from linestart
- if toktext == self.LINESTART and not self.dolinenums \
- or toktext == self.LINENUMHOLDER:
- self.skip=1
-
-
- # Skip blank token that made it thru
- ## bugfix for the last empty tag.
- if toktext == '':
- return
-
- # Last token text history
- self.lasttext = toktext
-
- # escape all but the urls in the comments
- if toktype in (DOUBLECOMMENT, COMMENT):
- if toktext.find('<a href=') == -1:
- toktext = escape(toktext)
- else:
- pass
- elif toktype == TEXT:
- pass
- else:
- toktext = escape(toktext)
-
- # Send text for any markup
- getattr(self, '_send%sText'%(self.markup))(toktype, toktext)
- return
-
- ################################################################# Helpers
-
- def _doSnippetStart(self):
- if self.markup == 'HTML':
- # Start of html snippet
- self.out.write('<pre>\n')
- else:
- # Start of css/xhtml snippet
- self.out.write(self.colors.get(CODESTART,'<pre class="py">\n'))
-
- def _doSnippetEnd(self):
- # End of html snippet
- self.out.write(self.colors.get(CODEEND,'</pre>\n'))
-
- ######################################################## markup selectors
-
- def _getFile(self, filepath):
- try:
- _file = open(filepath,'r')
- content = _file.read()
- _file.close()
- except:
- traceback.print_exc()
- content = ''
- return content
-
- def _doPageStart(self):
- getattr(self, '_do%sStart'%(self.markup))()
-
- def _doPageHeader(self):
- if self.header != None:
- if self.header.find('#$#') != -1 or \
- self.header.find('#$#') != -1 or \
- self.header.find('#%#') != -1:
- self.out.write(self.header[3:])
- else:
- if self.header != '':
- self.header = self._getFile(self.header)
- getattr(self, '_do%sHeader'%(self.markup))()
-
- def _doPageFooter(self):
- if self.footer != None:
- if self.footer.find('#$#') != -1 or \
- self.footer.find('#@#') != -1 or \
- self.footer.find('#%#') != -1:
- self.out.write(self.footer[3:])
- else:
- if self.footer != '':
- self.footer = self._getFile(self.footer)
- getattr(self, '_do%sFooter'%(self.markup))()
-
- def _doPageEnd(self):
- getattr(self, '_do%sEnd'%(self.markup))()
-
- ################################################### color/style retrieval
- ## Some of these are not used anymore but are kept for documentation
-
- def _getLineNumber(self):
- num = self.linenum
- self.linenum+=1
- return str(num).rjust(5)+" "
-
- def _getTags(self, key):
- # style tags
- return self.colors.get(key, self.colors[NAME])[0]
-
- def _getForeColor(self, key):
- # get text foreground color, if not set to black
- color = self.colors.get(key, self.colors[NAME])[1]
- if color[:1] != '#':
- color = '#000000'
- return color
-
- def _getBackColor(self, key):
- # get text background color
- return self.colors.get(key, self.colors[NAME])[2]
-
- def _getPageColor(self):
- # get page background color
- return self.colors.get(PAGEBACKGROUND, '#FFFFFF')
-
- def _getStyle(self, key):
- # get the token style from the color dictionary
- return self.colors.get(key, self.colors[NAME])
-
- def _getMarkupClass(self, key):
- # get the markup class name from the markup dictionary
- return MARKUPDICT.get(key, MARKUPDICT[NAME])
-
- def _getDocumentCreatedBy(self):
- return '<!--This document created by %s ver.%s on: %s-->\n'%(
- __title__,__version__,time.ctime())
-
- ################################################### HTML markup functions
-
- def _doHTMLStart(self):
- # Start of html page
- self.out.write('<!DOCTYPE html PUBLIC \
-"-//W3C//DTD HTML 4.01//EN">\n')
- self.out.write('<html><head><title>%s</title>\n'%(self.title))
- self.out.write(self._getDocumentCreatedBy())
- self.out.write('<meta http-equiv="Content-Type" \
-content="text/html;charset=iso-8859-1">\n')
- # Get background
- self.out.write('</head><body bgcolor="%s">\n'%self._getPageColor())
- self._doPageHeader()
- self.out.write('<pre>')
-
- def _getHTMLStyles(self, toktype, toktext):
- # Get styles
- tags, color = self.colors.get(toktype, self.colors[NAME])[:2]#
- tagstart=[]
- tagend=[]
- # check for styles and set them if needed.
- if 'b' in tags:#Bold
- tagstart.append('<b>')
- tagend.append('</b>')
- if 'i' in tags:#Italics
- tagstart.append('<i>')
- tagend.append('</i>')
- if 'u' in tags:#Underline
- tagstart.append('<u>')
- tagend.append('</u>')
- # HTML tags should be paired like so : <b><i><u>Doh!</u></i></b>
- tagend.reverse()
- starttags="".join(tagstart)
- endtags="".join(tagend)
- return starttags,endtags,color
-
- def _sendHTMLText(self, toktype, toktext):
- numberlinks = self.numberlinks
-
- # If it is an error, set a red box around the bad tokens
- # older browsers should ignore it
- if toktype == ERRORTOKEN:
- style = ' style="border: solid 1.5pt #FF0000;"'
- else:
- style = ''
- # Get styles
- starttag, endtag, color = self._getHTMLStyles(toktype, toktext)
- # This is a hack to 'fix' multi-line strings.
- # Multi-line strings are treated as only one token
- # even though they can be several physical lines.
- # That makes it hard to spot the start of a line,
- # because at this level all we know about are tokens.
-
- if toktext.count(self.LINENUMHOLDER):
- # rip apart the string and separate it by line.
- # count lines and change all linenum token to line numbers.
- # embedded all the new font tags inside the current one.
- # Do this by ending the tag first then writing our new tags,
- # then starting another font tag exactly like the first one.
- if toktype == LINENUMBER:
- splittext = toktext.split(self.LINENUMHOLDER)
- else:
- splittext = toktext.split(self.LINENUMHOLDER+' ')
- store = []
- store.append(splittext.pop(0))
- lstarttag, lendtag, lcolor = self._getHTMLStyles(LINENUMBER, toktext)
- count = len(splittext)
- for item in splittext:
- num = self._getLineNumber()
- if numberlinks:
- numstrip = num.strip()
- content = '<a name="%s" href="#%s">%s</a>' \
- %(numstrip,numstrip,num)
- else:
- content = num
- if count <= 1:
- endtag,starttag = '',''
- linenumber = ''.join([endtag,'<font color=', lcolor, '>',
- lstarttag, content, lendtag, '</font>' ,starttag])
- store.append(linenumber+item)
- toktext = ''.join(store)
- # send text
- ## Output optimization
- # skip font tag if black text, but styles will still be sent. (b,u,i)
- if color !='#000000':
- startfont = '<font color="%s"%s>'%(color, style)
- endfont = '</font>'
- else:
- startfont, endfont = ('','')
- if toktype != LINENUMBER:
- self.out.write(''.join([startfont,starttag,
- toktext,endtag,endfont]))
- else:
- self.out.write(toktext)
- return
-
- def _doHTMLHeader(self):
- # Optional
- if self.header != '':
- self.out.write('%s\n'%self.header)
- else:
- color = self._getForeColor(NAME)
- self.out.write('<b><font color="%s"># %s \
- <br># %s</font></b><hr>\n'%
- (color, self.title, time.ctime()))
-
- def _doHTMLFooter(self):
- # Optional
- if self.footer != '':
- self.out.write('%s\n'%self.footer)
- else:
- color = self._getForeColor(NAME)
- self.out.write('<b><font color="%s"> \
- <hr># %s<br># %s</font></b>\n'%
- (color, self.title, time.ctime()))
-
- def _doHTMLEnd(self):
- # End of html page
- self.out.write('</pre>\n')
- # Write a little info at the bottom
- self._doPageFooter()
- self.out.write('</body></html>\n')
-
- #################################################### CSS markup functions
-
- def _getCSSStyle(self, key):
- # Get the tags and colors from the dictionary
- tags, forecolor, backcolor = self._getStyle(key)
- style=[]
- border = None
- bordercolor = None
- tags = tags.lower()
- if tags:
- # get the border color if specified
- # the border color will be appended to
- # the list after we define a border
- if '#' in tags:# border color
- start = tags.find('#')
- end = start + 7
- bordercolor = tags[start:end]
- tags.replace(bordercolor,'',1)
- # text styles
- if 'b' in tags:# Bold
- style.append('font-weight:bold;')
- else:
- style.append('font-weight:normal;')
- if 'i' in tags:# Italic
- style.append('font-style:italic;')
- if 'u' in tags:# Underline
- style.append('text-decoration:underline;')
- # border size
- if 'l' in tags:# thick border
- size='thick'
- elif 'm' in tags:# medium border
- size='medium'
- elif 't' in tags:# thin border
- size='thin'
- else:# default
- size='medium'
- # border styles
- if 'n' in tags:# inset border
- border='inset'
- elif 'o' in tags:# outset border
- border='outset'
- elif 'r' in tags:# ridge border
- border='ridge'
- elif 'g' in tags:# groove border
- border='groove'
- elif '=' in tags:# double border
- border='double'
- elif '.' in tags:# dotted border
- border='dotted'
- elif '-' in tags:# dashed border
- border='dashed'
- elif 's' in tags:# solid border
- border='solid'
- # border type check
- seperate_sides=0
- for side in ['<','>','^','v']:
- if side in tags:
- seperate_sides+=1
- # border box or seperate sides
- if seperate_sides==0 and border:
- style.append('border: %s %s;'%(border,size))
- else:
- if border == None:
- border = 'solid'
- if 'v' in tags:# bottom border
- style.append('border-bottom:%s %s;'%(border,size))
- if '<' in tags:# left border
- style.append('border-left:%s %s;'%(border,size))
- if '>' in tags:# right border
- style.append('border-right:%s %s;'%(border,size))
- if '^' in tags:# top border
- style.append('border-top:%s %s;'%(border,size))
- else:
- style.append('font-weight:normal;')# css inherited style fix
- # we have to define our borders before we set colors
- if bordercolor:
- style.append('border-color:%s;'%bordercolor)
- # text forecolor
- style.append('color:%s;'% forecolor)
- # text backcolor
- if backcolor:
- style.append('background-color:%s;'%backcolor)
- return (self._getMarkupClass(key),' '.join(style))
-
- def _sendCSSStyle(self, external=0):
- """ create external and internal style sheets"""
- styles = []
- external += self.external
- if not external:
- styles.append('<style type="text/css">\n<!--\n')
- # Get page background color and write styles ignore any we don't know
- styles.append('body { background:%s; }\n'%self._getPageColor())
- # write out the various css styles
- for key in MARKUPDICT:
- styles.append('.%s { %s }\n'%self._getCSSStyle(key))
- # If you want to style the pre tag you must modify the color dict.
- # Example:
- # lite[PY] = .py {border: solid thin #000000;background:#555555}\n'''
- styles.append(self.colors.get(PY, '.py { }\n'))
- # Extra css can be added here
- # add CSSHOOK to the color dict if you need it.
- # Example:
- #lite[CSSHOOK] = """.mytag { border: solid thin #000000; } \n
- # .myothertag { font-weight:bold; )\n"""
- styles.append(self.colors.get(CSSHOOK,''))
- if not self.external:
- styles.append('--></style>\n')
- return ''.join(styles)
-
- def _doCSSStart(self):
- # Start of css/html 4.01 page
- self.out.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">\n')
- self.out.write('<html><head><title>%s</title>\n'%(self.title))
- self.out.write(self._getDocumentCreatedBy())
- self.out.write('<meta http-equiv="Content-Type" \
-content="text/html;charset=iso-8859-1">\n')
- self._doCSSStyleSheet()
- self.out.write('</head>\n<body>\n')
- # Write a little info at the top.
- self._doPageHeader()
- self.out.write(self.colors.get(CODESTART,'<pre class="py">\n'))
- return
-
- def _doCSSStyleSheet(self):
- if not self.external:
- # write an embedded style sheet
- self.out.write(self._sendCSSStyle())
- else:
- # write a link to an external style sheet
- self.out.write('<link rel="stylesheet" \
-href="pystyle.css" type="text/css">')
- return
-
- def _sendCSSText(self, toktype, toktext):
- # This is a hack to 'fix' multi-line strings.
- # Multi-line strings are treated as only one token
- # even though they can be several physical lines.
- # That makes it hard to spot the start of a line,
- # because at this level all we know about are tokens.
- markupclass = MARKUPDICT.get(toktype, MARKUPDICT[NAME])
- # if it is a LINENUMBER type then we can skip the rest
- if toktext == self.LINESTART and toktype == LINENUMBER:
- self.out.write('<span class="py_line">')
- return
- if toktext.count(self.LINENUMHOLDER):
- # rip apart the string and separate it by line
- # count lines and change all linenum token to line numbers
- # also convert linestart and lineend tokens
- # <linestart> <lnumstart> lnum <lnumend> text <lineend>
- #################################################
- newmarkup = MARKUPDICT.get(LINENUMBER, MARKUPDICT[NAME])
- lstartspan = '<span class="%s">'%(newmarkup)
- if toktype == LINENUMBER:
- splittext = toktext.split(self.LINENUMHOLDER)
- else:
- splittext = toktext.split(self.LINENUMHOLDER+' ')
- store = []
- # we have already seen the first linenumber token
- # so we can skip the first one
- store.append(splittext.pop(0))
- for item in splittext:
- num = self._getLineNumber()
- if self.numberlinks:
- numstrip = num.strip()
- content= '<a name="%s" href="#%s">%s</a>' \
- %(numstrip,numstrip,num)
- else:
- content = num
- linenumber= ''.join([lstartspan,content,'</span>'])
- store.append(linenumber+item)
- toktext = ''.join(store)
- if toktext.count(self.LINESTART):
- # wraps the textline in a line span
- # this adds a lot of kludges, is it really worth it?
- store = []
- parts = toktext.split(self.LINESTART+' ')
- # handle the first part differently
- # the whole token gets wraqpped in a span later on
- first = parts.pop(0)
- # place spans before the newline
- pos = first.rfind('\n')
- if pos != -1:
- first=first[:pos]+'</span></span>'+first[pos:]
- store.append(first)
- #process the rest of the string
- for item in parts:
- #handle line numbers if present
- if self.dolinenums:
- item = item.replace('</span>',
- '</span><span class="%s">'%(markupclass))
- else:
- item = '<span class="%s">%s'%(markupclass,item)
- # add endings for line and string tokens
- pos = item.rfind('\n')
- if pos != -1:
- item=item[:pos]+'</span></span>\n'
- store.append(item)
- # add start tags for lines
- toktext = '<span class="py_line">'.join(store)
- # Send text
- if toktype != LINENUMBER:
- if toktype == TEXT and self.textFlag == 'DIV':
- startspan = '<div class="%s">'%(markupclass)
- endspan = '</div>'
- elif toktype == TEXT and self.textFlag == 'RAW':
- startspan,endspan = ('','')
- else:
- startspan = '<span class="%s">'%(markupclass)
- endspan = '</span>'
- self.out.write(''.join([startspan, toktext, endspan]))
- else:
- self.out.write(toktext)
- return
-
- def _doCSSHeader(self):
- if self.header != '':
- self.out.write('%s\n'%self.header)
- else:
- name = MARKUPDICT.get(NAME)
- self.out.write('<div class="%s"># %s <br> \
-# %s</div><hr>\n'%(name, self.title, time.ctime()))
-
- def _doCSSFooter(self):
- # Optional
- if self.footer != '':
- self.out.write('%s\n'%self.footer)
- else:
- self.out.write('<hr><div class="%s"># %s <br> \
-# %s</div>\n'%(MARKUPDICT.get(NAME),self.title, time.ctime()))
-
- def _doCSSEnd(self):
- # End of css/html page
- self.out.write(self.colors.get(CODEEND,'</pre>\n'))
- # Write a little info at the bottom
- self._doPageFooter()
- self.out.write('</body></html>\n')
- return
-
- ################################################## XHTML markup functions
-
- def _doXHTMLStart(self):
- # XHTML is really just XML + HTML 4.01.
- # We only need to change the page headers,
- # and a few tags to get valid XHTML.
- # Start of xhtml page
- self.out.write('<?xml version="1.0"?>\n \
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n \
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n \
-<html xmlns="http://www.w3.org/1999/xhtml">\n')
- self.out.write('<head><title>%s</title>\n'%(self.title))
- self.out.write(self._getDocumentCreatedBy())
- self.out.write('<meta http-equiv="Content-Type" \
-content="text/html;charset=iso-8859-1"/>\n')
- self._doXHTMLStyleSheet()
- self.out.write('</head>\n<body>\n')
- # Write a little info at the top.
- self._doPageHeader()
- self.out.write(self.colors.get(CODESTART,'<pre class="py">\n'))
- return
-
- def _doXHTMLStyleSheet(self):
- if not self.external:
- # write an embedded style sheet
- self.out.write(self._sendCSSStyle())
- else:
- # write a link to an external style sheet
- self.out.write('<link rel="stylesheet" \
-href="pystyle.css" type="text/css"/>\n')
- return
-
- def _sendXHTMLText(self, toktype, toktext):
- self._sendCSSText(toktype, toktext)
-
- def _doXHTMLHeader(self):
- # Optional
- if self.header:
- self.out.write('%s\n'%self.header)
- else:
- name = MARKUPDICT.get(NAME)
- self.out.write('<div class="%s"># %s <br/> \
-# %s</div><hr/>\n '%(
- name, self.title, time.ctime()))
-
- def _doXHTMLFooter(self):
- # Optional
- if self.footer:
- self.out.write('%s\n'%self.footer)
- else:
- self.out.write('<hr/><div class="%s"># %s <br/> \
-# %s</div>\n'%(MARKUPDICT.get(NAME), self.title, time.ctime()))
-
- def _doXHTMLEnd(self):
- self._doCSSEnd()
-
-#############################################################################
-
-if __name__ == '__main__':
- cli()
-
-#############################################################################
-# PySourceColor.py
-# 2004, 2005 M.E.Farmer Jr.
-# Python license
+# -*- coding: Latin-1 -*-
+"""
+PySourceColor: color Python source code
+"""
+
+"""
+ PySourceColor.py
+
+----------------------------------------------------------------------------
+
+ A python source to colorized html/css/xhtml converter.
+ Hacked by M.E.Farmer Jr. 2004, 2005
+ Python license
+
+----------------------------------------------------------------------------
+
+ - HTML markup does not create w3c valid html, but it works on every
+ browser i've tried so far.(I.E.,Mozilla/Firefox,Opera,Konqueror,wxHTML).
+ - CSS markup is w3c validated html 4.01 strict,
+ but will not render correctly on all browsers.
+ - XHTML markup is w3c validated xhtml 1.0 strict,
+ like html 4.01, will not render correctly on all browsers.
+
+----------------------------------------------------------------------------
+
+Features:
+
+ -Three types of markup:
+ html (default)
+ css/html 4.01 strict
+ xhtml 1.0 strict
+
+ -Can tokenize and colorize:
+ 12 types of strings
+ 2 comment types
+ numbers
+ operators
+ brackets
+ math operators
+ class / name
+ def / name
+ decorator / name
+ keywords
+ arguments class/def/decorator
+ linenumbers
+ names
+ text
+
+ -Eight colorschemes built-in:
+ null
+ mono
+ lite (default)
+ dark
+ dark2
+ idle
+ viewcvs
+ pythonwin
+
+ -Header and footer
+ set to '' for builtin header / footer.
+ give path to a file containing the html
+ you want added as header or footer.
+
+ -Arbitrary text and html
+ html markup converts all to raw (TEXT token)
+ #@# for raw -> send raw text.
+ #$# for span -> inline html and text.
+ #%# for div -> block level html and text.
+
+ -Linenumbers
+ Supports all styles. New token is called LINENUMBER.
+ Defaults to NAME if not defined.
+
+ Style options
+
+ -ALL markups support these text styles:
+ b = bold
+ i = italic
+ u = underline
+ -CSS and XHTML has limited support for borders:
+ HTML markup functions will ignore these.
+ Optional: Border color in RGB hex
+ Defaults to the text forecolor.
+ #rrggbb = border color
+ Border size:
+ l = thick
+ m = medium
+ t = thin
+ Border type:
+ - = dashed
+ . = dotted
+ s = solid
+ d = double
+ g = groove
+ r = ridge
+ n = inset
+ o = outset
+ You can specify multiple sides,
+ they will all use the same style.
+ Optional: Default is full border.
+ v = bottom
+ < = left
+ > = right
+ ^ = top
+ NOTE: Specify the styles you want.
+ The markups will ignore unsupported styles
+ Also note not all browsers can show these options
+
+ -All tokens default to NAME if not defined
+ so the only absolutely critical ones to define are:
+ NAME, ERRORTOKEN, PAGEBACKGROUND
+
+----------------------------------------------------------------------------
+
+Example usage::
+
+ # import
+ import PySourceColor as psc
+ psc.convert('c:/Python22/PySourceColor.py', colors=psc.idle, show=1)
+
+ # from module import *
+ from PySourceColor import *
+ convert('c:/Python22/Lib', colors=lite, markup="css",
+ header='#$#<b>This is a simpe heading</b><hr/>')
+
+ # How to use a custom colorscheme, and most of the 'features'
+ from PySourceColor import *
+ new = {
+ ERRORTOKEN: ('bui','#FF8080',''),
+ DECORATOR_NAME: ('s','#AACBBC',''),
+ DECORATOR: ('n','#333333',''),
+ NAME: ('t.<v','#1133AA','#DDFF22'),
+ NUMBER: ('','#236676','#FF5555'),
+ OPERATOR: ('b','#454567','#BBBB11'),
+ MATH_OPERATOR: ('','#935623','#423afb'),
+ BRACKETS: ('b','#ac34bf','#6457a5'),
+ COMMENT: ('t-#0022FF','#545366','#AABBFF'),
+ DOUBLECOMMENT: ('<l#553455','#553455','#FF00FF'),
+ CLASS_NAME: ('m^v-','#000000','#FFFFFF'),
+ DEF_NAME: ('l=<v','#897845','#000022'),
+ KEYWORD: ('.b','#345345','#FFFF22'),
+ SINGLEQUOTE: ('mn','#223344','#AADDCC'),
+ SINGLEQUOTE_R: ('','#344522',''),
+ SINGLEQUOTE_U: ('','#234234',''),
+ DOUBLEQUOTE: ('m#0022FF','#334421',''),
+ DOUBLEQUOTE_R: ('','#345345',''),
+ DOUBLEQUOTE_U: ('','#678673',''),
+ TRIPLESINGLEQUOTE: ('tv','#FFFFFF','#000000'),
+ TRIPLESINGLEQUOTE_R: ('tbu','#443256','#DDFFDA'),
+ TRIPLESINGLEQUOTE_U: ('','#423454','#DDFFDA'),
+ TRIPLEDOUBLEQUOTE: ('li#236fd3b<>','#000000','#FFFFFF'),
+ TRIPLEDOUBLEQUOTE_R: ('tub','#000000','#FFFFFF'),
+ TRIPLEDOUBLEQUOTE_U: ('-', '#CCAABB','#FFFAFF'),
+ LINENUMBER: ('ib-','#ff66aa','#7733FF'),]
+ TEXT: ('','#546634',''),
+ PAGEBACKGROUND: '#FFFAAA',
+ }
+ if __name__ == '__main__':
+ import sys
+ convert(sys.argv[1], './xhtml.html', colors=new, markup='xhtml', show=1,
+ linenumbers=1)
+ convert(sys.argv[1], './html.html', colors=new, markup='html', show=1,
+ linenumbers=1)
+
+"""
+
+__all__ = ['ERRORTOKEN','DECORATOR_NAME', 'DECORATOR', 'ARGS', 'EXTRASPACE',
+ 'NAME', 'NUMBER', 'OPERATOR', 'COMMENT', 'MATH_OPERATOR',
+ 'DOUBLECOMMENT', 'CLASS_NAME', 'DEF_NAME', 'KEYWORD', 'BRACKETS',
+ 'SINGLEQUOTE','SINGLEQUOTE_R','SINGLEQUOTE_U','DOUBLEQUOTE',
+ 'DOUBLEQUOTE_R', 'DOUBLEQUOTE_U', 'TRIPLESINGLEQUOTE', 'TEXT',
+ 'TRIPLESINGLEQUOTE_R', 'TRIPLESINGLEQUOTE_U', 'TRIPLEDOUBLEQUOTE',
+ 'TRIPLEDOUBLEQUOTE_R', 'TRIPLEDOUBLEQUOTE_U', 'PAGEBACKGROUND',
+ 'LINENUMBER', 'CODESTART', 'CODEEND', 'PY', 'TOKEN_NAMES', 'CSSHOOK',
+ 'null', 'mono', 'lite', 'dark','dark2', 'pythonwin','idle',
+ 'viewcvs', 'Usage', 'cli', 'str2stdout', 'path2stdout', 'Parser',
+ 'str2file', 'str2html', 'str2css', 'str2markup', 'path2file',
+ 'path2html', 'convert', 'walkdir', 'defaultColors', 'showpage',
+ 'pageconvert','tagreplace', 'MARKUPDICT']
+__title__ = 'PySourceColor'
+__version__ = "2.1a"
+__date__ = '25 April 2005'
+__author__ = "M.E.Farmer Jr."
+__credits__ = '''This was originally based on a python recipe
+submitted by Jrgen Hermann to ASPN. Now based on the voices in my head.
+M.E.Farmer 2004, 2005
+Python license
+'''
+import os
+import sys
+import time
+import glob
+import getopt
+import keyword
+import token
+import tokenize
+import traceback
+from six.moves import cStringIO as StringIO
+# Do not edit
+NAME = token.NAME
+NUMBER = token.NUMBER
+COMMENT = tokenize.COMMENT
+OPERATOR = token.OP
+ERRORTOKEN = token.ERRORTOKEN
+ARGS = token.NT_OFFSET + 1
+DOUBLECOMMENT = token.NT_OFFSET + 2
+CLASS_NAME = token.NT_OFFSET + 3
+DEF_NAME = token.NT_OFFSET + 4
+KEYWORD = token.NT_OFFSET + 5
+SINGLEQUOTE = token.NT_OFFSET + 6
+SINGLEQUOTE_R = token.NT_OFFSET + 7
+SINGLEQUOTE_U = token.NT_OFFSET + 8
+DOUBLEQUOTE = token.NT_OFFSET + 9
+DOUBLEQUOTE_R = token.NT_OFFSET + 10
+DOUBLEQUOTE_U = token.NT_OFFSET + 11
+TRIPLESINGLEQUOTE = token.NT_OFFSET + 12
+TRIPLESINGLEQUOTE_R = token.NT_OFFSET + 13
+TRIPLESINGLEQUOTE_U = token.NT_OFFSET + 14
+TRIPLEDOUBLEQUOTE = token.NT_OFFSET + 15
+TRIPLEDOUBLEQUOTE_R = token.NT_OFFSET + 16
+TRIPLEDOUBLEQUOTE_U = token.NT_OFFSET + 17
+PAGEBACKGROUND = token.NT_OFFSET + 18
+DECORATOR = token.NT_OFFSET + 19
+DECORATOR_NAME = token.NT_OFFSET + 20
+BRACKETS = token.NT_OFFSET + 21
+MATH_OPERATOR = token.NT_OFFSET + 22
+LINENUMBER = token.NT_OFFSET + 23
+TEXT = token.NT_OFFSET + 24
+PY = token.NT_OFFSET + 25
+CODESTART = token.NT_OFFSET + 26
+CODEEND = token.NT_OFFSET + 27
+CSSHOOK = token.NT_OFFSET + 28
+EXTRASPACE = token.NT_OFFSET + 29
+
+# markup classname lookup
+MARKUPDICT = {
+ ERRORTOKEN: 'py_err',
+ DECORATOR_NAME: 'py_decn',
+ DECORATOR: 'py_dec',
+ ARGS: 'py_args',
+ NAME: 'py_name',
+ NUMBER: 'py_num',
+ OPERATOR: 'py_op',
+ COMMENT: 'py_com',
+ DOUBLECOMMENT: 'py_dcom',
+ CLASS_NAME: 'py_clsn',
+ DEF_NAME: 'py_defn',
+ KEYWORD: 'py_key',
+ SINGLEQUOTE: 'py_sq',
+ SINGLEQUOTE_R: 'py_sqr',
+ SINGLEQUOTE_U: 'py_squ',
+ DOUBLEQUOTE: 'py_dq',
+ DOUBLEQUOTE_R: 'py_dqr',
+ DOUBLEQUOTE_U: 'py_dqu',
+ TRIPLESINGLEQUOTE: 'py_tsq',
+ TRIPLESINGLEQUOTE_R: 'py_tsqr',
+ TRIPLESINGLEQUOTE_U: 'py_tsqu',
+ TRIPLEDOUBLEQUOTE: 'py_tdq',
+ TRIPLEDOUBLEQUOTE_R: 'py_tdqr',
+ TRIPLEDOUBLEQUOTE_U: 'py_tdqu',
+ BRACKETS: 'py_bra',
+ MATH_OPERATOR: 'py_mop',
+ LINENUMBER: 'py_lnum',
+ TEXT: 'py_text',
+ }
+# might help users that want to create custom schemes
+TOKEN_NAMES= {
+ ERRORTOKEN:'ERRORTOKEN',
+ DECORATOR_NAME:'DECORATOR_NAME',
+ DECORATOR:'DECORATOR',
+ ARGS:'ARGS',
+ NAME:'NAME',
+ NUMBER:'NUMBER',
+ OPERATOR:'OPERATOR',
+ COMMENT:'COMMENT',
+ DOUBLECOMMENT:'DOUBLECOMMENT',
+ CLASS_NAME:'CLASS_NAME',
+ DEF_NAME:'DEF_NAME',
+ KEYWORD:'KEYWORD',
+ SINGLEQUOTE:'SINGLEQUOTE',
+ SINGLEQUOTE_R:'SINGLEQUOTE_R',
+ SINGLEQUOTE_U:'SINGLEQUOTE_U',
+ DOUBLEQUOTE:'DOUBLEQUOTE',
+ DOUBLEQUOTE_R:'DOUBLEQUOTE_R',
+ DOUBLEQUOTE_U:'DOUBLEQUOTE_U',
+ TRIPLESINGLEQUOTE:'TRIPLESINGLEQUOTE',
+ TRIPLESINGLEQUOTE_R:'TRIPLESINGLEQUOTE_R',
+ TRIPLESINGLEQUOTE_U:'TRIPLESINGLEQUOTE_U',
+ TRIPLEDOUBLEQUOTE:'TRIPLEDOUBLEQUOTE',
+ TRIPLEDOUBLEQUOTE_R:'TRIPLEDOUBLEQUOTE_R',
+ TRIPLEDOUBLEQUOTE_U:'TRIPLEDOUBLEQUOTE_U',
+ BRACKETS:'BRACKETS',
+ MATH_OPERATOR:'MATH_OPERATOR',
+ LINENUMBER:'LINENUMBER',
+ TEXT:'TEXT',
+ PAGEBACKGROUND:'PAGEBACKGROUND',
+ }
+
+######################################################################
+# Edit colors and styles to taste
+# Create your own scheme, just copy one below , rename and edit.
+# Custom styles must at least define NAME, ERRORTOKEN, PAGEBACKGROUND,
+# all missing elements will default to NAME.
+# See module docstring for details on style attributes.
+######################################################################
+# Copy null and use it as a starter colorscheme.
+null = {# tokentype: ('tags border_color', 'textforecolor', 'textbackcolor')
+ ERRORTOKEN: ('','#000000',''),# Error token
+ DECORATOR_NAME: ('','#000000',''),# Decorator name
+ DECORATOR: ('','#000000',''),# @ symbol
+ ARGS: ('','#000000',''),# class,def,deco arguments
+ NAME: ('','#000000',''),# All other python text
+ NUMBER: ('','#000000',''),# 0->10
+ OPERATOR: ('','#000000',''),# ':','<=',';',',','.','==', etc
+ MATH_OPERATOR: ('','#000000',''),# '+','-','=','','**',etc
+ BRACKETS: ('','#000000',''),# '[',']','(',')','{','}'
+ COMMENT: ('','#000000',''),# Single comment
+ DOUBLECOMMENT: ('','#000000',''),## Double comment
+ CLASS_NAME: ('','#000000',''),# Class name
+ DEF_NAME: ('','#000000',''),# Def name
+ KEYWORD: ('','#000000',''),# Python keywords
+ SINGLEQUOTE: ('','#000000',''),# 'SINGLEQUOTE'
+ SINGLEQUOTE_R: ('','#000000',''),# r'SINGLEQUOTE'
+ SINGLEQUOTE_U: ('','#000000',''),# u'SINGLEQUOTE'
+ DOUBLEQUOTE: ('','#000000',''),# "DOUBLEQUOTE"
+ DOUBLEQUOTE_R: ('','#000000',''),# r"DOUBLEQUOTE"
+ DOUBLEQUOTE_U: ('','#000000',''),# u"DOUBLEQUOTE"
+ TRIPLESINGLEQUOTE: ('','#000000',''),# '''TRIPLESINGLEQUOTE'''
+ TRIPLESINGLEQUOTE_R: ('','#000000',''),# r'''TRIPLESINGLEQUOTE'''
+ TRIPLESINGLEQUOTE_U: ('','#000000',''),# u'''TRIPLESINGLEQUOTE'''
+ TRIPLEDOUBLEQUOTE: ('','#000000',''),# """TRIPLEDOUBLEQUOTE"""
+ TRIPLEDOUBLEQUOTE_R: ('','#000000',''),# r"""TRIPLEDOUBLEQUOTE"""
+ TRIPLEDOUBLEQUOTE_U: ('','#000000',''),# u"""TRIPLEDOUBLEQUOTE"""
+ TEXT: ('','#000000',''),# non python text
+ LINENUMBER: ('>ti#555555','#000000',''),# Linenumbers
+ PAGEBACKGROUND: '#FFFFFF'# set the page background
+ }
+
+mono = {
+ ERRORTOKEN: ('s#FF0000','#FF8080',''),
+ DECORATOR_NAME: ('bu','#000000',''),
+ DECORATOR: ('b','#000000',''),
+ ARGS: ('b','#555555',''),
+ NAME: ('','#000000',''),
+ NUMBER: ('b','#000000',''),
+ OPERATOR: ('b','#000000',''),
+ MATH_OPERATOR: ('b','#000000',''),
+ BRACKETS: ('b','#000000',''),
+ COMMENT: ('i','#999999',''),
+ DOUBLECOMMENT: ('b','#999999',''),
+ CLASS_NAME: ('bu','#000000',''),
+ DEF_NAME: ('b','#000000',''),
+ KEYWORD: ('b','#000000',''),
+ SINGLEQUOTE: ('','#000000',''),
+ SINGLEQUOTE_R: ('','#000000',''),
+ SINGLEQUOTE_U: ('','#000000',''),
+ DOUBLEQUOTE: ('','#000000',''),
+ DOUBLEQUOTE_R: ('','#000000',''),
+ DOUBLEQUOTE_U: ('','#000000',''),
+ TRIPLESINGLEQUOTE: ('','#000000',''),
+ TRIPLESINGLEQUOTE_R: ('','#000000',''),
+ TRIPLESINGLEQUOTE_U: ('','#000000',''),
+ TRIPLEDOUBLEQUOTE: ('i','#000000',''),
+ TRIPLEDOUBLEQUOTE_R: ('i','#000000',''),
+ TRIPLEDOUBLEQUOTE_U: ('i','#000000',''),
+ TEXT: ('','#000000',''),
+ LINENUMBER: ('>ti#555555','#000000',''),
+ PAGEBACKGROUND: '#FFFFFF'
+ }
+
+dark = {
+ ERRORTOKEN: ('s#FF0000','#FF8080',''),
+ DECORATOR_NAME: ('b','#FFBBAA',''),
+ DECORATOR: ('b','#CC5511',''),
+ ARGS: ('b','#DDDDFF',''),
+ NAME: ('','#DDDDDD',''),
+ NUMBER: ('','#FF0000',''),
+ OPERATOR: ('b','#FAF785',''),
+ MATH_OPERATOR: ('b','#FAF785',''),
+ BRACKETS: ('b','#FAF785',''),
+ COMMENT: ('','#45FCA0',''),
+ DOUBLECOMMENT: ('i','#A7C7A9',''),
+ CLASS_NAME: ('b','#B666FD',''),
+ DEF_NAME: ('b','#EBAE5C',''),
+ KEYWORD: ('b','#8680FF',''),
+ SINGLEQUOTE: ('','#F8BAFE',''),
+ SINGLEQUOTE_R: ('','#F8BAFE',''),
+ SINGLEQUOTE_U: ('','#F8BAFE',''),
+ DOUBLEQUOTE: ('','#FF80C0',''),
+ DOUBLEQUOTE_R: ('','#FF80C0',''),
+ DOUBLEQUOTE_U: ('','#FF80C0',''),
+ TRIPLESINGLEQUOTE: ('','#FF9595',''),
+ TRIPLESINGLEQUOTE_R: ('','#FF9595',''),
+ TRIPLESINGLEQUOTE_U: ('','#FF9595',''),
+ TRIPLEDOUBLEQUOTE: ('','#B3FFFF',''),
+ TRIPLEDOUBLEQUOTE_R: ('','#B3FFFF',''),
+ TRIPLEDOUBLEQUOTE_U: ('','#B3FFFF',''),
+ TEXT: ('','#FFFFFF',''),
+ LINENUMBER: ('>mi#555555','#bbccbb','#333333'),
+ PAGEBACKGROUND: '#000000'
+ }
+
+dark2 = {
+ ERRORTOKEN: ('','#FF0000',''),
+ DECORATOR_NAME: ('b','#FFBBAA',''),
+ DECORATOR: ('b','#CC5511',''),
+ ARGS: ('b','#DDDDDD',''),
+ NAME: ('','#C0C0C0',''),
+ NUMBER: ('b','#00FF00',''),
+ OPERATOR: ('b','#FF090F',''),
+ MATH_OPERATOR: ('b','#EE7020',''),
+ BRACKETS: ('b','#FFB90F',''),
+ COMMENT: ('i','#D0D000','#522000'),#'#88AA88','#11111F'),
+ DOUBLECOMMENT: ('i','#D0D000','#522000'),#'#77BB77','#11111F'),
+ CLASS_NAME: ('b','#DD4080',''),
+ DEF_NAME: ('b','#FF8040',''),
+ KEYWORD: ('b','#4726d1',''),
+ SINGLEQUOTE: ('','#8080C0',''),
+ SINGLEQUOTE_R: ('','#8080C0',''),
+ SINGLEQUOTE_U: ('','#8080C0',''),
+ DOUBLEQUOTE: ('','#ADB9F1',''),
+ DOUBLEQUOTE_R: ('','#ADB9F1',''),
+ DOUBLEQUOTE_U: ('','#ADB9F1',''),
+ TRIPLESINGLEQUOTE: ('','#00C1C1',''),#A050C0
+ TRIPLESINGLEQUOTE_R: ('','#00C1C1',''),#A050C0
+ TRIPLESINGLEQUOTE_U: ('','#00C1C1',''),#A050C0
+ TRIPLEDOUBLEQUOTE: ('','#33E3E3',''),#B090E0
+ TRIPLEDOUBLEQUOTE_R: ('','#33E3E3',''),#B090E0
+ TRIPLEDOUBLEQUOTE_U: ('','#33E3E3',''),#B090E0
+ TEXT: ('','#C0C0C0',''),
+ LINENUMBER: ('>mi#555555','#bbccbb','#333333'),
+ PAGEBACKGROUND: '#000000'
+ }
+
+lite = {
+ ERRORTOKEN: ('s#FF0000','#FF8080',''),
+ DECORATOR_NAME: ('b','#BB4422',''),
+ DECORATOR: ('b','#3333AF',''),
+ ARGS: ('b','#000000',''),
+ NAME: ('','#333333',''),
+ NUMBER: ('b','#DD2200',''),
+ OPERATOR: ('b','#000000',''),
+ MATH_OPERATOR: ('b','#000000',''),
+ BRACKETS: ('b','#000000',''),
+ COMMENT: ('','#007F00',''),
+ DOUBLECOMMENT: ('','#608060',''),
+ CLASS_NAME: ('b','#0000DF',''),
+ DEF_NAME: ('b','#9C7A00',''),#f09030
+ KEYWORD: ('b','#0000AF',''),
+ SINGLEQUOTE: ('','#600080',''),
+ SINGLEQUOTE_R: ('','#600080',''),
+ SINGLEQUOTE_U: ('','#600080',''),
+ DOUBLEQUOTE: ('','#A0008A',''),
+ DOUBLEQUOTE_R: ('','#A0008A',''),
+ DOUBLEQUOTE_U: ('','#A0008A',''),
+ TRIPLESINGLEQUOTE: ('','#337799',''),
+ TRIPLESINGLEQUOTE_R: ('','#337799',''),
+ TRIPLESINGLEQUOTE_U: ('','#337799',''),
+ TRIPLEDOUBLEQUOTE: ('','#1166AA',''),
+ TRIPLEDOUBLEQUOTE_R: ('','#1166AA',''),
+ TRIPLEDOUBLEQUOTE_U: ('','#1166AA',''),
+ TEXT: ('','#000000',''),
+ LINENUMBER: ('>ti#555555','#000000',''),
+ PAGEBACKGROUND: '#FFFFFF'
+ }
+
+idle = {
+ ERRORTOKEN: ('s#FF0000','#FF8080',''),
+ DECORATOR_NAME: ('','#900090',''),
+ DECORATOR: ('','#FF7700',''),
+ NAME: ('','#000000',''),
+ NUMBER: ('','#000000',''),
+ OPERATOR: ('','#000000',''),
+ MATH_OPERATOR: ('','#000000',''),
+ BRACKETS: ('','#000000',''),
+ COMMENT: ('','#DD0000',''),
+ DOUBLECOMMENT: ('','#DD0000',''),
+ CLASS_NAME: ('','#0000FF',''),
+ DEF_NAME: ('','#0000FF',''),
+ KEYWORD: ('','#FF7700',''),
+ SINGLEQUOTE: ('','#00AA00',''),
+ SINGLEQUOTE_R: ('','#00AA00',''),
+ SINGLEQUOTE_U: ('','#00AA00',''),
+ DOUBLEQUOTE: ('','#00AA00',''),
+ DOUBLEQUOTE_R: ('','#00AA00',''),
+ DOUBLEQUOTE_U: ('','#00AA00',''),
+ TRIPLESINGLEQUOTE: ('','#00AA00',''),
+ TRIPLESINGLEQUOTE_R: ('','#00AA00',''),
+ TRIPLESINGLEQUOTE_U: ('','#00AA00',''),
+ TRIPLEDOUBLEQUOTE: ('','#00AA00',''),
+ TRIPLEDOUBLEQUOTE_R: ('','#00AA00',''),
+ TRIPLEDOUBLEQUOTE_U: ('','#00AA00',''),
+ TEXT: ('','#000000',''),
+ LINENUMBER: ('>ti#555555','#000000',''),
+ PAGEBACKGROUND: '#FFFFFF'
+ }
+
+pythonwin = {
+ ERRORTOKEN: ('s#FF0000','#FF8080',''),
+ DECORATOR_NAME: ('b','#DD0080',''),
+ DECORATOR: ('b','#000080',''),
+ ARGS: ('','#000000',''),
+ NAME: ('','#303030',''),
+ NUMBER: ('','#008080',''),
+ OPERATOR: ('','#000000',''),
+ MATH_OPERATOR: ('','#000000',''),
+ BRACKETS: ('','#000000',''),
+ COMMENT: ('','#007F00',''),
+ DOUBLECOMMENT: ('','#7F7F7F',''),
+ CLASS_NAME: ('b','#0000FF',''),
+ DEF_NAME: ('b','#007F7F',''),
+ KEYWORD: ('b','#000080',''),
+ SINGLEQUOTE: ('','#808000',''),
+ SINGLEQUOTE_R: ('','#808000',''),
+ SINGLEQUOTE_U: ('','#808000',''),
+ DOUBLEQUOTE: ('','#808000',''),
+ DOUBLEQUOTE_R: ('','#808000',''),
+ DOUBLEQUOTE_U: ('','#808000',''),
+ TRIPLESINGLEQUOTE: ('','#808000',''),
+ TRIPLESINGLEQUOTE_R: ('','#808000',''),
+ TRIPLESINGLEQUOTE_U: ('','#808000',''),
+ TRIPLEDOUBLEQUOTE: ('','#808000',''),
+ TRIPLEDOUBLEQUOTE_R: ('','#808000',''),
+ TRIPLEDOUBLEQUOTE_U: ('','#808000',''),
+ TEXT: ('','#303030',''),
+ LINENUMBER: ('>ti#555555','#000000',''),
+ PAGEBACKGROUND: '#FFFFFF'
+ }
+
+viewcvs = {
+ ERRORTOKEN: ('s#FF0000','#FF8080',''),
+ DECORATOR_NAME: ('','#000000',''),
+ DECORATOR: ('','#000000',''),
+ ARGS: ('','#000000',''),
+ NAME: ('','#000000',''),
+ NUMBER: ('','#000000',''),
+ OPERATOR: ('','#000000',''),
+ MATH_OPERATOR: ('','#000000',''),
+ BRACKETS: ('','#000000',''),
+ COMMENT: ('i','#b22222',''),
+ DOUBLECOMMENT: ('i','#b22222',''),
+ CLASS_NAME: ('','#000000',''),
+ DEF_NAME: ('b','#0000ff',''),
+ KEYWORD: ('b','#a020f0',''),
+ SINGLEQUOTE: ('b','#bc8f8f',''),
+ SINGLEQUOTE_R: ('b','#bc8f8f',''),
+ SINGLEQUOTE_U: ('b','#bc8f8f',''),
+ DOUBLEQUOTE: ('b','#bc8f8f',''),
+ DOUBLEQUOTE_R: ('b','#bc8f8f',''),
+ DOUBLEQUOTE_U: ('b','#bc8f8f',''),
+ TRIPLESINGLEQUOTE: ('b','#bc8f8f',''),
+ TRIPLESINGLEQUOTE_R: ('b','#bc8f8f',''),
+ TRIPLESINGLEQUOTE_U: ('b','#bc8f8f',''),
+ TRIPLEDOUBLEQUOTE: ('b','#bc8f8f',''),
+ TRIPLEDOUBLEQUOTE_R: ('b','#bc8f8f',''),
+ TRIPLEDOUBLEQUOTE_U: ('b','#bc8f8f',''),
+ TEXT: ('','#000000',''),
+ LINENUMBER: ('>ti#555555','#000000',''),
+ PAGEBACKGROUND: '#FFFFFF'
+ }
+
+defaultColors = lite
+
+def Usage():
+ doc = """
+ -----------------------------------------------------------------------------
+ PySourceColor.py ver: %s
+ -----------------------------------------------------------------------------
+ Module summary:
+ This module is designed to colorize python source code.
+ Input--->python source
+ Output-->colorized (html, html4.01/css, xhtml1.0)
+ Standalone:
+ This module will work from the command line with options.
+ This module will work with redirected stdio.
+ Imported:
+ This module can be imported and used directly in your code.
+ -----------------------------------------------------------------------------
+ Command line options:
+ -h, --help
+ Optional-> Display this help message.
+ -t, --test
+ Optional-> Will ignore all others flags but --profile
+ test all schemes and markup combinations
+ -p, --profile
+ Optional-> Works only with --test or -t
+ runs profile.py and makes the test work in quiet mode.
+ -i, --in, --input
+ Optional-> If you give input on stdin.
+ Use any of these for the current dir (.,cwd)
+ Input can be file or dir.
+ Input from stdin use one of the following (-,stdin)
+ If stdin is used as input stdout is output unless specified.
+ -o, --out, --output
+ Optional-> output dir for the colorized source.
+ default: output dir is the input dir.
+ To output html to stdout use one of the following (-,stdout)
+ Stdout can be used without stdin if you give a file as input.
+ -c, --color
+ Optional-> null, mono, dark, dark2, lite, idle, pythonwin, viewcvs
+ default: dark
+ -s, --show
+ Optional-> Show page after creation.
+ default: no show
+ -m, --markup
+ Optional-> html, css, xhtml
+ css, xhtml also support external stylesheets (-e,--external)
+ default: HTML
+ -e, --external
+ Optional-> use with css, xhtml
+ Writes an style sheet instead of embedding it in the page
+ saves it as pystyle.css in the same directory.
+ html markup will silently ignore this flag.
+ -H, --header
+ Opional-> add a page header to the top of the output
+ -H
+ Builtin header (name,date,hrule)
+ --header
+ You must specify a filename.
+ The header file must be valid html
+ and must handle its own font colors.
+ ex. --header c:/tmp/header.txt
+ -F, --footer
+ Opional-> add a page footer to the bottom of the output
+ -F
+ Builtin footer (hrule,name,date)
+ --footer
+ You must specify a filename.
+ The footer file must be valid html
+ and must handle its own font colors.
+ ex. --footer c:/tmp/footer.txt
+ -l, --linenumbers
+ Optional-> default is no linenumbers
+ Adds line numbers to the start of each line in the code.
+ --convertpage
+ Given a webpage that has code embedded in tags it will
+ convert embedded code to colorized html.
+ (see pageconvert for details)
+ -----------------------------------------------------------------------------
+ Option usage:
+ # Test and show pages
+ python PySourceColor.py -t -s
+ # Test and only show profile results
+ python PySourceColor.py -t -p
+ # Colorize all .py,.pyw files in cwdir you can also use: (.,cwd)
+ python PySourceColor.py -i .
+ # Using long options w/ =
+ python PySourceColor.py --in=c:/myDir/my.py --color=lite --show
+ # Using short options w/out =
+ python PySourceColor.py -i c:/myDir/ -c idle -m css -e
+ # Using any mix
+ python PySourceColor.py --in . -o=c:/myDir --show
+ # Place a custom header on your files
+ python PySourceColor.py -i . -o c:/tmp -m xhtml --header c:/header.txt
+ -----------------------------------------------------------------------------
+ Stdio usage:
+ # Stdio using no options
+ python PySourceColor.py < c:/MyFile.py > c:/tmp/MyFile.html
+ # Using stdin alone automatically uses stdout for output: (stdin,-)
+ python PySourceColor.py -i- < c:/MyFile.py > c:/tmp/myfile.html
+ # Stdout can also be written to directly from a file instead of stdin
+ python PySourceColor.py -i c:/MyFile.py -m css -o- > c:/tmp/myfile.html
+ # Stdin can be used as input , but output can still be specified
+ python PySourceColor.py -i- -o c:/pydoc.py.html -s < c:/Python22/my.py
+ _____________________________________________________________________________
+ """
+ print(doc % (__version__))
+ sys.exit(1)
+
+###################################################### Command line interface
+
+def cli():
+ """Handle command line args and redirections"""
+ try:
+ # try to get command line args
+ opts, args = getopt.getopt(sys.argv[1:],
+ "hseqtplHFi:o:c:m:h:f:",["help", "show", "quiet",
+ "test", "external", "linenumbers", "convertpage", "profile",
+ "input=", "output=", "color=", "markup=","header=", "footer="])
+ except getopt.GetoptError:
+ # on error print help information and exit:
+ Usage()
+ # init some names
+ input = None
+ output = None
+ colorscheme = None
+ markup = 'html'
+ header = None
+ footer = None
+ linenumbers = 0
+ show = 0
+ quiet = 0
+ test = 0
+ profile = 0
+ convertpage = 0
+ form = None
+ # if we have args then process them
+ for o, a in opts:
+ if o in ["-h", "--help"]:
+ Usage()
+ sys.exit()
+ if o in ["-o", "--output", "--out"]:
+ output = a
+ if o in ["-i", "--input", "--in"]:
+ input = a
+ if input in [".", "cwd"]:
+ input = os.getcwd()
+ if o in ["-s", "--show"]:
+ show = 1
+ if o in ["-q", "--quiet"]:
+ quiet = 1
+ if o in ["-t", "--test"]:
+ test = 1
+ if o in ["--convertpage"]:
+ convertpage = 1
+ if o in ["-p", "--profile"]:
+ profile = 1
+ if o in ["-e", "--external"]:
+ form = 'external'
+ if o in ["-m", "--markup"]:
+ markup = str(a)
+ if o in ["-l", "--linenumbers"]:
+ linenumbers = 1
+ if o in ["--header"]:
+ header = str(a)
+ elif o == "-H":
+ header = ''
+ if o in ["--footer"]:
+ footer = str(a)
+ elif o == "-F":
+ footer = ''
+ if o in ["-c", "--color"]:
+ try:
+ colorscheme = globals().get(a.lower())
+ except:
+ traceback.print_exc()
+ Usage()
+ if test:
+ if profile:
+ import profile
+ profile.run('_test(show=%s, quiet=%s)'%(show,quiet))
+ else:
+ # Parse this script in every possible colorscheme and markup
+ _test(show,quiet)
+ elif input in [None, "-", "stdin"] or output in ["-", "stdout"]:
+ # determine if we are going to use stdio
+ if input not in [None, "-", "stdin"]:
+ if os.path.isfile(input) :
+ path2stdout(input, colors=colorscheme, markup=markup,
+ linenumbers=linenumbers, header=header,
+ footer=footer, form=form)
+ else:
+ raise PathError('File does not exists!')
+ else:
+ try:
+ if sys.stdin.isatty():
+ raise InputError('Please check input!')
+ else:
+ if output in [None,"-","stdout"]:
+ str2stdout(sys.stdin.read(), colors=colorscheme,
+ markup=markup, header=header,
+ footer=footer, linenumbers=linenumbers,
+ form=form)
+ else:
+ str2file(sys.stdin.read(), outfile=output, show=show,
+ markup=markup, header=header, footer=footer,
+ linenumbers=linenumbers, form=form)
+ except:
+ traceback.print_exc()
+ Usage()
+ else:
+ if os.path.exists(input):
+ if convertpage:
+ # if there was at least an input given we can proceed
+ pageconvert(input, out=output, colors=colorscheme,
+ show=show, markup=markup,linenumbers=linenumbers)
+ else:
+ # if there was at least an input given we can proceed
+ convert(source=input, outdir=output, colors=colorscheme,
+ show=show, markup=markup, quiet=quiet, header=header,
+ footer=footer, linenumbers=linenumbers, form=form)
+ else:
+ raise PathError('File does not exists!')
+ Usage()
+
+######################################################### Simple markup tests
+
+def _test(show=0, quiet=0):
+ """Test the parser and most of the functions.
+
+ There are 19 test total(eight colorschemes in three diffrent markups,
+ and a str2file test. Most functions are tested by this.
+ """
+ fi = sys.argv[0]
+ if not fi.endswith('.exe'):# Do not test if frozen as an archive
+ # this is a collection of test, most things are covered.
+ path2file(fi, '/tmp/null.html', null, show=show, quiet=quiet)
+ path2file(fi, '/tmp/null_css.html', null, show=show,
+ markup='css', quiet=quiet)
+ path2file(fi, '/tmp/mono.html', mono, show=show, quiet=quiet)
+ path2file(fi, '/tmp/mono_css.html', mono, show=show,
+ markup='css', quiet=quiet)
+ path2file(fi, '/tmp/lite.html', lite, show=show, quiet=quiet)
+ path2file(fi, '/tmp/lite_css.html', lite, show=show,
+ markup='css', quiet=quiet, header='', footer='',
+ linenumbers=1)
+ path2file(fi, '/tmp/lite_xhtml.html', lite, show=show,
+ markup='xhtml', quiet=quiet)
+ path2file(fi, '/tmp/dark.html', dark, show=show, quiet=quiet)
+ path2file(fi, '/tmp/dark_css.html', dark, show=show,
+ markup='css', quiet=quiet, linenumbers=1)
+ path2file(fi, '/tmp/dark2.html', dark2, show=show, quiet=quiet)
+ path2file(fi, '/tmp/dark2_css.html', dark2, show=show,
+ markup='css', quiet=quiet)
+ path2file(fi, '/tmp/dark2_xhtml.html', dark2, show=show,
+ markup='xhtml', quiet=quiet, header='', footer='',
+ linenumbers=1, form='external')
+ path2file(fi, '/tmp/idle.html', idle, show=show, quiet=quiet)
+ path2file(fi, '/tmp/idle_css.html', idle, show=show,
+ markup='css', quiet=quiet)
+ path2file(fi, '/tmp/viewcvs.html', viewcvs, show=show,
+ quiet=quiet, linenumbers=1)
+ path2file(fi, '/tmp/viewcvs_css.html', viewcvs, show=show,
+ markup='css', linenumbers=1, quiet=quiet)
+ path2file(fi, '/tmp/pythonwin.html', pythonwin, show=show,
+ quiet=quiet)
+ path2file(fi, '/tmp/pythonwin_css.html', pythonwin, show=show,
+ markup='css', quiet=quiet)
+ teststr=r'''"""This is a test of decorators and other things"""
+# This should be line 421...
+@whatever(arg,arg2)
+@A @B(arghh) @C
+def LlamaSaysNi(arg='Ni!',arg2="RALPH"):
+ """This docstring is deeply disturbed by all the llama references"""
+ print('%s The Wonder Llama says %s'% (arg2,arg))
+# So I was like duh!, and he was like ya know?!,
+# and so we were both like huh...wtf!? RTFM!! LOL!!;)
+@staticmethod## Double comments are KewL.
+def LlamasRLumpy():
+ """This docstring is too sexy to be here.
+ """
+ u"""
+=============================
+A Mse once bit my sister...
+=============================
+ """
+ ## Relax, this won't hurt a bit, just a simple, painless procedure,
+ ## hold still while I get the anesthetizing hammer.
+ m = {'three':'1','won':'2','too':'3'}
+ o = r'fishy\fishy\fishy/fish\oh/where/is\my/little\..'
+ python = uR"""
+ No realli! She was Karving her initials n the mse with the sharpened end
+ of an interspace tthbrush given her by Svenge - her brother-in-law -an Oslo
+ dentist and star of many Norwegian mvies: "The Ht Hands of an Oslo
+ Dentist", "Fillings of Passion", "The Huge Mlars of Horst Nordfink"..."""
+ RU"""142 MEXICAN WHOOPING LLAMAS"""#<-Can you fit 142 llamas in a red box?
+ n = u' HERMSGERVRDENBRTBRDA ' + """ YUTTE """
+ t = """SAMALLNIATNUOMNAIRODAUCE"""+"DENIARTYLLAICEPS04"
+ ## We apologise for the fault in the
+ ## comments. Those responsible have been
+ ## sacked.
+ y = '14 NORTH CHILEAN GUANACOS \
+(CLOSELY RELATED TO THE LLAMA)'
+ rules = [0,1,2,3,4,5]
+ print y'''
+ htmlPath = os.path.abspath('/tmp/strtest_lines.html')
+ str2file(teststr, htmlPath, colors=dark, markup='xhtml',
+ linenumbers=420, show=show)
+ _printinfo(" wrote %s" % htmlPath, quiet)
+ htmlPath = os.path.abspath('/tmp/strtest_nolines.html')
+ str2file(teststr, htmlPath, colors=dark, markup='xhtml',
+ show=show)
+ _printinfo(" wrote %s" % htmlPath, quiet)
+ else:
+ Usage()
+ return
+
+# emacs wants this: '
+
+####################################################### User funtctions
+
+def str2stdout(sourcestring, colors=None, title='', markup='html',
+ header=None, footer=None,
+ linenumbers=0, form=None):
+ """Converts a code(string) to colorized HTML. Writes to stdout.
+
+ form='code',or'snip' (for "<pre>yourcode</pre>" only)
+ colors=null,mono,lite,dark,dark2,idle,or pythonwin
+ """
+ Parser(sourcestring, colors=colors, title=title, markup=markup,
+ header=header, footer=footer,
+ linenumbers=linenumbers).format(form)
+
+def path2stdout(sourcepath, title='', colors=None, markup='html',
+ header=None, footer=None,
+ linenumbers=0, form=None):
+ """Converts code(file) to colorized HTML. Writes to stdout.
+
+ form='code',or'snip' (for "<pre>yourcode</pre>" only)
+ colors=null,mono,lite,dark,dark2,idle,or pythonwin
+ """
+ sourcestring = open(sourcepath).read()
+ Parser(sourcestring, colors=colors, title=sourcepath,
+ markup=markup, header=header, footer=footer,
+ linenumbers=linenumbers).format(form)
+
+def str2html(sourcestring, colors=None, title='',
+ markup='html', header=None, footer=None,
+ linenumbers=0, form=None):
+ """Converts a code(string) to colorized HTML. Returns an HTML string.
+
+ form='code',or'snip' (for "<pre>yourcode</pre>" only)
+ colors=null,mono,lite,dark,dark2,idle,or pythonwin
+ """
+ stringIO = StringIO.StringIO()
+ Parser(sourcestring, colors=colors, title=title, out=stringIO,
+ markup=markup, header=header, footer=footer,
+ linenumbers=linenumbers).format(form)
+ stringIO.seek(0)
+ return stringIO.read()
+
+def str2css(sourcestring, colors=None, title='',
+ markup='css', header=None, footer=None,
+ linenumbers=0, form=None):
+ """Converts a code string to colorized CSS/HTML. Returns CSS/HTML string
+
+ If form != None then this will return (stylesheet_str, code_str)
+ colors=null,mono,lite,dark,dark2,idle,or pythonwin
+ """
+ if markup.lower() not in ['css' ,'xhtml']:
+ markup = 'css'
+ stringIO = StringIO.StringIO()
+ parse = Parser(sourcestring, colors=colors, title=title,
+ out=stringIO, markup=markup,
+ header=header, footer=footer,
+ linenumbers=linenumbers)
+ parse.format(form)
+ stringIO.seek(0)
+ if form != None:
+ return parse._sendCSSStyle(external=1), stringIO.read()
+ else:
+ return None, stringIO.read()
+
+def str2markup(sourcestring, colors=None, title = '',
+ markup='xhtml', header=None, footer=None,
+ linenumbers=0, form=None):
+ """ Convert code strings into ([stylesheet or None], colorized string) """
+ if markup.lower() == 'html':
+ return None, str2html(sourcestring, colors=colors, title=title,
+ header=header, footer=footer, markup=markup,
+ linenumbers=linenumbers, form=form)
+ else:
+ return str2css(sourcestring, colors=colors, title=title,
+ header=header, footer=footer, markup=markup,
+ linenumbers=linenumbers, form=form)
+
+def str2file(sourcestring, outfile, colors=None, title='',
+ markup='html', header=None, footer=None,
+ linenumbers=0, show=0, dosheet=1, form=None):
+ """Converts a code string to a file.
+
+ makes no attempt at correcting bad pathnames
+ """
+ css , html = str2markup(sourcestring, colors=colors, title='',
+ markup=markup, header=header, footer=footer,
+ linenumbers=linenumbers, form=form)
+ # write html
+ f = open(outfile,'wt')
+ f.writelines(html)
+ f.close()
+ #write css
+ if css != None and dosheet:
+ dir = os.path.dirname(outfile)
+ outcss = os.path.join(dir,'pystyle.css')
+ f = open(outcss,'wt')
+ f.writelines(css)
+ f.close()
+ if show:
+ showpage(outfile)
+
+def path2html(sourcepath, colors=None, markup='html',
+ header=None, footer=None,
+ linenumbers=0, form=None):
+ """Converts code(file) to colorized HTML. Returns an HTML string.
+
+ form='code',or'snip' (for "<pre>yourcode</pre>" only)
+ colors=null,mono,lite,dark,dark2,idle,or pythonwin
+ """
+ stringIO = StringIO.StringIO()
+ sourcestring = open(sourcepath).read()
+ Parser(sourcestring, colors, title=sourcepath, out=stringIO,
+ markup=markup, header=header, footer=footer,
+ linenumbers=linenumbers).format(form)
+ stringIO.seek(0)
+ return stringIO.read()
+
+def convert(source, outdir=None, colors=None,
+ show=0, markup='html', quiet=0,
+ header=None, footer=None, linenumbers=0, form=None):
+ """Takes a file or dir as input and places the html in the outdir.
+
+ If outdir is none it defaults to the input dir
+ """
+ count=0
+ # If it is a filename then path2file
+ if not os.path.isdir(source):
+ if os.path.isfile(source):
+ count+=1
+ path2file(source, outdir, colors, show, markup,
+ quiet, form, header, footer, linenumbers, count)
+ else:
+ raise PathError('File does not exist!')
+ # If we pass in a dir we need to walkdir for files.
+ # Then we need to colorize them with path2file
+ else:
+ fileList = walkdir(source)
+ if fileList != None:
+ # make sure outdir is a dir
+ if outdir != None:
+ if os.path.splitext(outdir)[1] != '':
+ outdir = os.path.split(outdir)[0]
+ for item in fileList:
+ count+=1
+ path2file(item, outdir, colors, show, markup,
+ quiet, form, header, footer, linenumbers, count)
+ _printinfo('Completed colorizing %s files.'%str(count), quiet)
+ else:
+ _printinfo("No files to convert in dir.", quiet)
+
+def path2file(sourcePath, out=None, colors=None, show=0,
+ markup='html', quiet=0, form=None,
+ header=None, footer=None, linenumbers=0, count=1):
+ """ Converts python source to html file"""
+ # If no outdir is given we use the sourcePath
+ if out == None:#this is a guess
+ htmlPath = sourcePath + '.html'
+ else:
+ # If we do give an out_dir, and it does
+ # not exist , it will be created.
+ if os.path.splitext(out)[1] == '':
+ if not os.path.isdir(out):
+ os.makedirs(out)
+ sourceName = os.path.basename(sourcePath)
+ htmlPath = os.path.join(out,sourceName)+'.html'
+ # If we do give an out_name, and its dir does
+ # not exist , it will be created.
+ else:
+ outdir = os.path.split(out)[0]
+ if not os.path.isdir(outdir):
+ os.makedirs(outdir)
+ htmlPath = out
+ htmlPath = os.path.abspath(htmlPath)
+ # Open the text and do the parsing.
+ source = open(sourcePath).read()
+ parse = Parser(source, colors, sourcePath, open(htmlPath, 'wt'),
+ markup, header, footer, linenumbers)
+ parse.format(form)
+ _printinfo(" wrote %s" % htmlPath, quiet)
+ # html markup will ignore the external flag, but
+ # we need to stop the blank file from being written.
+ if form == 'external' and count == 1 and markup != 'html':
+ cssSheet = parse._sendCSSStyle(external=1)
+ cssPath = os.path.join(os.path.dirname(htmlPath),'pystyle.css')
+ css = open(cssPath, 'wt')
+ css.write(cssSheet)
+ css.close()
+ _printinfo(" wrote %s" % cssPath, quiet)
+ if show:
+ # load HTML page into the default web browser.
+ showpage(htmlPath)
+ return htmlPath
+
+def tagreplace(sourcestr, colors=lite, markup='xhtml',
+ linenumbers=0, dosheet=1, tagstart='<PY>'.lower(),
+ tagend='</PY>'.lower(), stylesheet='pystyle.css'):
+ """This is a helper function for pageconvert. Returns css, page.
+ """
+ if markup.lower() != 'html':
+ link = '<link rel="stylesheet" href="%s" type="text/css"/></head>'
+ css = link%stylesheet
+ if sourcestr.find(css) == -1:
+ sourcestr = sourcestr.replace('</head>', css, 1)
+ starttags = sourcestr.count(tagstart)
+ endtags = sourcestr.count(tagend)
+ if starttags:
+ if starttags == endtags:
+ for _ in range(starttags):
+ datastart = sourcestr.find(tagstart)
+ dataend = sourcestr.find(tagend)
+ data = sourcestr[datastart+len(tagstart):dataend]
+ data = unescape(data)
+ css , data = str2markup(data, colors=colors,
+ linenumbers=linenumbers, markup=markup, form='embed')
+ start = sourcestr[:datastart]
+ end = sourcestr[dataend+len(tagend):]
+ sourcestr = ''.join([start,data,end])
+ else:
+ raise InputError('Tag mismatch!\nCheck %s,%s tags'%tagstart,tagend)
+ if not dosheet:
+ css = None
+ return css, sourcestr
+
+def pageconvert(path, out=None, colors=lite, markup='xhtml', linenumbers=0,
+ dosheet=1, tagstart='<PY>'.lower(), tagend='</PY>'.lower(),
+ stylesheet='pystyle', show=1, returnstr=0):
+ """This function can colorize Python source
+
+ that is written in a webpage enclosed in tags.
+ """
+ if out == None:
+ out = os.path.dirname(path)
+ infile = open(path, 'r').read()
+ css,page = tagreplace(sourcestr=infile,colors=colors,
+ markup=markup, linenumbers=linenumbers, dosheet=dosheet,
+ tagstart=tagstart, tagend=tagend, stylesheet=stylesheet)
+ if not returnstr:
+ newpath = os.path.abspath(os.path.join(
+ out,'tmp', os.path.basename(path)))
+ if not os.path.exists(newpath):
+ try:
+ os.makedirs(os.path.dirname(newpath))
+ except:
+ pass#traceback.print_exc()
+ #Usage()
+ y = open(newpath, 'w')
+ y.write(page)
+ y.close()
+ if css:
+ csspath = os.path.abspath(os.path.join(
+ out,'tmp','%s.css'%stylesheet))
+ x = open(csspath,'w')
+ x.write(css)
+ x.close()
+ if show:
+ try:
+ os.startfile(newpath)
+ except:
+ traceback.print_exc()
+ return newpath
+ else:
+ return css, page
+
+##################################################################### helpers
+
+def walkdir(dir):
+ """Return a list of .py and .pyw files from a given directory.
+
+ This function can be written as a generator Python 2.3, or a genexp
+ in Python 2.4. But 2.2 and 2.1 would be left out....
+ """
+ # Get a list of files that match *.py*
+ GLOB_PATTERN = os.path.join(dir, "*.[p][y]*")
+ pathlist = glob.glob(GLOB_PATTERN)
+ # Now filter out all but py and pyw
+ filterlist = [x for x in pathlist
+ if x.endswith('.py')
+ or x.endswith('.pyw')]
+ if filterlist != []:
+ # if we have a list send it
+ return filterlist
+ else:
+ return None
+
+def showpage(path):
+ """Helper function to open webpages"""
+ try:
+ import webbrowser
+ webbrowser.open_new(os.path.abspath(path))
+ except:
+ traceback.print_exc()
+
+def _printinfo(message, quiet):
+ """Helper to print messages"""
+ if not quiet:
+ print(message)
+
+def escape(text):
+ """escape text for html. similar to cgi.escape"""
+ text = text.replace("&", "&amp;")
+ text = text.replace("<", "&lt;")
+ text = text.replace(">", "&gt;")
+ return text
+
+def unescape(text):
+ """unsecape escaped text"""
+ text = text.replace("&quot;", '"')
+ text = text.replace("&gt;", ">")
+ text = text.replace("&lt;", "<")
+ text = text.replace("&amp;", "&")
+ return text
+
+########################################################### Custom Exceptions
+
+class PySourceColorError(Exception):
+ # Base for custom errors
+ def __init__(self, msg=''):
+ self._msg = msg
+ Exception.__init__(self, msg)
+ def __repr__(self):
+ return self._msg
+ __str__ = __repr__
+
+class PathError(PySourceColorError):
+ def __init__(self, msg):
+ PySourceColorError.__init__(self,
+ 'Path error! : %s'% msg)
+
+class InputError(PySourceColorError):
+ def __init__(self, msg):
+ PySourceColorError.__init__(self,
+ 'Input error! : %s'% msg)
+
+########################################################## Python code parser
+
+class Parser(object):
+
+ """MoinMoin python parser heavily chopped :)"""
+
+ def __init__(self, raw, colors=None, title='', out=sys.stdout,
+ markup='html', header=None, footer=None, linenumbers=0):
+ """Store the source text & set some flags"""
+ if colors == None:
+ colors = defaultColors
+ self.raw = raw.expandtabs().rstrip()
+ self.title = os.path.basename(title)
+ self.out = out
+ self.line = ''
+ self.lasttext = ''
+ self.argFlag = 0
+ self.classFlag = 0
+ self.defFlag = 0
+ self.decoratorFlag = 0
+ self.external = 0
+ self.markup = markup.upper()
+ self.colors = colors
+ self.header = header
+ self.footer = footer
+ self.doArgs = 1 # overrides the new tokens
+ self.doNames = 1 # overrides the new tokens
+ self.doMathOps = 1 # overrides the new tokens
+ self.doBrackets = 1 # overrides the new tokens
+ self.doURL = 1 # override url conversion
+ self.LINENUMHOLDER = "___line___".upper()
+ self.LINESTART = "___start___".upper()
+ self.skip = 0
+ # add space left side of code for padding.Override in color dict.
+ self.extraspace = self.colors.get(EXTRASPACE, '')
+ # Linenumbers less then zero also have numberlinks
+ self.dolinenums = self.linenum = abs(linenumbers)
+ if linenumbers < 0:
+ self.numberlinks = 1
+ else:
+ self.numberlinks = 0
+
+ def format(self, form=None):
+ """Parse and send the colorized source"""
+ if form in ('snip','code'):
+ self.addEnds = 0
+ elif form == 'embed':
+ self.addEnds = 0
+ self.external = 1
+ else:
+ if form == 'external':
+ self.external = 1
+ self.addEnds = 1
+
+ # Store line offsets in self.lines
+ self.lines = [0, 0]
+ pos = 0
+
+ # Add linenumbers
+ if self.dolinenums:
+ start=self.LINENUMHOLDER+' '+self.extraspace
+ else:
+ start=''+self.extraspace
+ newlines = []
+ lines = self.raw.splitlines(0)
+ for l in lines:
+ # span and div escape for customizing and embedding raw text
+ if (l.startswith('#$#')
+ or l.startswith('#%#')
+ or l.startswith('#@#')):
+ newlines.append(l)
+ else:
+ # kludge for line spans in css,xhtml
+ if self.markup in ['XHTML','CSS']:
+ newlines.append(self.LINESTART+' '+start+l)
+ else:
+ newlines.append(start+l)
+ self.raw = "\n".join(newlines)+'\n'# plus an extra newline at the end
+
+ # Gather lines
+ while 1:
+ pos = self.raw.find('\n', pos) + 1
+ if not pos: break
+ self.lines.append(pos)
+ self.lines.append(len(self.raw))
+
+ # Wrap text in a filelike object
+ self.pos = 0
+ text = StringIO.StringIO(self.raw)
+
+ # Markup start
+ if self.addEnds:
+ self._doPageStart()
+ else:
+ self._doSnippetStart()
+
+ ## Tokenize calls the __call__
+ ## function for each token till done.
+ # Parse the source and write out the results.
+ try:
+ tokenize.tokenize(text.readline, self)
+ except tokenize.TokenError as ex:
+ msg = ex[0]
+ line = ex[1][0]
+ self.out.write("<h3>ERROR: %s</h3>%s\n"%
+ (msg, self.raw[self.lines[line]:]))
+ #traceback.print_exc()
+
+ # Markup end
+ if self.addEnds:
+ self._doPageEnd()
+ else:
+ self._doSnippetEnd()
+
+ def __call__(self, toktype, toktext, srow_col, erow_col, line):
+ """Token handler. Order is important do not rearrange."""
+ self.line = line
+ srow, scol = srow_col
+ erow, ecol = erow_col
+ # Calculate new positions
+ oldpos = self.pos
+ newpos = self.lines[srow] + scol
+ self.pos = newpos + len(toktext)
+ # Handle newlines
+ if toktype in (token.NEWLINE, tokenize.NL):
+ self.decoratorFlag = self.argFlag = 0
+ # kludge for line spans in css,xhtml
+ if self.markup in ['XHTML','CSS']:
+ self.out.write('</span>')
+ self.out.write('\n')
+ return
+
+ # Send the original whitespace, and tokenize backslashes if present.
+ # Tokenizer.py just sends continued line backslashes with whitespace.
+ # This is a hack to tokenize continued line slashes as operators.
+ # Should continued line backslashes be treated as operators
+ # or some other token?
+
+ if newpos > oldpos:
+ if self.raw[oldpos:newpos].isspace():
+ # consume a single space after linestarts and linenumbers
+ # had to have them so tokenizer could seperate them.
+ # multiline strings are handled by do_Text functions
+ if self.lasttext != self.LINESTART \
+ and self.lasttext != self.LINENUMHOLDER:
+ self.out.write(self.raw[oldpos:newpos])
+ else:
+ self.out.write(self.raw[oldpos+1:newpos])
+ else:
+ slash = self.raw[oldpos:newpos].find('\\')+oldpos
+ self.out.write(self.raw[oldpos:slash])
+ getattr(self, '_send%sText'%(self.markup))(OPERATOR, '\\')
+ self.linenum+=1
+ # kludge for line spans in css,xhtml
+ if self.markup in ['XHTML','CSS']:
+ self.out.write('</span>')
+ self.out.write(self.raw[slash+1:newpos])
+
+ # Skip indenting tokens
+ if toktype in (token.INDENT, token.DEDENT):
+ self.pos = newpos
+ return
+
+ # Look for operators
+ if token.LPAR <= toktype and toktype <= token.OP:
+ # Trap decorators py2.4 >
+ if toktext == '@':
+ toktype = DECORATOR
+ # Set a flag if this was the decorator start so
+ # the decorator name and arguments can be identified
+ self.decoratorFlag = self.argFlag = 1
+ else:
+ if self.doArgs:
+ # Find the start for arguments
+ if toktext == '(' and self.argFlag:
+ self.argFlag = 2
+ # Find the end for arguments
+ elif toktext == ':':
+ self.argFlag = 0
+ ## Seperate the diffrent operator types
+ # Brackets
+ if self.doBrackets and toktext in ['[',']','(',')','{','}']:
+ toktype = BRACKETS
+ # Math operators
+ elif self.doMathOps and toktext in ['*=','**=','-=','+=','|=',
+ '%=','>>=','<<=','=','^=',
+ '/=', '+','-','**','*','/','%']:
+ toktype = MATH_OPERATOR
+ # Operator
+ else:
+ toktype = OPERATOR
+ # example how flags should work.
+ # def fun(arg=argvalue,arg2=argvalue2):
+ # 0 1 2 A 1 N 2 A 1 N 0
+ if toktext == "=" and self.argFlag == 2:
+ self.argFlag = 1
+ elif toktext == "," and self.argFlag == 1:
+ self.argFlag = 2
+ # Look for keywords
+ elif toktype == NAME and keyword.iskeyword(toktext):
+ toktype = KEYWORD
+ # Set a flag if this was the class / def start so
+ # the class / def name and arguments can be identified
+ if toktext in ['class', 'def']:
+ if toktext =='class' and \
+ not line[:line.find('class')].endswith('.'):
+ self.classFlag = self.argFlag = 1
+ elif toktext == 'def' and \
+ not line[:line.find('def')].endswith('.'):
+ self.defFlag = self.argFlag = 1
+ else:
+ # must have used a keyword as a name i.e. self.class
+ toktype = ERRORTOKEN
+
+ # Look for class, def, decorator name
+ elif (self.classFlag or self.defFlag or self.decoratorFlag) \
+ and self.doNames:
+ if self.classFlag:
+ self.classFlag = 0
+ toktype = CLASS_NAME
+ elif self.defFlag:
+ self.defFlag = 0
+ toktype = DEF_NAME
+ elif self.decoratorFlag:
+ self.decoratorFlag = 0
+ toktype = DECORATOR_NAME
+
+ # Look for strings
+ # Order of evaluation is important do not change.
+ elif toktype == token.STRING:
+ text = toktext.lower()
+ # TRIPLE DOUBLE QUOTE's
+ if (text[:3] == '"""'):
+ toktype = TRIPLEDOUBLEQUOTE
+ elif (text[:4] == 'r"""'):
+ toktype = TRIPLEDOUBLEQUOTE_R
+ elif (text[:4] == 'u"""' or
+ text[:5] == 'ur"""'):
+ toktype = TRIPLEDOUBLEQUOTE_U
+ # DOUBLE QUOTE's
+ elif (text[:1] == '"'):
+ toktype = DOUBLEQUOTE
+ elif (text[:2] == 'r"'):
+ toktype = DOUBLEQUOTE_R
+ elif (text[:2] == 'u"' or
+ text[:3] == 'ur"'):
+ toktype = DOUBLEQUOTE_U
+ # TRIPLE SINGLE QUOTE's
+ elif (text[:3] == "'''"):
+ toktype = TRIPLESINGLEQUOTE
+ elif (text[:4] == "r'''"):
+ toktype = TRIPLESINGLEQUOTE_R
+ elif (text[:4] == "u'''" or
+ text[:5] == "ur'''"):
+ toktype = TRIPLESINGLEQUOTE_U
+ # SINGLE QUOTE's
+ elif (text[:1] == "'"):
+ toktype = SINGLEQUOTE
+ elif (text[:2] == "r'"):
+ toktype = SINGLEQUOTE_R
+ elif (text[:2] == "u'" or
+ text[:3] == "ur'"):
+ toktype = SINGLEQUOTE_U
+
+ # test for invalid string declaration
+ if self.lasttext.lower() == 'ru':
+ toktype = ERRORTOKEN
+
+ # Look for comments
+ elif toktype == COMMENT:
+ if toktext[:2] == "##":
+ toktype = DOUBLECOMMENT
+ elif toktext[:3] == '#$#':
+ toktype = TEXT
+ self.textFlag = 'SPAN'
+ toktext = toktext[3:]
+ elif toktext[:3] == '#%#':
+ toktype = TEXT
+ self.textFlag = 'DIV'
+ toktext = toktext[3:]
+ elif toktext[:3] == '#@#':
+ toktype = TEXT
+ self.textFlag = 'RAW'
+ toktext = toktext[3:]
+ if self.doURL:
+ # this is a 'fake helper function'
+ # url(URI,Alias_name) or url(URI)
+ url_pos = toktext.find('url(')
+ if url_pos != -1:
+ before = toktext[:url_pos]
+ url = toktext[url_pos+4:]
+ splitpoint = url.find(',')
+ endpoint = url.find(')')
+ after = url[endpoint+1:]
+ url = url[:endpoint]
+ if splitpoint != -1:
+ urlparts = url.split(',',1)
+ toktext = '%s<a href="%s">%s</a>%s'%(
+ before,urlparts[0],urlparts[1].lstrip(),after)
+ else:
+ toktext = '%s<a href="%s">%s</a>%s'%(before,url,url,after)
+
+ # Seperate errors from decorators
+ elif toktype == ERRORTOKEN:
+ # Bug fix for < py2.4
+ # space between decorators
+ if self.argFlag and toktext.isspace():
+ #toktype = NAME
+ self.out.write(toktext)
+ return
+ # Bug fix for py2.2 linenumbers with decorators
+ elif toktext.isspace():
+ # What if we have a decorator after a >>> or ...
+ #p = line.find('@')
+ #if p >= 0 and not line[:p].isspace():
+ #self.out.write(toktext)
+ #return
+ if self.skip:
+ self.skip=0
+ return
+ else:
+ self.out.write(toktext)
+ return
+ # trap decorators < py2.4
+ elif toktext == '@':
+ toktype = DECORATOR
+ # Set a flag if this was the decorator start so
+ # the decorator name and arguments can be identified
+ self.decoratorFlag = self.argFlag = 1
+
+ # Seperate args from names
+ elif (self.argFlag == 2 and
+ toktype == NAME and
+ toktext != 'None' and
+ self.doArgs):
+ toktype = ARGS
+
+ # Look for line numbers
+ # The conversion code for them is in the send_text functions.
+ if toktext in [self.LINENUMHOLDER,self.LINESTART]:
+ toktype = LINENUMBER
+ # if we don't have linenumbers set flag
+ # to skip the trailing space from linestart
+ if toktext == self.LINESTART and not self.dolinenums \
+ or toktext == self.LINENUMHOLDER:
+ self.skip=1
+
+
+ # Skip blank token that made it thru
+ ## bugfix for the last empty tag.
+ if toktext == '':
+ return
+
+ # Last token text history
+ self.lasttext = toktext
+
+ # escape all but the urls in the comments
+ if toktype in (DOUBLECOMMENT, COMMENT):
+ if toktext.find('<a href=') == -1:
+ toktext = escape(toktext)
+ else:
+ pass
+ elif toktype == TEXT:
+ pass
+ else:
+ toktext = escape(toktext)
+
+ # Send text for any markup
+ getattr(self, '_send%sText'%(self.markup))(toktype, toktext)
+ return
+
+ ################################################################# Helpers
+
+ def _doSnippetStart(self):
+ if self.markup == 'HTML':
+ # Start of html snippet
+ self.out.write('<pre>\n')
+ else:
+ # Start of css/xhtml snippet
+ self.out.write(self.colors.get(CODESTART,'<pre class="py">\n'))
+
+ def _doSnippetEnd(self):
+ # End of html snippet
+ self.out.write(self.colors.get(CODEEND,'</pre>\n'))
+
+ ######################################################## markup selectors
+
+ def _getFile(self, filepath):
+ try:
+ _file = open(filepath,'r')
+ content = _file.read()
+ _file.close()
+ except:
+ traceback.print_exc()
+ content = ''
+ return content
+
+ def _doPageStart(self):
+ getattr(self, '_do%sStart'%(self.markup))()
+
+ def _doPageHeader(self):
+ if self.header != None:
+ if self.header.find('#$#') != -1 or \
+ self.header.find('#$#') != -1 or \
+ self.header.find('#%#') != -1:
+ self.out.write(self.header[3:])
+ else:
+ if self.header != '':
+ self.header = self._getFile(self.header)
+ getattr(self, '_do%sHeader'%(self.markup))()
+
+ def _doPageFooter(self):
+ if self.footer != None:
+ if self.footer.find('#$#') != -1 or \
+ self.footer.find('#@#') != -1 or \
+ self.footer.find('#%#') != -1:
+ self.out.write(self.footer[3:])
+ else:
+ if self.footer != '':
+ self.footer = self._getFile(self.footer)
+ getattr(self, '_do%sFooter'%(self.markup))()
+
+ def _doPageEnd(self):
+ getattr(self, '_do%sEnd'%(self.markup))()
+
+ ################################################### color/style retrieval
+ ## Some of these are not used anymore but are kept for documentation
+
+ def _getLineNumber(self):
+ num = self.linenum
+ self.linenum+=1
+ return str(num).rjust(5)+" "
+
+ def _getTags(self, key):
+ # style tags
+ return self.colors.get(key, self.colors[NAME])[0]
+
+ def _getForeColor(self, key):
+ # get text foreground color, if not set to black
+ color = self.colors.get(key, self.colors[NAME])[1]
+ if color[:1] != '#':
+ color = '#000000'
+ return color
+
+ def _getBackColor(self, key):
+ # get text background color
+ return self.colors.get(key, self.colors[NAME])[2]
+
+ def _getPageColor(self):
+ # get page background color
+ return self.colors.get(PAGEBACKGROUND, '#FFFFFF')
+
+ def _getStyle(self, key):
+ # get the token style from the color dictionary
+ return self.colors.get(key, self.colors[NAME])
+
+ def _getMarkupClass(self, key):
+ # get the markup class name from the markup dictionary
+ return MARKUPDICT.get(key, MARKUPDICT[NAME])
+
+ def _getDocumentCreatedBy(self):
+ return '<!--This document created by %s ver.%s on: %s-->\n'%(
+ __title__,__version__,time.ctime())
+
+ ################################################### HTML markup functions
+
+ def _doHTMLStart(self):
+ # Start of html page
+ self.out.write('<!DOCTYPE html PUBLIC \
+"-//W3C//DTD HTML 4.01//EN">\n')
+ self.out.write('<html><head><title>%s</title>\n'%(self.title))
+ self.out.write(self._getDocumentCreatedBy())
+ self.out.write('<meta http-equiv="Content-Type" \
+content="text/html;charset=iso-8859-1">\n')
+ # Get background
+ self.out.write('</head><body bgcolor="%s">\n'%self._getPageColor())
+ self._doPageHeader()
+ self.out.write('<pre>')
+
+ def _getHTMLStyles(self, toktype, toktext):
+ # Get styles
+ tags, color = self.colors.get(toktype, self.colors[NAME])[:2]#
+ tagstart=[]
+ tagend=[]
+ # check for styles and set them if needed.
+ if 'b' in tags:#Bold
+ tagstart.append('<b>')
+ tagend.append('</b>')
+ if 'i' in tags:#Italics
+ tagstart.append('<i>')
+ tagend.append('</i>')
+ if 'u' in tags:#Underline
+ tagstart.append('<u>')
+ tagend.append('</u>')
+ # HTML tags should be paired like so : <b><i><u>Doh!</u></i></b>
+ tagend.reverse()
+ starttags="".join(tagstart)
+ endtags="".join(tagend)
+ return starttags,endtags,color
+
+ def _sendHTMLText(self, toktype, toktext):
+ numberlinks = self.numberlinks
+
+ # If it is an error, set a red box around the bad tokens
+ # older browsers should ignore it
+ if toktype == ERRORTOKEN:
+ style = ' style="border: solid 1.5pt #FF0000;"'
+ else:
+ style = ''
+ # Get styles
+ starttag, endtag, color = self._getHTMLStyles(toktype, toktext)
+ # This is a hack to 'fix' multi-line strings.
+ # Multi-line strings are treated as only one token
+ # even though they can be several physical lines.
+ # That makes it hard to spot the start of a line,
+ # because at this level all we know about are tokens.
+
+ if toktext.count(self.LINENUMHOLDER):
+ # rip apart the string and separate it by line.
+ # count lines and change all linenum token to line numbers.
+ # embedded all the new font tags inside the current one.
+ # Do this by ending the tag first then writing our new tags,
+ # then starting another font tag exactly like the first one.
+ if toktype == LINENUMBER:
+ splittext = toktext.split(self.LINENUMHOLDER)
+ else:
+ splittext = toktext.split(self.LINENUMHOLDER+' ')
+ store = []
+ store.append(splittext.pop(0))
+ lstarttag, lendtag, lcolor = self._getHTMLStyles(LINENUMBER, toktext)
+ count = len(splittext)
+ for item in splittext:
+ num = self._getLineNumber()
+ if numberlinks:
+ numstrip = num.strip()
+ content = '<a name="%s" href="#%s">%s</a>' \
+ %(numstrip,numstrip,num)
+ else:
+ content = num
+ if count <= 1:
+ endtag,starttag = '',''
+ linenumber = ''.join([endtag,'<font color=', lcolor, '>',
+ lstarttag, content, lendtag, '</font>' ,starttag])
+ store.append(linenumber+item)
+ toktext = ''.join(store)
+ # send text
+ ## Output optimization
+ # skip font tag if black text, but styles will still be sent. (b,u,i)
+ if color !='#000000':
+ startfont = '<font color="%s"%s>'%(color, style)
+ endfont = '</font>'
+ else:
+ startfont, endfont = ('','')
+ if toktype != LINENUMBER:
+ self.out.write(''.join([startfont,starttag,
+ toktext,endtag,endfont]))
+ else:
+ self.out.write(toktext)
+ return
+
+ def _doHTMLHeader(self):
+ # Optional
+ if self.header != '':
+ self.out.write('%s\n'%self.header)
+ else:
+ color = self._getForeColor(NAME)
+ self.out.write('<b><font color="%s"># %s \
+ <br># %s</font></b><hr>\n'%
+ (color, self.title, time.ctime()))
+
+ def _doHTMLFooter(self):
+ # Optional
+ if self.footer != '':
+ self.out.write('%s\n'%self.footer)
+ else:
+ color = self._getForeColor(NAME)
+ self.out.write('<b><font color="%s"> \
+ <hr># %s<br># %s</font></b>\n'%
+ (color, self.title, time.ctime()))
+
+ def _doHTMLEnd(self):
+ # End of html page
+ self.out.write('</pre>\n')
+ # Write a little info at the bottom
+ self._doPageFooter()
+ self.out.write('</body></html>\n')
+
+ #################################################### CSS markup functions
+
+ def _getCSSStyle(self, key):
+ # Get the tags and colors from the dictionary
+ tags, forecolor, backcolor = self._getStyle(key)
+ style=[]
+ border = None
+ bordercolor = None
+ tags = tags.lower()
+ if tags:
+ # get the border color if specified
+ # the border color will be appended to
+ # the list after we define a border
+ if '#' in tags:# border color
+ start = tags.find('#')
+ end = start + 7
+ bordercolor = tags[start:end]
+ tags.replace(bordercolor,'',1)
+ # text styles
+ if 'b' in tags:# Bold
+ style.append('font-weight:bold;')
+ else:
+ style.append('font-weight:normal;')
+ if 'i' in tags:# Italic
+ style.append('font-style:italic;')
+ if 'u' in tags:# Underline
+ style.append('text-decoration:underline;')
+ # border size
+ if 'l' in tags:# thick border
+ size='thick'
+ elif 'm' in tags:# medium border
+ size='medium'
+ elif 't' in tags:# thin border
+ size='thin'
+ else:# default
+ size='medium'
+ # border styles
+ if 'n' in tags:# inset border
+ border='inset'
+ elif 'o' in tags:# outset border
+ border='outset'
+ elif 'r' in tags:# ridge border
+ border='ridge'
+ elif 'g' in tags:# groove border
+ border='groove'
+ elif '=' in tags:# double border
+ border='double'
+ elif '.' in tags:# dotted border
+ border='dotted'
+ elif '-' in tags:# dashed border
+ border='dashed'
+ elif 's' in tags:# solid border
+ border='solid'
+ # border type check
+ seperate_sides=0
+ for side in ['<','>','^','v']:
+ if side in tags:
+ seperate_sides+=1
+ # border box or seperate sides
+ if seperate_sides==0 and border:
+ style.append('border: %s %s;'%(border,size))
+ else:
+ if border == None:
+ border = 'solid'
+ if 'v' in tags:# bottom border
+ style.append('border-bottom:%s %s;'%(border,size))
+ if '<' in tags:# left border
+ style.append('border-left:%s %s;'%(border,size))
+ if '>' in tags:# right border
+ style.append('border-right:%s %s;'%(border,size))
+ if '^' in tags:# top border
+ style.append('border-top:%s %s;'%(border,size))
+ else:
+ style.append('font-weight:normal;')# css inherited style fix
+ # we have to define our borders before we set colors
+ if bordercolor:
+ style.append('border-color:%s;'%bordercolor)
+ # text forecolor
+ style.append('color:%s;'% forecolor)
+ # text backcolor
+ if backcolor:
+ style.append('background-color:%s;'%backcolor)
+ return (self._getMarkupClass(key),' '.join(style))
+
+ def _sendCSSStyle(self, external=0):
+ """ create external and internal style sheets"""
+ styles = []
+ external += self.external
+ if not external:
+ styles.append('<style type="text/css">\n<!--\n')
+ # Get page background color and write styles ignore any we don't know
+ styles.append('body { background:%s; }\n'%self._getPageColor())
+ # write out the various css styles
+ for key in MARKUPDICT:
+ styles.append('.%s { %s }\n'%self._getCSSStyle(key))
+ # If you want to style the pre tag you must modify the color dict.
+ # Example:
+ # lite[PY] = .py {border: solid thin #000000;background:#555555}\n'''
+ styles.append(self.colors.get(PY, '.py { }\n'))
+ # Extra css can be added here
+ # add CSSHOOK to the color dict if you need it.
+ # Example:
+ #lite[CSSHOOK] = """.mytag { border: solid thin #000000; } \n
+ # .myothertag { font-weight:bold; )\n"""
+ styles.append(self.colors.get(CSSHOOK,''))
+ if not self.external:
+ styles.append('--></style>\n')
+ return ''.join(styles)
+
+ def _doCSSStart(self):
+ # Start of css/html 4.01 page
+ self.out.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">\n')
+ self.out.write('<html><head><title>%s</title>\n'%(self.title))
+ self.out.write(self._getDocumentCreatedBy())
+ self.out.write('<meta http-equiv="Content-Type" \
+content="text/html;charset=iso-8859-1">\n')
+ self._doCSSStyleSheet()
+ self.out.write('</head>\n<body>\n')
+ # Write a little info at the top.
+ self._doPageHeader()
+ self.out.write(self.colors.get(CODESTART,'<pre class="py">\n'))
+ return
+
+ def _doCSSStyleSheet(self):
+ if not self.external:
+ # write an embedded style sheet
+ self.out.write(self._sendCSSStyle())
+ else:
+ # write a link to an external style sheet
+ self.out.write('<link rel="stylesheet" \
+href="pystyle.css" type="text/css">')
+ return
+
+ def _sendCSSText(self, toktype, toktext):
+ # This is a hack to 'fix' multi-line strings.
+ # Multi-line strings are treated as only one token
+ # even though they can be several physical lines.
+ # That makes it hard to spot the start of a line,
+ # because at this level all we know about are tokens.
+ markupclass = MARKUPDICT.get(toktype, MARKUPDICT[NAME])
+ # if it is a LINENUMBER type then we can skip the rest
+ if toktext == self.LINESTART and toktype == LINENUMBER:
+ self.out.write('<span class="py_line">')
+ return
+ if toktext.count(self.LINENUMHOLDER):
+ # rip apart the string and separate it by line
+ # count lines and change all linenum token to line numbers
+ # also convert linestart and lineend tokens
+ # <linestart> <lnumstart> lnum <lnumend> text <lineend>
+ #################################################
+ newmarkup = MARKUPDICT.get(LINENUMBER, MARKUPDICT[NAME])
+ lstartspan = '<span class="%s">'%(newmarkup)
+ if toktype == LINENUMBER:
+ splittext = toktext.split(self.LINENUMHOLDER)
+ else:
+ splittext = toktext.split(self.LINENUMHOLDER+' ')
+ store = []
+ # we have already seen the first linenumber token
+ # so we can skip the first one
+ store.append(splittext.pop(0))
+ for item in splittext:
+ num = self._getLineNumber()
+ if self.numberlinks:
+ numstrip = num.strip()
+ content= '<a name="%s" href="#%s">%s</a>' \
+ %(numstrip,numstrip,num)
+ else:
+ content = num
+ linenumber= ''.join([lstartspan,content,'</span>'])
+ store.append(linenumber+item)
+ toktext = ''.join(store)
+ if toktext.count(self.LINESTART):
+ # wraps the textline in a line span
+ # this adds a lot of kludges, is it really worth it?
+ store = []
+ parts = toktext.split(self.LINESTART+' ')
+ # handle the first part differently
+ # the whole token gets wraqpped in a span later on
+ first = parts.pop(0)
+ # place spans before the newline
+ pos = first.rfind('\n')
+ if pos != -1:
+ first=first[:pos]+'</span></span>'+first[pos:]
+ store.append(first)
+ #process the rest of the string
+ for item in parts:
+ #handle line numbers if present
+ if self.dolinenums:
+ item = item.replace('</span>',
+ '</span><span class="%s">'%(markupclass))
+ else:
+ item = '<span class="%s">%s'%(markupclass,item)
+ # add endings for line and string tokens
+ pos = item.rfind('\n')
+ if pos != -1:
+ item=item[:pos]+'</span></span>\n'
+ store.append(item)
+ # add start tags for lines
+ toktext = '<span class="py_line">'.join(store)
+ # Send text
+ if toktype != LINENUMBER:
+ if toktype == TEXT and self.textFlag == 'DIV':
+ startspan = '<div class="%s">'%(markupclass)
+ endspan = '</div>'
+ elif toktype == TEXT and self.textFlag == 'RAW':
+ startspan,endspan = ('','')
+ else:
+ startspan = '<span class="%s">'%(markupclass)
+ endspan = '</span>'
+ self.out.write(''.join([startspan, toktext, endspan]))
+ else:
+ self.out.write(toktext)
+ return
+
+ def _doCSSHeader(self):
+ if self.header != '':
+ self.out.write('%s\n'%self.header)
+ else:
+ name = MARKUPDICT.get(NAME)
+ self.out.write('<div class="%s"># %s <br> \
+# %s</div><hr>\n'%(name, self.title, time.ctime()))
+
+ def _doCSSFooter(self):
+ # Optional
+ if self.footer != '':
+ self.out.write('%s\n'%self.footer)
+ else:
+ self.out.write('<hr><div class="%s"># %s <br> \
+# %s</div>\n'%(MARKUPDICT.get(NAME),self.title, time.ctime()))
+
+ def _doCSSEnd(self):
+ # End of css/html page
+ self.out.write(self.colors.get(CODEEND,'</pre>\n'))
+ # Write a little info at the bottom
+ self._doPageFooter()
+ self.out.write('</body></html>\n')
+ return
+
+ ################################################## XHTML markup functions
+
+ def _doXHTMLStart(self):
+ # XHTML is really just XML + HTML 4.01.
+ # We only need to change the page headers,
+ # and a few tags to get valid XHTML.
+ # Start of xhtml page
+ self.out.write('<?xml version="1.0"?>\n \
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n \
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n \
+<html xmlns="http://www.w3.org/1999/xhtml">\n')
+ self.out.write('<head><title>%s</title>\n'%(self.title))
+ self.out.write(self._getDocumentCreatedBy())
+ self.out.write('<meta http-equiv="Content-Type" \
+content="text/html;charset=iso-8859-1"/>\n')
+ self._doXHTMLStyleSheet()
+ self.out.write('</head>\n<body>\n')
+ # Write a little info at the top.
+ self._doPageHeader()
+ self.out.write(self.colors.get(CODESTART,'<pre class="py">\n'))
+ return
+
+ def _doXHTMLStyleSheet(self):
+ if not self.external:
+ # write an embedded style sheet
+ self.out.write(self._sendCSSStyle())
+ else:
+ # write a link to an external style sheet
+ self.out.write('<link rel="stylesheet" \
+href="pystyle.css" type="text/css"/>\n')
+ return
+
+ def _sendXHTMLText(self, toktype, toktext):
+ self._sendCSSText(toktype, toktext)
+
+ def _doXHTMLHeader(self):
+ # Optional
+ if self.header:
+ self.out.write('%s\n'%self.header)
+ else:
+ name = MARKUPDICT.get(NAME)
+ self.out.write('<div class="%s"># %s <br/> \
+# %s</div><hr/>\n '%(
+ name, self.title, time.ctime()))
+
+ def _doXHTMLFooter(self):
+ # Optional
+ if self.footer:
+ self.out.write('%s\n'%self.footer)
+ else:
+ self.out.write('<hr/><div class="%s"># %s <br/> \
+# %s</div>\n'%(MARKUPDICT.get(NAME), self.title, time.ctime()))
+
+ def _doXHTMLEnd(self):
+ self._doCSSEnd()
+
+#############################################################################
+
+if __name__ == '__main__':
+ cli()
+
+#############################################################################
+# PySourceColor.py
+# 2004, 2005 M.E.Farmer Jr.
+# Python license
diff --git a/paste/util/UserDict24.py b/paste/util/UserDict24.py
deleted file mode 100644
index e5b64f5..0000000
--- a/paste/util/UserDict24.py
+++ /dev/null
@@ -1,167 +0,0 @@
-"""A more or less complete user-defined wrapper around dictionary objects."""
-
-class UserDict:
- def __init__(self, dict=None, **kwargs):
- self.data = {}
- if dict is not None:
- if not hasattr(dict,'keys'):
- dict = type({})(dict) # make mapping from a sequence
- self.update(dict)
- if len(kwargs):
- self.update(kwargs)
- def __repr__(self): return repr(self.data)
- def __cmp__(self, dict):
- if isinstance(dict, UserDict):
- return cmp(self.data, dict.data)
- else:
- return cmp(self.data, dict)
- def __len__(self): return len(self.data)
- def __getitem__(self, key): return self.data[key]
- def __setitem__(self, key, item): self.data[key] = item
- def __delitem__(self, key): del self.data[key]
- def clear(self): self.data.clear()
- def copy(self):
- if self.__class__ is UserDict:
- return UserDict(self.data)
- import copy
- data = self.data
- try:
- self.data = {}
- c = copy.copy(self)
- finally:
- self.data = data
- c.update(self)
- return c
- def keys(self): return self.data.keys()
- def items(self): return self.data.items()
- def iteritems(self): return self.data.iteritems()
- def iterkeys(self): return self.data.iterkeys()
- def itervalues(self): return self.data.itervalues()
- def values(self): return self.data.values()
- def has_key(self, key): return self.data.has_key(key)
- def update(self, dict):
- if isinstance(dict, UserDict):
- self.data.update(dict.data)
- elif isinstance(dict, type(self.data)):
- self.data.update(dict)
- else:
- for k, v in dict.items():
- self[k] = v
- def get(self, key, failobj=None):
- if not self.has_key(key):
- return failobj
- return self[key]
- def setdefault(self, key, failobj=None):
- if not self.has_key(key):
- self[key] = failobj
- return self[key]
- def pop(self, key, *args):
- return self.data.pop(key, *args)
- def popitem(self):
- return self.data.popitem()
- def __contains__(self, key):
- return key in self.data
- def fromkeys(cls, iterable, value=None):
- d = cls()
- for key in iterable:
- d[key] = value
- return d
- fromkeys = classmethod(fromkeys)
-
-class IterableUserDict(UserDict):
- def __iter__(self):
- return iter(self.data)
-
-class DictMixin:
- # Mixin defining all dictionary methods for classes that already have
- # a minimum dictionary interface including getitem, setitem, delitem,
- # and keys. Without knowledge of the subclass constructor, the mixin
- # does not define __init__() or copy(). In addition to the four base
- # methods, progressively more efficiency comes with defining
- # __contains__(), __iter__(), and iteritems().
-
- # second level definitions support higher levels
- def __iter__(self):
- for k in self.keys():
- yield k
- def has_key(self, key):
- try:
- value = self[key]
- except KeyError:
- return False
- return True
- def __contains__(self, key):
- return self.has_key(key)
-
- # third level takes advantage of second level definitions
- def iteritems(self):
- for k in self:
- yield (k, self[k])
- def iterkeys(self):
- return self.__iter__()
-
- # fourth level uses definitions from lower levels
- def itervalues(self):
- for _, v in self.iteritems():
- yield v
- def values(self):
- return [v for _, v in self.iteritems()]
- def items(self):
- return list(self.iteritems())
- def clear(self):
- for key in self.keys():
- del self[key]
- def setdefault(self, key, default):
- try:
- return self[key]
- except KeyError:
- self[key] = default
- return default
- def pop(self, key, *args):
- if len(args) > 1:
- raise TypeError, "pop expected at most 2 arguments, got "\
- + repr(1 + len(args))
- try:
- value = self[key]
- except KeyError:
- if args:
- return args[0]
- raise
- del self[key]
- return value
- def popitem(self):
- try:
- k, v = self.iteritems().next()
- except StopIteration:
- raise KeyError, 'container is empty'
- del self[k]
- return (k, v)
- def update(self, other):
- # Make progressively weaker assumptions about "other"
- if hasattr(other, 'iteritems'): # iteritems saves memory and lookups
- for k, v in other.iteritems():
- self[k] = v
- elif hasattr(other, '__iter__'): # iter saves memory
- for k in other:
- self[k] = other[k]
- else:
- for k in other.keys():
- self[k] = other[k]
- def get(self, key, default=None):
- try:
- return self[key]
- except KeyError:
- return default
- def __repr__(self):
- return repr(dict(self.iteritems()))
- def __cmp__(self, other):
- if other is None:
- return 1
- if isinstance(other, DictMixin):
- other = dict(other.iteritems())
- return cmp(dict(self.iteritems()), other)
- def __len__(self):
- return len(self.keys())
-
- def __nonzero__(self):
- return bool(self.iteritems())
diff --git a/paste/util/dateinterval.py b/paste/util/dateinterval.py
index 5109e28..023bce4 100644
--- a/paste/util/dateinterval.py
+++ b/paste/util/dateinterval.py
@@ -7,7 +7,7 @@ years (leap years in particular).
Accepts (y)ear, (b)month, (w)eek, (d)ay, (h)our, (m)inute, (s)econd.
-Exports only timeEncode and timeDecode functions.
+Exports only timeEncode and timeDecode functions.
"""
import re
@@ -33,7 +33,7 @@ timeValues = {
timeOrdered = list(timeValues.items())
timeOrdered.sort(key=lambda x: x[1], reverse=True)
-
+
def interval_encode(seconds, include_sign=False):
"""Encodes a number of seconds (representing a time interval)
into a form like 1h2d3s.
@@ -80,7 +80,7 @@ def interval_decode(s):
s = s[1:]
for match in allMatches(s, _timeRE):
char = match.group(0)[-1].lower()
- if not timeValues.has_key(char):
+ if char not in timeValues:
# @@: should signal error
continue
time += int(match.group(0)[:-1]) * timeValues[char]
diff --git a/paste/util/datetimeutil.py b/paste/util/datetimeutil.py
index c19e001..3c6d7d9 100644
--- a/paste/util/datetimeutil.py
+++ b/paste/util/datetimeutil.py
@@ -1,361 +1,359 @@
-# (c) 2005 Clark C. Evans and contributors
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# Some of this code was funded by: http://prometheusresearch.com
-"""
-Date, Time, and Timespan Parsing Utilities
-
-This module contains parsing support to create "human friendly"
-``datetime`` object parsing. The explicit goal of these routines is
-to provide a multi-format date/time support not unlike that found in
-Microsoft Excel. In most approaches, the input is very "strict" to
-prevent errors -- however, this approach is much more liberal since we
-are assuming the user-interface is parroting back the normalized value
-and thus the user has immediate feedback if the data is not typed in
-correctly.
-
- ``parse_date`` and ``normalize_date``
-
- These functions take a value like '9 jan 2007' and returns either an
- ``date`` object, or an ISO 8601 formatted date value such
- as '2007-01-09'. There is an option to provide an Oracle database
- style output as well, ``09 JAN 2007``, but this is not the default.
-
- This module always treats '/' delimiters as using US date order
- (since the author's clients are US based), hence '1/9/2007' is
- January 9th. Since this module treats the '-' as following
- European order this supports both modes of data-entry; together
- with immediate parroting back the result to the screen, the author
- has found this approach to work well in pratice.
-
- ``parse_time`` and ``normalize_time``
-
- These functions take a value like '1 pm' and returns either an
- ``time`` object, or an ISO 8601 formatted 24h clock time
- such as '13:00'. There is an option to provide for US style time
- values, '1:00 PM', however this is not the default.
-
- ``parse_datetime`` and ``normalize_datetime``
-
- These functions take a value like '9 jan 2007 at 1 pm' and returns
- either an ``datetime`` object, or an ISO 8601 formatted
- return (without the T) such as '2007-01-09 13:00'. There is an
- option to provide for Oracle / US style, '09 JAN 2007 @ 1:00 PM',
- however this is not the default.
-
- ``parse_delta`` and ``normalize_delta``
-
- These functions take a value like '1h 15m' and returns either an
- ``timedelta`` object, or an 2-decimal fixed-point
- numerical value in hours, such as '1.25'. The rationale is to
- support meeting or time-billing lengths, not to be an accurate
- representation in mili-seconds. As such not all valid
- ``timedelta`` values will have a normalized representation.
-
-"""
-from datetime import timedelta, time, date
-from time import localtime
-import string
-
-__all__ = ['parse_timedelta', 'normalize_timedelta',
- 'parse_time', 'normalize_time',
- 'parse_date', 'normalize_date']
-
-def _number(val):
- try:
- return string.atoi(val)
- except:
- return None
-
-#
-# timedelta
-#
-def parse_timedelta(val):
- """
- returns a ``timedelta`` object, or None
- """
- if not val:
- return None
- val = val.lower()
- if "." in val:
- val = float(val)
- return timedelta(hours=int(val), minutes=60*(val % 1.0))
- fHour = ("h" in val or ":" in val)
- fMin = ("m" in val or ":" in val)
- fFraction = "." in val
- for noise in "minu:teshour()":
- val = val.replace(noise, ' ')
- val = val.strip()
- val = val.split()
- hr = 0.0
- mi = 0
- val.reverse()
- if fHour:
- hr = int(val.pop())
- if fMin:
- mi = int(val.pop())
- if len(val) > 0 and not hr:
- hr = int(val.pop())
- return timedelta(hours=hr, minutes=mi)
-
-def normalize_timedelta(val):
- """
- produces a normalized string value of the timedelta
-
- This module returns a normalized time span value consisting of the
- number of hours in fractional form. For example '1h 15min' is
- formatted as 01.25.
- """
- if type(val) == str:
- val = parse_timedelta(val)
- if not val:
- return ''
- hr = val.seconds/3600
- mn = (val.seconds % 3600)/60
- return "%d.%02d" % (hr, mn * 100/60)
-
-#
-# time
-#
-def parse_time(val):
- if not val:
- return None
- hr = mi = 0
- val = val.lower()
- amflag = (-1 != val.find('a')) # set if AM is found
- pmflag = (-1 != val.find('p')) # set if PM is found
- for noise in ":amp.":
- val = val.replace(noise, ' ')
- val = val.split()
- if len(val) > 1:
- hr = int(val[0])
- mi = int(val[1])
- else:
- val = val[0]
- if len(val) < 1:
- pass
- elif 'now' == val:
- tm = localtime()
- hr = tm[3]
- mi = tm[4]
- elif 'noon' == val:
- hr = 12
- elif len(val) < 3:
- hr = int(val)
- if not amflag and not pmflag and hr < 7:
- hr += 12
- elif len(val) < 5:
- hr = int(val[:-2])
- mi = int(val[-2:])
- else:
- hr = int(val[:1])
- if amflag and hr >= 12:
- hr = hr - 12
- if pmflag and hr < 12:
- hr = hr + 12
- return time(hr, mi)
-
-def normalize_time(value, ampm):
- if not value:
- return ''
- if type(value) == str:
- value = parse_time(value)
- if not ampm:
- return "%02d:%02d" % (value.hour, value.minute)
- hr = value.hour
- am = "AM"
- if hr < 1 or hr > 23:
- hr = 12
- elif hr >= 12:
- am = "PM"
- if hr > 12:
- hr = hr - 12
- return "%02d:%02d %s" % (hr, value.minute, am)
-
-#
-# Date Processing
-#
-
-_one_day = timedelta(days=1)
-
-_str2num = {'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6,
- 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 }
-
-def _month(val):
- for (key, mon) in _str2num.items():
- if key in val:
- return mon
- raise TypeError("unknown month '%s'" % val)
-
-_days_in_month = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30,
- 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31,
- }
-_num2str = {1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
- 7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec',
- }
-_wkdy = ("mon", "tue", "wed", "thu", "fri", "sat", "sun")
-
-def parse_date(val):
- if not(val):
- return None
- val = val.lower()
- now = None
-
- # optimized check for YYYY-MM-DD
- strict = val.split("-")
- if len(strict) == 3:
- (y, m, d) = strict
- if "+" in d:
- d = d.split("+")[0]
- if " " in d:
- d = d.split(" ")[0]
- try:
- now = date(int(y), int(m), int(d))
- val = "xxx" + val[10:]
- except ValueError:
- pass
-
- # allow for 'now', 'mon', 'tue', etc.
- if not now:
- chk = val[:3]
- if chk in ('now','tod'):
- now = date.today()
- elif chk in _wkdy:
- now = date.today()
- idx = list(_wkdy).index(chk) + 1
- while now.isoweekday() != idx:
- now += _one_day
-
- # allow dates to be modified via + or - /w number of days, so
- # that now+3 is three days from now
- if now:
- tail = val[3:].strip()
- tail = tail.replace("+"," +").replace("-"," -")
- for item in tail.split():
- try:
- days = int(item)
- except ValueError:
- pass
- else:
- now += timedelta(days=days)
- return now
-
- # ok, standard parsing
- yr = mo = dy = None
- for noise in ('/', '-', ',', '*'):
- val = val.replace(noise, ' ')
- for noise in _wkdy:
- val = val.replace(noise, ' ')
- out = []
- last = False
- ldig = False
- for ch in val:
- if ch.isdigit():
- if last and not ldig:
- out.append(' ')
- last = ldig = True
- else:
- if ldig:
- out.append(' ')
- ldig = False
- last = True
- out.append(ch)
- val = "".join(out).split()
- if 3 == len(val):
- a = _number(val[0])
- b = _number(val[1])
- c = _number(val[2])
- if len(val[0]) == 4:
- yr = a
- if b: # 1999 6 23
- mo = b
- dy = c
- else: # 1999 Jun 23
- mo = _month(val[1])
- dy = c
- elif a > 0:
- yr = c
- if len(val[2]) < 4:
- raise TypeError("four digit year required")
- if b: # 6 23 1999
- dy = b
- mo = a
- else: # 23 Jun 1999
- dy = a
- mo = _month(val[1])
- else: # Jun 23, 2000
- dy = b
- yr = c
- if len(val[2]) < 4:
- raise TypeError("four digit year required")
- mo = _month(val[0])
- elif 2 == len(val):
- a = _number(val[0])
- b = _number(val[1])
- if a > 999:
- yr = a
- dy = 1
- if b > 0: # 1999 6
- mo = b
- else: # 1999 Jun
- mo = _month(val[1])
- elif a > 0:
- if b > 999: # 6 1999
- mo = a
- yr = b
- dy = 1
- elif b > 0: # 6 23
- mo = a
- dy = b
- else: # 23 Jun
- dy = a
- mo = _month(val[1])
- else:
- if b > 999: # Jun 2001
- yr = b
- dy = 1
- else: # Jun 23
- dy = b
- mo = _month(val[0])
- elif 1 == len(val):
- val = val[0]
- if not val.isdigit():
- mo = _month(val)
- if mo is not None:
- dy = 1
- else:
- v = _number(val)
- val = str(v)
- if 8 == len(val): # 20010623
- yr = _number(val[:4])
- mo = _number(val[4:6])
- dy = _number(val[6:])
- elif len(val) in (3,4):
- if v > 1300: # 2004
- yr = v
- mo = 1
- dy = 1
- else: # 1202
- mo = _number(val[:-2])
- dy = _number(val[-2:])
- elif v < 32:
- dy = v
- else:
- raise TypeError("four digit year required")
- tm = localtime()
- if mo is None:
- mo = tm[1]
- if dy is None:
- dy = tm[2]
- if yr is None:
- yr = tm[0]
- return date(yr, mo, dy)
-
-def normalize_date(val, iso8601=True):
- if not val:
- return ''
- if type(val) == str:
- val = parse_date(val)
- if iso8601:
- return "%4d-%02d-%02d" % (val.year, val.month, val.day)
- return "%02d %s %4d" % (val.day, _num2str[val.month], val.year)
+# (c) 2005 Clark C. Evans and contributors
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+# Some of this code was funded by: http://prometheusresearch.com
+"""
+Date, Time, and Timespan Parsing Utilities
+
+This module contains parsing support to create "human friendly"
+``datetime`` object parsing. The explicit goal of these routines is
+to provide a multi-format date/time support not unlike that found in
+Microsoft Excel. In most approaches, the input is very "strict" to
+prevent errors -- however, this approach is much more liberal since we
+are assuming the user-interface is parroting back the normalized value
+and thus the user has immediate feedback if the data is not typed in
+correctly.
+
+ ``parse_date`` and ``normalize_date``
+
+ These functions take a value like '9 jan 2007' and returns either an
+ ``date`` object, or an ISO 8601 formatted date value such
+ as '2007-01-09'. There is an option to provide an Oracle database
+ style output as well, ``09 JAN 2007``, but this is not the default.
+
+ This module always treats '/' delimiters as using US date order
+ (since the author's clients are US based), hence '1/9/2007' is
+ January 9th. Since this module treats the '-' as following
+ European order this supports both modes of data-entry; together
+ with immediate parroting back the result to the screen, the author
+ has found this approach to work well in pratice.
+
+ ``parse_time`` and ``normalize_time``
+
+ These functions take a value like '1 pm' and returns either an
+ ``time`` object, or an ISO 8601 formatted 24h clock time
+ such as '13:00'. There is an option to provide for US style time
+ values, '1:00 PM', however this is not the default.
+
+ ``parse_datetime`` and ``normalize_datetime``
+
+ These functions take a value like '9 jan 2007 at 1 pm' and returns
+ either an ``datetime`` object, or an ISO 8601 formatted
+ return (without the T) such as '2007-01-09 13:00'. There is an
+ option to provide for Oracle / US style, '09 JAN 2007 @ 1:00 PM',
+ however this is not the default.
+
+ ``parse_delta`` and ``normalize_delta``
+
+ These functions take a value like '1h 15m' and returns either an
+ ``timedelta`` object, or an 2-decimal fixed-point
+ numerical value in hours, such as '1.25'. The rationale is to
+ support meeting or time-billing lengths, not to be an accurate
+ representation in mili-seconds. As such not all valid
+ ``timedelta`` values will have a normalized representation.
+
+"""
+from datetime import timedelta, time, date
+from time import localtime
+
+__all__ = ['parse_timedelta', 'normalize_timedelta',
+ 'parse_time', 'normalize_time',
+ 'parse_date', 'normalize_date']
+
+def _number(val):
+ try:
+ return int(val)
+ except:
+ return None
+
+#
+# timedelta
+#
+def parse_timedelta(val):
+ """
+ returns a ``timedelta`` object, or None
+ """
+ if not val:
+ return None
+ val = val.lower()
+ if "." in val:
+ val = float(val)
+ return timedelta(hours=int(val), minutes=60*(val % 1.0))
+ fHour = ("h" in val or ":" in val)
+ fMin = ("m" in val or ":" in val)
+ for noise in "minu:teshour()":
+ val = val.replace(noise, ' ')
+ val = val.strip()
+ val = val.split()
+ hr = 0.0
+ mi = 0
+ val.reverse()
+ if fHour:
+ hr = int(val.pop())
+ if fMin:
+ mi = int(val.pop())
+ if len(val) > 0 and not hr:
+ hr = int(val.pop())
+ return timedelta(hours=hr, minutes=mi)
+
+def normalize_timedelta(val):
+ """
+ produces a normalized string value of the timedelta
+
+ This module returns a normalized time span value consisting of the
+ number of hours in fractional form. For example '1h 15min' is
+ formatted as 01.25.
+ """
+ if type(val) == str:
+ val = parse_timedelta(val)
+ if not val:
+ return ''
+ hr = val.seconds/3600
+ mn = (val.seconds % 3600)/60
+ return "%d.%02d" % (hr, mn * 100/60)
+
+#
+# time
+#
+def parse_time(val):
+ if not val:
+ return None
+ hr = mi = 0
+ val = val.lower()
+ amflag = (-1 != val.find('a')) # set if AM is found
+ pmflag = (-1 != val.find('p')) # set if PM is found
+ for noise in ":amp.":
+ val = val.replace(noise, ' ')
+ val = val.split()
+ if len(val) > 1:
+ hr = int(val[0])
+ mi = int(val[1])
+ else:
+ val = val[0]
+ if len(val) < 1:
+ pass
+ elif 'now' == val:
+ tm = localtime()
+ hr = tm[3]
+ mi = tm[4]
+ elif 'noon' == val:
+ hr = 12
+ elif len(val) < 3:
+ hr = int(val)
+ if not amflag and not pmflag and hr < 7:
+ hr += 12
+ elif len(val) < 5:
+ hr = int(val[:-2])
+ mi = int(val[-2:])
+ else:
+ hr = int(val[:1])
+ if amflag and hr >= 12:
+ hr = hr - 12
+ if pmflag and hr < 12:
+ hr = hr + 12
+ return time(hr, mi)
+
+def normalize_time(value, ampm):
+ if not value:
+ return ''
+ if type(value) == str:
+ value = parse_time(value)
+ if not ampm:
+ return "%02d:%02d" % (value.hour, value.minute)
+ hr = value.hour
+ am = "AM"
+ if hr < 1 or hr > 23:
+ hr = 12
+ elif hr >= 12:
+ am = "PM"
+ if hr > 12:
+ hr = hr - 12
+ return "%02d:%02d %s" % (hr, value.minute, am)
+
+#
+# Date Processing
+#
+
+_one_day = timedelta(days=1)
+
+_str2num = {'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6,
+ 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 }
+
+def _month(val):
+ for (key, mon) in _str2num.items():
+ if key in val:
+ return mon
+ raise TypeError("unknown month '%s'" % val)
+
+_days_in_month = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30,
+ 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31,
+ }
+_num2str = {1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
+ 7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec',
+ }
+_wkdy = ("mon", "tue", "wed", "thu", "fri", "sat", "sun")
+
+def parse_date(val):
+ if not(val):
+ return None
+ val = val.lower()
+ now = None
+
+ # optimized check for YYYY-MM-DD
+ strict = val.split("-")
+ if len(strict) == 3:
+ (y, m, d) = strict
+ if "+" in d:
+ d = d.split("+")[0]
+ if " " in d:
+ d = d.split(" ")[0]
+ try:
+ now = date(int(y), int(m), int(d))
+ val = "xxx" + val[10:]
+ except ValueError:
+ pass
+
+ # allow for 'now', 'mon', 'tue', etc.
+ if not now:
+ chk = val[:3]
+ if chk in ('now','tod'):
+ now = date.today()
+ elif chk in _wkdy:
+ now = date.today()
+ idx = list(_wkdy).index(chk) + 1
+ while now.isoweekday() != idx:
+ now += _one_day
+
+ # allow dates to be modified via + or - /w number of days, so
+ # that now+3 is three days from now
+ if now:
+ tail = val[3:].strip()
+ tail = tail.replace("+"," +").replace("-"," -")
+ for item in tail.split():
+ try:
+ days = int(item)
+ except ValueError:
+ pass
+ else:
+ now += timedelta(days=days)
+ return now
+
+ # ok, standard parsing
+ yr = mo = dy = None
+ for noise in ('/', '-', ',', '*'):
+ val = val.replace(noise, ' ')
+ for noise in _wkdy:
+ val = val.replace(noise, ' ')
+ out = []
+ last = False
+ ldig = False
+ for ch in val:
+ if ch.isdigit():
+ if last and not ldig:
+ out.append(' ')
+ last = ldig = True
+ else:
+ if ldig:
+ out.append(' ')
+ ldig = False
+ last = True
+ out.append(ch)
+ val = "".join(out).split()
+ if 3 == len(val):
+ a = _number(val[0])
+ b = _number(val[1])
+ c = _number(val[2])
+ if len(val[0]) == 4:
+ yr = a
+ if b: # 1999 6 23
+ mo = b
+ dy = c
+ else: # 1999 Jun 23
+ mo = _month(val[1])
+ dy = c
+ elif a is not None and a > 0:
+ yr = c
+ if len(val[2]) < 4:
+ raise TypeError("four digit year required")
+ if b: # 6 23 1999
+ dy = b
+ mo = a
+ else: # 23 Jun 1999
+ dy = a
+ mo = _month(val[1])
+ else: # Jun 23, 2000
+ dy = b
+ yr = c
+ if len(val[2]) < 4:
+ raise TypeError("four digit year required")
+ mo = _month(val[0])
+ elif 2 == len(val):
+ a = _number(val[0])
+ b = _number(val[1])
+ if a is not None and a > 999:
+ yr = a
+ dy = 1
+ if b is not None and b > 0: # 1999 6
+ mo = b
+ else: # 1999 Jun
+ mo = _month(val[1])
+ elif a is not None and a > 0:
+ if b is not None and b > 999: # 6 1999
+ mo = a
+ yr = b
+ dy = 1
+ elif b is not None and b > 0: # 6 23
+ mo = a
+ dy = b
+ else: # 23 Jun
+ dy = a
+ mo = _month(val[1])
+ else:
+ if b > 999: # Jun 2001
+ yr = b
+ dy = 1
+ else: # Jun 23
+ dy = b
+ mo = _month(val[0])
+ elif 1 == len(val):
+ val = val[0]
+ if not val.isdigit():
+ mo = _month(val)
+ if mo is not None:
+ dy = 1
+ else:
+ v = _number(val)
+ val = str(v)
+ if 8 == len(val): # 20010623
+ yr = _number(val[:4])
+ mo = _number(val[4:6])
+ dy = _number(val[6:])
+ elif len(val) in (3,4):
+ if v is not None and v > 1300: # 2004
+ yr = v
+ mo = 1
+ dy = 1
+ else: # 1202
+ mo = _number(val[:-2])
+ dy = _number(val[-2:])
+ elif v < 32:
+ dy = v
+ else:
+ raise TypeError("four digit year required")
+ tm = localtime()
+ if mo is None:
+ mo = tm[1]
+ if dy is None:
+ dy = tm[2]
+ if yr is None:
+ yr = tm[0]
+ return date(yr, mo, dy)
+
+def normalize_date(val, iso8601=True):
+ if not val:
+ return ''
+ if type(val) == str:
+ val = parse_date(val)
+ if iso8601:
+ return "%4d-%02d-%02d" % (val.year, val.month, val.day)
+ return "%02d %s %4d" % (val.day, _num2str[val.month], val.year)
diff --git a/paste/util/doctest24.py b/paste/util/doctest24.py
deleted file mode 100644
index 28849ed..0000000
--- a/paste/util/doctest24.py
+++ /dev/null
@@ -1,2665 +0,0 @@
-# Module doctest.
-# Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org).
-# Major enhancements and refactoring by:
-# Jim Fulton
-# Edward Loper
-
-# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
-
-r"""Module doctest -- a framework for running examples in docstrings.
-
-In simplest use, end each module M to be tested with:
-
-def _test():
- import doctest
- doctest.testmod()
-
-if __name__ == "__main__":
- _test()
-
-Then running the module as a script will cause the examples in the
-docstrings to get executed and verified:
-
-python M.py
-
-This won't display anything unless an example fails, in which case the
-failing example(s) and the cause(s) of the failure(s) are printed to stdout
-(why not stderr? because stderr is a lame hack <0.2 wink>), and the final
-line of output is "Test failed.".
-
-Run it with the -v switch instead:
-
-python M.py -v
-
-and a detailed report of all examples tried is printed to stdout, along
-with assorted summaries at the end.
-
-You can force verbose mode by passing "verbose=True" to testmod, or prohibit
-it by passing "verbose=False". In either of those cases, sys.argv is not
-examined by testmod.
-
-There are a variety of other ways to run doctests, including integration
-with the unittest framework, and support for running non-Python text
-files containing doctests. There are also many ways to override parts
-of doctest's default behaviors. See the Library Reference Manual for
-details.
-"""
-
-__docformat__ = 'reStructuredText en'
-
-__all__ = [
- # 0, Option Flags
- 'register_optionflag',
- 'DONT_ACCEPT_TRUE_FOR_1',
- 'DONT_ACCEPT_BLANKLINE',
- 'NORMALIZE_WHITESPACE',
- 'ELLIPSIS',
- 'IGNORE_EXCEPTION_DETAIL',
- 'COMPARISON_FLAGS',
- 'REPORT_UDIFF',
- 'REPORT_CDIFF',
- 'REPORT_NDIFF',
- 'REPORT_ONLY_FIRST_FAILURE',
- 'REPORTING_FLAGS',
- # 1. Utility Functions
- 'is_private',
- # 2. Example & DocTest
- 'Example',
- 'DocTest',
- # 3. Doctest Parser
- 'DocTestParser',
- # 4. Doctest Finder
- 'DocTestFinder',
- # 5. Doctest Runner
- 'DocTestRunner',
- 'OutputChecker',
- 'DocTestFailure',
- 'UnexpectedException',
- 'DebugRunner',
- # 6. Test Functions
- 'testmod',
- 'testfile',
- 'run_docstring_examples',
- # 7. Tester
- 'Tester',
- # 8. Unittest Support
- 'DocTestSuite',
- 'DocFileSuite',
- 'set_unittest_reportflags',
- # 9. Debugging Support
- 'script_from_examples',
- 'testsource',
- 'debug_src',
- 'debug',
-]
-
-import __future__
-
-import sys, traceback, inspect, linecache, os, re, types
-import unittest, difflib, pdb, tempfile
-import warnings
-from StringIO import StringIO
-
-# Don't whine about the deprecated is_private function in this
-# module's tests.
-warnings.filterwarnings("ignore", "is_private", DeprecationWarning,
- __name__, 0)
-
-# There are 4 basic classes:
-# - Example: a <source, want> pair, plus an intra-docstring line number.
-# - DocTest: a collection of examples, parsed from a docstring, plus
-# info about where the docstring came from (name, filename, lineno).
-# - DocTestFinder: extracts DocTests from a given object's docstring and
-# its contained objects' docstrings.
-# - DocTestRunner: runs DocTest cases, and accumulates statistics.
-#
-# So the basic picture is:
-#
-# list of:
-# +------+ +---------+ +-------+
-# |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results|
-# +------+ +---------+ +-------+
-# | Example |
-# | ... |
-# | Example |
-# +---------+
-
-# Option constants.
-
-OPTIONFLAGS_BY_NAME = {}
-def register_optionflag(name):
- flag = 1 << len(OPTIONFLAGS_BY_NAME)
- OPTIONFLAGS_BY_NAME[name] = flag
- return flag
-
-DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1')
-DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE')
-NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
-ELLIPSIS = register_optionflag('ELLIPSIS')
-IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL')
-
-COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 |
- DONT_ACCEPT_BLANKLINE |
- NORMALIZE_WHITESPACE |
- ELLIPSIS |
- IGNORE_EXCEPTION_DETAIL)
-
-REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
-REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
-REPORT_NDIFF = register_optionflag('REPORT_NDIFF')
-REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE')
-
-REPORTING_FLAGS = (REPORT_UDIFF |
- REPORT_CDIFF |
- REPORT_NDIFF |
- REPORT_ONLY_FIRST_FAILURE)
-
-# Special string markers for use in `want` strings:
-BLANKLINE_MARKER = '<BLANKLINE>'
-ELLIPSIS_MARKER = '...'
-
-######################################################################
-## Table of Contents
-######################################################################
-# 1. Utility Functions
-# 2. Example & DocTest -- store test cases
-# 3. DocTest Parser -- extracts examples from strings
-# 4. DocTest Finder -- extracts test cases from objects
-# 5. DocTest Runner -- runs test cases
-# 6. Test Functions -- convenient wrappers for testing
-# 7. Tester Class -- for backwards compatibility
-# 8. Unittest Support
-# 9. Debugging Support
-# 10. Example Usage
-
-######################################################################
-## 1. Utility Functions
-######################################################################
-
-def is_private(prefix, base):
- """prefix, base -> true iff name prefix + "." + base is "private".
-
- Prefix may be an empty string, and base does not contain a period.
- Prefix is ignored (although functions you write conforming to this
- protocol may make use of it).
- Return true iff base begins with an (at least one) underscore, but
- does not both begin and end with (at least) two underscores.
-
- >>> is_private("a.b", "my_func")
- False
- >>> is_private("____", "_my_func")
- True
- >>> is_private("someclass", "__init__")
- False
- >>> is_private("sometypo", "__init_")
- True
- >>> is_private("x.y.z", "_")
- True
- >>> is_private("_x.y.z", "__")
- False
- >>> is_private("", "") # senseless but consistent
- False
- """
- warnings.warn("is_private is deprecated; it wasn't useful; "
- "examine DocTestFinder.find() lists instead",
- DeprecationWarning, stacklevel=2)
- return base[:1] == "_" and not base[:2] == "__" == base[-2:]
-
-def _extract_future_flags(globs):
- """
- Return the compiler-flags associated with the future features that
- have been imported into the given namespace (globs).
- """
- flags = 0
- for fname in __future__.all_feature_names:
- feature = globs.get(fname, None)
- if feature is getattr(__future__, fname):
- flags |= feature.compiler_flag
- return flags
-
-def _normalize_module(module, depth=2):
- """
- Return the module specified by `module`. In particular:
- - If `module` is a module, then return module.
- - If `module` is a string, then import and return the
- module with that name.
- - If `module` is None, then return the calling module.
- The calling module is assumed to be the module of
- the stack frame at the given depth in the call stack.
- """
- if inspect.ismodule(module):
- return module
- elif isinstance(module, (str, unicode)):
- return __import__(module, globals(), locals(), ["*"])
- elif module is None:
- return sys.modules[sys._getframe(depth).f_globals['__name__']]
- else:
- raise TypeError("Expected a module, string, or None")
-
-def _indent(s, indent=4):
- """
- Add the given number of space characters to the beginning every
- non-blank line in `s`, and return the result.
- """
- # This regexp matches the start of non-blank lines:
- return re.sub('(?m)^(?!$)', indent*' ', s)
-
-def _exception_traceback(exc_info):
- """
- Return a string containing a traceback message for the given
- exc_info tuple (as returned by sys.exc_info()).
- """
- # Get a traceback message.
- excout = StringIO()
- exc_type, exc_val, exc_tb = exc_info
- traceback.print_exception(exc_type, exc_val, exc_tb, file=excout)
- return excout.getvalue()
-
-# Override some StringIO methods.
-class _SpoofOut(StringIO):
- def getvalue(self):
- result = StringIO.getvalue(self)
- # If anything at all was written, make sure there's a trailing
- # newline. There's no way for the expected output to indicate
- # that a trailing newline is missing.
- if result and not result.endswith("\n"):
- result += "\n"
- # Prevent softspace from screwing up the next test case, in
- # case they used print with a trailing comma in an example.
- if hasattr(self, "softspace"):
- del self.softspace
- return result
-
- def truncate(self, size=None):
- StringIO.truncate(self, size)
- if hasattr(self, "softspace"):
- del self.softspace
-
-# Worst-case linear-time ellipsis matching.
-def _ellipsis_match(want, got):
- """
- Essentially the only subtle case:
- >>> _ellipsis_match('aa...aa', 'aaa')
- False
- """
- if ELLIPSIS_MARKER not in want:
- return want == got
-
- # Find "the real" strings.
- ws = want.split(ELLIPSIS_MARKER)
- assert len(ws) >= 2
-
- # Deal with exact matches possibly needed at one or both ends.
- startpos, endpos = 0, len(got)
- w = ws[0]
- if w: # starts with exact match
- if got.startswith(w):
- startpos = len(w)
- del ws[0]
- else:
- return False
- w = ws[-1]
- if w: # ends with exact match
- if got.endswith(w):
- endpos -= len(w)
- del ws[-1]
- else:
- return False
-
- if startpos > endpos:
- # Exact end matches required more characters than we have, as in
- # _ellipsis_match('aa...aa', 'aaa')
- return False
-
- # For the rest, we only need to find the leftmost non-overlapping
- # match for each piece. If there's no overall match that way alone,
- # there's no overall match period.
- for w in ws:
- # w may be '' at times, if there are consecutive ellipses, or
- # due to an ellipsis at the start or end of `want`. That's OK.
- # Search for an empty string succeeds, and doesn't change startpos.
- startpos = got.find(w, startpos, endpos)
- if startpos < 0:
- return False
- startpos += len(w)
-
- return True
-
-def _comment_line(line):
- "Return a commented form of the given line"
- line = line.rstrip()
- if line:
- return '# '+line
- else:
- return '#'
-
-class _OutputRedirectingPdb(pdb.Pdb):
- """
- A specialized version of the python debugger that redirects stdout
- to a given stream when interacting with the user. Stdout is *not*
- redirected when traced code is executed.
- """
- def __init__(self, out):
- self.__out = out
- pdb.Pdb.__init__(self)
-
- def trace_dispatch(self, *args):
- # Redirect stdout to the given stream.
- save_stdout = sys.stdout
- sys.stdout = self.__out
- # Call Pdb's trace dispatch method.
- try:
- return pdb.Pdb.trace_dispatch(self, *args)
- finally:
- sys.stdout = save_stdout
-
-# [XX] Normalize with respect to os.path.pardir?
-def _module_relative_path(module, path):
- if not inspect.ismodule(module):
- raise TypeError, 'Expected a module: %r' % module
- if path.startswith('/'):
- raise ValueError, 'Module-relative files may not have absolute paths'
-
- # Find the base directory for the path.
- if hasattr(module, '__file__'):
- # A normal module/package
- basedir = os.path.split(module.__file__)[0]
- elif module.__name__ == '__main__':
- # An interactive session.
- if len(sys.argv)>0 and sys.argv[0] != '':
- basedir = os.path.split(sys.argv[0])[0]
- else:
- basedir = os.curdir
- else:
- # A module w/o __file__ (this includes builtins)
- raise ValueError("Can't resolve paths relative to the module " +
- module + " (it has no __file__)")
-
- # Combine the base directory and the path.
- return os.path.join(basedir, *(path.split('/')))
-
-######################################################################
-## 2. Example & DocTest
-######################################################################
-## - An "example" is a <source, want> pair, where "source" is a
-## fragment of source code, and "want" is the expected output for
-## "source." The Example class also includes information about
-## where the example was extracted from.
-##
-## - A "doctest" is a collection of examples, typically extracted from
-## a string (such as an object's docstring). The DocTest class also
-## includes information about where the string was extracted from.
-
-class Example:
- """
- A single doctest example, consisting of source code and expected
- output. `Example` defines the following attributes:
-
- - source: A single Python statement, always ending with a newline.
- The constructor adds a newline if needed.
-
- - want: The expected output from running the source code (either
- from stdout, or a traceback in case of exception). `want` ends
- with a newline unless it's empty, in which case it's an empty
- string. The constructor adds a newline if needed.
-
- - exc_msg: The exception message generated by the example, if
- the example is expected to generate an exception; or `None` if
- it is not expected to generate an exception. This exception
- message is compared against the return value of
- `traceback.format_exception_only()`. `exc_msg` ends with a
- newline unless it's `None`. The constructor adds a newline
- if needed.
-
- - lineno: The line number within the DocTest string containing
- this Example where the Example begins. This line number is
- zero-based, with respect to the beginning of the DocTest.
-
- - indent: The example's indentation in the DocTest string.
- I.e., the number of space characters that preceed the
- example's first prompt.
-
- - options: A dictionary mapping from option flags to True or
- False, which is used to override default options for this
- example. Any option flags not contained in this dictionary
- are left at their default value (as specified by the
- DocTestRunner's optionflags). By default, no options are set.
- """
- def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
- options=None):
- # Normalize inputs.
- if not source.endswith('\n'):
- source += '\n'
- if want and not want.endswith('\n'):
- want += '\n'
- if exc_msg is not None and not exc_msg.endswith('\n'):
- exc_msg += '\n'
- # Store properties.
- self.source = source
- self.want = want
- self.lineno = lineno
- self.indent = indent
- if options is None: options = {}
- self.options = options
- self.exc_msg = exc_msg
-
-class DocTest:
- """
- A collection of doctest examples that should be run in a single
- namespace. Each `DocTest` defines the following attributes:
-
- - examples: the list of examples.
-
- - globs: The namespace (aka globals) that the examples should
- be run in.
-
- - name: A name identifying the DocTest (typically, the name of
- the object whose docstring this DocTest was extracted from).
-
- - filename: The name of the file that this DocTest was extracted
- from, or `None` if the filename is unknown.
-
- - lineno: The line number within filename where this DocTest
- begins, or `None` if the line number is unavailable. This
- line number is zero-based, with respect to the beginning of
- the file.
-
- - docstring: The string that the examples were extracted from,
- or `None` if the string is unavailable.
- """
- def __init__(self, examples, globs, name, filename, lineno, docstring):
- """
- Create a new DocTest containing the given examples. The
- DocTest's globals are initialized with a copy of `globs`.
- """
- assert not isinstance(examples, basestring), \
- "DocTest no longer accepts str; use DocTestParser instead"
- self.examples = examples
- self.docstring = docstring
- self.globs = globs.copy()
- self.name = name
- self.filename = filename
- self.lineno = lineno
-
- def __repr__(self):
- if len(self.examples) == 0:
- examples = 'no examples'
- elif len(self.examples) == 1:
- examples = '1 example'
- else:
- examples = '%d examples' % len(self.examples)
- return ('<DocTest %s from %s:%s (%s)>' %
- (self.name, self.filename, self.lineno, examples))
-
-
- # This lets us sort tests by name:
- def __cmp__(self, other):
- if not isinstance(other, DocTest):
- return -1
- return cmp((self.name, self.filename, self.lineno, id(self)),
- (other.name, other.filename, other.lineno, id(other)))
-
-######################################################################
-## 3. DocTestParser
-######################################################################
-
-class DocTestParser:
- """
- A class used to parse strings containing doctest examples.
- """
- # This regular expression is used to find doctest examples in a
- # string. It defines three groups: `source` is the source code
- # (including leading indentation and prompts); `indent` is the
- # indentation of the first (PS1) line of the source code; and
- # `want` is the expected output (including leading indentation).
- _EXAMPLE_RE = re.compile(r'''
- # Source consists of a PS1 line followed by zero or more PS2 lines.
- (?P<source>
- (?:^(?P<indent> [ ]*) >>> .*) # PS1 line
- (?:\n [ ]* \.\.\. .*)*) # PS2 lines
- \n?
- # Want consists of any non-blank lines that do not start with PS1.
- (?P<want> (?:(?![ ]*$) # Not a blank line
- (?![ ]*>>>) # Not a line starting with PS1
- .*$\n? # But any other line
- )*)
- ''', re.MULTILINE | re.VERBOSE)
-
- # A regular expression for handling `want` strings that contain
- # expected exceptions. It divides `want` into three pieces:
- # - the traceback header line (`hdr`)
- # - the traceback stack (`stack`)
- # - the exception message (`msg`), as generated by
- # traceback.format_exception_only()
- # `msg` may have multiple lines. We assume/require that the
- # exception message is the first non-indented line starting with a word
- # character following the traceback header line.
- _EXCEPTION_RE = re.compile(r"""
- # Grab the traceback header. Different versions of Python have
- # said different things on the first traceback line.
- ^(?P<hdr> Traceback\ \(
- (?: most\ recent\ call\ last
- | innermost\ last
- ) \) :
- )
- \s* $ # toss trailing whitespace on the header.
- (?P<stack> .*?) # don't blink: absorb stuff until...
- ^ (?P<msg> \w+ .*) # a line *starts* with alphanum.
- """, re.VERBOSE | re.MULTILINE | re.DOTALL)
-
- # A callable returning a true value iff its argument is a blank line
- # or contains a single comment.
- _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match
-
- def parse(self, string, name='<string>'):
- """
- Divide the given string into examples and intervening text,
- and return them as a list of alternating Examples and strings.
- Line numbers for the Examples are 0-based. The optional
- argument `name` is a name identifying this string, and is only
- used for error messages.
- """
- string = string.expandtabs()
- # If all lines begin with the same indentation, then strip it.
- min_indent = self._min_indent(string)
- if min_indent > 0:
- string = '\n'.join([l[min_indent:] for l in string.split('\n')])
-
- output = []
- charno, lineno = 0, 0
- # Find all doctest examples in the string:
- for m in self._EXAMPLE_RE.finditer(string):
- # Add the pre-example text to `output`.
- output.append(string[charno:m.start()])
- # Update lineno (lines before this example)
- lineno += string.count('\n', charno, m.start())
- # Extract info from the regexp match.
- (source, options, want, exc_msg) = \
- self._parse_example(m, name, lineno)
- # Create an Example, and add it to the list.
- if not self._IS_BLANK_OR_COMMENT(source):
- output.append( Example(source, want, exc_msg,
- lineno=lineno,
- indent=min_indent+len(m.group('indent')),
- options=options) )
- # Update lineno (lines inside this example)
- lineno += string.count('\n', m.start(), m.end())
- # Update charno.
- charno = m.end()
- # Add any remaining post-example text to `output`.
- output.append(string[charno:])
- return output
-
- def get_doctest(self, string, globs, name, filename, lineno):
- """
- Extract all doctest examples from the given string, and
- collect them into a `DocTest` object.
-
- `globs`, `name`, `filename`, and `lineno` are attributes for
- the new `DocTest` object. See the documentation for `DocTest`
- for more information.
- """
- return DocTest(self.get_examples(string, name), globs,
- name, filename, lineno, string)
-
- def get_examples(self, string, name='<string>'):
- """
- Extract all doctest examples from the given string, and return
- them as a list of `Example` objects. Line numbers are
- 0-based, because it's most common in doctests that nothing
- interesting appears on the same line as opening triple-quote,
- and so the first interesting line is called \"line 1\" then.
-
- The optional argument `name` is a name identifying this
- string, and is only used for error messages.
- """
- return [x for x in self.parse(string, name)
- if isinstance(x, Example)]
-
- def _parse_example(self, m, name, lineno):
- """
- Given a regular expression match from `_EXAMPLE_RE` (`m`),
- return a pair `(source, want)`, where `source` is the matched
- example's source code (with prompts and indentation stripped);
- and `want` is the example's expected output (with indentation
- stripped).
-
- `name` is the string's name, and `lineno` is the line number
- where the example starts; both are used for error messages.
- """
- # Get the example's indentation level.
- indent = len(m.group('indent'))
-
- # Divide source into lines; check that they're properly
- # indented; and then strip their indentation & prompts.
- source_lines = m.group('source').split('\n')
- self._check_prompt_blank(source_lines, indent, name, lineno)
- self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno)
- source = '\n'.join([sl[indent+4:] for sl in source_lines])
-
- # Divide want into lines; check that it's properly indented; and
- # then strip the indentation. Spaces before the last newline should
- # be preserved, so plain rstrip() isn't good enough.
- want = m.group('want')
- want_lines = want.split('\n')
- if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
- del want_lines[-1] # forget final newline & spaces after it
- self._check_prefix(want_lines, ' '*indent, name,
- lineno + len(source_lines))
- want = '\n'.join([wl[indent:] for wl in want_lines])
-
- # If `want` contains a traceback message, then extract it.
- m = self._EXCEPTION_RE.match(want)
- if m:
- exc_msg = m.group('msg')
- else:
- exc_msg = None
-
- # Extract options from the source.
- options = self._find_options(source, name, lineno)
-
- return source, options, want, exc_msg
-
- # This regular expression looks for option directives in the
- # source code of an example. Option directives are comments
- # starting with "doctest:". Warning: this may give false
- # positives for string-literals that contain the string
- # "#doctest:". Eliminating these false positives would require
- # actually parsing the string; but we limit them by ignoring any
- # line containing "#doctest:" that is *followed* by a quote mark.
- _OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$',
- re.MULTILINE)
-
- def _find_options(self, source, name, lineno):
- """
- Return a dictionary containing option overrides extracted from
- option directives in the given source string.
-
- `name` is the string's name, and `lineno` is the line number
- where the example starts; both are used for error messages.
- """
- options = {}
- # (note: with the current regexp, this will match at most once:)
- for m in self._OPTION_DIRECTIVE_RE.finditer(source):
- option_strings = m.group(1).replace(',', ' ').split()
- for option in option_strings:
- if (option[0] not in '+-' or
- option[1:] not in OPTIONFLAGS_BY_NAME):
- raise ValueError('line %r of the doctest for %s '
- 'has an invalid option: %r' %
- (lineno+1, name, option))
- flag = OPTIONFLAGS_BY_NAME[option[1:]]
- options[flag] = (option[0] == '+')
- if options and self._IS_BLANK_OR_COMMENT(source):
- raise ValueError('line %r of the doctest for %s has an option '
- 'directive on a line with no example: %r' %
- (lineno, name, source))
- return options
-
- # This regular expression finds the indentation of every non-blank
- # line in a string.
- _INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE)
-
- def _min_indent(self, s):
- "Return the minimum indentation of any non-blank line in `s`"
- indents = [len(indent) for indent in self._INDENT_RE.findall(s)]
- if len(indents) > 0:
- return min(indents)
- else:
- return 0
-
- def _check_prompt_blank(self, lines, indent, name, lineno):
- """
- Given the lines of a source string (including prompts and
- leading indentation), check to make sure that every prompt is
- followed by a space character. If any line is not followed by
- a space character, then raise ValueError.
- """
- for i, line in enumerate(lines):
- if len(line) >= indent+4 and line[indent+3] != ' ':
- raise ValueError('line %r of the docstring for %s '
- 'lacks blank after %s: %r' %
- (lineno+i+1, name,
- line[indent:indent+3], line))
-
- def _check_prefix(self, lines, prefix, name, lineno):
- """
- Check that every line in the given list starts with the given
- prefix; if any line does not, then raise a ValueError.
- """
- for i, line in enumerate(lines):
- if line and not line.startswith(prefix):
- raise ValueError('line %r of the docstring for %s has '
- 'inconsistent leading whitespace: %r' %
- (lineno+i+1, name, line))
-
-
-######################################################################
-## 4. DocTest Finder
-######################################################################
-
-class DocTestFinder:
- """
- A class used to extract the DocTests that are relevant to a given
- object, from its docstring and the docstrings of its contained
- objects. Doctests can currently be extracted from the following
- object types: modules, functions, classes, methods, staticmethods,
- classmethods, and properties.
- """
-
- def __init__(self, verbose=False, parser=DocTestParser(),
- recurse=True, _namefilter=None, exclude_empty=True):
- """
- Create a new doctest finder.
-
- The optional argument `parser` specifies a class or
- function that should be used to create new DocTest objects (or
- objects that implement the same interface as DocTest). The
- signature for this factory function should match the signature
- of the DocTest constructor.
-
- If the optional argument `recurse` is false, then `find` will
- only examine the given object, and not any contained objects.
-
- If the optional argument `exclude_empty` is false, then `find`
- will include tests for objects with empty docstrings.
- """
- self._parser = parser
- self._verbose = verbose
- self._recurse = recurse
- self._exclude_empty = exclude_empty
- # _namefilter is undocumented, and exists only for temporary backward-
- # compatibility support of testmod's deprecated isprivate mess.
- self._namefilter = _namefilter
-
- def find(self, obj, name=None, module=None, globs=None,
- extraglobs=None):
- """
- Return a list of the DocTests that are defined by the given
- object's docstring, or by any of its contained objects'
- docstrings.
-
- The optional parameter `module` is the module that contains
- the given object. If the module is not specified or is None, then
- the test finder will attempt to automatically determine the
- correct module. The object's module is used:
-
- - As a default namespace, if `globs` is not specified.
- - To prevent the DocTestFinder from extracting DocTests
- from objects that are imported from other modules.
- - To find the name of the file containing the object.
- - To help find the line number of the object within its
- file.
-
- Contained objects whose module does not match `module` are ignored.
-
- If `module` is False, no attempt to find the module will be made.
- This is obscure, of use mostly in tests: if `module` is False, or
- is None but cannot be found automatically, then all objects are
- considered to belong to the (non-existent) module, so all contained
- objects will (recursively) be searched for doctests.
-
- The globals for each DocTest is formed by combining `globs`
- and `extraglobs` (bindings in `extraglobs` override bindings
- in `globs`). A new copy of the globals dictionary is created
- for each DocTest. If `globs` is not specified, then it
- defaults to the module's `__dict__`, if specified, or {}
- otherwise. If `extraglobs` is not specified, then it defaults
- to {}.
-
- """
- # If name was not specified, then extract it from the object.
- if name is None:
- name = getattr(obj, '__name__', None)
- if name is None:
- raise ValueError("DocTestFinder.find: name must be given "
- "when obj.__name__ doesn't exist: %r" %
- (type(obj),))
-
- # Find the module that contains the given object (if obj is
- # a module, then module=obj.). Note: this may fail, in which
- # case module will be None.
- if module is False:
- module = None
- elif module is None:
- module = inspect.getmodule(obj)
-
- # Read the module's source code. This is used by
- # DocTestFinder._find_lineno to find the line number for a
- # given object's docstring.
- try:
- file = inspect.getsourcefile(obj) or inspect.getfile(obj)
- source_lines = linecache.getlines(file)
- if not source_lines:
- source_lines = None
- except TypeError:
- source_lines = None
-
- # Initialize globals, and merge in extraglobs.
- if globs is None:
- if module is None:
- globs = {}
- else:
- globs = module.__dict__.copy()
- else:
- globs = globs.copy()
- if extraglobs is not None:
- globs.update(extraglobs)
-
- # Recursively expore `obj`, extracting DocTests.
- tests = []
- self._find(tests, obj, name, module, source_lines, globs, {})
- return tests
-
- def _filter(self, obj, prefix, base):
- """
- Return true if the given object should not be examined.
- """
- return (self._namefilter is not None and
- self._namefilter(prefix, base))
-
- def _from_module(self, module, object):
- """
- Return true if the given object is defined in the given
- module.
- """
- if module is None:
- return True
- elif inspect.isfunction(object):
- return module.__dict__ is object.func_globals
- elif inspect.isclass(object):
- return module.__name__ == object.__module__
- elif inspect.getmodule(object) is not None:
- return module is inspect.getmodule(object)
- elif hasattr(object, '__module__'):
- return module.__name__ == object.__module__
- elif isinstance(object, property):
- return True # [XX] no way not be sure.
- else:
- raise ValueError("object must be a class or function")
-
- def _find(self, tests, obj, name, module, source_lines, globs, seen):
- """
- Find tests for the given object and any contained objects, and
- add them to `tests`.
- """
- if self._verbose:
- print 'Finding tests in %s' % name
-
- # If we've already processed this object, then ignore it.
- if id(obj) in seen:
- return
- seen[id(obj)] = 1
-
- # Find a test for this object, and add it to the list of tests.
- test = self._get_test(obj, name, module, globs, source_lines)
- if test is not None:
- tests.append(test)
-
- # Look for tests in a module's contained objects.
- if inspect.ismodule(obj) and self._recurse:
- for valname, val in obj.__dict__.items():
- # Check if this contained object should be ignored.
- if self._filter(val, name, valname):
- continue
- valname = '%s.%s' % (name, valname)
- # Recurse to functions & classes.
- if ((inspect.isfunction(val) or inspect.isclass(val)) and
- self._from_module(module, val)):
- self._find(tests, val, valname, module, source_lines,
- globs, seen)
-
- # Look for tests in a module's __test__ dictionary.
- if inspect.ismodule(obj) and self._recurse:
- for valname, val in getattr(obj, '__test__', {}).items():
- if not isinstance(valname, basestring):
- raise ValueError("DocTestFinder.find: __test__ keys "
- "must be strings: %r" %
- (type(valname),))
- if not (inspect.isfunction(val) or inspect.isclass(val) or
- inspect.ismethod(val) or inspect.ismodule(val) or
- isinstance(val, basestring)):
- raise ValueError("DocTestFinder.find: __test__ values "
- "must be strings, functions, methods, "
- "classes, or modules: %r" %
- (type(val),))
- valname = '%s.__test__.%s' % (name, valname)
- self._find(tests, val, valname, module, source_lines,
- globs, seen)
-
- # Look for tests in a class's contained objects.
- if inspect.isclass(obj) and self._recurse:
- for valname, val in obj.__dict__.items():
- # Check if this contained object should be ignored.
- if self._filter(val, name, valname):
- continue
- # Special handling for staticmethod/classmethod.
- if isinstance(val, staticmethod):
- val = getattr(obj, valname)
- if isinstance(val, classmethod):
- val = getattr(obj, valname).im_func
-
- # Recurse to methods, properties, and nested classes.
- if ((inspect.isfunction(val) or inspect.isclass(val) or
- isinstance(val, property)) and
- self._from_module(module, val)):
- valname = '%s.%s' % (name, valname)
- self._find(tests, val, valname, module, source_lines,
- globs, seen)
-
- def _get_test(self, obj, name, module, globs, source_lines):
- """
- Return a DocTest for the given object, if it defines a docstring;
- otherwise, return None.
- """
- # Extract the object's docstring. If it doesn't have one,
- # then return None (no test for this object).
- if isinstance(obj, basestring):
- docstring = obj
- else:
- try:
- if obj.__doc__ is None:
- docstring = ''
- else:
- docstring = obj.__doc__
- if not isinstance(docstring, basestring):
- docstring = str(docstring)
- except (TypeError, AttributeError):
- docstring = ''
-
- # Find the docstring's location in the file.
- lineno = self._find_lineno(obj, source_lines)
-
- # Don't bother if the docstring is empty.
- if self._exclude_empty and not docstring:
- return None
-
- # Return a DocTest for this object.
- if module is None:
- filename = None
- else:
- filename = getattr(module, '__file__', module.__name__)
- if filename[-4:] in (".pyc", ".pyo"):
- filename = filename[:-1]
- return self._parser.get_doctest(docstring, globs, name,
- filename, lineno)
-
- def _find_lineno(self, obj, source_lines):
- """
- Return a line number of the given object's docstring. Note:
- this method assumes that the object has a docstring.
- """
- lineno = None
-
- # Find the line number for modules.
- if inspect.ismodule(obj):
- lineno = 0
-
- # Find the line number for classes.
- # Note: this could be fooled if a class is defined multiple
- # times in a single file.
- if inspect.isclass(obj):
- if source_lines is None:
- return None
- pat = re.compile(r'^\s*class\s*%s\b' %
- getattr(obj, '__name__', '-'))
- for i, line in enumerate(source_lines):
- if pat.match(line):
- lineno = i
- break
-
- # Find the line number for functions & methods.
- if inspect.ismethod(obj): obj = obj.im_func
- if inspect.isfunction(obj): obj = obj.func_code
- if inspect.istraceback(obj): obj = obj.tb_frame
- if inspect.isframe(obj): obj = obj.f_code
- if inspect.iscode(obj):
- lineno = getattr(obj, 'co_firstlineno', None)-1
-
- # Find the line number where the docstring starts. Assume
- # that it's the first line that begins with a quote mark.
- # Note: this could be fooled by a multiline function
- # signature, where a continuation line begins with a quote
- # mark.
- if lineno is not None:
- if source_lines is None:
- return lineno+1
- pat = re.compile('(^|.*:)\s*\w*("|\')')
- for lineno in range(lineno, len(source_lines)):
- if pat.match(source_lines[lineno]):
- return lineno
-
- # We couldn't find the line number.
- return None
-
-######################################################################
-## 5. DocTest Runner
-######################################################################
-
-class DocTestRunner:
- """
- A class used to run DocTest test cases, and accumulate statistics.
- The `run` method is used to process a single DocTest case. It
- returns a tuple `(f, t)`, where `t` is the number of test cases
- tried, and `f` is the number of test cases that failed.
-
- >>> tests = DocTestFinder().find(_TestClass)
- >>> runner = DocTestRunner(verbose=False)
- >>> for test in tests:
- ... print runner.run(test)
- (0, 2)
- (0, 1)
- (0, 2)
- (0, 2)
-
- The `summarize` method prints a summary of all the test cases that
- have been run by the runner, and returns an aggregated `(f, t)`
- tuple:
-
- >>> runner.summarize(verbose=1)
- 4 items passed all tests:
- 2 tests in _TestClass
- 2 tests in _TestClass.__init__
- 2 tests in _TestClass.get
- 1 tests in _TestClass.square
- 7 tests in 4 items.
- 7 passed and 0 failed.
- Test passed.
- (0, 7)
-
- The aggregated number of tried examples and failed examples is
- also available via the `tries` and `failures` attributes:
-
- >>> runner.tries
- 7
- >>> runner.failures
- 0
-
- The comparison between expected outputs and actual outputs is done
- by an `OutputChecker`. This comparison may be customized with a
- number of option flags; see the documentation for `testmod` for
- more information. If the option flags are insufficient, then the
- comparison may also be customized by passing a subclass of
- `OutputChecker` to the constructor.
-
- The test runner's display output can be controlled in two ways.
- First, an output function (`out) can be passed to
- `TestRunner.run`; this function will be called with strings that
- should be displayed. It defaults to `sys.stdout.write`. If
- capturing the output is not sufficient, then the display output
- can be also customized by subclassing DocTestRunner, and
- overriding the methods `report_start`, `report_success`,
- `report_unexpected_exception`, and `report_failure`.
- """
- # This divider string is used to separate failure messages, and to
- # separate sections of the summary.
- DIVIDER = "*" * 70
-
- def __init__(self, checker=None, verbose=None, optionflags=0):
- """
- Create a new test runner.
-
- Optional keyword arg `checker` is the `OutputChecker` that
- should be used to compare the expected outputs and actual
- outputs of doctest examples.
-
- Optional keyword arg 'verbose' prints lots of stuff if true,
- only failures if false; by default, it's true iff '-v' is in
- sys.argv.
-
- Optional argument `optionflags` can be used to control how the
- test runner compares expected output to actual output, and how
- it displays failures. See the documentation for `testmod` for
- more information.
- """
- self._checker = checker or OutputChecker()
- if verbose is None:
- verbose = '-v' in sys.argv
- self._verbose = verbose
- self.optionflags = optionflags
- self.original_optionflags = optionflags
-
- # Keep track of the examples we've run.
- self.tries = 0
- self.failures = 0
- self._name2ft = {}
-
- # Create a fake output target for capturing doctest output.
- self._fakeout = _SpoofOut()
-
- #/////////////////////////////////////////////////////////////////
- # Reporting methods
- #/////////////////////////////////////////////////////////////////
-
- def report_start(self, out, test, example):
- """
- Report that the test runner is about to process the given
- example. (Only displays a message if verbose=True)
- """
- if self._verbose:
- if example.want:
- out('Trying:\n' + _indent(example.source) +
- 'Expecting:\n' + _indent(example.want))
- else:
- out('Trying:\n' + _indent(example.source) +
- 'Expecting nothing\n')
-
- def report_success(self, out, test, example, got):
- """
- Report that the given example ran successfully. (Only
- displays a message if verbose=True)
- """
- if self._verbose:
- out("ok\n")
-
- def report_failure(self, out, test, example, got):
- """
- Report that the given example failed.
- """
- out(self._failure_header(test, example) +
- self._checker.output_difference(example, got, self.optionflags))
-
- def report_unexpected_exception(self, out, test, example, exc_info):
- """
- Report that the given example raised an unexpected exception.
- """
- out(self._failure_header(test, example) +
- 'Exception raised:\n' + _indent(_exception_traceback(exc_info)))
-
- def _failure_header(self, test, example):
- out = [self.DIVIDER]
- if test.filename:
- if test.lineno is not None and example.lineno is not None:
- lineno = test.lineno + example.lineno + 1
- else:
- lineno = '?'
- out.append('File "%s", line %s, in %s' %
- (test.filename, lineno, test.name))
- else:
- out.append('Line %s, in %s' % (example.lineno+1, test.name))
- out.append('Failed example:')
- source = example.source
- out.append(_indent(source))
- return '\n'.join(out)
-
- #/////////////////////////////////////////////////////////////////
- # DocTest Running
- #/////////////////////////////////////////////////////////////////
-
- def __run(self, test, compileflags, out):
- """
- Run the examples in `test`. Write the outcome of each example
- with one of the `DocTestRunner.report_*` methods, using the
- writer function `out`. `compileflags` is the set of compiler
- flags that should be used to execute examples. Return a tuple
- `(f, t)`, where `t` is the number of examples tried, and `f`
- is the number of examples that failed. The examples are run
- in the namespace `test.globs`.
- """
- # Keep track of the number of failures and tries.
- failures = tries = 0
-
- # Save the option flags (since option directives can be used
- # to modify them).
- original_optionflags = self.optionflags
-
- SUCCESS, FAILURE, BOOM = range(3) # `outcome` state
-
- check = self._checker.check_output
-
- # Process each example.
- for examplenum, example in enumerate(test.examples):
-
- # If REPORT_ONLY_FIRST_FAILURE is set, then supress
- # reporting after the first failure.
- quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and
- failures > 0)
-
- # Merge in the example's options.
- self.optionflags = original_optionflags
- if example.options:
- for (optionflag, val) in example.options.items():
- if val:
- self.optionflags |= optionflag
- else:
- self.optionflags &= ~optionflag
-
- # Record that we started this example.
- tries += 1
- if not quiet:
- self.report_start(out, test, example)
-
- # Use a special filename for compile(), so we can retrieve
- # the source code during interactive debugging (see
- # __patched_linecache_getlines).
- filename = '<doctest %s[%d]>' % (test.name, examplenum)
-
- # Run the example in the given context (globs), and record
- # any exception that gets raised. (But don't intercept
- # keyboard interrupts.)
- try:
- # Don't blink! This is where the user's code gets run.
- exec compile(example.source, filename, "single",
- compileflags, 1) in test.globs
- self.debugger.set_continue() # ==== Example Finished ====
- exception = None
- except KeyboardInterrupt:
- raise
- except:
- exception = sys.exc_info()
- self.debugger.set_continue() # ==== Example Finished ====
-
- got = self._fakeout.getvalue() # the actual output
- self._fakeout.truncate(0)
- outcome = FAILURE # guilty until proved innocent or insane
-
- # If the example executed without raising any exceptions,
- # verify its output.
- if exception is None:
- if check(example.want, got, self.optionflags):
- outcome = SUCCESS
-
- # The example raised an exception: check if it was expected.
- else:
- exc_info = sys.exc_info()
- exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
- if not quiet:
- got += _exception_traceback(exc_info)
-
- # If `example.exc_msg` is None, then we weren't expecting
- # an exception.
- if example.exc_msg is None:
- outcome = BOOM
-
- # We expected an exception: see whether it matches.
- elif check(example.exc_msg, exc_msg, self.optionflags):
- outcome = SUCCESS
-
- # Another chance if they didn't care about the detail.
- elif self.optionflags & IGNORE_EXCEPTION_DETAIL:
- m1 = re.match(r'[^:]*:', example.exc_msg)
- m2 = re.match(r'[^:]*:', exc_msg)
- if m1 and m2 and check(m1.group(0), m2.group(0),
- self.optionflags):
- outcome = SUCCESS
-
- # Report the outcome.
- if outcome is SUCCESS:
- if not quiet:
- self.report_success(out, test, example, got)
- elif outcome is FAILURE:
- if not quiet:
- self.report_failure(out, test, example, got)
- failures += 1
- elif outcome is BOOM:
- if not quiet:
- self.report_unexpected_exception(out, test, example,
- exc_info)
- failures += 1
- else:
- assert False, ("unknown outcome", outcome)
-
- # Restore the option flags (in case they were modified)
- self.optionflags = original_optionflags
-
- # Record and return the number of failures and tries.
- self.__record_outcome(test, failures, tries)
- return failures, tries
-
- def __record_outcome(self, test, f, t):
- """
- Record the fact that the given DocTest (`test`) generated `f`
- failures out of `t` tried examples.
- """
- f2, t2 = self._name2ft.get(test.name, (0,0))
- self._name2ft[test.name] = (f+f2, t+t2)
- self.failures += f
- self.tries += t
-
- __LINECACHE_FILENAME_RE = re.compile(r'<doctest '
- r'(?P<name>[\w\.]+)'
- r'\[(?P<examplenum>\d+)\]>$')
- def __patched_linecache_getlines(self, filename, module_globals=None):
- m = self.__LINECACHE_FILENAME_RE.match(filename)
- if m and m.group('name') == self.test.name:
- example = self.test.examples[int(m.group('examplenum'))]
- return example.source.splitlines(True)
- else:
- return self.save_linecache_getlines(filename)#?, module_globals)
-
- def run(self, test, compileflags=None, out=None, clear_globs=True):
- """
- Run the examples in `test`, and display the results using the
- writer function `out`.
-
- The examples are run in the namespace `test.globs`. If
- `clear_globs` is true (the default), then this namespace will
- be cleared after the test runs, to help with garbage
- collection. If you would like to examine the namespace after
- the test completes, then use `clear_globs=False`.
-
- `compileflags` gives the set of flags that should be used by
- the Python compiler when running the examples. If not
- specified, then it will default to the set of future-import
- flags that apply to `globs`.
-
- The output of each example is checked using
- `DocTestRunner.check_output`, and the results are formatted by
- the `DocTestRunner.report_*` methods.
- """
- self.test = test
-
- if compileflags is None:
- compileflags = _extract_future_flags(test.globs)
-
- save_stdout = sys.stdout
- if out is None:
- out = save_stdout.write
- sys.stdout = self._fakeout
-
- # Patch pdb.set_trace to restore sys.stdout during interactive
- # debugging (so it's not still redirected to self._fakeout).
- # Note that the interactive output will go to *our*
- # save_stdout, even if that's not the real sys.stdout; this
- # allows us to write test cases for the set_trace behavior.
- save_set_trace = pdb.set_trace
- self.debugger = _OutputRedirectingPdb(save_stdout)
- self.debugger.reset()
- pdb.set_trace = self.debugger.set_trace
-
- # Patch linecache.getlines, so we can see the example's source
- # when we're inside the debugger.
- self.save_linecache_getlines = linecache.getlines
- linecache.getlines = self.__patched_linecache_getlines
-
- try:
- return self.__run(test, compileflags, out)
- finally:
- sys.stdout = save_stdout
- pdb.set_trace = save_set_trace
- linecache.getlines = self.save_linecache_getlines
- if clear_globs:
- test.globs.clear()
-
- #/////////////////////////////////////////////////////////////////
- # Summarization
- #/////////////////////////////////////////////////////////////////
- def summarize(self, verbose=None):
- """
- Print a summary of all the test cases that have been run by
- this DocTestRunner, and return a tuple `(f, t)`, where `f` is
- the total number of failed examples, and `t` is the total
- number of tried examples.
-
- The optional `verbose` argument controls how detailed the
- summary is. If the verbosity is not specified, then the
- DocTestRunner's verbosity is used.
- """
- if verbose is None:
- verbose = self._verbose
- notests = []
- passed = []
- failed = []
- totalt = totalf = 0
- for x in self._name2ft.items():
- name, (f, t) = x
- assert f <= t
- totalt += t
- totalf += f
- if t == 0:
- notests.append(name)
- elif f == 0:
- passed.append( (name, t) )
- else:
- failed.append(x)
- if verbose:
- if notests:
- print len(notests), "items had no tests:"
- notests.sort()
- for thing in notests:
- print " ", thing
- if passed:
- print len(passed), "items passed all tests:"
- passed.sort()
- for thing, count in passed:
- print " %3d tests in %s" % (count, thing)
- if failed:
- print self.DIVIDER
- print len(failed), "items had failures:"
- failed.sort()
- for thing, (f, t) in failed:
- print " %3d of %3d in %s" % (f, t, thing)
- if verbose:
- print totalt, "tests in", len(self._name2ft), "items."
- print totalt - totalf, "passed and", totalf, "failed."
- if totalf:
- print "***Test Failed***", totalf, "failures."
- elif verbose:
- print "Test passed."
- return totalf, totalt
-
- #/////////////////////////////////////////////////////////////////
- # Backward compatibility cruft to maintain doctest.master.
- #/////////////////////////////////////////////////////////////////
- def merge(self, other):
- d = self._name2ft
- for name, (f, t) in other._name2ft.items():
- if name in d:
- print "*** DocTestRunner.merge: '" + name + "' in both" \
- " testers; summing outcomes."
- f2, t2 = d[name]
- f = f + f2
- t = t + t2
- d[name] = f, t
-
-class OutputChecker:
- """
- A class used to check the whether the actual output from a doctest
- example matches the expected output. `OutputChecker` defines two
- methods: `check_output`, which compares a given pair of outputs,
- and returns true if they match; and `output_difference`, which
- returns a string describing the differences between two outputs.
- """
- def check_output(self, want, got, optionflags):
- """
- Return True iff the actual output from an example (`got`)
- matches the expected output (`want`). These strings are
- always considered to match if they are identical; but
- depending on what option flags the test runner is using,
- several non-exact match types are also possible. See the
- documentation for `TestRunner` for more information about
- option flags.
- """
- # Handle the common case first, for efficiency:
- # if they're string-identical, always return true.
- if got == want:
- return True
-
- # The values True and False replaced 1 and 0 as the return
- # value for boolean comparisons in Python 2.3.
- if not (optionflags & DONT_ACCEPT_TRUE_FOR_1):
- if (got,want) == ("True\n", "1\n"):
- return True
- if (got,want) == ("False\n", "0\n"):
- return True
-
- # <BLANKLINE> can be used as a special sequence to signify a
- # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used.
- if not (optionflags & DONT_ACCEPT_BLANKLINE):
- # Replace <BLANKLINE> in want with a blank line.
- want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER),
- '', want)
- # If a line in got contains only spaces, then remove the
- # spaces.
- got = re.sub('(?m)^\s*?$', '', got)
- if got == want:
- return True
-
- # This flag causes doctest to ignore any differences in the
- # contents of whitespace strings. Note that this can be used
- # in conjunction with the ELLIPSIS flag.
- if optionflags & NORMALIZE_WHITESPACE:
- got = ' '.join(got.split())
- want = ' '.join(want.split())
- if got == want:
- return True
-
- # The ELLIPSIS flag says to let the sequence "..." in `want`
- # match any substring in `got`.
- if optionflags & ELLIPSIS:
- if _ellipsis_match(want, got):
- return True
-
- # We didn't find any match; return false.
- return False
-
- # Should we do a fancy diff?
- def _do_a_fancy_diff(self, want, got, optionflags):
- # Not unless they asked for a fancy diff.
- if not optionflags & (REPORT_UDIFF |
- REPORT_CDIFF |
- REPORT_NDIFF):
- return False
-
- # If expected output uses ellipsis, a meaningful fancy diff is
- # too hard ... or maybe not. In two real-life failures Tim saw,
- # a diff was a major help anyway, so this is commented out.
- # [todo] _ellipsis_match() knows which pieces do and don't match,
- # and could be the basis for a kick-ass diff in this case.
- ##if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want:
- ## return False
-
- # ndiff does intraline difference marking, so can be useful even
- # for 1-line differences.
- if optionflags & REPORT_NDIFF:
- return True
-
- # The other diff types need at least a few lines to be helpful.
- return want.count('\n') > 2 and got.count('\n') > 2
-
- def output_difference(self, example, got, optionflags):
- """
- Return a string describing the differences between the
- expected output for a given example (`example`) and the actual
- output (`got`). `optionflags` is the set of option flags used
- to compare `want` and `got`.
- """
- want = example.want
- # If <BLANKLINE>s are being used, then replace blank lines
- # with <BLANKLINE> in the actual output string.
- if not (optionflags & DONT_ACCEPT_BLANKLINE):
- got = re.sub('(?m)^[ ]*(?=\n)', BLANKLINE_MARKER, got)
-
- # Check if we should use diff.
- if self._do_a_fancy_diff(want, got, optionflags):
- # Split want & got into lines.
- want_lines = want.splitlines(True) # True == keep line ends
- got_lines = got.splitlines(True)
- # Use difflib to find their differences.
- if optionflags & REPORT_UDIFF:
- diff = difflib.unified_diff(want_lines, got_lines, n=2)
- diff = list(diff)[2:] # strip the diff header
- kind = 'unified diff with -expected +actual'
- elif optionflags & REPORT_CDIFF:
- diff = difflib.context_diff(want_lines, got_lines, n=2)
- diff = list(diff)[2:] # strip the diff header
- kind = 'context diff with expected followed by actual'
- elif optionflags & REPORT_NDIFF:
- engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK)
- diff = list(engine.compare(want_lines, got_lines))
- kind = 'ndiff with -expected +actual'
- else:
- assert 0, 'Bad diff option'
- # Remove trailing whitespace on diff output.
- diff = [line.rstrip() + '\n' for line in diff]
- return 'Differences (%s):\n' % kind + _indent(''.join(diff))
-
- # If we're not using diff, then simply list the expected
- # output followed by the actual output.
- if want and got:
- return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got))
- elif want:
- return 'Expected:\n%sGot nothing\n' % _indent(want)
- elif got:
- return 'Expected nothing\nGot:\n%s' % _indent(got)
- else:
- return 'Expected nothing\nGot nothing\n'
-
-class DocTestFailure(Exception):
- """A DocTest example has failed in debugging mode.
-
- The exception instance has variables:
-
- - test: the DocTest object being run
-
- - excample: the Example object that failed
-
- - got: the actual output
- """
- def __init__(self, test, example, got):
- self.test = test
- self.example = example
- self.got = got
-
- def __str__(self):
- return str(self.test)
-
-class UnexpectedException(Exception):
- """A DocTest example has encountered an unexpected exception
-
- The exception instance has variables:
-
- - test: the DocTest object being run
-
- - excample: the Example object that failed
-
- - exc_info: the exception info
- """
- def __init__(self, test, example, exc_info):
- self.test = test
- self.example = example
- self.exc_info = exc_info
-
- def __str__(self):
- return str(self.test)
-
-class DebugRunner(DocTestRunner):
- r"""Run doc tests but raise an exception as soon as there is a failure.
-
- If an unexpected exception occurs, an UnexpectedException is raised.
- It contains the test, the example, and the original exception:
-
- >>> runner = DebugRunner(verbose=False)
- >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
- ... {}, 'foo', 'foo.py', 0)
- >>> try:
- ... runner.run(test)
- ... except UnexpectedException, failure:
- ... pass
-
- >>> failure.test is test
- True
-
- >>> failure.example.want
- '42\n'
-
- >>> exc_info = failure.exc_info
- >>> raise exc_info[0], exc_info[1], exc_info[2]
- Traceback (most recent call last):
- ...
- KeyError
-
- We wrap the original exception to give the calling application
- access to the test and example information.
-
- If the output doesn't match, then a DocTestFailure is raised:
-
- >>> test = DocTestParser().get_doctest('''
- ... >>> x = 1
- ... >>> x
- ... 2
- ... ''', {}, 'foo', 'foo.py', 0)
-
- >>> try:
- ... runner.run(test)
- ... except DocTestFailure, failure:
- ... pass
-
- DocTestFailure objects provide access to the test:
-
- >>> failure.test is test
- True
-
- As well as to the example:
-
- >>> failure.example.want
- '2\n'
-
- and the actual output:
-
- >>> failure.got
- '1\n'
-
- If a failure or error occurs, the globals are left intact:
-
- >>> del test.globs['__builtins__']
- >>> test.globs
- {'x': 1}
-
- >>> test = DocTestParser().get_doctest('''
- ... >>> x = 2
- ... >>> raise KeyError
- ... ''', {}, 'foo', 'foo.py', 0)
-
- >>> runner.run(test)
- Traceback (most recent call last):
- ...
- UnexpectedException: <DocTest foo from foo.py:0 (2 examples)>
-
- >>> del test.globs['__builtins__']
- >>> test.globs
- {'x': 2}
-
- But the globals are cleared if there is no error:
-
- >>> test = DocTestParser().get_doctest('''
- ... >>> x = 2
- ... ''', {}, 'foo', 'foo.py', 0)
-
- >>> runner.run(test)
- (0, 1)
-
- >>> test.globs
- {}
-
- """
-
- def run(self, test, compileflags=None, out=None, clear_globs=True):
- r = DocTestRunner.run(self, test, compileflags, out, False)
- if clear_globs:
- test.globs.clear()
- return r
-
- def report_unexpected_exception(self, out, test, example, exc_info):
- raise UnexpectedException(test, example, exc_info)
-
- def report_failure(self, out, test, example, got):
- raise DocTestFailure(test, example, got)
-
-######################################################################
-## 6. Test Functions
-######################################################################
-# These should be backwards compatible.
-
-# For backward compatibility, a global instance of a DocTestRunner
-# class, updated by testmod.
-master = None
-
-def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
- report=True, optionflags=0, extraglobs=None,
- raise_on_error=False, exclude_empty=False):
- """m=None, name=None, globs=None, verbose=None, isprivate=None,
- report=True, optionflags=0, extraglobs=None, raise_on_error=False,
- exclude_empty=False
-
- Test examples in docstrings in functions and classes reachable
- from module m (or the current module if m is not supplied), starting
- with m.__doc__. Unless isprivate is specified, private names
- are not skipped.
-
- Also test examples reachable from dict m.__test__ if it exists and is
- not None. m.__test__ maps names to functions, classes and strings;
- function and class docstrings are tested even if the name is private;
- strings are tested directly, as if they were docstrings.
-
- Return (#failures, #tests).
-
- See doctest.__doc__ for an overview.
-
- Optional keyword arg "name" gives the name of the module; by default
- use m.__name__.
-
- Optional keyword arg "globs" gives a dict to be used as the globals
- when executing examples; by default, use m.__dict__. A copy of this
- dict is actually used for each docstring, so that each docstring's
- examples start with a clean slate.
-
- Optional keyword arg "extraglobs" gives a dictionary that should be
- merged into the globals that are used to execute examples. By
- default, no extra globals are used. This is new in 2.4.
-
- Optional keyword arg "verbose" prints lots of stuff if true, prints
- only failures if false; by default, it's true iff "-v" is in sys.argv.
-
- Optional keyword arg "report" prints a summary at the end when true,
- else prints nothing at the end. In verbose mode, the summary is
- detailed, else very brief (in fact, empty if all tests passed).
-
- Optional keyword arg "optionflags" or's together module constants,
- and defaults to 0. This is new in 2.3. Possible values (see the
- docs for details):
-
- DONT_ACCEPT_TRUE_FOR_1
- DONT_ACCEPT_BLANKLINE
- NORMALIZE_WHITESPACE
- ELLIPSIS
- IGNORE_EXCEPTION_DETAIL
- REPORT_UDIFF
- REPORT_CDIFF
- REPORT_NDIFF
- REPORT_ONLY_FIRST_FAILURE
-
- Optional keyword arg "raise_on_error" raises an exception on the
- first unexpected exception or failure. This allows failures to be
- post-mortem debugged.
-
- Deprecated in Python 2.4:
- Optional keyword arg "isprivate" specifies a function used to
- determine whether a name is private. The default function is
- treat all functions as public. Optionally, "isprivate" can be
- set to doctest.is_private to skip over functions marked as private
- using the underscore naming convention; see its docs for details.
-
- Advanced tomfoolery: testmod runs methods of a local instance of
- class doctest.Tester, then merges the results into (or creates)
- global Tester instance doctest.master. Methods of doctest.master
- can be called directly too, if you want to do something unusual.
- Passing report=0 to testmod is especially useful then, to delay
- displaying a summary. Invoke doctest.master.summarize(verbose)
- when you're done fiddling.
- """
- global master
-
- if isprivate is not None:
- warnings.warn("the isprivate argument is deprecated; "
- "examine DocTestFinder.find() lists instead",
- DeprecationWarning)
-
- # If no module was given, then use __main__.
- if m is None:
- # DWA - m will still be None if this wasn't invoked from the command
- # line, in which case the following TypeError is about as good an error
- # as we should expect
- m = sys.modules.get('__main__')
-
- # Check that we were actually given a module.
- if not inspect.ismodule(m):
- raise TypeError("testmod: module required; %r" % (m,))
-
- # If no name was given, then use the module's name.
- if name is None:
- name = m.__name__
-
- # Find, parse, and run all tests in the given module.
- finder = DocTestFinder(_namefilter=isprivate, exclude_empty=exclude_empty)
-
- if raise_on_error:
- runner = DebugRunner(verbose=verbose, optionflags=optionflags)
- else:
- runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
-
- for test in finder.find(m, name, globs=globs, extraglobs=extraglobs):
- runner.run(test)
-
- if report:
- runner.summarize()
-
- if master is None:
- master = runner
- else:
- master.merge(runner)
-
- return runner.failures, runner.tries
-
-def testfile(filename, module_relative=True, name=None, package=None,
- globs=None, verbose=None, report=True, optionflags=0,
- extraglobs=None, raise_on_error=False, parser=DocTestParser()):
- """
- Test examples in the given file. Return (#failures, #tests).
-
- Optional keyword arg "module_relative" specifies how filenames
- should be interpreted:
-
- - If "module_relative" is True (the default), then "filename"
- specifies a module-relative path. By default, this path is
- relative to the calling module's directory; but if the
- "package" argument is specified, then it is relative to that
- package. To ensure os-independence, "filename" should use
- "/" characters to separate path segments, and should not
- be an absolute path (i.e., it may not begin with "/").
-
- - If "module_relative" is False, then "filename" specifies an
- os-specific path. The path may be absolute or relative (to
- the current working directory).
-
- Optional keyword arg "name" gives the name of the test; by default
- use the file's basename.
-
- Optional keyword argument "package" is a Python package or the
- name of a Python package whose directory should be used as the
- base directory for a module relative filename. If no package is
- specified, then the calling module's directory is used as the base
- directory for module relative filenames. It is an error to
- specify "package" if "module_relative" is False.
-
- Optional keyword arg "globs" gives a dict to be used as the globals
- when executing examples; by default, use {}. A copy of this dict
- is actually used for each docstring, so that each docstring's
- examples start with a clean slate.
-
- Optional keyword arg "extraglobs" gives a dictionary that should be
- merged into the globals that are used to execute examples. By
- default, no extra globals are used.
-
- Optional keyword arg "verbose" prints lots of stuff if true, prints
- only failures if false; by default, it's true iff "-v" is in sys.argv.
-
- Optional keyword arg "report" prints a summary at the end when true,
- else prints nothing at the end. In verbose mode, the summary is
- detailed, else very brief (in fact, empty if all tests passed).
-
- Optional keyword arg "optionflags" or's together module constants,
- and defaults to 0. Possible values (see the docs for details):
-
- DONT_ACCEPT_TRUE_FOR_1
- DONT_ACCEPT_BLANKLINE
- NORMALIZE_WHITESPACE
- ELLIPSIS
- IGNORE_EXCEPTION_DETAIL
- REPORT_UDIFF
- REPORT_CDIFF
- REPORT_NDIFF
- REPORT_ONLY_FIRST_FAILURE
-
- Optional keyword arg "raise_on_error" raises an exception on the
- first unexpected exception or failure. This allows failures to be
- post-mortem debugged.
-
- Optional keyword arg "parser" specifies a DocTestParser (or
- subclass) that should be used to extract tests from the files.
-
- Advanced tomfoolery: testmod runs methods of a local instance of
- class doctest.Tester, then merges the results into (or creates)
- global Tester instance doctest.master. Methods of doctest.master
- can be called directly too, if you want to do something unusual.
- Passing report=0 to testmod is especially useful then, to delay
- displaying a summary. Invoke doctest.master.summarize(verbose)
- when you're done fiddling.
- """
- global master
-
- if package and not module_relative:
- raise ValueError("Package may only be specified for module-"
- "relative paths.")
-
- # Relativize the path
- if module_relative:
- package = _normalize_module(package)
- filename = _module_relative_path(package, filename)
-
- # If no name was given, then use the file's name.
- if name is None:
- name = os.path.basename(filename)
-
- # Assemble the globals.
- if globs is None:
- globs = {}
- else:
- globs = globs.copy()
- if extraglobs is not None:
- globs.update(extraglobs)
-
- if raise_on_error:
- runner = DebugRunner(verbose=verbose, optionflags=optionflags)
- else:
- runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
-
- # Read the file, convert it to a test, and run it.
- s = open(filename).read()
- test = parser.get_doctest(s, globs, name, filename, 0)
- runner.run(test)
-
- if report:
- runner.summarize()
-
- if master is None:
- master = runner
- else:
- master.merge(runner)
-
- return runner.failures, runner.tries
-
-def run_docstring_examples(f, globs, verbose=False, name="NoName",
- compileflags=None, optionflags=0):
- """
- Test examples in the given object's docstring (`f`), using `globs`
- as globals. Optional argument `name` is used in failure messages.
- If the optional argument `verbose` is true, then generate output
- even if there are no failures.
-
- `compileflags` gives the set of flags that should be used by the
- Python compiler when running the examples. If not specified, then
- it will default to the set of future-import flags that apply to
- `globs`.
-
- Optional keyword arg `optionflags` specifies options for the
- testing and output. See the documentation for `testmod` for more
- information.
- """
- # Find, parse, and run all tests in the given module.
- finder = DocTestFinder(verbose=verbose, recurse=False)
- runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
- for test in finder.find(f, name, globs=globs):
- runner.run(test, compileflags=compileflags)
-
-######################################################################
-## 7. Tester
-######################################################################
-# This is provided only for backwards compatibility. It's not
-# actually used in any way.
-
-class Tester:
- def __init__(self, mod=None, globs=None, verbose=None,
- isprivate=None, optionflags=0):
-
- warnings.warn("class Tester is deprecated; "
- "use class doctest.DocTestRunner instead",
- DeprecationWarning, stacklevel=2)
- if mod is None and globs is None:
- raise TypeError("Tester.__init__: must specify mod or globs")
- if mod is not None and not inspect.ismodule(mod):
- raise TypeError("Tester.__init__: mod must be a module; %r" %
- (mod,))
- if globs is None:
- globs = mod.__dict__
- self.globs = globs
-
- self.verbose = verbose
- self.isprivate = isprivate
- self.optionflags = optionflags
- self.testfinder = DocTestFinder(_namefilter=isprivate)
- self.testrunner = DocTestRunner(verbose=verbose,
- optionflags=optionflags)
-
- def runstring(self, s, name):
- test = DocTestParser().get_doctest(s, self.globs, name, None, None)
- if self.verbose:
- print "Running string", name
- (f,t) = self.testrunner.run(test)
- if self.verbose:
- print f, "of", t, "examples failed in string", name
- return (f,t)
-
- def rundoc(self, object, name=None, module=None):
- f = t = 0
- tests = self.testfinder.find(object, name, module=module,
- globs=self.globs)
- for test in tests:
- (f2, t2) = self.testrunner.run(test)
- (f,t) = (f+f2, t+t2)
- return (f,t)
-
- def rundict(self, d, name, module=None):
- import new
- m = new.module(name)
- m.__dict__.update(d)
- if module is None:
- module = False
- return self.rundoc(m, name, module)
-
- def run__test__(self, d, name):
- import new
- m = new.module(name)
- m.__test__ = d
- return self.rundoc(m, name)
-
- def summarize(self, verbose=None):
- return self.testrunner.summarize(verbose)
-
- def merge(self, other):
- self.testrunner.merge(other.testrunner)
-
-######################################################################
-## 8. Unittest Support
-######################################################################
-
-_unittest_reportflags = 0
-
-def set_unittest_reportflags(flags):
- """Sets the unittest option flags.
-
- The old flag is returned so that a runner could restore the old
- value if it wished to:
-
- >>> old = _unittest_reportflags
- >>> set_unittest_reportflags(REPORT_NDIFF |
- ... REPORT_ONLY_FIRST_FAILURE) == old
- True
-
- >>> import doctest
- >>> doctest._unittest_reportflags == (REPORT_NDIFF |
- ... REPORT_ONLY_FIRST_FAILURE)
- True
-
- Only reporting flags can be set:
-
- >>> set_unittest_reportflags(ELLIPSIS)
- Traceback (most recent call last):
- ...
- ValueError: ('Only reporting flags allowed', 8)
-
- >>> set_unittest_reportflags(old) == (REPORT_NDIFF |
- ... REPORT_ONLY_FIRST_FAILURE)
- True
- """
- global _unittest_reportflags
-
- if (flags & REPORTING_FLAGS) != flags:
- raise ValueError("Only reporting flags allowed", flags)
- old = _unittest_reportflags
- _unittest_reportflags = flags
- return old
-
-
-class DocTestCase(unittest.TestCase):
-
- def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
- checker=None):
-
- unittest.TestCase.__init__(self)
- self._dt_optionflags = optionflags
- self._dt_checker = checker
- self._dt_test = test
- self._dt_setUp = setUp
- self._dt_tearDown = tearDown
-
- def setUp(self):
- test = self._dt_test
-
- if self._dt_setUp is not None:
- self._dt_setUp(test)
-
- def tearDown(self):
- test = self._dt_test
-
- if self._dt_tearDown is not None:
- self._dt_tearDown(test)
-
- test.globs.clear()
-
- def runTest(self):
- test = self._dt_test
- old = sys.stdout
- new = StringIO()
- optionflags = self._dt_optionflags
-
- if not (optionflags & REPORTING_FLAGS):
- # The option flags don't include any reporting flags,
- # so add the default reporting flags
- optionflags |= _unittest_reportflags
-
- runner = DocTestRunner(optionflags=optionflags,
- checker=self._dt_checker, verbose=False)
-
- try:
- runner.DIVIDER = "-"*70
- failures, tries = runner.run(
- test, out=new.write, clear_globs=False)
- finally:
- sys.stdout = old
-
- if failures:
- raise self.failureException(self.format_failure(new.getvalue()))
-
- def format_failure(self, err):
- test = self._dt_test
- if test.lineno is None:
- lineno = 'unknown line number'
- else:
- lineno = '%s' % test.lineno
- lname = '.'.join(test.name.split('.')[-1:])
- return ('Failed doctest test for %s\n'
- ' File "%s", line %s, in %s\n\n%s'
- % (test.name, test.filename, lineno, lname, err)
- )
-
- def debug(self):
- r"""Run the test case without results and without catching exceptions
-
- The unit test framework includes a debug method on test cases
- and test suites to support post-mortem debugging. The test code
- is run in such a way that errors are not caught. This way a
- caller can catch the errors and initiate post-mortem debugging.
-
- The DocTestCase provides a debug method that raises
- UnexpectedException errors if there is an unexepcted
- exception:
-
- >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42',
- ... {}, 'foo', 'foo.py', 0)
- >>> case = DocTestCase(test)
- >>> try:
- ... case.debug()
- ... except UnexpectedException, failure:
- ... pass
-
- The UnexpectedException contains the test, the example, and
- the original exception:
-
- >>> failure.test is test
- True
-
- >>> failure.example.want
- '42\n'
-
- >>> exc_info = failure.exc_info
- >>> raise exc_info[0], exc_info[1], exc_info[2]
- Traceback (most recent call last):
- ...
- KeyError
-
- If the output doesn't match, then a DocTestFailure is raised:
-
- >>> test = DocTestParser().get_doctest('''
- ... >>> x = 1
- ... >>> x
- ... 2
- ... ''', {}, 'foo', 'foo.py', 0)
- >>> case = DocTestCase(test)
-
- >>> try:
- ... case.debug()
- ... except DocTestFailure, failure:
- ... pass
-
- DocTestFailure objects provide access to the test:
-
- >>> failure.test is test
- True
-
- As well as to the example:
-
- >>> failure.example.want
- '2\n'
-
- and the actual output:
-
- >>> failure.got
- '1\n'
-
- """
-
- self.setUp()
- runner = DebugRunner(optionflags=self._dt_optionflags,
- checker=self._dt_checker, verbose=False)
- runner.run(self._dt_test)
- self.tearDown()
-
- def id(self):
- return self._dt_test.name
-
- def __repr__(self):
- name = self._dt_test.name.split('.')
- return "%s (%s)" % (name[-1], '.'.join(name[:-1]))
-
- __str__ = __repr__
-
- def shortDescription(self):
- return "Doctest: " + self._dt_test.name
-
-def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
- **options):
- """
- Convert doctest tests for a module to a unittest test suite.
-
- This converts each documentation string in a module that
- contains doctest tests to a unittest test case. If any of the
- tests in a doc string fail, then the test case fails. An exception
- is raised showing the name of the file containing the test and a
- (sometimes approximate) line number.
-
- The `module` argument provides the module to be tested. The argument
- can be either a module or a module name.
-
- If no argument is given, the calling module is used.
-
- A number of options may be provided as keyword arguments:
-
- setUp
- A set-up function. This is called before running the
- tests in each file. The setUp function will be passed a DocTest
- object. The setUp function can access the test globals as the
- globs attribute of the test passed.
-
- tearDown
- A tear-down function. This is called after running the
- tests in each file. The tearDown function will be passed a DocTest
- object. The tearDown function can access the test globals as the
- globs attribute of the test passed.
-
- globs
- A dictionary containing initial global variables for the tests.
-
- optionflags
- A set of doctest option flags expressed as an integer.
- """
-
- if test_finder is None:
- test_finder = DocTestFinder()
-
- module = _normalize_module(module)
- tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
- if globs is None:
- globs = module.__dict__
- if not tests:
- # Why do we want to do this? Because it reveals a bug that might
- # otherwise be hidden.
- raise ValueError(module, "has no tests")
-
- tests.sort()
- suite = unittest.TestSuite()
- for test in tests:
- if len(test.examples) == 0:
- continue
- if not test.filename:
- filename = module.__file__
- if filename[-4:] in (".pyc", ".pyo"):
- filename = filename[:-1]
- test.filename = filename
- suite.addTest(DocTestCase(test, **options))
-
- return suite
-
-class DocFileCase(DocTestCase):
-
- def id(self):
- return '_'.join(self._dt_test.name.split('.'))
-
- def __repr__(self):
- return self._dt_test.filename
- __str__ = __repr__
-
- def format_failure(self, err):
- return ('Failed doctest test for %s\n File "%s", line 0\n\n%s'
- % (self._dt_test.name, self._dt_test.filename, err)
- )
-
-def DocFileTest(path, module_relative=True, package=None,
- globs=None, parser=DocTestParser(), **options):
- if globs is None:
- globs = {}
-
- if package and not module_relative:
- raise ValueError("Package may only be specified for module-"
- "relative paths.")
-
- # Relativize the path.
- if module_relative:
- package = _normalize_module(package)
- path = _module_relative_path(package, path)
-
- # Find the file and read it.
- name = os.path.basename(path)
- doc = open(path).read()
-
- # Convert it to a test, and wrap it in a DocFileCase.
- test = parser.get_doctest(doc, globs, name, path, 0)
- return DocFileCase(test, **options)
-
-def DocFileSuite(*paths, **kw):
- """A unittest suite for one or more doctest files.
-
- The path to each doctest file is given as a string; the
- interpretation of that string depends on the keyword argument
- "module_relative".
-
- A number of options may be provided as keyword arguments:
-
- module_relative
- If "module_relative" is True, then the given file paths are
- interpreted as os-independent module-relative paths. By
- default, these paths are relative to the calling module's
- directory; but if the "package" argument is specified, then
- they are relative to that package. To ensure os-independence,
- "filename" should use "/" characters to separate path
- segments, and may not be an absolute path (i.e., it may not
- begin with "/").
-
- If "module_relative" is False, then the given file paths are
- interpreted as os-specific paths. These paths may be absolute
- or relative (to the current working directory).
-
- package
- A Python package or the name of a Python package whose directory
- should be used as the base directory for module relative paths.
- If "package" is not specified, then the calling module's
- directory is used as the base directory for module relative
- filenames. It is an error to specify "package" if
- "module_relative" is False.
-
- setUp
- A set-up function. This is called before running the
- tests in each file. The setUp function will be passed a DocTest
- object. The setUp function can access the test globals as the
- globs attribute of the test passed.
-
- tearDown
- A tear-down function. This is called after running the
- tests in each file. The tearDown function will be passed a DocTest
- object. The tearDown function can access the test globals as the
- globs attribute of the test passed.
-
- globs
- A dictionary containing initial global variables for the tests.
-
- optionflags
- A set of doctest option flags expressed as an integer.
-
- parser
- A DocTestParser (or subclass) that should be used to extract
- tests from the files.
- """
- suite = unittest.TestSuite()
-
- # We do this here so that _normalize_module is called at the right
- # level. If it were called in DocFileTest, then this function
- # would be the caller and we might guess the package incorrectly.
- if kw.get('module_relative', True):
- kw['package'] = _normalize_module(kw.get('package'))
-
- for path in paths:
- suite.addTest(DocFileTest(path, **kw))
-
- return suite
-
-######################################################################
-## 9. Debugging Support
-######################################################################
-
-def script_from_examples(s):
- r"""Extract script from text with examples.
-
- Converts text with examples to a Python script. Example input is
- converted to regular code. Example output and all other words
- are converted to comments:
-
- >>> text = '''
- ... Here are examples of simple math.
- ...
- ... Python has super accurate integer addition
- ...
- ... >>> 2 + 2
- ... 5
- ...
- ... And very friendly error messages:
- ...
- ... >>> 1/0
- ... To Infinity
- ... And
- ... Beyond
- ...
- ... You can use logic if you want:
- ...
- ... >>> if 0:
- ... ... blah
- ... ... blah
- ... ...
- ...
- ... Ho hum
- ... '''
-
- >>> print script_from_examples(text)
- # Here are examples of simple math.
- #
- # Python has super accurate integer addition
- #
- 2 + 2
- # Expected:
- ## 5
- #
- # And very friendly error messages:
- #
- 1/0
- # Expected:
- ## To Infinity
- ## And
- ## Beyond
- #
- # You can use logic if you want:
- #
- if 0:
- blah
- blah
- #
- # Ho hum
- """
- output = []
- for piece in DocTestParser().parse(s):
- if isinstance(piece, Example):
- # Add the example's source code (strip trailing NL)
- output.append(piece.source[:-1])
- # Add the expected output:
- want = piece.want
- if want:
- output.append('# Expected:')
- output += ['## '+l for l in want.split('\n')[:-1]]
- else:
- # Add non-example text.
- output += [_comment_line(l)
- for l in piece.split('\n')[:-1]]
-
- # Trim junk on both ends.
- while output and output[-1] == '#':
- output.pop()
- while output and output[0] == '#':
- output.pop(0)
- # Combine the output, and return it.
- return '\n'.join(output)
-
-def testsource(module, name):
- """Extract the test sources from a doctest docstring as a script.
-
- Provide the module (or dotted name of the module) containing the
- test to be debugged and the name (within the module) of the object
- with the doc string with tests to be debugged.
- """
- module = _normalize_module(module)
- tests = DocTestFinder().find(module)
- test = [t for t in tests if t.name == name]
- if not test:
- raise ValueError(name, "not found in tests")
- test = test[0]
- testsrc = script_from_examples(test.docstring)
- return testsrc
-
-def debug_src(src, pm=False, globs=None):
- """Debug a single doctest docstring, in argument `src`'"""
- testsrc = script_from_examples(src)
- debug_script(testsrc, pm, globs)
-
-def debug_script(src, pm=False, globs=None):
- "Debug a test script. `src` is the script, as a string."
- import pdb
-
- # Note that tempfile.NameTemporaryFile() cannot be used. As the
- # docs say, a file so created cannot be opened by name a second time
- # on modern Windows boxes, and execfile() needs to open it.
- srcfilename = tempfile.mktemp(".py", "doctestdebug")
- f = open(srcfilename, 'w')
- f.write(src)
- f.close()
-
- try:
- if globs:
- globs = globs.copy()
- else:
- globs = {}
-
- if pm:
- try:
- execfile(srcfilename, globs, globs)
- except:
- print sys.exc_info()[1]
- pdb.post_mortem(sys.exc_info()[2])
- else:
- # Note that %r is vital here. '%s' instead can, e.g., cause
- # backslashes to get treated as metacharacters on Windows.
- pdb.run("execfile(%r)" % srcfilename, globs, globs)
-
- finally:
- os.remove(srcfilename)
-
-def debug(module, name, pm=False):
- """Debug a single doctest docstring.
-
- Provide the module (or dotted name of the module) containing the
- test to be debugged and the name (within the module) of the object
- with the docstring with tests to be debugged.
- """
- module = _normalize_module(module)
- testsrc = testsource(module, name)
- debug_script(testsrc, pm, module.__dict__)
-
-######################################################################
-## 10. Example Usage
-######################################################################
-class _TestClass:
- """
- A pointless class, for sanity-checking of docstring testing.
-
- Methods:
- square()
- get()
-
- >>> _TestClass(13).get() + _TestClass(-12).get()
- 1
- >>> hex(_TestClass(13).square().get())
- '0xa9'
- """
-
- def __init__(self, val):
- """val -> _TestClass object with associated value val.
-
- >>> t = _TestClass(123)
- >>> print t.get()
- 123
- """
-
- self.val = val
-
- def square(self):
- """square() -> square TestClass's associated value
-
- >>> _TestClass(13).square().get()
- 169
- """
-
- self.val = self.val ** 2
- return self
-
- def get(self):
- """get() -> return TestClass's associated value.
-
- >>> x = _TestClass(-42)
- >>> print x.get()
- -42
- """
-
- return self.val
-
-__test__ = {"_TestClass": _TestClass,
- "string": r"""
- Example of a string object, searched as-is.
- >>> x = 1; y = 2
- >>> x + y, x * y
- (3, 2)
- """,
-
- "bool-int equivalence": r"""
- In 2.2, boolean expressions displayed
- 0 or 1. By default, we still accept
- them. This can be disabled by passing
- DONT_ACCEPT_TRUE_FOR_1 to the new
- optionflags argument.
- >>> 4 == 4
- 1
- >>> 4 == 4
- True
- >>> 4 > 4
- 0
- >>> 4 > 4
- False
- """,
-
- "blank lines": r"""
- Blank lines can be marked with <BLANKLINE>:
- >>> print 'foo\n\nbar\n'
- foo
- <BLANKLINE>
- bar
- <BLANKLINE>
- """,
-
- "ellipsis": r"""
- If the ellipsis flag is used, then '...' can be used to
- elide substrings in the desired output:
- >>> print range(1000) #doctest: +ELLIPSIS
- [0, 1, 2, ..., 999]
- """,
-
- "whitespace normalization": r"""
- If the whitespace normalization flag is used, then
- differences in whitespace are ignored.
- >>> print range(30) #doctest: +NORMALIZE_WHITESPACE
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
- 27, 28, 29]
- """,
- }
-
-def _test():
- r = unittest.TextTestRunner()
- r.run(DocTestSuite())
-
-if __name__ == "__main__":
- _test()
diff --git a/paste/util/filemixin.py b/paste/util/filemixin.py
index 10a9e7c..b06b039 100644
--- a/paste/util/filemixin.py
+++ b/paste/util/filemixin.py
@@ -50,4 +50,4 @@ class FileMixin(object):
for line in lines:
self.write(line)
-
+
diff --git a/paste/util/finddata.py b/paste/util/finddata.py
index 05c8546..bb7c760 100644
--- a/paste/util/finddata.py
+++ b/paste/util/finddata.py
@@ -48,7 +48,7 @@ def find_package_data(
Note patterns use wildcards, or can be exact paths (including
leading ``./``), and all searching is case-insensitive.
"""
-
+
out = {}
stack = [(convert_path(where), '', package, only_in_packages)]
while stack:
diff --git a/paste/util/findpackage.py b/paste/util/findpackage.py
index 68b5e8b..9d653e5 100644
--- a/paste/util/findpackage.py
+++ b/paste/util/findpackage.py
@@ -23,4 +23,4 @@ def find_package(dir):
raise ValueError(
"%s is not under any path found in sys.path" % orig_dir)
last_dir = dir
-
+
diff --git a/paste/util/intset.py b/paste/util/intset.py
index c124eb6..3e026e2 100644
--- a/paste/util/intset.py
+++ b/paste/util/intset.py
@@ -6,6 +6,7 @@ Integer set class.
Copyright (C) 2006, Heiko Wundram.
Released under the MIT license.
"""
+import six
# Version information
# -------------------
@@ -28,40 +29,43 @@ class _Infinity(object):
self._neg = neg
def __lt__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
+ if not isinstance(value, _VALID_TYPES):
return NotImplemented
return ( self._neg and
not ( isinstance(value,_Infinity) and value._neg ) )
def __le__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
+ if not isinstance(value, _VALID_TYPES):
return NotImplemented
return self._neg
def __gt__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
+ if not isinstance(value, _VALID_TYPES):
return NotImplemented
return not ( self._neg or
( isinstance(value,_Infinity) and not value._neg ) )
def __ge__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
+ if not isinstance(value, _VALID_TYPES):
return NotImplemented
return not self._neg
def __eq__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
+ if not isinstance(value, _VALID_TYPES):
return NotImplemented
return isinstance(value,_Infinity) and self._neg == value._neg
def __ne__(self,value):
- if not isinstance(value,(int,long,_Infinity)):
+ if not isinstance(value, _VALID_TYPES):
return NotImplemented
return not isinstance(value,_Infinity) or self._neg != value._neg
def __repr__(self):
return "None"
+_VALID_TYPES = six.integer_types + (_Infinity,)
+
+
# Constants
# ---------
@@ -117,19 +121,19 @@ class IntSet(object):
# Check keyword arguments.
if kwargs:
raise ValueError("Invalid keyword argument.")
- if not ( isinstance(self._min,(int,long)) or self._min is _MININF ):
+ if not ( isinstance(self._min, six.integer_types) or self._min is _MININF ):
raise TypeError("Invalid type of min argument.")
- if not ( isinstance(self._max,(int,long)) or self._max is _MAXINF ):
+ if not ( isinstance(self._max, six.integer_types) or self._max is _MAXINF ):
raise TypeError("Invalid type of max argument.")
if ( self._min is not _MININF and self._max is not _MAXINF and
self._min > self._max ):
raise ValueError("Minimum is not smaller than maximum.")
- if isinstance(self._max,(int,long)):
+ if isinstance(self._max, six.integer_types):
self._max += 1
# Process arguments.
for arg in args:
- if isinstance(arg,(int,long)):
+ if isinstance(arg, six.integer_types):
start, stop = arg, arg+1
elif isinstance(arg,tuple):
if len(arg) != 2:
@@ -143,14 +147,14 @@ class IntSet(object):
stop = self._max
# Check arguments.
- if not ( isinstance(start,(int,long)) or start is _MININF ):
+ if not ( isinstance(start, six.integer_types) or start is _MININF ):
raise TypeError("Invalid type of tuple start.")
- if not ( isinstance(stop,(int,long)) or stop is _MAXINF ):
+ if not ( isinstance(stop, six.integer_types) or stop is _MAXINF ):
raise TypeError("Invalid type of tuple stop.")
if ( start is not _MININF and stop is not _MAXINF and
start > stop ):
continue
- if isinstance(stop,(int,long)):
+ if isinstance(stop, six.integer_types):
stop += 1
else:
raise TypeError("Invalid argument.")
@@ -211,7 +215,7 @@ class IntSet(object):
def __coerce__(self,other):
if isinstance(other,IntSet):
return self, other
- elif isinstance(other,(int,long,tuple)):
+ elif isinstance(other, six.integer_types + (tuple,)):
try:
return self, self.__class__(other)
except TypeError:
@@ -471,10 +475,10 @@ class IntSet(object):
rv = []
for start, stop in self._ranges:
- if ( isinstance(start,(int,long)) and isinstance(stop,(int,long))
+ if ( isinstance(start, six.integer_types) and isinstance(stop, six.integer_types)
and stop-start == 1 ):
rv.append("%r" % start)
- elif isinstance(stop,(int,long)):
+ elif isinstance(stop, six.integer_types):
rv.append("(%r,%r)" % (start,stop-1))
else:
rv.append("(%r,%r)" % (start,stop))
diff --git a/paste/util/ip4.py b/paste/util/ip4.py
index f691abc..9ce17b8 100644
--- a/paste/util/ip4.py
+++ b/paste/util/ip4.py
@@ -19,8 +19,9 @@ __date__ = "2006-01-20"
# Imports
# -------
-import intset
+from paste.util import intset
import socket
+import six
# IP4Range class
@@ -87,14 +88,14 @@ class IP4Range(intset.IntSet):
addr1, addr2 = argval
if isinstance(addr1,str):
addr1 = self._parseAddrRange(addr1)[0]
- elif not isinstance(addr1,(int,long)):
+ elif not isinstance(addr1, six.integer_types):
raise TypeError("Invalid argument.")
if isinstance(addr2,str):
addr2 = self._parseAddrRange(addr2)[1]
- elif not isinstance(addr2,(int,long)):
+ elif not isinstance(addr2, six.integer_types):
raise TypeError("Invalid argument.")
args[i] = (addr1,addr2)
- elif not isinstance(argval,(int,long)):
+ elif not isinstance(argval, six.integer_types):
raise TypeError("Invalid argument.")
# Initialize the integer set.
@@ -231,7 +232,7 @@ class IP4Range(intset.IntSet):
return "%s(%s)" % (self.__class__.__name__,",".join(rv))
def _parseAddr(addr,lookup=True):
- if lookup and addr.translate(IP4Range._UNITYTRANS, IP4Range._IPREMOVE):
+ if lookup and any(ch not in IP4Range._IPREMOVE for ch in addr):
try:
addr = socket.gethostbyname(addr)
except socket.error:
diff --git a/paste/util/looper.py b/paste/util/looper.py
index efe7fdf..b56358a 100644
--- a/paste/util/looper.py
+++ b/paste/util/looper.py
@@ -7,7 +7,7 @@ These can be awkward to manage in a normal Python loop, but using the
looper you can get a better sense of the context. Use like::
>>> for loop, item in looper(['a', 'b', 'c']):
- ... print(loop.number, item)
+ ... print("%s %s" % (loop.number, item))
... if not loop.last:
... print('---')
1 a
@@ -26,9 +26,9 @@ import six
class looper(object):
"""
Helper for looping (particularly in templates)
-
+
Use this like::
-
+
for loop, item in looper(seq):
if loop.first:
...
@@ -59,6 +59,7 @@ class looper_iter(object):
result = loop_pos(self.seq, self.pos), self.seq[self.pos]
self.pos += 1
return result
+ __next__ = next
class loop_pos(object):
@@ -68,7 +69,7 @@ class loop_pos(object):
def __repr__(self):
return '<loop pos=%r at %r>' % (
- self.seq[pos], pos)
+ self.seq[self.pos], self.pos)
def index(self):
return self.pos
@@ -152,4 +153,4 @@ class loop_pos(object):
return getter(item) != getter(other)
else:
return item[getter] != other[getter]
-
+
diff --git a/paste/util/mimeparse.py b/paste/util/mimeparse.py
index fe699f7..b796c8b 100644
--- a/paste/util/mimeparse.py
+++ b/paste/util/mimeparse.py
@@ -132,7 +132,7 @@ def best_match(supported, header):
"""
if not supported:
return ''
- parsed_header = map(parse_media_range, header.split(','))
+ parsed_header = list(map(parse_media_range, header.split(',')))
best_type = max([
(fitness_and_quality_parsed(mime_type, parsed_header), -n)
for n, mime_type in enumerate(supported)])
@@ -154,7 +154,7 @@ def desired_matches(desired, header):
>>> desired_matches(['text/html', 'application/xml'], 'application/xml,application/json')
['application/xml']
"""
- parsed_ranges = map(parse_media_range, header.split(','))
+ parsed_ranges = list(map(parse_media_range, header.split(',')))
return [mimetype for mimetype in desired
if quality_parsed(mimetype, parsed_ranges)]
diff --git a/paste/util/multidict.py b/paste/util/multidict.py
index eb32e53..701d1ac 100644
--- a/paste/util/multidict.py
+++ b/paste/util/multidict.py
@@ -61,7 +61,7 @@ class MultiDict(DictMixin):
"""
result = []
for k, v in self._items:
- if key == k:
+ if type(key) == type(k) and key == k:
result.append(v)
return result
@@ -117,7 +117,7 @@ class MultiDict(DictMixin):
items = self._items
found = False
for i in range(len(items)-1, -1, -1):
- if items[i][0] == key:
+ if type(items[i][0]) == type(key) and items[i][0] == key:
del items[i]
found = True
if not found:
@@ -125,7 +125,7 @@ class MultiDict(DictMixin):
def __contains__(self, key):
for k, v in self._items:
- if k == key:
+ if type(k) == type(key) and k == key:
return True
return False
@@ -149,7 +149,7 @@ class MultiDict(DictMixin):
raise TypeError("pop expected at most 2 arguments, got "
+ repr(1 + len(args)))
for i in range(len(self._items)):
- if self._items[i][0] == key:
+ if type(self._items[i][0]) == type(key) and self._items[i][0] == key:
v = self._items[i][1]
del self._items[i]
return v
@@ -233,6 +233,20 @@ class UnicodeMultiDict(DictMixin):
self.encoding = encoding
self.errors = errors
self.decode_keys = decode_keys
+ if self.decode_keys:
+ items = self.multi._items
+ for index, item in enumerate(items):
+ key, value = item
+ key = self._encode_key(key)
+ items[index] = (key, value)
+
+ def _encode_key(self, key):
+ if self.decode_keys:
+ try:
+ key = key.encode(self.encoding, self.errors)
+ except AttributeError:
+ pass
+ return key
def _decode_key(self, key):
if self.decode_keys:
@@ -252,9 +266,10 @@ class UnicodeMultiDict(DictMixin):
if isinstance(value, cgi.FieldStorage):
# decode FieldStorage's field name and filename
value = copy.copy(value)
- if self.decode_keys:
+ if self.decode_keys and isinstance(value.name, six.binary_type):
value.name = value.name.decode(self.encoding, self.errors)
- value.filename = value.filename.decode(self.encoding, self.errors)
+ if six.PY2:
+ value.filename = value.filename.decode(self.encoding, self.errors)
else:
try:
value = value.decode(self.encoding, self.errors)
@@ -263,21 +278,25 @@ class UnicodeMultiDict(DictMixin):
return value
def __getitem__(self, key):
+ key = self._encode_key(key)
return self._decode_value(self.multi.__getitem__(key))
def __setitem__(self, key, value):
+ key = self._encode_key(key)
self.multi.__setitem__(key, value)
def add(self, key, value):
"""
Add the key and value, not overwriting any previous value.
"""
+ key = self._encode_key(key)
self.multi.add(key, value)
def getall(self, key):
"""
Return a list of all values matching the key (may be an empty list)
"""
+ key = self._encode_key(key)
return [self._decode_value(v) for v in self.multi.getall(key)]
def getone(self, key):
@@ -285,6 +304,7 @@ class UnicodeMultiDict(DictMixin):
Get one value matching the key, raising a KeyError if multiple
values were found.
"""
+ key = self._encode_key(key)
return self._decode_value(self.multi.getone(key))
def mixed(self):
@@ -316,9 +336,11 @@ class UnicodeMultiDict(DictMixin):
return unicode_dict
def __delitem__(self, key):
+ key = self._encode_key(key)
self.multi.__delitem__(key)
def __contains__(self, key):
+ key = self._encode_key(key)
return self.multi.__contains__(key)
has_key = __contains__
@@ -327,12 +349,15 @@ class UnicodeMultiDict(DictMixin):
self.multi.clear()
def copy(self):
- return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors)
+ return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors,
+ decode_keys=self.decode_keys)
def setdefault(self, key, default=None):
+ key = self._encode_key(key)
return self._decode_value(self.multi.setdefault(key, default))
def pop(self, key, *args):
+ key = self._encode_key(key)
return self._decode_value(self.multi.pop(key, *args))
def popitem(self):
diff --git a/paste/util/quoting.py b/paste/util/quoting.py
index 8c4c749..2e26434 100644
--- a/paste/util/quoting.py
+++ b/paste/util/quoting.py
@@ -30,18 +30,24 @@ def html_quote(v, encoding=None):
encoding = encoding or default_encoding
if v is None:
return ''
- elif isinstance(v, str):
+ elif isinstance(v, six.binary_type):
return cgi.escape(v, 1)
elif isinstance(v, six.text_type):
- return cgi.escape(v.encode(encoding), 1)
+ if six.PY3:
+ return cgi.escape(v, 1)
+ else:
+ return cgi.escape(v.encode(encoding), 1)
else:
- return cgi.escape(six.text_type(v).encode(encoding), 1)
+ if six.PY3:
+ return cgi.escape(six.text_type(v), 1)
+ else:
+ return cgi.escape(six.text_type(v).encode(encoding), 1)
_unquote_re = re.compile(r'&([a-zA-Z]+);')
def _entity_subber(match, name2c=html_entities.name2codepoint):
code = name2c.get(match.group(1))
if code:
- return unichr(code)
+ return six.unichr(code)
else:
return match.group(0)
@@ -58,7 +64,7 @@ def html_unquote(s, encoding=None):
>>> html_unquote('\xe1\x80\xa9')
u'\u1029'
"""
- if isinstance(s, str):
+ if isinstance(s, six.binary_type):
if s == '':
# workaround re.sub('', '', u'') returning '' < 2.5.2
# instead of u'' >= 2.5.2
diff --git a/paste/util/scgiserver.py b/paste/util/scgiserver.py
index f65294d..1c86c86 100644
--- a/paste/util/scgiserver.py
+++ b/paste/util/scgiserver.py
@@ -42,7 +42,7 @@ class SWAP(scgi_server.SCGIHandler):
"""
app_obj = None
prefix = None
-
+
def __init__(self, *args, **kwargs):
assert self.app_obj, "must set app_obj"
assert self.prefix is not None, "must set prefix"
@@ -85,7 +85,7 @@ class SWAP(scgi_server.SCGIHandler):
chunks = []
def write(data):
chunks.append(data)
-
+
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
@@ -106,7 +106,7 @@ class SWAP(scgi_server.SCGIHandler):
try:
for data in result:
chunks.append(data)
-
+
# Before the first output, send the stored headers
if not headers_set:
# Error -- the app never called start_response
@@ -115,7 +115,7 @@ class SWAP(scgi_server.SCGIHandler):
chunks = ["XXX start_response never called"]
else:
status, response_headers = headers_sent[:] = headers_set
-
+
output.write('Status: %s\r\n' % status)
for header in response_headers:
output.write('%s: %s\r\n' % header)
diff --git a/paste/util/string24.py b/paste/util/string24.py
deleted file mode 100644
index 7c0e001..0000000
--- a/paste/util/string24.py
+++ /dev/null
@@ -1,531 +0,0 @@
-"""A collection of string operations (most are no longer used).
-
-Warning: most of the code you see here isn't normally used nowadays.
-Beginning with Python 1.6, many of these functions are implemented as
-methods on the standard string object. They used to be implemented by
-a built-in module called strop, but strop is now obsolete itself.
-
-Public module variables:
-
-whitespace -- a string containing all characters considered whitespace
-lowercase -- a string containing all characters considered lowercase letters
-uppercase -- a string containing all characters considered uppercase letters
-letters -- a string containing all characters considered letters
-digits -- a string containing all characters considered decimal digits
-hexdigits -- a string containing all characters considered hexadecimal digits
-octdigits -- a string containing all characters considered octal digits
-punctuation -- a string containing all characters considered punctuation
-printable -- a string containing all characters considered printable
-
-"""
-
-# Some strings for ctype-style character classification
-whitespace = ' \t\n\r\v\f'
-lowercase = 'abcdefghijklmnopqrstuvwxyz'
-uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
-letters = lowercase + uppercase
-ascii_lowercase = lowercase
-ascii_uppercase = uppercase
-ascii_letters = ascii_lowercase + ascii_uppercase
-digits = '0123456789'
-hexdigits = digits + 'abcdef' + 'ABCDEF'
-octdigits = '01234567'
-punctuation = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
-printable = digits + letters + punctuation + whitespace
-
-# Case conversion helpers
-# Use str to convert Unicode literal in case of -U
-# Note that Cookie.py bogusly uses _idmap :(
-l = map(chr, xrange(256))
-_idmap = str('').join(l)
-del l
-
-# Functions which aren't available as string methods.
-
-# Capitalize the words in a string, e.g. " aBc dEf " -> "Abc Def".
-# See also regsub.capwords().
-def capwords(s, sep=None):
- """capwords(s, [sep]) -> string
-
- Split the argument into words using split, capitalize each
- word using capitalize, and join the capitalized words using
- join. Note that this replaces runs of whitespace characters by
- a single space.
-
- """
- return (sep or ' ').join([x.capitalize() for x in s.split(sep)])
-
-
-# Construct a translation string
-_idmapL = None
-def maketrans(fromstr, tostr):
- """maketrans(frm, to) -> string
-
- Return a translation table (a string of 256 bytes long)
- suitable for use in string.translate. The strings frm and to
- must be of the same length.
-
- """
- if len(fromstr) != len(tostr):
- raise ValueError, "maketrans arguments must have same length"
- global _idmapL
- if not _idmapL:
- _idmapL = map(None, _idmap)
- L = _idmapL[:]
- fromstr = map(ord, fromstr)
- for i in range(len(fromstr)):
- L[fromstr[i]] = tostr[i]
- return ''.join(L)
-
-
-
-####################################################################
-import re as _re
-
-class _multimap:
- """Helper class for combining multiple mappings.
-
- Used by .{safe_,}substitute() to combine the mapping and keyword
- arguments.
- """
- def __init__(self, primary, secondary):
- self._primary = primary
- self._secondary = secondary
-
- def __getitem__(self, key):
- try:
- return self._primary[key]
- except KeyError:
- return self._secondary[key]
-
-
-class _TemplateMetaclass(type):
- pattern = r"""
- %(delim)s(?:
- (?P<escaped>%(delim)s) | # Escape sequence of two delimiters
- (?P<named>%(id)s) | # delimiter and a Python identifier
- {(?P<braced>%(id)s)} | # delimiter and a braced identifier
- (?P<invalid>) # Other ill-formed delimiter exprs
- )
- """
-
- def __init__(cls, name, bases, dct):
- super(_TemplateMetaclass, cls).__init__(name, bases, dct)
- if 'pattern' in dct:
- pattern = cls.pattern
- else:
- pattern = _TemplateMetaclass.pattern % {
- 'delim' : _re.escape(cls.delimiter),
- 'id' : cls.idpattern,
- }
- cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)
-
-
-class Template:
- """A string class for supporting $-substitutions."""
- __metaclass__ = _TemplateMetaclass
-
- delimiter = '$'
- idpattern = r'[_a-z][_a-z0-9]*'
-
- def __init__(self, template):
- self.template = template
-
- # Search for $$, $identifier, ${identifier}, and any bare $'s
-
- def _invalid(self, mo):
- i = mo.start('invalid')
- lines = self.template[:i].splitlines(True)
- if not lines:
- colno = 1
- lineno = 1
- else:
- colno = i - len(''.join(lines[:-1]))
- lineno = len(lines)
- raise ValueError('Invalid placeholder in string: line %d, col %d' %
- (lineno, colno))
-
- def substitute(self, *args, **kws):
- if len(args) > 1:
- raise TypeError('Too many positional arguments')
- if not args:
- mapping = kws
- elif kws:
- mapping = _multimap(kws, args[0])
- else:
- mapping = args[0]
- # Helper function for .sub()
- def convert(mo):
- # Check the most common path first.
- named = mo.group('named') or mo.group('braced')
- if named is not None:
- val = mapping[named]
- # We use this idiom instead of str() because the latter will
- # fail if val is a Unicode containing non-ASCII characters.
- return '%s' % val
- if mo.group('escaped') is not None:
- return self.delimiter
- if mo.group('invalid') is not None:
- self._invalid(mo)
- raise ValueError('Unrecognized named group in pattern',
- self.pattern)
- return self.pattern.sub(convert, self.template)
-
- def safe_substitute(self, *args, **kws):
- if len(args) > 1:
- raise TypeError('Too many positional arguments')
- if not args:
- mapping = kws
- elif kws:
- mapping = _multimap(kws, args[0])
- else:
- mapping = args[0]
- # Helper function for .sub()
- def convert(mo):
- named = mo.group('named')
- if named is not None:
- try:
- # We use this idiom instead of str() because the latter
- # will fail if val is a Unicode containing non-ASCII
- return '%s' % mapping[named]
- except KeyError:
- return self.delimiter + named
- braced = mo.group('braced')
- if braced is not None:
- try:
- return '%s' % mapping[braced]
- except KeyError:
- return self.delimiter + '{' + braced + '}'
- if mo.group('escaped') is not None:
- return self.delimiter
- if mo.group('invalid') is not None:
- return self.delimiter
- raise ValueError('Unrecognized named group in pattern',
- self.pattern)
- return self.pattern.sub(convert, self.template)
-
-
-
-####################################################################
-# NOTE: Everything below here is deprecated. Use string methods instead.
-# This stuff will go away in Python 3.0.
-
-# Backward compatible names for exceptions
-index_error = ValueError
-atoi_error = ValueError
-atof_error = ValueError
-atol_error = ValueError
-
-# convert UPPER CASE letters to lower case
-def lower(s):
- """lower(s) -> string
-
- Return a copy of the string s converted to lowercase.
-
- """
- return s.lower()
-
-# Convert lower case letters to UPPER CASE
-def upper(s):
- """upper(s) -> string
-
- Return a copy of the string s converted to uppercase.
-
- """
- return s.upper()
-
-# Swap lower case letters and UPPER CASE
-def swapcase(s):
- """swapcase(s) -> string
-
- Return a copy of the string s with upper case characters
- converted to lowercase and vice versa.
-
- """
- return s.swapcase()
-
-# Strip leading and trailing tabs and spaces
-def strip(s, chars=None):
- """strip(s [,chars]) -> string
-
- Return a copy of the string s with leading and trailing
- whitespace removed.
- If chars is given and not None, remove characters in chars instead.
- If chars is unicode, S will be converted to unicode before stripping.
-
- """
- return s.strip(chars)
-
-# Strip leading tabs and spaces
-def lstrip(s, chars=None):
- """lstrip(s [,chars]) -> string
-
- Return a copy of the string s with leading whitespace removed.
- If chars is given and not None, remove characters in chars instead.
-
- """
- return s.lstrip(chars)
-
-# Strip trailing tabs and spaces
-def rstrip(s, chars=None):
- """rstrip(s [,chars]) -> string
-
- Return a copy of the string s with trailing whitespace removed.
- If chars is given and not None, remove characters in chars instead.
-
- """
- return s.rstrip(chars)
-
-
-# Split a string into a list of space/tab-separated words
-def split(s, sep=None, maxsplit=-1):
- """split(s [,sep [,maxsplit]]) -> list of strings
-
- Return a list of the words in the string s, using sep as the
- delimiter string. If maxsplit is given, splits at no more than
- maxsplit places (resulting in at most maxsplit+1 words). If sep
- is not specified or is None, any whitespace string is a separator.
-
- (split and splitfields are synonymous)
-
- """
- return s.split(sep, maxsplit)
-splitfields = split
-
-# Split a string into a list of space/tab-separated words
-def rsplit(s, sep=None, maxsplit=-1):
- """rsplit(s [,sep [,maxsplit]]) -> list of strings
-
- Return a list of the words in the string s, using sep as the
- delimiter string, starting at the end of the string and working
- to the front. If maxsplit is given, at most maxsplit splits are
- done. If sep is not specified or is None, any whitespace string
- is a separator.
- """
- return s.rsplit(sep, maxsplit)
-
-# Join fields with optional separator
-def join(words, sep = ' '):
- """join(list [,sep]) -> string
-
- Return a string composed of the words in list, with
- intervening occurrences of sep. The default separator is a
- single space.
-
- (joinfields and join are synonymous)
-
- """
- return sep.join(words)
-joinfields = join
-
-# Find substring, raise exception if not found
-def index(s, *args):
- """index(s, sub [,start [,end]]) -> int
-
- Like find but raises ValueError when the substring is not found.
-
- """
- return s.index(*args)
-
-# Find last substring, raise exception if not found
-def rindex(s, *args):
- """rindex(s, sub [,start [,end]]) -> int
-
- Like rfind but raises ValueError when the substring is not found.
-
- """
- return s.rindex(*args)
-
-# Count non-overlapping occurrences of substring
-def count(s, *args):
- """count(s, sub[, start[,end]]) -> int
-
- Return the number of occurrences of substring sub in string
- s[start:end]. Optional arguments start and end are
- interpreted as in slice notation.
-
- """
- return s.count(*args)
-
-# Find substring, return -1 if not found
-def find(s, *args):
- """find(s, sub [,start [,end]]) -> in
-
- Return the lowest index in s where substring sub is found,
- such that sub is contained within s[start,end]. Optional
- arguments start and end are interpreted as in slice notation.
-
- Return -1 on failure.
-
- """
- return s.find(*args)
-
-# Find last substring, return -1 if not found
-def rfind(s, *args):
- """rfind(s, sub [,start [,end]]) -> int
-
- Return the highest index in s where substring sub is found,
- such that sub is contained within s[start,end]. Optional
- arguments start and end are interpreted as in slice notation.
-
- Return -1 on failure.
-
- """
- return s.rfind(*args)
-
-# for a bit of speed
-_float = float
-_int = int
-_long = long
-
-# Convert string to float
-def atof(s):
- """atof(s) -> float
-
- Return the floating point number represented by the string s.
-
- """
- return _float(s)
-
-
-# Convert string to integer
-def atoi(s , base=10):
- """atoi(s [,base]) -> int
-
- Return the integer represented by the string s in the given
- base, which defaults to 10. The string s must consist of one
- or more digits, possibly preceded by a sign. If base is 0, it
- is chosen from the leading characters of s, 0 for octal, 0x or
- 0X for hexadecimal. If base is 16, a preceding 0x or 0X is
- accepted.
-
- """
- return _int(s, base)
-
-
-# Convert string to long integer
-def atol(s, base=10):
- """atol(s [,base]) -> long
-
- Return the long integer represented by the string s in the
- given base, which defaults to 10. The string s must consist
- of one or more digits, possibly preceded by a sign. If base
- is 0, it is chosen from the leading characters of s, 0 for
- octal, 0x or 0X for hexadecimal. If base is 16, a preceding
- 0x or 0X is accepted. A trailing L or l is not accepted,
- unless base is 0.
-
- """
- return _long(s, base)
-
-
-# Left-justify a string
-def ljust(s, width, *args):
- """ljust(s, width[, fillchar]) -> string
-
- Return a left-justified version of s, in a field of the
- specified width, padded with spaces as needed. The string is
- never truncated. If specified the fillchar is used instead of spaces.
-
- """
- return s.ljust(width, *args)
-
-# Right-justify a string
-def rjust(s, width, *args):
- """rjust(s, width[, fillchar]) -> string
-
- Return a right-justified version of s, in a field of the
- specified width, padded with spaces as needed. The string is
- never truncated. If specified the fillchar is used instead of spaces.
-
- """
- return s.rjust(width, *args)
-
-# Center a string
-def center(s, width, *args):
- """center(s, width[, fillchar]) -> string
-
- Return a center version of s, in a field of the specified
- width. padded with spaces as needed. The string is never
- truncated. If specified the fillchar is used instead of spaces.
-
- """
- return s.center(width, *args)
-
-# Zero-fill a number, e.g., (12, 3) --> '012' and (-3, 3) --> '-03'
-# Decadent feature: the argument may be a string or a number
-# (Use of this is deprecated; it should be a string as with ljust c.s.)
-def zfill(x, width):
- """zfill(x, width) -> string
-
- Pad a numeric string x with zeros on the left, to fill a field
- of the specified width. The string x is never truncated.
-
- """
- if not isinstance(x, basestring):
- x = repr(x)
- return x.zfill(width)
-
-# Expand tabs in a string.
-# Doesn't take non-printing chars into account, but does understand \n.
-def expandtabs(s, tabsize=8):
- """expandtabs(s [,tabsize]) -> string
-
- Return a copy of the string s with all tab characters replaced
- by the appropriate number of spaces, depending on the current
- column, and the tabsize (default 8).
-
- """
- return s.expandtabs(tabsize)
-
-# Character translation through look-up table.
-def translate(s, table, deletions=""):
- """translate(s,table [,deletions]) -> string
-
- Return a copy of the string s, where all characters occurring
- in the optional argument deletions are removed, and the
- remaining characters have been mapped through the given
- translation table, which must be a string of length 256. The
- deletions argument is not allowed for Unicode strings.
-
- """
- if deletions:
- return s.translate(table, deletions)
- else:
- # Add s[:0] so that if s is Unicode and table is an 8-bit string,
- # table is converted to Unicode. This means that table *cannot*
- # be a dictionary -- for that feature, use u.translate() directly.
- return s.translate(table + s[:0])
-
-# Capitalize a string, e.g. "aBc dEf" -> "Abc def".
-def capitalize(s):
- """capitalize(s) -> string
-
- Return a copy of the string s with only its first character
- capitalized.
-
- """
- return s.capitalize()
-
-# Substring replacement (global)
-def replace(s, old, new, maxsplit=-1):
- """replace (str, old, new[, maxsplit]) -> string
-
- Return a copy of string str with all occurrences of substring
- old replaced by new. If the optional argument maxsplit is
- given, only the first maxsplit occurrences are replaced.
-
- """
- return s.replace(old, new, maxsplit)
-
-
-# Try importing optional built-in module "strop" -- if it exists,
-# it redefines some string operations that are 100-1000 times faster.
-# It also defines values for whitespace, lowercase and uppercase
-# that match <ctype.h>'s definitions.
-
-try:
- from strop import maketrans, lowercase, uppercase, whitespace
- letters = lowercase + uppercase
-except ImportError:
- pass # Use the original versions
diff --git a/paste/util/subprocess24.py b/paste/util/subprocess24.py
deleted file mode 100644
index 57ec119..0000000
--- a/paste/util/subprocess24.py
+++ /dev/null
@@ -1,1152 +0,0 @@
-# subprocess - Subprocesses with accessible I/O streams
-#
-# For more information about this module, see PEP 324.
-#
-# This module should remain compatible with Python 2.2, see PEP 291.
-#
-# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se>
-#
-# Licensed to PSF under a Contributor Agreement.
-# See http://www.python.org/2.4/license for licensing details.
-
-r"""subprocess - Subprocesses with accessible I/O streams
-
-This module allows you to spawn processes, connect to their
-input/output/error pipes, and obtain their return codes. This module
-intends to replace several other, older modules and functions, like:
-
-os.system
-os.spawn*
-os.popen*
-popen2.*
-commands.*
-
-Information about how the subprocess module can be used to replace these
-modules and functions can be found below.
-
-
-
-Using the subprocess module
-===========================
-This module defines one class called Popen:
-
-class Popen(args, bufsize=0, executable=None,
- stdin=None, stdout=None, stderr=None,
- preexec_fn=None, close_fds=False, shell=False,
- cwd=None, env=None, universal_newlines=False,
- startupinfo=None, creationflags=0):
-
-
-Arguments are:
-
-args should be a string, or a sequence of program arguments. The
-program to execute is normally the first item in the args sequence or
-string, but can be explicitly set by using the executable argument.
-
-On UNIX, with shell=False (default): In this case, the Popen class
-uses os.execvp() to execute the child program. args should normally
-be a sequence. A string will be treated as a sequence with the string
-as the only item (the program to execute).
-
-On UNIX, with shell=True: If args is a string, it specifies the
-command string to execute through the shell. If args is a sequence,
-the first item specifies the command string, and any additional items
-will be treated as additional shell arguments.
-
-On Windows: the Popen class uses CreateProcess() to execute the child
-program, which operates on strings. If args is a sequence, it will be
-converted to a string using the list2cmdline method. Please note that
-not all MS Windows applications interpret the command line the same
-way: The list2cmdline is designed for applications using the same
-rules as the MS C runtime.
-
-bufsize, if given, has the same meaning as the corresponding argument
-to the built-in open() function: 0 means unbuffered, 1 means line
-buffered, any other positive value means use a buffer of
-(approximately) that size. A negative bufsize means to use the system
-default, which usually means fully buffered. The default value for
-bufsize is 0 (unbuffered).
-
-stdin, stdout and stderr specify the executed programs' standard
-input, standard output and standard error file handles, respectively.
-Valid values are PIPE, an existing file descriptor (a positive
-integer), an existing file object, and None. PIPE indicates that a
-new pipe to the child should be created. With None, no redirection
-will occur; the child's file handles will be inherited from the
-parent. Additionally, stderr can be STDOUT, which indicates that the
-stderr data from the applications should be captured into the same
-file handle as for stdout.
-
-If preexec_fn is set to a callable object, this object will be called
-in the child process just before the child is executed.
-
-If close_fds is true, all file descriptors except 0, 1 and 2 will be
-closed before the child process is executed.
-
-if shell is true, the specified command will be executed through the
-shell.
-
-If cwd is not None, the current directory will be changed to cwd
-before the child is executed.
-
-If env is not None, it defines the environment variables for the new
-process.
-
-If universal_newlines is true, the file objects stdout and stderr are
-opened as a text files, but lines may be terminated by any of '\n',
-the Unix end-of-line convention, '\r', the Macintosh convention or
-'\r\n', the Windows convention. All of these external representations
-are seen as '\n' by the Python program. Note: This feature is only
-available if Python is built with universal newline support (the
-default). Also, the newlines attribute of the file objects stdout,
-stdin and stderr are not updated by the communicate() method.
-
-The startupinfo and creationflags, if given, will be passed to the
-underlying CreateProcess() function. They can specify things such as
-appearance of the main window and priority for the new process.
-(Windows only)
-
-
-This module also defines two shortcut functions:
-
-call(*args, **kwargs):
- Run command with arguments. Wait for command to complete, then
- return the returncode attribute. The arguments are the same as for
- the Popen constructor. Example:
-
- retcode = call(["ls", "-l"])
-
-
-Exceptions
-----------
-Exceptions raised in the child process, before the new program has
-started to execute, will be re-raised in the parent. Additionally,
-the exception object will have one extra attribute called
-'child_traceback', which is a string containing traceback information
-from the childs point of view.
-
-The most common exception raised is OSError. This occurs, for
-example, when trying to execute a non-existent file. Applications
-should prepare for OSErrors.
-
-A ValueError will be raised if Popen is called with invalid arguments.
-
-
-Security
---------
-Unlike some other popen functions, this implementation will never call
-/bin/sh implicitly. This means that all characters, including shell
-metacharacters, can safely be passed to child processes.
-
-
-Popen objects
-=============
-Instances of the Popen class have the following methods:
-
-poll()
- Check if child process has terminated. Returns returncode
- attribute.
-
-wait()
- Wait for child process to terminate. Returns returncode attribute.
-
-communicate(input=None)
- Interact with process: Send data to stdin. Read data from stdout
- and stderr, until end-of-file is reached. Wait for process to
- terminate. The optional stdin argument should be a string to be
- sent to the child process, or None, if no data should be sent to
- the child.
-
- communicate() returns a tuple (stdout, stderr).
-
- Note: The data read is buffered in memory, so do not use this
- method if the data size is large or unlimited.
-
-The following attributes are also available:
-
-stdin
- If the stdin argument is PIPE, this attribute is a file object
- that provides input to the child process. Otherwise, it is None.
-
-stdout
- If the stdout argument is PIPE, this attribute is a file object
- that provides output from the child process. Otherwise, it is
- None.
-
-stderr
- If the stderr argument is PIPE, this attribute is file object that
- provides error output from the child process. Otherwise, it is
- None.
-
-pid
- The process ID of the child process.
-
-returncode
- The child return code. A None value indicates that the process
- hasn't terminated yet. A negative value -N indicates that the
- child was terminated by signal N (UNIX only).
-
-
-Replacing older functions with the subprocess module
-====================================================
-In this section, "a ==> b" means that b can be used as a replacement
-for a.
-
-Note: All functions in this section fail (more or less) silently if
-the executed program cannot be found; this module raises an OSError
-exception.
-
-In the following examples, we assume that the subprocess module is
-imported with "from subprocess import *".
-
-
-Replacing /bin/sh shell backquote
----------------------------------
-output=`mycmd myarg`
-==>
-output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0]
-
-
-Replacing shell pipe line
--------------------------
-output=`dmesg | grep hda`
-==>
-p1 = Popen(["dmesg"], stdout=PIPE)
-p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
-output = p2.communicate()[0]
-
-
-Replacing os.system()
----------------------
-sts = os.system("mycmd" + " myarg")
-==>
-p = Popen("mycmd" + " myarg", shell=True)
-sts = os.waitpid(p.pid, 0)
-
-Note:
-
-* Calling the program through the shell is usually not required.
-
-* It's easier to look at the returncode attribute than the
- exitstatus.
-
-A more real-world example would look like this:
-
-try:
- retcode = call("mycmd" + " myarg", shell=True)
- if retcode < 0:
- print >>sys.stderr, "Child was terminated by signal", -retcode
- else:
- print >>sys.stderr, "Child returned", retcode
-except OSError, e:
- print >>sys.stderr, "Execution failed:", e
-
-
-Replacing os.spawn*
--------------------
-P_NOWAIT example:
-
-pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg")
-==>
-pid = Popen(["/bin/mycmd", "myarg"]).pid
-
-
-P_WAIT example:
-
-retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg")
-==>
-retcode = call(["/bin/mycmd", "myarg"])
-
-
-Vector example:
-
-os.spawnvp(os.P_NOWAIT, path, args)
-==>
-Popen([path] + args[1:])
-
-
-Environment example:
-
-os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env)
-==>
-Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"})
-
-
-Replacing os.popen*
--------------------
-pipe = os.popen(cmd, mode='r', bufsize)
-==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout
-
-pipe = os.popen(cmd, mode='w', bufsize)
-==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin
-
-
-(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
- stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdin, child_stdout) = (p.stdin, p.stdout)
-
-
-(child_stdin,
- child_stdout,
- child_stderr) = os.popen3(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
- stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
-(child_stdin,
- child_stdout,
- child_stderr) = (p.stdin, p.stdout, p.stderr)
-
-
-(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
- stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
-(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)
-
-
-Replacing popen2.*
-------------------
-Note: If the cmd argument to popen2 functions is a string, the command
-is executed through /bin/sh. If it is a list, the command is directly
-executed.
-
-(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode)
-==>
-p = Popen(["somestring"], shell=True, bufsize=bufsize
- stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdout, child_stdin) = (p.stdout, p.stdin)
-
-
-(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode)
-==>
-p = Popen(["mycmd", "myarg"], bufsize=bufsize,
- stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdout, child_stdin) = (p.stdout, p.stdin)
-
-The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen,
-except that:
-
-* subprocess.Popen raises an exception if the execution fails
-* the capturestderr argument is replaced with the stderr argument.
-* stdin=PIPE and stdout=PIPE must be specified.
-* popen2 closes all filedescriptors by default, but you have to specify
- close_fds=True with subprocess.Popen.
-
-
-"""
-
-import sys
-mswindows = (sys.platform == "win32")
-
-import os
-import types
-import traceback
-
-if mswindows:
- import threading
- import msvcrt
- ## @@: Changed in Paste
- ## Since this module is only used on pre-python-2.4 systems, they probably
- ## don't have _subprocess installed, but hopefully have the win32 stuff
- ## installed.
- if 1: # <-- change this to use pywin32 instead of the _subprocess driver
- import pywintypes
- from win32api import GetStdHandle, STD_INPUT_HANDLE, \
- STD_OUTPUT_HANDLE, STD_ERROR_HANDLE
- from win32api import GetCurrentProcess, DuplicateHandle, \
- GetModuleFileName, GetVersion
- from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE
- from win32pipe import CreatePipe
- from win32process import CreateProcess, STARTUPINFO, \
- GetExitCodeProcess, STARTF_USESTDHANDLES, \
- STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
- from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0
- else:
- from _subprocess import *
- class STARTUPINFO:
- dwFlags = 0
- hStdInput = None
- hStdOutput = None
- hStdError = None
- class pywintypes:
- error = IOError
-else:
- import select
- import errno
- import fcntl
- import pickle
-
-__all__ = ["Popen", "PIPE", "STDOUT", "call"]
-
-try:
- MAXFD = os.sysconf("SC_OPEN_MAX")
-except:
- MAXFD = 256
-
-# True/False does not exist on 2.2.0
-try:
- False
-except NameError:
- False = 0
- True = 1
-
-_active = []
-
-def _cleanup():
- for inst in _active[:]:
- inst.poll()
-
-PIPE = -1
-STDOUT = -2
-
-
-def call(*args, **kwargs):
- """Run command with arguments. Wait for command to complete, then
- return the returncode attribute.
-
- The arguments are the same as for the Popen constructor. Example:
-
- retcode = call(["ls", "-l"])
- """
- return Popen(*args, **kwargs).wait()
-
-
-def list2cmdline(seq):
- """
- Translate a sequence of arguments into a command line
- string, using the same rules as the MS C runtime:
-
- 1) Arguments are delimited by white space, which is either a
- space or a tab.
-
- 2) A string surrounded by double quotation marks is
- interpreted as a single argument, regardless of white space
- contained within. A quoted string can be embedded in an
- argument.
-
- 3) A double quotation mark preceded by a backslash is
- interpreted as a literal double quotation mark.
-
- 4) Backslashes are interpreted literally, unless they
- immediately precede a double quotation mark.
-
- 5) If backslashes immediately precede a double quotation mark,
- every pair of backslashes is interpreted as a literal
- backslash. If the number of backslashes is odd, the last
- backslash escapes the next double quotation mark as
- described in rule 3.
- """
-
- # See
- # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp
- result = []
- needquote = False
- for arg in seq:
- bs_buf = []
-
- # Add a space to separate this argument from the others
- if result:
- result.append(' ')
-
- needquote = (" " in arg) or ("\t" in arg)
- if needquote:
- result.append('"')
-
- for c in arg:
- if c == '\\':
- # Don't know if we need to double yet.
- bs_buf.append(c)
- elif c == '"':
- # Double backspaces.
- result.append('\\' * len(bs_buf)*2)
- bs_buf = []
- result.append('\\"')
- else:
- # Normal char
- if bs_buf:
- result.extend(bs_buf)
- bs_buf = []
- result.append(c)
-
- # Add remaining backspaces, if any.
- if bs_buf:
- result.extend(bs_buf)
-
- if needquote:
- result.extend(bs_buf)
- result.append('"')
-
- return ''.join(result)
-
-
-class Popen(object):
- def __init__(self, args, bufsize=0, executable=None,
- stdin=None, stdout=None, stderr=None,
- preexec_fn=None, close_fds=False, shell=False,
- cwd=None, env=None, universal_newlines=False,
- startupinfo=None, creationflags=0):
- """Create new Popen instance."""
- _cleanup()
-
- if not isinstance(bufsize, (int, long)):
- raise TypeError("bufsize must be an integer")
-
- if mswindows:
- if preexec_fn is not None:
- raise ValueError("preexec_fn is not supported on Windows "
- "platforms")
- if close_fds:
- raise ValueError("close_fds is not supported on Windows "
- "platforms")
- else:
- # POSIX
- if startupinfo is not None:
- raise ValueError("startupinfo is only supported on Windows "
- "platforms")
- if creationflags != 0:
- raise ValueError("creationflags is only supported on Windows "
- "platforms")
-
- self.stdin = None
- self.stdout = None
- self.stderr = None
- self.pid = None
- self.returncode = None
- self.universal_newlines = universal_newlines
-
- # Input and output objects. The general principle is like
- # this:
- #
- # Parent Child
- # ------ -----
- # p2cwrite ---stdin---> p2cread
- # c2pread <--stdout--- c2pwrite
- # errread <--stderr--- errwrite
- #
- # On POSIX, the child objects are file descriptors. On
- # Windows, these are Windows file handles. The parent objects
- # are file descriptors on both platforms. The parent objects
- # are None when not using PIPEs. The child objects are None
- # when not redirecting.
-
- (p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite) = self._get_handles(stdin, stdout, stderr)
-
- self._execute_child(args, executable, preexec_fn, close_fds,
- cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite)
-
- if p2cwrite:
- self.stdin = os.fdopen(p2cwrite, 'wb', bufsize)
- if c2pread:
- if universal_newlines:
- self.stdout = os.fdopen(c2pread, 'rU', bufsize)
- else:
- self.stdout = os.fdopen(c2pread, 'rb', bufsize)
- if errread:
- if universal_newlines:
- self.stderr = os.fdopen(errread, 'rU', bufsize)
- else:
- self.stderr = os.fdopen(errread, 'rb', bufsize)
-
- _active.append(self)
-
-
- def _translate_newlines(self, data):
- data = data.replace("\r\n", "\n")
- data = data.replace("\r", "\n")
- return data
-
-
- if mswindows:
- #
- # Windows methods
- #
- def _get_handles(self, stdin, stdout, stderr):
- """Construct and return tupel with IO objects:
- p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
- """
- if stdin == None and stdout == None and stderr == None:
- return (None, None, None, None, None, None)
-
- p2cread, p2cwrite = None, None
- c2pread, c2pwrite = None, None
- errread, errwrite = None, None
-
- if stdin == None:
- p2cread = GetStdHandle(STD_INPUT_HANDLE)
- elif stdin == PIPE:
- p2cread, p2cwrite = CreatePipe(None, 0)
- # Detach and turn into fd
- p2cwrite = p2cwrite.Detach()
- p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0)
- elif type(stdin) == types.IntType:
- p2cread = msvcrt.get_osfhandle(stdin)
- else:
- # Assuming file-like object
- p2cread = msvcrt.get_osfhandle(stdin.fileno())
- p2cread = self._make_inheritable(p2cread)
-
- if stdout == None:
- c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE)
- elif stdout == PIPE:
- c2pread, c2pwrite = CreatePipe(None, 0)
- # Detach and turn into fd
- c2pread = c2pread.Detach()
- c2pread = msvcrt.open_osfhandle(c2pread, 0)
- elif type(stdout) == types.IntType:
- c2pwrite = msvcrt.get_osfhandle(stdout)
- else:
- # Assuming file-like object
- c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
- c2pwrite = self._make_inheritable(c2pwrite)
-
- if stderr == None:
- errwrite = GetStdHandle(STD_ERROR_HANDLE)
- elif stderr == PIPE:
- errread, errwrite = CreatePipe(None, 0)
- # Detach and turn into fd
- errread = errread.Detach()
- errread = msvcrt.open_osfhandle(errread, 0)
- elif stderr == STDOUT:
- errwrite = c2pwrite
- elif type(stderr) == types.IntType:
- errwrite = msvcrt.get_osfhandle(stderr)
- else:
- # Assuming file-like object
- errwrite = msvcrt.get_osfhandle(stderr.fileno())
- errwrite = self._make_inheritable(errwrite)
-
- return (p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite)
-
-
- def _make_inheritable(self, handle):
- """Return a duplicate of handle, which is inheritable"""
- return DuplicateHandle(GetCurrentProcess(), handle,
- GetCurrentProcess(), 0, 1,
- DUPLICATE_SAME_ACCESS)
-
-
- def _find_w9xpopen(self):
- """Find and return absolut path to w9xpopen.exe"""
- w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)),
- "w9xpopen.exe")
- if not os.path.exists(w9xpopen):
- # Eeek - file-not-found - possibly an embedding
- # situation - see if we can locate it in sys.exec_prefix
- w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix),
- "w9xpopen.exe")
- if not os.path.exists(w9xpopen):
- raise RuntimeError("Cannot locate w9xpopen.exe, which is "
- "needed for Popen to work with your "
- "shell or platform.")
- return w9xpopen
-
-
- def _execute_child(self, args, executable, preexec_fn, close_fds,
- cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite):
- """Execute program (MS Windows version)"""
-
- if not isinstance(args, types.StringTypes):
- args = list2cmdline(args)
-
- # Process startup details
- default_startupinfo = STARTUPINFO()
- if startupinfo == None:
- startupinfo = default_startupinfo
- if not None in (p2cread, c2pwrite, errwrite):
- startupinfo.dwFlags |= STARTF_USESTDHANDLES
- startupinfo.hStdInput = p2cread
- startupinfo.hStdOutput = c2pwrite
- startupinfo.hStdError = errwrite
-
- if shell:
- default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW
- default_startupinfo.wShowWindow = SW_HIDE
- comspec = os.environ.get("COMSPEC", "cmd.exe")
- args = comspec + " /c " + args
- if (GetVersion() >= 0x80000000L or
- os.path.basename(comspec).lower() == "command.com"):
- # Win9x, or using command.com on NT. We need to
- # use the w9xpopen intermediate program. For more
- # information, see KB Q150956
- # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp)
- w9xpopen = self._find_w9xpopen()
- args = '"%s" %s' % (w9xpopen, args)
- # Not passing CREATE_NEW_CONSOLE has been known to
- # cause random failures on win9x. Specifically a
- # dialog: "Your program accessed mem currently in
- # use at xxx" and a hopeful warning about the
- # stability of your system. Cost is Ctrl+C wont
- # kill children.
- creationflags |= CREATE_NEW_CONSOLE
-
- # Start the process
- try:
- hp, ht, pid, tid = CreateProcess(executable, args,
- # no special security
- None, None,
- # must inherit handles to pass std
- # handles
- 1,
- creationflags,
- env,
- cwd,
- startupinfo)
- except pywintypes.error, e:
- # Translate pywintypes.error to WindowsError, which is
- # a subclass of OSError. FIXME: We should really
- # translate errno using _sys_errlist (or simliar), but
- # how can this be done from Python?
- raise WindowsError(*e.args)
-
- # Retain the process handle, but close the thread handle
- self._handle = hp
- self.pid = pid
- ht.Close()
-
- # Child is launched. Close the parent's copy of those pipe
- # handles that only the child should have open. You need
- # to make sure that no handles to the write end of the
- # output pipe are maintained in this process or else the
- # pipe will not close when the child process exits and the
- # ReadFile will hang.
- if p2cread != None:
- p2cread.Close()
- if c2pwrite != None:
- c2pwrite.Close()
- if errwrite != None:
- errwrite.Close()
-
-
- def poll(self):
- """Check if child process has terminated. Returns returncode
- attribute."""
- if self.returncode == None:
- if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0:
- self.returncode = GetExitCodeProcess(self._handle)
- _active.remove(self)
- return self.returncode
-
-
- def wait(self):
- """Wait for child process to terminate. Returns returncode
- attribute."""
- if self.returncode == None:
- obj = WaitForSingleObject(self._handle, INFINITE)
- self.returncode = GetExitCodeProcess(self._handle)
- _active.remove(self)
- return self.returncode
-
-
- def _readerthread(self, fh, buffer):
- buffer.append(fh.read())
-
-
- def communicate(self, input=None):
- """Interact with process: Send data to stdin. Read data from
- stdout and stderr, until end-of-file is reached. Wait for
- process to terminate. The optional input argument should be a
- string to be sent to the child process, or None, if no data
- should be sent to the child.
-
- communicate() returns a tuple (stdout, stderr)."""
- stdout = None # Return
- stderr = None # Return
-
- if self.stdout:
- stdout = []
- stdout_thread = threading.Thread(target=self._readerthread,
- args=(self.stdout, stdout))
- stdout_thread.setDaemon(True)
- stdout_thread.start()
- if self.stderr:
- stderr = []
- stderr_thread = threading.Thread(target=self._readerthread,
- args=(self.stderr, stderr))
- stderr_thread.setDaemon(True)
- stderr_thread.start()
-
- if self.stdin:
- if input != None:
- self.stdin.write(input)
- self.stdin.close()
-
- if self.stdout:
- stdout_thread.join()
- if self.stderr:
- stderr_thread.join()
-
- # All data exchanged. Translate lists into strings.
- if stdout != None:
- stdout = stdout[0]
- if stderr != None:
- stderr = stderr[0]
-
- # Translate newlines, if requested. We cannot let the file
- # object do the translation: It is based on stdio, which is
- # impossible to combine with select (unless forcing no
- # buffering).
- if self.universal_newlines and hasattr(open, 'newlines'):
- if stdout:
- stdout = self._translate_newlines(stdout)
- if stderr:
- stderr = self._translate_newlines(stderr)
-
- self.wait()
- return (stdout, stderr)
-
- else:
- #
- # POSIX methods
- #
- def _get_handles(self, stdin, stdout, stderr):
- """Construct and return tupel with IO objects:
- p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
- """
- p2cread, p2cwrite = None, None
- c2pread, c2pwrite = None, None
- errread, errwrite = None, None
-
- if stdin == None:
- pass
- elif stdin == PIPE:
- p2cread, p2cwrite = os.pipe()
- elif type(stdin) == types.IntType:
- p2cread = stdin
- else:
- # Assuming file-like object
- p2cread = stdin.fileno()
-
- if stdout == None:
- pass
- elif stdout == PIPE:
- c2pread, c2pwrite = os.pipe()
- elif type(stdout) == types.IntType:
- c2pwrite = stdout
- else:
- # Assuming file-like object
- c2pwrite = stdout.fileno()
-
- if stderr == None:
- pass
- elif stderr == PIPE:
- errread, errwrite = os.pipe()
- elif stderr == STDOUT:
- errwrite = c2pwrite
- elif type(stderr) == types.IntType:
- errwrite = stderr
- else:
- # Assuming file-like object
- errwrite = stderr.fileno()
-
- return (p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite)
-
-
- def _set_cloexec_flag(self, fd):
- try:
- cloexec_flag = fcntl.FD_CLOEXEC
- except AttributeError:
- cloexec_flag = 1
-
- old = fcntl.fcntl(fd, fcntl.F_GETFD)
- fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag)
-
-
- def _close_fds(self, but):
- for i in range(3, MAXFD):
- if i == but:
- continue
- try:
- os.close(i)
- except:
- pass
-
-
- def _execute_child(self, args, executable, preexec_fn, close_fds,
- cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite):
- """Execute program (POSIX version)"""
-
- if isinstance(args, types.StringTypes):
- args = [args]
-
- if shell:
- args = ["/bin/sh", "-c"] + args
-
- if executable == None:
- executable = args[0]
-
- # For transferring possible exec failure from child to parent
- # The first char specifies the exception type: 0 means
- # OSError, 1 means some other error.
- errpipe_read, errpipe_write = os.pipe()
- self._set_cloexec_flag(errpipe_write)
-
- self.pid = os.fork()
- if self.pid == 0:
- # Child
- try:
- # Close parent's pipe ends
- if p2cwrite:
- os.close(p2cwrite)
- if c2pread:
- os.close(c2pread)
- if errread:
- os.close(errread)
- os.close(errpipe_read)
-
- # Dup fds for child
- if p2cread:
- os.dup2(p2cread, 0)
- if c2pwrite:
- os.dup2(c2pwrite, 1)
- if errwrite:
- os.dup2(errwrite, 2)
-
- # Close pipe fds. Make sure we doesn't close the same
- # fd more than once.
- if p2cread:
- os.close(p2cread)
- if c2pwrite and c2pwrite not in (p2cread,):
- os.close(c2pwrite)
- if errwrite and errwrite not in (p2cread, c2pwrite):
- os.close(errwrite)
-
- # Close all other fds, if asked for
- if close_fds:
- self._close_fds(but=errpipe_write)
-
- if cwd != None:
- os.chdir(cwd)
-
- if preexec_fn:
- apply(preexec_fn)
-
- if env == None:
- os.execvp(executable, args)
- else:
- os.execvpe(executable, args, env)
-
- except:
- exc_type, exc_value, tb = sys.exc_info()
- # Save the traceback and attach it to the exception object
- exc_lines = traceback.format_exception(exc_type,
- exc_value,
- tb)
- exc_value.child_traceback = ''.join(exc_lines)
- os.write(errpipe_write, pickle.dumps(exc_value))
-
- # This exitcode won't be reported to applications, so it
- # really doesn't matter what we return.
- os._exit(255)
-
- # Parent
- os.close(errpipe_write)
- if p2cread and p2cwrite:
- os.close(p2cread)
- if c2pwrite and c2pread:
- os.close(c2pwrite)
- if errwrite and errread:
- os.close(errwrite)
-
- # Wait for exec to fail or succeed; possibly raising exception
- data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB
- os.close(errpipe_read)
- if data != "":
- os.waitpid(self.pid, 0)
- child_exception = pickle.loads(data)
- raise child_exception
-
-
- def _handle_exitstatus(self, sts):
- if os.WIFSIGNALED(sts):
- self.returncode = -os.WTERMSIG(sts)
- elif os.WIFEXITED(sts):
- self.returncode = os.WEXITSTATUS(sts)
- else:
- # Should never happen
- raise RuntimeError("Unknown child exit status!")
-
- _active.remove(self)
-
-
- def poll(self):
- """Check if child process has terminated. Returns returncode
- attribute."""
- if self.returncode == None:
- try:
- pid, sts = os.waitpid(self.pid, os.WNOHANG)
- if pid == self.pid:
- self._handle_exitstatus(sts)
- except os.error:
- pass
- return self.returncode
-
-
- def wait(self):
- """Wait for child process to terminate. Returns returncode
- attribute."""
- if self.returncode == None:
- pid, sts = os.waitpid(self.pid, 0)
- self._handle_exitstatus(sts)
- return self.returncode
-
-
- def communicate(self, input=None):
- """Interact with process: Send data to stdin. Read data from
- stdout and stderr, until end-of-file is reached. Wait for
- process to terminate. The optional input argument should be a
- string to be sent to the child process, or None, if no data
- should be sent to the child.
-
- communicate() returns a tuple (stdout, stderr)."""
- read_set = []
- write_set = []
- stdout = None # Return
- stderr = None # Return
-
- if self.stdin:
- # Flush stdio buffer. This might block, if the user has
- # been writing to .stdin in an uncontrolled fashion.
- self.stdin.flush()
- if input:
- write_set.append(self.stdin)
- else:
- self.stdin.close()
- if self.stdout:
- read_set.append(self.stdout)
- stdout = []
- if self.stderr:
- read_set.append(self.stderr)
- stderr = []
-
- while read_set or write_set:
- rlist, wlist, xlist = select.select(read_set, write_set, [])
-
- if self.stdin in wlist:
- # When select has indicated that the file is writable,
- # we can write up to PIPE_BUF bytes without risk
- # blocking. POSIX defines PIPE_BUF >= 512
- bytes_written = os.write(self.stdin.fileno(), input[:512])
- input = input[bytes_written:]
- if not input:
- self.stdin.close()
- write_set.remove(self.stdin)
-
- if self.stdout in rlist:
- data = os.read(self.stdout.fileno(), 1024)
- if data == "":
- self.stdout.close()
- read_set.remove(self.stdout)
- stdout.append(data)
-
- if self.stderr in rlist:
- data = os.read(self.stderr.fileno(), 1024)
- if data == "":
- self.stderr.close()
- read_set.remove(self.stderr)
- stderr.append(data)
-
- # All data exchanged. Translate lists into strings.
- if stdout != None:
- stdout = ''.join(stdout)
- if stderr != None:
- stderr = ''.join(stderr)
-
- # Translate newlines, if requested. We cannot let the file
- # object do the translation: It is based on stdio, which is
- # impossible to combine with select (unless forcing no
- # buffering).
- if self.universal_newlines and hasattr(open, 'newlines'):
- if stdout:
- stdout = self._translate_newlines(stdout)
- if stderr:
- stderr = self._translate_newlines(stderr)
-
- self.wait()
- return (stdout, stderr)
-
-
-def _demo_posix():
- #
- # Example 1: Simple redirection: Get process list
- #
- plist = Popen(["ps"], stdout=PIPE).communicate()[0]
- print "Process list:"
- print plist
-
- #
- # Example 2: Change uid before executing child
- #
- if os.getuid() == 0:
- p = Popen(["id"], preexec_fn=lambda: os.setuid(100))
- p.wait()
-
- #
- # Example 3: Connecting several subprocesses
- #
- print "Looking for 'hda'..."
- p1 = Popen(["dmesg"], stdout=PIPE)
- p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
- print repr(p2.communicate()[0])
-
- #
- # Example 4: Catch execution error
- #
- print
- print "Trying a weird file..."
- try:
- print Popen(["/this/path/does/not/exist"]).communicate()
- except OSError, e:
- if e.errno == errno.ENOENT:
- print "The file didn't exist. I thought so..."
- print "Child traceback:"
- print e.child_traceback
- else:
- print "Error", e.errno
- else:
- print >>sys.stderr, "Gosh. No error."
-
-
-def _demo_windows():
- #
- # Example 1: Connecting several subprocesses
- #
- print "Looking for 'PROMPT' in set output..."
- p1 = Popen("set", stdout=PIPE, shell=True)
- p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE)
- print repr(p2.communicate()[0])
-
- #
- # Example 2: Simple execution of program
- #
- print "Executing calc..."
- p = Popen("calc")
- p.wait()
-
-
-if __name__ == "__main__":
- if mswindows:
- _demo_windows()
- else:
- _demo_posix()
diff --git a/paste/util/template.py b/paste/util/template.py
index afeb353..f0826af 100644
--- a/paste/util/template.py
+++ b/paste/util/template.py
@@ -78,7 +78,7 @@ class Template(object):
def __init__(self, content, name=None, namespace=None):
self.content = content
- self._unicode = isinstance(content, unicode)
+ self._unicode = isinstance(content, six.text_type)
self.name = name
self._parsed = parse(content, name=name)
if namespace is None:
@@ -227,7 +227,7 @@ class Template(object):
return ''
if self._unicode:
try:
- value = unicode(value)
+ value = six.text_type(value)
except UnicodeDecodeError:
value = str(value)
else:
@@ -238,20 +238,20 @@ class Template(object):
e.args = (self._add_line_info(e.args[0], pos),)
six.reraise(exc_info[0], e, exc_info[2])
else:
- if self._unicode and isinstance(value, str):
+ if self._unicode and isinstance(value, six.binary_type):
if not self.decode_encoding:
raise UnicodeDecodeError(
'Cannot decode str value %r into unicode '
'(no default_encoding provided)' % value)
value = value.decode(self.default_encoding)
- elif not self._unicode and isinstance(value, unicode):
+ elif not self._unicode and isinstance(value, six.text_type):
if not self.decode_encoding:
raise UnicodeEncodeError(
'Cannot encode unicode value %r into str '
'(no default_encoding provided)' % value)
value = value.encode(self.default_encoding)
return value
-
+
def _add_line_info(self, msg, pos):
msg = "%s at line %s column %s" % (
@@ -264,7 +264,6 @@ def sub(content, **kw):
name = kw.get('__name')
tmpl = Template(content, name=name)
return tmpl.substitute(kw)
- return result
def paste_script_template_renderer(content, vars, filename=None):
tmpl = Template(content, name=filename)
@@ -370,7 +369,6 @@ def sub_html(content, **kw):
name = kw.get('__name')
tmpl = HTMLTemplate(content, name=name)
return tmpl.substitute(kw)
- return result
############################################################
@@ -479,7 +477,7 @@ def trim_lex(tokens):
next = next[m.end():]
tokens[i+1] = next
return tokens
-
+
def find_position(string, index):
"""Given a string and index, return (line, column)"""
@@ -506,7 +504,7 @@ def parse(s, name=None):
[('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]
Some exceptions::
-
+
>>> parse('{{continue}}')
Traceback (most recent call last):
...
@@ -627,7 +625,7 @@ def parse_one_cond(tokens, name, context):
return part, tokens
next, tokens = parse_expr(tokens, name, context)
content.append(next)
-
+
def parse_for(tokens, name, context):
first, pos = tokens[0]
tokens = tokens[1:]
@@ -755,5 +753,5 @@ def fill_command(args=None):
if __name__ == '__main__':
from paste.util.template import fill_command
fill_command()
-
-
+
+
diff --git a/paste/util/threadedprint.py b/paste/util/threadedprint.py
index ae7dc0d..820311e 100644
--- a/paste/util/threadedprint.py
+++ b/paste/util/threadedprint.py
@@ -114,7 +114,7 @@ class PrintCatcher(filemixin.FileMixin):
self._default.read(*args)
else:
catchers[name].read(*args)
-
+
def _writedefault(self, name, v):
self._default.write(v)
@@ -237,8 +237,8 @@ def install_stdin(**kw):
register_stdin = _stdincatcher.register
deregister_stdin = _stdincatcher.deregister
-def uninstall():
- global _stdincatcher, _oldstin, register_stdin, deregister_stdin
+def uninstall_stdin():
+ global _stdincatcher, _oldstdin, register_stdin, deregister_stdin
if _stdincatcher:
sys.stdin = _oldstdin
_stdincatcher = _oldstdin = None
diff --git a/paste/wsgilib.py b/paste/wsgilib.py
index 1234ef9..98299e2 100644
--- a/paste/wsgilib.py
+++ b/paste/wsgilib.py
@@ -81,7 +81,8 @@ class add_start_close(object):
if self.first:
self.start_func()
self.first = False
- return self.app_iter.next()
+ return next(self.app_iter)
+ __next__ = next
def close(self):
self._closed = True
@@ -157,10 +158,11 @@ class encode_unicode_app_iter(object):
return self
def next(self):
- content = self.app_iter.next()
- if isinstance(content, unicode):
+ content = next(self.app_iter)
+ if isinstance(content, six.text_type):
content = content.encode(self.encoding, self.errors)
return content
+ __next__ = next
def close(self):
if hasattr(self.app_iterable, 'close'):
@@ -282,19 +284,19 @@ def raw_interactive(application, path='', raise_on_wsgi_error=False,
if raise_on_wsgi_error:
errors = ErrorRaiser()
else:
- errors = StringIO()
+ errors = six.BytesIO()
basic_environ = {
# mandatory CGI variables
'REQUEST_METHOD': 'GET', # always mandatory
'SCRIPT_NAME': '', # may be empty if app is at the root
'PATH_INFO': '', # may be empty if at root of app
'SERVER_NAME': 'localhost', # always mandatory
- 'SERVER_PORT': '80', # always mandatory
+ 'SERVER_PORT': '80', # always mandatory
'SERVER_PROTOCOL': 'HTTP/1.0',
# mandatory wsgi variables
'wsgi.version': (1, 0),
'wsgi.url_scheme': 'http',
- 'wsgi.input': StringIO(''),
+ 'wsgi.input': six.BytesIO(),
'wsgi.errors': errors,
'wsgi.multithread': False,
'wsgi.multiprocess': False,
@@ -315,8 +317,8 @@ def raw_interactive(application, path='', raise_on_wsgi_error=False,
and 'HTTP_HOST' not in basic_environ):
basic_environ['HTTP_HOST'] = basic_environ['SERVER_NAME']
istream = basic_environ['wsgi.input']
- if isinstance(istream, str):
- basic_environ['wsgi.input'] = StringIO(istream)
+ if isinstance(istream, bytes):
+ basic_environ['wsgi.input'] = six.BytesIO(istream)
basic_environ['CONTENT_LENGTH'] = len(istream)
data = {}
output = []
@@ -343,9 +345,9 @@ def raw_interactive(application, path='', raise_on_wsgi_error=False,
try:
try:
for s in app_iter:
- if not isinstance(s, str):
+ if not isinstance(s, six.binary_type):
raise ValueError(
- "The app_iter response can only contain str (not "
+ "The app_iter response can only contain bytes (not "
"unicode); got: %r" % s)
headers_sent.append(True)
if not headers_set:
@@ -359,7 +361,7 @@ def raw_interactive(application, path='', raise_on_wsgi_error=False,
finally:
if hasattr(app_iter, 'close'):
app_iter.close()
- return (data['status'], data['headers'], ''.join(output),
+ return (data['status'], data['headers'], b''.join(output),
errors.getvalue())
class ErrorRaiser(object):
@@ -416,6 +418,8 @@ def dump_environ(environ, start_response):
output.append(environ['wsgi.input'].read(int(content_length)))
output.append("\n")
output = "".join(output)
+ if six.PY3:
+ output = output.encode('utf8')
headers = [('Content-Type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response("200 OK", headers)
@@ -435,7 +439,7 @@ def capture_output(environ, start_response, application):
Sends status and header, but *not* body. Returns (status,
headers, body). Typically this is used like:
-
+
.. code-block:: python
def dehtmlifying_middleware(application):
@@ -486,7 +490,7 @@ def intercept_output(environ, application, conditional=None,
``capture_output``)
Typically this is used like:
-
+
.. code-block:: python
def dehtmlifying_middleware(application):
@@ -508,7 +512,7 @@ def intercept_output(environ, application, conditional=None,
``start_response`` will be called and ``(None, None, app_iter)``
will be returned. You must detect that in your code and return
the app_iter, like:
-
+
.. code-block:: python
def dehtmlifying_middleware(application):
@@ -593,4 +597,4 @@ for _name in __all__:
if __name__ == '__main__':
import doctest
doctest.testmod()
-
+
diff --git a/paste/wsgiwrappers.py b/paste/wsgiwrappers.py
index 9b614ec..1cbae4f 100644
--- a/paste/wsgiwrappers.py
+++ b/paste/wsgiwrappers.py
@@ -14,6 +14,7 @@ try:
except ImportError:
# Python 2
from Cookie import SimpleCookie
+import six
from paste.request import EnvironHeaders, get_cookie_dict, \
parse_dict_querystring, parse_formvars
@@ -84,18 +85,18 @@ class WSGIRequest(object):
The class variable ``defaults`` specifies default values for
``charset``, ``errors``, and ``langauge``. These can be overridden for the
current request via the registry.
-
+
The ``language`` default value is considered the fallback during i18n
translations to ensure in odd cases that mixed languages don't occur should
the ``language`` file contain the string but not another language in the
accepted languages list. The ``language`` value only applies when getting
a list of accepted languages from the HTTP Accept header.
-
+
This behavior is duplicated from Aquarium, and may seem strange but is
- very useful. Normally, everything in the code is in "en-us". However,
+ very useful. Normally, everything in the code is in "en-us". However,
the "en-us" translation catalog is usually empty. If the user requests
``["en-us", "zh-cn"]`` and a translation isn't found for a string in
- "en-us", you don't want gettext to fallback to "zh-cn". You want it to
+ "en-us", you don't want gettext to fallback to "zh-cn". You want it to
just use the string itself. Hence, if a string isn't found in the
``language`` catalog, the string in the source code will be used.
@@ -112,7 +113,7 @@ class WSGIRequest(object):
self.environ = environ
# This isn't "state" really, since the object is derivative:
self.headers = EnvironHeaders(environ)
-
+
defaults = self.defaults._current_obj()
self.charset = defaults.get('charset')
if self.charset:
@@ -124,7 +125,7 @@ class WSGIRequest(object):
self.errors = defaults.get('errors', 'strict')
self.decode_param_names = defaults.get('decode_param_names', False)
self._languages = None
-
+
body = environ_getter('wsgi.input')
scheme = environ_getter('wsgi.url_scheme')
method = environ_getter('REQUEST_METHOD')
@@ -143,12 +144,12 @@ class WSGIRequest(object):
else:
return {}
urlvars = property(urlvars, doc=urlvars.__doc__)
-
+
def is_xhr(self):
"""Returns a boolean if X-Requested-With is present and a XMLHttpRequest"""
return self.environ.get('HTTP_X_REQUESTED_WITH', '') == 'XMLHttpRequest'
is_xhr = property(is_xhr, doc=is_xhr.__doc__)
-
+
def host(self):
"""Host name provided in HTTP_HOST, with fall-back to SERVER_NAME"""
return self.environ.get('HTTP_HOST', self.environ.get('SERVER_NAME'))
@@ -156,7 +157,7 @@ class WSGIRequest(object):
def languages(self):
"""Return a list of preferred languages, most preferred first.
-
+
The list may be empty.
"""
if self._languages is not None:
@@ -173,7 +174,7 @@ class WSGIRequest(object):
self._languages = langs
return self._languages
languages = property(languages, doc=languages.__doc__)
-
+
def _GET(self):
return parse_dict_querystring(self.environ)
@@ -253,7 +254,7 @@ class WSGIRequest(object):
"""Dictionary of cookies keyed by cookie name.
Just a plain dictionary, may be empty but not None.
-
+
"""
return get_cookie_dict(self.environ)
cookies = property(cookies, doc=cookies.__doc__)
@@ -270,7 +271,7 @@ class WSGIRequest(object):
def match_accept(self, mimetypes):
"""Return a list of specified mime-types that the browser's HTTP Accept
header allows in the order provided."""
- return desired_matches(mimetypes,
+ return desired_matches(mimetypes,
self.environ.get('HTTP_ACCEPT', '*/*'))
def __repr__(self):
@@ -300,10 +301,10 @@ class WSGIResponse(object):
"""
defaults = StackedObjectProxy(
- default=dict(content_type='text/html', charset='utf-8',
+ default=dict(content_type='text/html', charset='utf-8',
errors='strict', headers={'Cache-Control':'no-cache'})
)
- def __init__(self, content='', mimetype=None, code=200):
+ def __init__(self, content=b'', mimetype=None, code=200):
self._iter = None
self._is_str_iter = True
@@ -335,14 +336,14 @@ class WSGIResponse(object):
return '\n'.join(['%s: %s' % (key, value)
for key, value in self.headers.headeritems()]) \
+ '\n\n' + content
-
+
def __call__(self, environ, start_response):
"""Convenience call to return output and set status information
-
+
Conforms to the WSGI interface for calling purposes only.
-
+
Example usage:
-
+
.. code-block:: python
def wsgi_app(environ, start_response):
@@ -350,7 +351,7 @@ class WSGIResponse(object):
response.write("Hello world")
response.headers['Content-Type'] = 'latin1'
return response(environ, start_response)
-
+
"""
status_text = STATUS_CODE_TEXT[self.status_code]
status = '%s %s' % (self.status_code, status_text)
@@ -364,7 +365,7 @@ class WSGIResponse(object):
elif is_file:
return iter(lambda: self.content.read(), '')
return self.get_content()
-
+
def determine_charset(self):
"""
Determine the encoding as specified by the Content-Type's charset
@@ -373,7 +374,7 @@ class WSGIResponse(object):
charset_match = _CHARSET_RE.search(self.headers.get('Content-Type', ''))
if charset_match:
return charset_match.group(1)
-
+
def has_header(self, header):
"""
Case-insensitive check for a header
@@ -409,7 +410,7 @@ class WSGIResponse(object):
self.cookies[key]['max-age'] = 0
def _set_content(self, content):
- if hasattr(content, '__iter__'):
+ if not isinstance(content, (six.binary_type, six.text_type)):
self._iter = content
if isinstance(content, list):
self._is_str_iter = True
@@ -434,7 +435,7 @@ class WSGIResponse(object):
return encode_unicode_app_iter(self.content, charset, self.errors)
else:
return self.content
-
+
def wsgi_response(self):
"""
Return this WSGIResponse as a tuple of WSGI formatted data, including:
@@ -446,12 +447,12 @@ class WSGIResponse(object):
for c in self.cookies.values():
response_headers.append(('Set-Cookie', c.output(header='')))
return status, response_headers, self.get_content()
-
+
# The remaining methods partially implement the file-like object interface.
# See http://docs.python.org/lib/bltin-file-objects.html
def write(self, content):
if not self._is_str_iter:
- raise IOError("This %s instance's content is not writable: (content "
+ raise IOError("This %s instance's content is not writable: (content "
'is an iterator)' % self.__class__.__name__)
self.content.append(content)
diff --git a/setup.cfg b/setup.cfg
index d79e2e3..e60fe4f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[egg_info]
-tag_build = bisque2
+tag_build =
tag_date = 0
tag_svn_revision = 0
diff --git a/setup.py b/setup.py
index b7e92b1..88ce93b 100644
--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,27 @@
+# Procedure to release a new version:
+#
+# - run tests: run tox
+# - update version in setup.py (__version__)
+# - update tag_build in setup.cfg
+# - update changelog: docs/news.txt
+# - modify setup.py: set RELEASE to True
+# - check that "python setup.py sdist" contains all files tracked by
+# the SCM (Mercurial): update MANIFEST.in if needed
+#
+# - hg ci
+# - hg tag VERSION
+# - hg push
+# - python2 setup.py register sdist bdist_wheel upload
+# - python3 setup.py bdist_wheel upload
+#
+# - increment version in setup.py (__version__)
+# - hg ci && hg push
+
# If true, then the svn revision won't be used to calculate the
# revision (set to True for real releases)
-RELEASE = False
+RELEASE = True
-__version__ = '1.7.5.1'
+__version__ = '2.0'
from setuptools import setup, find_packages
import sys, os
diff --git a/tests/cgiapp_data/error.cgi b/tests/cgiapp_data/error.cgi
index 5afc9c9..e11c766 100755
--- a/tests/cgiapp_data/error.cgi
+++ b/tests/cgiapp_data/error.cgi
@@ -1,3 +1,3 @@
#!/usr/bin/env python
-print 'hey you!'
+print('hey you!')
diff --git a/tests/cgiapp_data/form.cgi b/tests/cgiapp_data/form.cgi
index 6d2e038..2181998 100755
--- a/tests/cgiapp_data/form.cgi
+++ b/tests/cgiapp_data/form.cgi
@@ -2,11 +2,11 @@
import cgi
-print 'Content-type: text/plain'
-print
+print('Content-type: text/plain')
+print('')
form = cgi.FieldStorage()
-print 'Filename:', form['up'].filename
-print 'Name:', form['name'].value
-print 'Content:', form['up'].file.read()
+print('Filename: %s' % form['up'].filename)
+print('Name: %s' % form['name'].value)
+print('Content: %s' % form['up'].file.read())
diff --git a/tests/cgiapp_data/ok.cgi b/tests/cgiapp_data/ok.cgi
index 8b8eb29..d03f0b9 100755
--- a/tests/cgiapp_data/ok.cgi
+++ b/tests/cgiapp_data/ok.cgi
@@ -1,6 +1,5 @@
#!/usr/bin/env python
-
-print 'Content-type: text/html; charset=UTF-8'
-print 'Status: 200 Okay'
-print
-print 'This is the body'
+print('Content-type: text/html; charset=UTF-8')
+print('Status: 200 Okay')
+print('')
+print('This is the body')
diff --git a/tests/cgiapp_data/stderr.cgi b/tests/cgiapp_data/stderr.cgi
index 89dae0a..d2520b6 100755
--- a/tests/cgiapp_data/stderr.cgi
+++ b/tests/cgiapp_data/stderr.cgi
@@ -1,8 +1,8 @@
#!/usr/bin/env python
-
+from __future__ import print_function
import sys
-print 'Status: 500 Server Error'
-print 'Content-type: text/html'
-print
-print 'There was an error'
-print >> sys.stderr, 'some data on the error'
+print('Status: 500 Server Error')
+print('Content-type: text/html')
+print()
+print('There was an error')
+print('some data on the error', file=sys.stderr)
diff --git a/tests/test_auth/test_auth_cookie.py b/tests/test_auth/test_auth_cookie.py
index d1db981..38e37b8 100644
--- a/tests/test_auth/test_auth_cookie.py
+++ b/tests/test_auth/test_auth_cookie.py
@@ -2,20 +2,14 @@
# This module is part of the Python Paste Project and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-import os
from six.moves import xrange
-try:
- # Python 3
- from http.cookies import SimpleCookie
-except ImportError:
- # Python 2
- from Cookie import SimpleCookie
+import six
from paste.auth import cookie
from paste.wsgilib import raw_interactive, dump_environ
from paste.response import header_value
from paste.httpexceptions import *
-
+
def build(application,setenv, *args, **kwargs):
def setter(environ, start_response):
save = environ['paste.auth.cookie'].append
@@ -41,7 +35,10 @@ def test_basic(key='key', val='bingles'):
cookie = value.split(";")[0]
(status,headers,content,errors) = \
raw_interactive(app,{'HTTP_COOKIE': cookie})
- assert ("%s: %s" % (key,val.replace("\n","\n "))) in content
+ expected = ("%s: %s" % (key,val.replace("\n","\n ")))
+ if six.PY3:
+ expected = expected.encode('utf8')
+ assert expected in content
def test_roundtrip():
roundtrip = str('').join(map(chr, xrange(256)))
diff --git a/tests/test_auth/test_auth_digest.py b/tests/test_auth/test_auth_digest.py
index a821720..1d44038 100644
--- a/tests/test_auth/test_auth_digest.py
+++ b/tests/test_auth/test_auth_digest.py
@@ -4,16 +4,19 @@
from paste.auth.digest import *
from paste.wsgilib import raw_interactive
-from paste.response import header_value
from paste.httpexceptions import *
from paste.httpheaders import AUTHORIZATION, WWW_AUTHENTICATE, REMOTE_USER
import os
+import six
def application(environ, start_response):
content = REMOTE_USER(environ)
start_response("200 OK",(('Content-Type', 'text/plain'),
('Content-Length', len(content))))
- return content
+
+ if six.PY3:
+ content = content.encode('utf8')
+ return [content]
realm = "tag:clarkevans.com,2005:testing"
@@ -46,7 +49,7 @@ def check(username, password, path="/"):
assert False, "Unexpected Status: %s" % status
def test_digest():
- assert 'bing' == check("bing","gnib")
+ assert b'bing' == check("bing","gnib")
assert check("bing","bad") is None
#
diff --git a/tests/test_cgiapp.py b/tests/test_cgiapp.py
index 2887271..12cb2be 100644
--- a/tests/test_cgiapp.py
+++ b/tests/test_cgiapp.py
@@ -17,8 +17,8 @@ if sys.platform != 'win32' and not sys.platform.startswith('java'):
def test_form():
app = TestApp(CGIApplication({}, script='form.cgi', path=[data_dir]))
- res = app.post('', params={'name': 'joe'},
- upload_files=[('up', 'file.txt', 'x'*10000)])
+ res = app.post('', params={'name': b'joe'},
+ upload_files=[('up', 'file.txt', b'x'*10000)])
assert 'file.txt' in res
assert 'joe' in res
assert 'x'*10000 in res
@@ -32,5 +32,5 @@ if sys.platform != 'win32' and not sys.platform.startswith('java'):
res = app.get('', expect_errors=True)
assert res.status == 500
assert 'error' in res
- assert 'some data' in res.errors
+ assert b'some data' in res.errors
diff --git a/tests/test_cgitb_catcher.py b/tests/test_cgitb_catcher.py
index 788ede2..a63f7d8 100644
--- a/tests/test_cgitb_catcher.py
+++ b/tests/test_cgitb_catcher.py
@@ -11,7 +11,7 @@ def do_request(app, expect_status=500):
res = testapp.get('', status=expect_status,
expect_errors=True)
return res
-
+
############################################################
## Applications that raise exceptions
@@ -31,7 +31,7 @@ def after_start_response_app(environ, start_response):
def iter_app(environ, start_response):
start_response("200 OK", [('Content-type', 'text/plain')])
- return yielder(['this', ' is ', ' a', None])
+ return yielder([b'this', b' is ', b' a', None])
def yielder(args):
for arg in args:
@@ -46,7 +46,10 @@ def yielder(args):
def test_makes_exception():
res = do_request(bad_app)
print(res)
- assert 'bad_app() takes no arguments (2 given' in res
+ if six.PY3:
+ assert 'bad_app() takes 0 positional arguments but 2 were given' in res
+ else:
+ assert 'bad_app() takes no arguments (2 given' in res
assert 'iterator = application(environ, start_response_wrapper)' in res
assert 'lint.py' in res
assert 'cgitb_catcher.py' in res
@@ -69,7 +72,7 @@ def test_iter_app():
print(res)
assert 'None raises error' in res
assert 'yielder' in res
-
-
-
+
+
+
diff --git a/tests/test_config.py b/tests/test_config.py
index ea6be75..8119157 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -3,13 +3,24 @@
from nose.tools import assert_raises
from paste.config import CONFIG, ConfigMiddleware
from paste.fixture import TestApp
+import six
test_key = 'test key'
+def reset_config():
+ while True:
+ try:
+ CONFIG._pop_object()
+ except IndexError:
+ break
+
def app_with_config(environ, start_response):
start_response('200 OK', [('Content-type','text/plain')])
- return ['Variable is: %s\n' % CONFIG[test_key],
+ lines = ['Variable is: %s\n' % CONFIG[test_key],
'Variable is (in environ): %s' % environ['paste.config'][test_key]]
+ if six.PY3:
+ lines = [line.encode('utf8') for line in lines]
+ return lines
class NestingAppWithConfig(object):
def __init__(self, app):
@@ -21,43 +32,54 @@ class NestingAppWithConfig(object):
supplement = ['Nesting variable is: %s' % CONFIG[test_key],
'Nesting variable is (in environ): %s' % \
environ['paste.config'][test_key]]
+ if six.PY3:
+ supplement = [line.encode('utf8') for line in supplement]
response.extend(supplement)
return response
def test_request_config():
- config = {test_key: 'test value'}
- app = ConfigMiddleware(app_with_config, config)
- res = TestApp(app).get('/')
- assert 'Variable is: test value' in res
- assert 'Variable is (in environ): test value' in res
+ try:
+ config = {test_key: 'test value'}
+ app = ConfigMiddleware(app_with_config, config)
+ res = TestApp(app).get('/')
+ assert 'Variable is: test value' in res
+ assert 'Variable is (in environ): test value' in res
+ finally:
+ reset_config()
def test_request_config_multi():
- config = {test_key: 'test value'}
- app = ConfigMiddleware(app_with_config, config)
- config = {test_key: 'nesting value'}
- app = ConfigMiddleware(NestingAppWithConfig(app), config)
- res = TestApp(app).get('/')
- assert 'Variable is: test value' in res
- assert 'Variable is (in environ): test value' in res
- assert 'Nesting variable is: nesting value' in res
- print(res)
- assert 'Nesting variable is (in environ): nesting value' in res
+ try:
+ config = {test_key: 'test value'}
+ app = ConfigMiddleware(app_with_config, config)
+ config = {test_key: 'nesting value'}
+ app = ConfigMiddleware(NestingAppWithConfig(app), config)
+ res = TestApp(app).get('/')
+ assert 'Variable is: test value' in res
+ assert 'Variable is (in environ): test value' in res
+ assert 'Nesting variable is: nesting value' in res
+ print(res)
+ assert 'Nesting variable is (in environ): nesting value' in res
+ finally:
+ reset_config()
def test_process_config(request_app=test_request_config):
- process_config = {test_key: 'bar', 'process_var': 'foo'}
- CONFIG.push_process_config(process_config)
+ try:
+ process_config = {test_key: 'bar', 'process_var': 'foo'}
+ CONFIG.push_process_config(process_config)
+
+ assert CONFIG[test_key] == 'bar'
+ assert CONFIG['process_var'] == 'foo'
- assert CONFIG[test_key] == 'bar'
- assert CONFIG['process_var'] == 'foo'
+ request_app()
- request_app()
+ assert CONFIG[test_key] == 'bar'
+ assert CONFIG['process_var'] == 'foo'
+ CONFIG.pop_process_config()
- assert CONFIG[test_key] == 'bar'
- assert CONFIG['process_var'] == 'foo'
- CONFIG.pop_process_config()
-
- assert_raises(AttributeError, lambda: 'process_var' not in CONFIG)
- assert_raises(IndexError, CONFIG.pop_process_config)
+ assert_raises(AttributeError, lambda: 'process_var' not in CONFIG)
+ assert_raises(IndexError, CONFIG.pop_process_config)
+ finally:
+ reset_config()
def test_process_config_multi():
test_process_config(test_request_config_multi)
diff --git a/tests/test_doctests.py b/tests/test_doctests.py
index 3db3857..875fbc2 100644
--- a/tests/test_doctests.py
+++ b/tests/test_doctests.py
@@ -1,4 +1,4 @@
-from paste.util import doctest24 as doctest
+import doctest
from paste.util.import_string import simple_import
import os
diff --git a/tests/test_errordocument.py b/tests/test_errordocument.py
index c284b93..efeae61 100644
--- a/tests/test_errordocument.py
+++ b/tests/test_errordocument.py
@@ -4,11 +4,11 @@ from paste.recursive import RecursiveMiddleware
def simple_app(environ, start_response):
start_response("200 OK", [('Content-type', 'text/plain')])
- return ['requested page returned']
+ return [b'requested page returned']
def not_found_app(environ, start_response):
start_response("404 Not found", [('Content-type', 'text/plain')])
- return ['requested page returned']
+ return [b'requested page returned']
def test_ok():
app = TestApp(simple_app)
@@ -20,10 +20,10 @@ def test_ok():
def error_docs_app(environ, start_response):
if environ['PATH_INFO'] == '/not_found':
start_response("404 Not found", [('Content-type', 'text/plain')])
- return ['Not found']
+ return [b'Not found']
elif environ['PATH_INFO'] == '/error':
start_response("200 OK", [('Content-type', 'text/plain')])
- return ['Page not found']
+ return [b'Page not found']
else:
return simple_app(environ, start_response)
@@ -68,7 +68,7 @@ def auth_docs_app(environ, start_response):
return auth_required_app(environ, start_response)
elif environ['PATH_INFO'] == '/auth_doc':
start_response("200 OK", [('Content-type', 'text/html')])
- return ['<html>Login!</html>']
+ return [b'<html>Login!</html>']
else:
return simple_app(environ, start_response)
@@ -80,7 +80,7 @@ def test_auth_docs_app():
res = app.get('/auth', status=401)
assert res.header('content-type') == 'text/html'
assert res.header('www-authenticate') == 'Basic realm="Foo"'
- assert res.body == '<html>Login!</html>'
+ assert res.body == b'<html>Login!</html>'
def test_bad_error():
def app(environ, start_response):
diff --git a/tests/test_exceptions/test_error_middleware.py b/tests/test_exceptions/test_error_middleware.py
index a34de73..95ab177 100644
--- a/tests/test_exceptions/test_error_middleware.py
+++ b/tests/test_exceptions/test_error_middleware.py
@@ -19,7 +19,7 @@ def do_request(app, expect_status=500):
def clear_middleware(app):
"""
The fixture sets paste.throw_errors, which suppresses exactly what
- we want to test in this case. This wrapper also strips exc_info
+ we want to test in this case. This wrapper also strips exc_info
on the *first* call to start_response (but not the second, or
subsequent calls.
"""
@@ -34,7 +34,7 @@ def clear_middleware(app):
del environ['paste.throw_errors']
return app(environ, replacement)
return clear_throw_errors
-
+
############################################################
## Applications that raise exceptions
@@ -57,7 +57,7 @@ def after_start_response_app(environ, start_response):
def iter_app(environ, start_response):
start_response("200 OK", [('Content-type', 'text/plain')])
- return yielder(['this', ' is ', ' a', None])
+ return yielder([b'this', b' is ', b' a', None])
def yielder(args):
for arg in args:
@@ -73,15 +73,17 @@ def test_makes_exception():
res = do_request(bad_app)
assert '<html' in res
res = strip_html(str(res))
- #print res
- assert 'bad_app() takes no arguments (2 given' in res
+ if six.PY3:
+ assert 'bad_app() takes 0 positional arguments but 2 were given' in res
+ else:
+ assert 'bad_app() takes no arguments (2 given' in res, repr(res)
assert 'iterator = application(environ, start_response_wrapper)' in res
assert 'paste.lint' in res
assert 'paste.exceptions.errormiddleware' in res
def test_unicode_exception():
res = do_request(unicode_bad_app)
-
+
def test_start_res():
res = do_request(start_response_app)
@@ -101,7 +103,7 @@ def test_iter_app():
#print res
assert 'None raises error' in res
assert 'yielder' in res
-
-
-
+
+
+
diff --git a/tests/test_exceptions/test_formatter.py b/tests/test_exceptions/test_formatter.py
index 3d5bdad..9c53a9a 100644
--- a/tests/test_exceptions/test_formatter.py
+++ b/tests/test_exceptions/test_formatter.py
@@ -1,10 +1,8 @@
from paste.exceptions import formatter
from paste.exceptions import collector
-from paste.util.quoting import strip_html
import sys
import os
import difflib
-import re
class Mock(object):
def __init__(self, **kw):
@@ -153,7 +151,6 @@ def test_hide_after():
raise_error)
except:
result = format(f)
- print(strip_html(result).encode('ascii', 'replace'))
assert 'AABB' in result
assert 'CCDD' not in result
assert 'raise_error' in result
diff --git a/tests/test_exceptions/test_httpexceptions.py b/tests/test_exceptions/test_httpexceptions.py
index 08e23d4..24e00dd 100644
--- a/tests/test_exceptions/test_httpexceptions.py
+++ b/tests/test_exceptions/test_httpexceptions.py
@@ -8,8 +8,8 @@ Regression Test Suite
"""
from nose.tools import assert_raises
from paste.httpexceptions import *
-from paste.wsgilib import raw_interactive
from paste.response import header_value
+import six
def test_HTTPMove():
@@ -30,8 +30,8 @@ def test_badapp():
start_response("200 OK",[])
raise HTTPBadRequest("Do not do this at home.")
newapp = HTTPExceptionHandler(badapp)
- assert 'Bad Request' in ''.join(newapp({'HTTP_ACCEPT': 'text/html'},
- (lambda a, b, c=None: None)))
+ assert b'Bad Request' in b''.join(newapp({'HTTP_ACCEPT': 'text/html'},
+ (lambda a, b, c=None: None)))
def test_unicode():
""" verify unicode output """
@@ -40,10 +40,10 @@ def test_unicode():
start_response("200 OK",[])
raise HTTPBadRequest(tstr)
newapp = HTTPExceptionHandler(badapp)
- assert tstr.encode("utf-8") in ''.join(newapp({'HTTP_ACCEPT':
+ assert tstr.encode("utf-8") in b''.join(newapp({'HTTP_ACCEPT':
'text/html'},
(lambda a, b, c=None: None)))
- assert tstr.encode("utf-8") in ''.join(newapp({'HTTP_ACCEPT':
+ assert tstr.encode("utf-8") in b''.join(newapp({'HTTP_ACCEPT':
'text/plain'},
(lambda a, b, c=None: None)))
@@ -67,15 +67,14 @@ def test_redapp():
raise HTTPFound("/bing/foo")
app = HTTPExceptionHandler(redapp)
result = list(app({'HTTP_ACCEPT': 'text/html'},saveit))
- assert '<a href="/bing/foo">' in result[0]
+ assert b'<a href="/bing/foo">' in result[0]
assert "302 Found" == saved[0][0]
- assert "text/html" == header_value(saved[0][1], 'content-type')
+ if six.PY3:
+ assert "text/html; charset=utf8" == header_value(saved[0][1], 'content-type')
+ else:
+ assert "text/html" == header_value(saved[0][1], 'content-type')
assert "/bing/foo" == header_value(saved[0][1],'location')
result = list(app({'HTTP_ACCEPT': 'text/plain'},saveit))
- print(result[0] == (
- '302 Found\n'
- 'This resource was found at /bing/foo;\n'
- 'you should be redirected automatically.\n'))
assert "text/plain; charset=utf8" == header_value(saved[1][1],'content-type')
assert "/bing/foo" == header_value(saved[1][1],'location')
diff --git a/tests/test_fileapp.py b/tests/test_fileapp.py
index d5b2a95..bdd7510 100644
--- a/tests/test_fileapp.py
+++ b/tests/test_fileapp.py
@@ -1,30 +1,38 @@
# (c) 2005 Ian Bicking, Clark C. Evans and contributors
# This module is part of the Python Paste Project and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-import string
import time
+import random
+import os
+import tempfile
try:
# Python 3
from email.utils import parsedate_tz, mktime_tz
except ImportError:
# Python 2
from rfc822 import parsedate_tz, mktime_tz
+import six
+from paste import fileapp
from paste.fileapp import *
from paste.fixture import *
+# NOTE(haypo): don't use string.letters because the order of lower and upper
+# case letters changes when locale.setlocale() is called for the first time
+LETTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+
def test_data():
- harness = TestApp(DataApp('mycontent'))
+ harness = TestApp(DataApp(b'mycontent'))
res = harness.get("/")
assert 'application/octet-stream' == res.header('content-type')
assert '9' == res.header('content-length')
assert "<Response 200 OK 'mycontent'>" == repr(res)
- harness.app.set_content("bingles")
+ harness.app.set_content(b"bingles")
assert "<Response 200 OK 'bingles'>" == repr(harness.get("/"))
def test_cache():
def build(*args,**kwargs):
- app = DataApp("SomeContent")
+ app = DataApp(b"SomeContent")
app.cache_control(*args,**kwargs)
return TestApp(app).get("/")
res = build()
@@ -48,7 +56,7 @@ def test_cache():
def test_disposition():
def build(*args,**kwargs):
- app = DataApp("SomeContent")
+ app = DataApp(b"SomeContent")
app.content_disposition(*args,**kwargs)
return TestApp(app).get("/")
res = build()
@@ -73,7 +81,7 @@ def test_disposition():
assert False, "should be an exception"
def test_modified():
- harness = TestApp(DataApp('mycontent'))
+ harness = TestApp(DataApp(b'mycontent'))
res = harness.get("/")
assert "<Response 200 OK 'mycontent'>" == repr(res)
last_modified = res.header('last-modified')
@@ -84,21 +92,20 @@ def test_modified():
assert "<Response 304 Not Modified ''>" == repr(res)
res = harness.get("/",status=400,
headers={'if-modified-since': 'garbage'})
- assert 400 == res.status and "ill-formed timestamp" in res.body
+ assert 400 == res.status and b"ill-formed timestamp" in res.body
res = harness.get("/",status=400,
headers={'if-modified-since':
'Thu, 22 Dec 2030 01:01:01 GMT'})
- assert 400 == res.status and "check your system clock" in res.body
+ assert 400 == res.status and b"check your system clock" in res.body
def test_file():
- import random, string, os
tempfile = "test_fileapp.%s.txt" % (random.random())
- content = string.letters * 20
- file = open(tempfile,"w")
- file.write(content)
- file.close()
+ content = LETTERS * 20
+ if six.PY3:
+ content = content.encode('utf8')
+ with open(tempfile, "wb") as fp:
+ fp.write(content)
try:
- from paste import fileapp
app = fileapp.FileApp(tempfile)
res = TestApp(app).get("/")
assert len(content) == int(res.header('content-length'))
@@ -113,7 +120,7 @@ def test_file():
res = TestApp(app).get("/",headers={'Cache-Control': 'max-age=0'})
assert len(content)+10 == int(res.header('content-length'))
assert 'text/plain' == res.header('content-type')
- assert content + "0123456789" == res.body
+ assert content + b"0123456789" == res.body
assert app.content # we are still cached
file = open(tempfile,"a+")
file.write("X" * fileapp.CACHE_SIZE) # exceed the cashe size
@@ -123,15 +130,12 @@ def test_file():
newsize = fileapp.CACHE_SIZE + len(content)+12
assert newsize == int(res.header('content-length'))
assert newsize == len(res.body)
- assert res.body.startswith(content) and res.body.endswith('XYZ')
+ assert res.body.startswith(content) and res.body.endswith(b'XYZ')
assert not app.content # we are no longer cached
finally:
- import os
os.unlink(tempfile)
def test_dir():
- import os
- import tempfile
tmpdir = tempfile.mkdtemp()
try:
tmpfile = os.path.join(tmpdir, 'file')
@@ -141,13 +145,12 @@ def test_dir():
fp.close()
os.mkdir(tmpsubdir)
try:
- from paste import fileapp
app = fileapp.DirectoryApp(tmpdir)
for path in ['/', '', '//', '/..', '/.', '/../..']:
assert TestApp(app).get(path, status=403).status == 403, ValueError(path)
for path in ['/~', '/foo', '/dir', '/dir/']:
assert TestApp(app).get(path, status=404).status == 404, ValueError(path)
- assert TestApp(app).get('/file').body == 'abcd'
+ assert TestApp(app).get('/file').body == b'abcd'
finally:
os.remove(tmpfile)
os.rmdir(tmpsubdir)
@@ -171,44 +174,43 @@ def _excercize_range(build,content):
assert res.body == content[:10]
assert res.header('content-length') == '10'
res = build("bytes=%d-" % (len(content)-1), status=206)
- assert res.body == 'Z'
+ assert res.body == b'Z'
assert res.header('content-length') == '1'
res = build("bytes=%d-%d" % (3,17), status=206)
assert res.body == content[3:18]
assert res.header('content-length') == '15'
def test_range():
- content = string.letters * 5
- def build(range, status=200):
+ content = LETTERS * 5
+ if six.PY3:
+ content = content.encode('utf8')
+ def build(range, status=206):
app = DataApp(content)
return TestApp(app).get("/",headers={'Range': range}, status=status)
_excercize_range(build,content)
build('bytes=0-%d' % (len(content)+1), 416)
def test_file_range():
- from paste import fileapp
- import random, string, os
tempfile = "test_fileapp.%s.txt" % (random.random())
- content = string.letters * (1+(fileapp.CACHE_SIZE / len(string.letters)))
+ content = LETTERS * (1+(fileapp.CACHE_SIZE // len(LETTERS)))
+ if six.PY3:
+ content = content.encode('utf8')
assert len(content) > fileapp.CACHE_SIZE
- file = open(tempfile,"w")
- file.write(content)
- file.close()
+ with open(tempfile, "wb") as fp:
+ fp.write(content)
try:
- def build(range, status=200):
+ def build(range, status=206):
app = fileapp.FileApp(tempfile)
return TestApp(app).get("/",headers={'Range': range},
status=status)
_excercize_range(build,content)
- for size in (13,len(string.letters),len(string.letters)-1):
+ for size in (13,len(LETTERS), len(LETTERS)-1):
fileapp.BLOCK_SIZE = size
_excercize_range(build,content)
finally:
- import os
os.unlink(tempfile)
def test_file_cache():
- from paste import fileapp
filename = os.path.join(os.path.dirname(__file__),
'urlparser_data', 'secured.txt')
app = TestApp(fileapp.FileApp(filename))
@@ -229,7 +231,6 @@ def test_file_cache():
status=400)
def test_methods():
- from paste import fileapp
filename = os.path.join(os.path.dirname(__file__),
'urlparser_data', 'secured.txt')
app = TestApp(fileapp.FileApp(filename))
diff --git a/tests/test_grantip.py b/tests/test_grantip.py
index 8d74280..2ddf7f1 100644
--- a/tests/test_grantip.py
+++ b/tests/test_grantip.py
@@ -4,11 +4,14 @@ from paste.fixture import *
def test_make_app():
def application(environ, start_response):
start_response('200 OK', [('content-type', 'text/plain')])
- return [
+ lines = [
str(environ.get('REMOTE_USER')),
':',
str(environ.get('REMOTE_USER_TOKENS')),
]
+ if six.PY3:
+ lines = [line.encode('utf8') for line in lines]
+ return lines
ip_map = {
'127.0.0.1': (None, 'system'),
'192.168.0.0/16': (None, 'worker'),
@@ -24,11 +27,11 @@ def test_req():
def doit(remote_addr):
res = app.get('/', extra_environ={'REMOTE_ADDR': remote_addr})
return res.body
- assert doit('127.0.0.1') == 'None:system'
- assert doit('192.168.15.12') == 'None:worker'
- assert doit('192.168.0.4') == 'None:worker'
+ assert doit('127.0.0.1') == b'None:system'
+ assert doit('192.168.15.12') == b'None:worker'
+ assert doit('192.168.0.4') == b'None:worker'
result = doit('192.168.0.5')
- assert result.startswith('bob:')
- assert 'editor' in result and 'worker' in result
- assert result.count(',') == 1
- assert doit('192.168.0.8') == 'None:editor'
+ assert result.startswith(b'bob:')
+ assert b'editor' in result and b'worker' in result
+ assert result.count(b',') == 1
+ assert doit('192.168.0.8') == b'None:editor'
diff --git a/tests/test_gzipper.py b/tests/test_gzipper.py
index 4f929b0..54b7901 100644
--- a/tests/test_gzipper.py
+++ b/tests/test_gzipper.py
@@ -1,11 +1,11 @@
from paste.fixture import TestApp
from paste.gzipper import middleware
import gzip
-from six.moves import cStringIO as StringIO
+import six
def simple_app(environ, start_response):
start_response('200 OK', [('content-type', 'text/plain')])
- return 'this is a test'
+ return [b'this is a test']
wsgi_app = middleware(simple_app)
app = TestApp(wsgi_app)
@@ -14,6 +14,6 @@ def test_gzip():
res = app.get(
'/', extra_environ=dict(HTTP_ACCEPT_ENCODING='gzip'))
assert int(res.header('content-length')) == len(res.body)
- assert res.body != 'this is a test'
- actual = gzip.GzipFile(fileobj=StringIO(res.body)).read()
- assert actual == 'this is a test'
+ assert res.body != b'this is a test'
+ actual = gzip.GzipFile(fileobj=six.BytesIO(res.body)).read()
+ assert actual == b'this is a test'
diff --git a/tests/test_import_string.py b/tests/test_import_string.py
index 96526ac..262cbdd 100644
--- a/tests/test_import_string.py
+++ b/tests/test_import_string.py
@@ -11,6 +11,6 @@ def test_simple():
def test_complex():
assert eval_import('sys:version') is sys.version
assert eval_import('os:getcwd()') == os.getcwd()
- assert (eval_import('sys:version.split()[0]') ==
+ assert (eval_import('sys:version.split()[0]') ==
sys.version.split()[0])
-
+
diff --git a/tests/test_multidict.py b/tests/test_multidict.py
index 820331e..50a746f 100644
--- a/tests/test_multidict.py
+++ b/tests/test_multidict.py
@@ -7,8 +7,6 @@ from six.moves import StringIO
from nose.tools import assert_raises
-from paste.fixture import TestApp
-from paste.wsgiwrappers import WSGIRequest
from paste.util.multidict import MultiDict, UnicodeMultiDict
def test_dict():
@@ -59,15 +57,17 @@ def test_unicode_dict():
_test_unicode_dict(decode_param_names=True)
def _test_unicode_dict(decode_param_names=False):
- d = UnicodeMultiDict(MultiDict({'a': 'a test'}))
+ d = UnicodeMultiDict(MultiDict({b'a': 'a test'}))
d.encoding = 'utf-8'
d.errors = 'ignore'
if decode_param_names:
key_str = six.text_type
+ k = lambda key: key
d.decode_keys = True
else:
- key_str = str
+ key_str = six.binary_type
+ k = lambda key: key.encode()
def assert_unicode(obj):
assert isinstance(obj, six.text_type)
@@ -80,67 +80,67 @@ def _test_unicode_dict(decode_param_names=False):
assert isinstance(key, key_str)
assert isinstance(value, six.text_type)
- assert d.items() == [('a', u'a test')]
+ assert d.items() == [(k('a'), u'a test')]
map(assert_key_str, d.keys())
map(assert_unicode, d.values())
- d['b'] = '2 test'
- d['c'] = '3 test'
- assert d.items() == [('a', u'a test'), ('b', u'2 test'), ('c', u'3 test')]
- map(assert_unicode_item, d.items())
+ d[b'b'] = b'2 test'
+ d[b'c'] = b'3 test'
+ assert d.items() == [(k('a'), u'a test'), (k('b'), u'2 test'), (k('c'), u'3 test')]
+ list(map(assert_unicode_item, d.items()))
- d['b'] = '4 test'
- assert d.items() == [('a', u'a test'), ('c', u'3 test'), ('b', u'4 test')]
- map(assert_unicode_item, d.items())
+ d[k('b')] = b'4 test'
+ assert d.items() == [(k('a'), u'a test'), (k('c'), u'3 test'), (k('b'), u'4 test')], d.items()
+ list(map(assert_unicode_item, d.items()))
- d.add('b', '5 test')
- assert_raises(KeyError, d.getone, "b")
- assert d.getall('b') == [u'4 test', u'5 test']
+ d.add(k('b'), b'5 test')
+ assert_raises(KeyError, d.getone, k("b"))
+ assert d.getall(k('b')) == [u'4 test', u'5 test']
map(assert_unicode, d.getall('b'))
- assert d.items() == [('a', u'a test'), ('c', u'3 test'), ('b', u'4 test'),
- ('b', u'5 test')]
- map(assert_unicode_item, d.items())
+ assert d.items() == [(k('a'), u'a test'), (k('c'), u'3 test'), (k('b'), u'4 test'),
+ (k('b'), u'5 test')]
+ list(map(assert_unicode_item, d.items()))
- del d['b']
- assert d.items() == [('a', u'a test'), ('c', u'3 test')]
- map(assert_unicode_item, d.items())
+ del d[k('b')]
+ assert d.items() == [(k('a'), u'a test'), (k('c'), u'3 test')]
+ list(map(assert_unicode_item, d.items()))
assert d.pop('xxx', u'5 test') == u'5 test'
assert isinstance(d.pop('xxx', u'5 test'), six.text_type)
- assert d.getone('a') == u'a test'
- assert isinstance(d.getone('a'), six.text_type)
- assert d.popitem() == ('c', u'3 test')
- d['c'] = '3 test'
+ assert d.getone(k('a')) == u'a test'
+ assert isinstance(d.getone(k('a')), six.text_type)
+ assert d.popitem() == (k('c'), u'3 test')
+ d[k('c')] = b'3 test'
assert_unicode_item(d.popitem())
- assert d.items() == [('a', u'a test')]
- map(assert_unicode_item, d.items())
+ assert d.items() == [(k('a'), u'a test')]
+ list(map(assert_unicode_item, d.items()))
item = []
- assert d.setdefault('z', item) is item
+ assert d.setdefault(k('z'), item) is item
items = d.items()
- assert items == [('a', u'a test'), ('z', item)]
+ assert items == [(k('a'), u'a test'), (k('z'), item)]
assert isinstance(items[1][0], key_str)
assert isinstance(items[1][1], list)
- assert isinstance(d.setdefault('y', 'y test'), six.text_type)
- assert isinstance(d['y'], six.text_type)
+ assert isinstance(d.setdefault(k('y'), b'y test'), six.text_type)
+ assert isinstance(d[k('y')], six.text_type)
- assert d.mixed() == {u'a': u'a test', u'y': u'y test', u'z': item}
- assert d.dict_of_lists() == {u'a': [u'a test'], u'y': [u'y test'],
- u'z': [item]}
- del d['z']
- map(assert_unicode_item, six.iteritems(d.mixed()))
- map(assert_unicode_item, [(k, v[0]) for \
- k, v in six.iteritems(d.dict_of_lists())])
+ assert d.mixed() == {k('a'): u'a test', k('y'): u'y test', k('z'): item}
+ assert d.dict_of_lists() == {k('a'): [u'a test'], k('y'): [u'y test'],
+ k('z'): [item]}
+ del d[k('z')]
+ list(map(assert_unicode_item, six.iteritems(d.mixed())))
+ list(map(assert_unicode_item, [(key, value[0]) for \
+ key, value in six.iteritems(d.dict_of_lists())]))
- assert u'a' in d
+ assert k('a') in d
dcopy = d.copy()
assert dcopy is not d
assert dcopy == d
- d['x'] = 'x test'
+ d[k('x')] = 'x test'
assert dcopy != d
d[(1, None)] = (None, 1)
- assert d.items() == [('a', u'a test'), ('y', u'y test'), ('x', u'x test'),
+ assert d.items() == [(k('a'), u'a test'), (k('y'), u'y test'), (k('x'), u'x test'),
((1, None), (None, 1))]
item = d.items()[-1]
assert isinstance(item[0], tuple)
@@ -150,12 +150,12 @@ def _test_unicode_dict(decode_param_names=False):
fs.name = 'thefile'
fs.filename = 'hello.txt'
fs.file = StringIO('hello')
- d['f'] = fs
- ufs = d['f']
+ d[k('f')] = fs
+ ufs = d[k('f')]
assert isinstance(ufs, cgi.FieldStorage)
assert ufs is not fs
assert ufs.name == fs.name
- assert isinstance(ufs.name, key_str)
+ assert isinstance(ufs.name, str if six.PY3 else key_str)
assert ufs.filename == fs.filename
assert isinstance(ufs.filename, six.text_type)
assert isinstance(ufs.value, str)
diff --git a/tests/test_proxy.py b/tests/test_proxy.py
index 36d16b5..44db9f3 100644
--- a/tests/test_proxy.py
+++ b/tests/test_proxy.py
@@ -9,4 +9,4 @@ def test_paste_website():
app = TestApp(app)
res = app.get('/')
assert 'documentation' in res
-
+
diff --git a/tests/test_recursive.py b/tests/test_recursive.py
index 114592e..1cb1984 100644
--- a/tests/test_recursive.py
+++ b/tests/test_recursive.py
@@ -1,14 +1,14 @@
-from .test_errordocument import error_docs_app, test_error_docs_app, simple_app
+from .test_errordocument import simple_app
from paste.fixture import *
from paste.recursive import RecursiveMiddleware, ForwardRequestException
def error_docs_app(environ, start_response):
if environ['PATH_INFO'] == '/not_found':
start_response("404 Not found", [('Content-type', 'text/plain')])
- return ['Not found']
+ return [b'Not found']
elif environ['PATH_INFO'] == '/error':
start_response("200 OK", [('Content-type', 'text/plain')])
- return ['Page not found']
+ return [b'Page not found']
elif environ['PATH_INFO'] == '/recurse':
raise ForwardRequestException('/recurse')
else:
@@ -43,15 +43,15 @@ def forward(app):
else:
raise AssertionError('Failed to detect forwarding loop')
-def test_ForwardRequest_url():
+def test_ForwardRequest_url():
class TestForwardRequestMiddleware(Middleware):
def __call__(self, environ, start_response):
if environ['PATH_INFO'] != '/not_found':
return self.app(environ, start_response)
raise ForwardRequestException(self.url)
forward(TestForwardRequestMiddleware(error_docs_app))
-
-def test_ForwardRequest_environ():
+
+def test_ForwardRequest_environ():
class TestForwardRequestMiddleware(Middleware):
def __call__(self, environ, start_response):
if environ['PATH_INFO'] != '/not_found':
@@ -59,11 +59,11 @@ def test_ForwardRequest_environ():
environ['PATH_INFO'] = self.url
raise ForwardRequestException(environ=environ)
forward(TestForwardRequestMiddleware(error_docs_app))
-
-def test_ForwardRequest_factory():
-
+
+def test_ForwardRequest_factory():
+
from paste.errordocument import StatusKeeper
-
+
class TestForwardRequestMiddleware(Middleware):
def __call__(self, environ, start_response):
if environ['PATH_INFO'] != '/not_found':
@@ -96,7 +96,7 @@ def test_ForwardRequest_factory():
raise AssertionError('Failed to detect forwarding loop')
# Test Deprecated Code
-def test_ForwardRequestException():
+def test_ForwardRequestException():
class TestForwardRequestExceptionMiddleware(Middleware):
def __call__(self, environ, start_response):
if environ['PATH_INFO'] != '/not_found':
diff --git a/tests/test_registry.py b/tests/test_registry.py
index fdbc26c..23cd9b6 100644
--- a/tests/test_registry.py
+++ b/tests/test_registry.py
@@ -15,19 +15,25 @@ def simpleapp(environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
- return ['Hello world!\n']
+ return [b'Hello world!\n']
def simpleapp_withregistry(environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
- return ['Hello world!Value is %s\n' % regobj.keys()]
+ body = 'Hello world!Value is %s\n' % regobj.keys()
+ if six.PY3:
+ body = body.encode('utf8')
+ return [body]
def simpleapp_withregistry_default(environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
- return ['Hello world!Value is %s\n' % secondobj]
+ body = 'Hello world!Value is %s\n' % secondobj
+ if six.PY3:
+ body = body.encode('utf8')
+ return [body]
class RegistryUsingApp(object):
@@ -35,7 +41,7 @@ class RegistryUsingApp(object):
self.var = var
self.value = value
self.raise_exc = raise_exc
-
+
def __call__(self, environ, start_response):
if 'paste.registry' in environ:
environ['paste.registry'].register(self.var, self.value)
@@ -44,20 +50,26 @@ class RegistryUsingApp(object):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
- return ['Hello world!\nThe variable is %s' % str(regobj)]
+ body = 'Hello world!\nThe variable is %s' % str(regobj)
+ if six.PY3:
+ body = body.encode('utf8')
+ return [body]
class RegistryUsingIteratorApp(object):
def __init__(self, var, value):
self.var = var
self.value = value
-
+
def __call__(self, environ, start_response):
if 'paste.registry' in environ:
environ['paste.registry'].register(self.var, self.value)
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
- return iter(['Hello world!\nThe variable is %s' % str(regobj)])
+ body = 'Hello world!\nThe variable is %s' % str(regobj)
+ if six.PY3:
+ body = body.encode('utf8')
+ return iter([body])
class RegistryMiddleMan(object):
def __init__(self, app, var, value, depth):
@@ -65,12 +77,15 @@ class RegistryMiddleMan(object):
self.var = var
self.value = value
self.depth = depth
-
+
def __call__(self, environ, start_response):
if 'paste.registry' in environ:
environ['paste.registry'].register(self.var, self.value)
- app_response = ['\nInserted by middleware!\nInsertValue at depth \
- %s is %s' % (self.depth, str(regobj))]
+ line = ('\nInserted by middleware!\nInsertValue at depth %s is %s'
+ % (self.depth, str(regobj)))
+ if six.PY3:
+ line = line.encode('utf8')
+ app_response = [line]
app_iter = None
app_iter = self.app(environ, start_response)
if type(app_iter) in (list, tuple):
@@ -82,10 +97,13 @@ class RegistryMiddleMan(object):
if hasattr(app_iter, 'close'):
app_iter.close()
app_response.extend(response)
- app_response.extend(['\nAppended by middleware!\nAppendValue at \
- depth %s is %s' % (self.depth, str(regobj))])
+ line = ('\nAppended by middleware!\nAppendValue at \
+ depth %s is %s' % (self.depth, str(regobj)))
+ if six.PY3:
+ line = line.encode('utf8')
+ app_response.append(line)
return app_response
-
+
def test_simple():
app = TestApp(simpleapp)
@@ -150,7 +168,7 @@ def test_really_deep_registry():
for depth in valuelist:
assert "AppendValue at depth %s is {'%s': %s}" % \
(depth, keylist[depth], depth) in res
-
+
def test_iterating_response():
obj = {'hi':'people'}
secondobj = {'bye':'friends'}
diff --git a/tests/test_request.py b/tests/test_request.py
index 3d882ed..072304d 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -4,24 +4,28 @@
from paste.fixture import *
from paste.request import *
from paste.wsgiwrappers import WSGIRequest
+import six
def simpleapp(environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
request = WSGIRequest(environ)
- return [
+ body = [
'Hello world!\n', 'The get is %s' % str(request.GET),
' and Val is %s\n' % request.GET.get('name'),
'The languages are: %s\n' % request.languages,
'The accepttypes is: %s\n' % request.match_accept(['text/html', 'application/xml'])]
+ if six.PY3:
+ body = [line.encode('utf8') for line in body]
+ return body
def test_gets():
app = TestApp(simpleapp)
res = app.get('/')
assert 'Hello' in res
assert "get is MultiDict([])" in res
-
+
res = app.get('/?name=george')
res.mustcontain("get is MultiDict([('name', 'george')])")
res.mustcontain("Val is george")
@@ -30,7 +34,7 @@ def test_language_parsing():
app = TestApp(simpleapp)
res = app.get('/')
assert "The languages are: ['en-us']" in res
-
+
res = app.get('/', headers={'Accept-Language':'da, en-gb;q=0.8, en;q=0.7'})
assert "languages are: ['da', 'en-gb', 'en', 'en-us']" in res
@@ -41,10 +45,10 @@ def test_mime_parsing():
app = TestApp(simpleapp)
res = app.get('/', headers={'Accept':'text/html'})
assert "accepttypes is: ['text/html']" in res
-
+
res = app.get('/', headers={'Accept':'application/xml'})
assert "accepttypes is: ['application/xml']" in res
-
+
res = app.get('/', headers={'Accept':'application/xml,*/*'})
assert "accepttypes is: ['text/html', 'application/xml']" in res
diff --git a/tests/test_request_form.py b/tests/test_request_form.py
index 036da3e..cf43721 100644
--- a/tests/test_request_form.py
+++ b/tests/test_request_form.py
@@ -1,5 +1,4 @@
-import cgi
-from six.moves import cStringIO as StringIO
+import six
from paste.request import *
from paste.util.multidict import MultiDict
@@ -19,13 +18,13 @@ def make_post(body):
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'CONTENT_LENGTH': str(len(body)),
'REQUEST_METHOD': 'POST',
- 'wsgi.input': StringIO(body),
+ 'wsgi.input': six.BytesIO(body),
}
return e
def test_parsevars():
- e = make_post('a=1&b=2&c=3&b=4')
- cur_input = e['wsgi.input']
+ e = make_post(b'a=1&b=2&c=3&b=4')
+ #cur_input = e['wsgi.input']
d = parse_formvars(e)
assert isinstance(d, MultiDict)
assert d == MultiDict([('a', '1'), ('b', '2'), ('c', '3'), ('b', '4')])
diff --git a/tests/test_response.py b/tests/test_response.py
index d4d4b65..71f6f97 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -8,4 +8,4 @@ def test_replace_header():
replace_header(h, 'Content-Type', 'text/html')
assert ('content-type', 'text/html') in h
assert ('content-type', 'text/plain') not in h
-
+
diff --git a/tests/test_session.py b/tests/test_session.py
index 621d284..b67bda5 100644
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -1,5 +1,6 @@
from paste.session import SessionMiddleware
from paste.fixture import TestApp
+import six
info = []
@@ -12,9 +13,12 @@ def wsgi_app(environ, start_response):
if pi == '/get2':
sess = environ['paste.session.factory']()
if 'info' in sess:
- return [str(sess['info'])]
+ body = str(sess['info'])
+ if six.PY3:
+ body = body.encode('utf8')
+ return [body]
else:
- return ['no-info']
+ return [b'no-info']
if pi in ('/put1', '/put2'):
if pi == '/put1':
sess = environ['paste.session.factory']()
@@ -23,30 +27,30 @@ def wsgi_app(environ, start_response):
if pi == '/put2':
sess = environ['paste.session.factory']()
sess['info'] = info[0]
- return ['foo']
+ return [b'foo']
wsgi_app = SessionMiddleware(wsgi_app)
-
+
def test_app1():
app = TestApp(wsgi_app)
res = app.get('/get1')
- assert res.body == 'no-info'
+ assert res.body == b'no-info'
res = app.get('/get2')
- assert res.body == 'no-info'
+ assert res.body ==b'no-info'
info[:] = ['test']
res = app.get('/put1')
res = app.get('/get1')
- assert res.body == 'test'
+ assert res.body == b'test'
res = app.get('/get2')
- assert res.body == 'test'
+ assert res.body == b'test'
def test_app2():
app = TestApp(wsgi_app)
info[:] = ['fluff']
res = app.get('/put2')
res = app.get('/get1')
- assert res.body == 'fluff'
+ assert res.body == b'fluff'
res = app.get('/get2')
- assert res.body == 'fluff'
-
-
+ assert res.body == b'fluff'
+
+
diff --git a/tests/test_urlmap.py b/tests/test_urlmap.py
index 9f77ca2..f7ec729 100644
--- a/tests/test_urlmap.py
+++ b/tests/test_urlmap.py
@@ -1,11 +1,15 @@
from paste.urlmap import *
from paste.fixture import *
+import six
def make_app(response_text):
def app(environ, start_response):
headers = [('Content-type', 'text/html')]
start_response('200 OK', headers)
- return [response_text % environ]
+ body = response_text % environ
+ if six.PY3:
+ body = body.encode('ascii')
+ return [body]
return app
def test_map():
@@ -44,6 +48,6 @@ def test_404():
mapper = URLMap({})
app = TestApp(mapper, extra_environ={'HTTP_ACCEPT': 'text/html'})
res = app.get("/-->%0D<script>alert('xss')</script>", status=404)
- assert '--><script' not in res.body
+ assert b'--><script' not in res.body
res = app.get("/--%01><script>", status=404)
- assert '--\x01><script>' not in res.body
+ assert b'--\x01><script>' not in res.body
diff --git a/tests/test_urlparser.py b/tests/test_urlparser.py
index d1f3377..21c210e 100644
--- a/tests/test_urlparser.py
+++ b/tests/test_urlparser.py
@@ -110,7 +110,7 @@ def test_xss():
app = TestApp(StaticURLParser(relative_path('find_file')),
extra_environ={'HTTP_ACCEPT': 'text/html'})
res = app.get("/-->%0D<script>alert('xss')</script>", status=404)
- assert '--><script>' not in res.body
+ assert b'--><script>' not in res.body
def test_static_parser():
app = StaticURLParser(path('find_file'))
@@ -118,16 +118,16 @@ def test_static_parser():
res = testapp.get('', status=301)
res = testapp.get('/', status=404)
res = testapp.get('/index.txt')
- assert res.body.strip() == 'index1'
+ assert res.body.strip() == b'index1'
res = testapp.get('/index.txt/foo', status=404)
res = testapp.get('/test 3.html')
- assert res.body.strip() == 'test 3'
+ assert res.body.strip() == b'test 3'
res = testapp.get('/test%203.html')
- assert res.body.strip() == 'test 3'
+ assert res.body.strip() == b'test 3'
res = testapp.get('/dir with spaces/test 4.html')
- assert res.body.strip() == 'test 4'
+ assert res.body.strip() == b'test 4'
res = testapp.get('/dir%20with%20spaces/test%204.html')
- assert res.body.strip() == 'test 4'
+ assert res.body.strip() == b'test 4'
# Ensure only data under the app's root directory is accessible
res = testapp.get('/../secured.txt', status=404)
res = testapp.get('/dir with spaces/../../secured.txt', status=404)
diff --git a/tests/test_util/test_datetimeutil.py b/tests/test_util/test_datetimeutil.py
index 17808f3..45d96c7 100644
--- a/tests/test_util/test_datetimeutil.py
+++ b/tests/test_util/test_datetimeutil.py
@@ -1,135 +1,135 @@
-# (c) 2005 Clark C. Evans and contributors
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# Some of this code was funded by: http://prometheusresearch.com
-from time import localtime
-from datetime import date
-from paste.util.datetimeutil import *
-
-def test_timedelta():
- assert('' == normalize_timedelta(""))
- assert('0.10' == normalize_timedelta("6m"))
- assert('0.50' == normalize_timedelta("30m"))
- assert('0.75' == normalize_timedelta("45m"))
- assert('1.00' == normalize_timedelta("60 min"))
- assert('1.50' == normalize_timedelta("90min"))
- assert('1.50' == normalize_timedelta("1.50"))
- assert('4.50' == normalize_timedelta("4 : 30"))
- assert('1.50' == normalize_timedelta("1h 30m"))
- assert('1.00' == normalize_timedelta("1"))
- assert('1.00' == normalize_timedelta("1 hour"))
- assert('8.00' == normalize_timedelta("480 mins"))
- assert('8.00' == normalize_timedelta("8h"))
- assert('0.50' == normalize_timedelta("0.5"))
- assert('0.10' == normalize_timedelta(".1"))
- assert('0.50' == normalize_timedelta(".50"))
- assert('0.75' == normalize_timedelta("0.75"))
-
-def test_time():
- assert('03:00 PM' == normalize_time("3p", ampm=True))
- assert('03:00 AM' == normalize_time("300", ampm=True))
- assert('03:22 AM' == normalize_time("322", ampm=True))
- assert('01:22 PM' == normalize_time("1322", ampm=True))
- assert('01:00 PM' == normalize_time("13", ampm=True))
- assert('12:00 PM' == normalize_time("noon", ampm=True))
- assert("06:00 PM" == normalize_time("6", ampm=True))
- assert("01:00 PM" == normalize_time("1", ampm=True))
- assert("07:00 AM" == normalize_time("7", ampm=True))
- assert("01:00 PM" == normalize_time("1 pm", ampm=True))
- assert("03:30 PM" == normalize_time("3:30 pm", ampm=True))
- assert("03:30 PM" == normalize_time("3 30 pm", ampm=True))
- assert("03:30 PM" == normalize_time("3 30 P.M.", ampm=True))
- assert("12:00 PM" == normalize_time("0", ampm=True))
- assert("12:00 AM" == normalize_time("1200 AM", ampm=True))
-
-def test_date():
- tm = localtime()
- yr = tm[0]
- mo = tm[1]
- assert(date(yr,4,11) == parse_date("411"))
- assert(date(yr,4,11) == parse_date("APR11"))
- assert(date(yr,4,11) == parse_date("11APR"))
- assert(date(yr,4,11) == parse_date("4 11"))
- assert(date(yr,4,11) == parse_date("11 APR"))
- assert(date(yr,4,11) == parse_date("APR 11"))
- assert(date(yr,mo,11) == parse_date("11"))
- assert(date(yr,4,1) == parse_date("APR"))
- assert(date(yr,4,11) == parse_date("4/11"))
- assert(date.today() == parse_date("today"))
- assert(date.today() == parse_date("now"))
- assert(None == parse_date(""))
- assert('' == normalize_date(None))
-
- assert('2001-02-03' == normalize_date("20010203"))
- assert('1999-04-11' == normalize_date("1999 4 11"))
- assert('1999-04-11' == normalize_date("1999 APR 11"))
- assert('1999-04-11' == normalize_date("APR 11 1999"))
- assert('1999-04-11' == normalize_date("11 APR 1999"))
- assert('1999-04-11' == normalize_date("4 11 1999"))
- assert('1999-04-01' == normalize_date("1999 APR"))
- assert('1999-04-01' == normalize_date("1999 4"))
- assert('1999-04-01' == normalize_date("4 1999"))
- assert('1999-04-01' == normalize_date("APR 1999"))
- assert('1999-01-01' == normalize_date("1999"))
-
- assert('1999-04-01' == normalize_date("1APR1999"))
- assert('2001-04-01' == normalize_date("1APR2001"))
-
- assert('1999-04-18' == normalize_date("1999-04-11+7"))
- assert('1999-04-18' == normalize_date("1999-04-11 7"))
- assert('1999-04-01' == normalize_date("1 apr 1999"))
- assert('1999-04-11' == normalize_date("11 apr 1999"))
- assert('1999-04-11' == normalize_date("11 Apr 1999"))
- assert('1999-04-11' == normalize_date("11-apr-1999"))
- assert('1999-04-11' == normalize_date("11 April 1999"))
- assert('1999-04-11' == normalize_date("11 APRIL 1999"))
- assert('1999-04-11' == normalize_date("11 april 1999"))
- assert('1999-04-11' == normalize_date("11 aprick 1999"))
- assert('1999-04-11' == normalize_date("APR 11, 1999"))
- assert('1999-04-11' == normalize_date("4/11/1999"))
- assert('1999-04-11' == normalize_date("4-11-1999"))
- assert('1999-04-11' == normalize_date("1999-4-11"))
- assert('1999-04-11' == normalize_date("19990411"))
-
- assert('1999-01-01' == normalize_date("1 Jan 1999"))
- assert('1999-02-01' == normalize_date("1 Feb 1999"))
- assert('1999-03-01' == normalize_date("1 Mar 1999"))
- assert('1999-04-01' == normalize_date("1 Apr 1999"))
- assert('1999-05-01' == normalize_date("1 May 1999"))
- assert('1999-06-01' == normalize_date("1 Jun 1999"))
- assert('1999-07-01' == normalize_date("1 Jul 1999"))
- assert('1999-08-01' == normalize_date("1 Aug 1999"))
- assert('1999-09-01' == normalize_date("1 Sep 1999"))
- assert('1999-10-01' == normalize_date("1 Oct 1999"))
- assert('1999-11-01' == normalize_date("1 Nov 1999"))
- assert('1999-12-01' == normalize_date("1 Dec 1999"))
-
- assert('1999-04-30' == normalize_date("1999-4-30"))
- assert('2000-02-29' == normalize_date("29 FEB 2000"))
- assert('2001-02-28' == normalize_date("28 FEB 2001"))
- assert('2004-02-29' == normalize_date("29 FEB 2004"))
- assert('2100-02-28' == normalize_date("28 FEB 2100"))
- assert('1900-02-28' == normalize_date("28 FEB 1900"))
-
- def assertError(val):
- try:
- normalize_date(val)
- except (TypeError,ValueError):
- return
- raise ValueError("type error expected", val)
-
- assertError("2000-13-11")
- assertError("APR 99")
- assertError("29 FEB 1900")
- assertError("29 FEB 2100")
- assertError("29 FEB 2001")
- assertError("1999-4-31")
- assertError("APR 99")
- assertError("20301")
- assertError("020301")
- assertError("1APR99")
- assertError("1APR01")
- assertError("1 APR 99")
- assertError("1 APR 01")
- assertError("11/5/01")
-
+# (c) 2005 Clark C. Evans and contributors
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+# Some of this code was funded by: http://prometheusresearch.com
+from time import localtime
+from datetime import date
+from paste.util.datetimeutil import *
+
+def test_timedelta():
+ assert('' == normalize_timedelta(""))
+ assert('0.10' == normalize_timedelta("6m"))
+ assert('0.50' == normalize_timedelta("30m"))
+ assert('0.75' == normalize_timedelta("45m"))
+ assert('1.00' == normalize_timedelta("60 min"))
+ assert('1.50' == normalize_timedelta("90min"))
+ assert('1.50' == normalize_timedelta("1.50"))
+ assert('4.50' == normalize_timedelta("4 : 30"))
+ assert('1.50' == normalize_timedelta("1h 30m"))
+ assert('1.00' == normalize_timedelta("1"))
+ assert('1.00' == normalize_timedelta("1 hour"))
+ assert('8.00' == normalize_timedelta("480 mins"))
+ assert('8.00' == normalize_timedelta("8h"))
+ assert('0.50' == normalize_timedelta("0.5"))
+ assert('0.10' == normalize_timedelta(".1"))
+ assert('0.50' == normalize_timedelta(".50"))
+ assert('0.75' == normalize_timedelta("0.75"))
+
+def test_time():
+ assert('03:00 PM' == normalize_time("3p", ampm=True))
+ assert('03:00 AM' == normalize_time("300", ampm=True))
+ assert('03:22 AM' == normalize_time("322", ampm=True))
+ assert('01:22 PM' == normalize_time("1322", ampm=True))
+ assert('01:00 PM' == normalize_time("13", ampm=True))
+ assert('12:00 PM' == normalize_time("noon", ampm=True))
+ assert("06:00 PM" == normalize_time("6", ampm=True))
+ assert("01:00 PM" == normalize_time("1", ampm=True))
+ assert("07:00 AM" == normalize_time("7", ampm=True))
+ assert("01:00 PM" == normalize_time("1 pm", ampm=True))
+ assert("03:30 PM" == normalize_time("3:30 pm", ampm=True))
+ assert("03:30 PM" == normalize_time("3 30 pm", ampm=True))
+ assert("03:30 PM" == normalize_time("3 30 P.M.", ampm=True))
+ assert("12:00 PM" == normalize_time("0", ampm=True))
+ assert("12:00 AM" == normalize_time("1200 AM", ampm=True))
+
+def test_date():
+ tm = localtime()
+ yr = tm[0]
+ mo = tm[1]
+ assert(date(yr,4,11) == parse_date("411"))
+ assert(date(yr,4,11) == parse_date("APR11"))
+ assert(date(yr,4,11) == parse_date("11APR"))
+ assert(date(yr,4,11) == parse_date("4 11"))
+ assert(date(yr,4,11) == parse_date("11 APR"))
+ assert(date(yr,4,11) == parse_date("APR 11"))
+ assert(date(yr,mo,11) == parse_date("11"))
+ assert(date(yr,4,1) == parse_date("APR"))
+ assert(date(yr,4,11) == parse_date("4/11"))
+ assert(date.today() == parse_date("today"))
+ assert(date.today() == parse_date("now"))
+ assert(None == parse_date(""))
+ assert('' == normalize_date(None))
+
+ assert('2001-02-03' == normalize_date("20010203"))
+ assert('1999-04-11' == normalize_date("1999 4 11"))
+ assert('1999-04-11' == normalize_date("1999 APR 11"))
+ assert('1999-04-11' == normalize_date("APR 11 1999"))
+ assert('1999-04-11' == normalize_date("11 APR 1999"))
+ assert('1999-04-11' == normalize_date("4 11 1999"))
+ assert('1999-04-01' == normalize_date("1999 APR"))
+ assert('1999-04-01' == normalize_date("1999 4"))
+ assert('1999-04-01' == normalize_date("4 1999"))
+ assert('1999-04-01' == normalize_date("APR 1999"))
+ assert('1999-01-01' == normalize_date("1999"))
+
+ assert('1999-04-01' == normalize_date("1APR1999"))
+ assert('2001-04-01' == normalize_date("1APR2001"))
+
+ assert('1999-04-18' == normalize_date("1999-04-11+7"))
+ assert('1999-04-18' == normalize_date("1999-04-11 7"))
+ assert('1999-04-01' == normalize_date("1 apr 1999"))
+ assert('1999-04-11' == normalize_date("11 apr 1999"))
+ assert('1999-04-11' == normalize_date("11 Apr 1999"))
+ assert('1999-04-11' == normalize_date("11-apr-1999"))
+ assert('1999-04-11' == normalize_date("11 April 1999"))
+ assert('1999-04-11' == normalize_date("11 APRIL 1999"))
+ assert('1999-04-11' == normalize_date("11 april 1999"))
+ assert('1999-04-11' == normalize_date("11 aprick 1999"))
+ assert('1999-04-11' == normalize_date("APR 11, 1999"))
+ assert('1999-04-11' == normalize_date("4/11/1999"))
+ assert('1999-04-11' == normalize_date("4-11-1999"))
+ assert('1999-04-11' == normalize_date("1999-4-11"))
+ assert('1999-04-11' == normalize_date("19990411"))
+
+ assert('1999-01-01' == normalize_date("1 Jan 1999"))
+ assert('1999-02-01' == normalize_date("1 Feb 1999"))
+ assert('1999-03-01' == normalize_date("1 Mar 1999"))
+ assert('1999-04-01' == normalize_date("1 Apr 1999"))
+ assert('1999-05-01' == normalize_date("1 May 1999"))
+ assert('1999-06-01' == normalize_date("1 Jun 1999"))
+ assert('1999-07-01' == normalize_date("1 Jul 1999"))
+ assert('1999-08-01' == normalize_date("1 Aug 1999"))
+ assert('1999-09-01' == normalize_date("1 Sep 1999"))
+ assert('1999-10-01' == normalize_date("1 Oct 1999"))
+ assert('1999-11-01' == normalize_date("1 Nov 1999"))
+ assert('1999-12-01' == normalize_date("1 Dec 1999"))
+
+ assert('1999-04-30' == normalize_date("1999-4-30"))
+ assert('2000-02-29' == normalize_date("29 FEB 2000"))
+ assert('2001-02-28' == normalize_date("28 FEB 2001"))
+ assert('2004-02-29' == normalize_date("29 FEB 2004"))
+ assert('2100-02-28' == normalize_date("28 FEB 2100"))
+ assert('1900-02-28' == normalize_date("28 FEB 1900"))
+
+ def assertError(val):
+ try:
+ normalize_date(val)
+ except (TypeError,ValueError):
+ return
+ raise ValueError("type error expected", val)
+
+ assertError("2000-13-11")
+ assertError("APR 99")
+ assertError("29 FEB 1900")
+ assertError("29 FEB 2100")
+ assertError("29 FEB 2001")
+ assertError("1999-4-31")
+ assertError("APR 99")
+ assertError("20301")
+ assertError("020301")
+ assertError("1APR99")
+ assertError("1APR01")
+ assertError("1 APR 99")
+ assertError("1 APR 01")
+ assertError("11/5/01")
+
diff --git a/tests/test_util/test_mimeparse.py b/tests/test_util/test_mimeparse.py
index c2d6cdf..9b9b675 100644
--- a/tests/test_util/test_mimeparse.py
+++ b/tests/test_util/test_mimeparse.py
@@ -1,237 +1,235 @@
-# (c) 2010 Ch. Zwerschke and contributors
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-
-from time import localtime
-from datetime import date
-from paste.util.mimeparse import *
-
-def test_parse_mime_type():
- parse = parse_mime_type
- assert parse('*/*') == ('*', '*', {})
- assert parse('text/html') == ('text', 'html', {})
- assert parse('audio/*; q=0.2') == ('audio', '*', {'q': '0.2'})
- assert parse('text/x-dvi;level=1') == ('text', 'x-dvi', {'level': '1'})
- assert parse('image/gif; level=2; q=0.4') == (
- 'image', 'gif', {'level': '2', 'q': '0.4'})
- assert parse('application/xhtml;level=3;q=0.5') == (
- 'application', 'xhtml', {'level': '3', 'q': '0.5'})
- assert parse('application/xml') == ('application', 'xml', {})
- assert parse('application/xml;q=1') == ('application', 'xml', {'q': '1'})
- assert parse('application/xml ; q=1;b=other') == (
- 'application', 'xml', {'q': '1', 'b': 'other'})
- assert parse('application/xml ; q=2;b=other') == (
- 'application', 'xml', {'q': '2', 'b': 'other'})
- assert parse('application/xhtml;q=0.5') == (
- 'application', 'xhtml', {'q': '0.5'})
- assert parse('application/xhtml;q=0.5;ver=1.2') == (
- 'application', 'xhtml', {'q': '0.5', 'ver': '1.2'})
-
-def test_parse_illformed_mime_type():
- parse = parse_mime_type
- assert parse('*') == ('*', '*', {})
- assert parse('text') == ('text', '*', {})
- assert parse('text/') == ('text', '*', {})
- assert parse('/plain') == ('*', 'plain', {})
- assert parse('/') == ('*', '*', {})
- assert parse('text/plain;') == ('text', 'plain', {})
- assert parse(';q=0.5') == ('*', '*', {'q': '0.5'})
- assert parse('*; q=.2') == ('*', '*', {'q': '.2'})
- assert parse('image; q=.7; level=3') == (
- 'image', '*', {'q': '.7', 'level': '3'})
- assert parse('*;q=1') == ('*', '*', {'q': '1'})
- assert parse('*;q=') == ('*', '*', {})
- assert parse('*;=0.5') == ('*', '*', {})
- assert parse('*;q=foobar') == ('*', '*', {'q': 'foobar'})
- assert parse('image/gif; level=2; q=2') == (
- 'image', 'gif', {'level': '2', 'q': '2'})
- assert parse('application/xml;q=') == ('application', 'xml', {})
- assert parse('application/xml ;q=') == ('application', 'xml', {})
- assert parse(' *; q =;') == ('*', '*', {})
- assert parse(' *; q=.2') == ('*', '*', {'q': '.2'})
-
-def test_parse_media_range():
- parse = parse_media_range
- assert parse('application/*;q=0.5') == ('application', '*', {'q': '0.5'})
- assert parse('text/plain') == ('text', 'plain', {'q': '1'})
- assert parse('*') == ('*', '*', {'q': '1'})
- assert parse(';q=0.5') == ('*', '*', {'q': '0.5'})
- assert parse('*;q=0.5') == ('*', '*', {'q': '0.5'})
- assert parse('*;q=1') == ('*', '*', {'q': '1'})
- assert parse('*;q=') == ('*', '*', {'q': '1'})
- assert parse('*;q=-1') == ('*', '*', {'q': '1'})
- assert parse('*;q=foobar') == ('*', '*', {'q': '1'})
- assert parse('*;q=0.0001') == ('*', '*', {'q': '0.0001'})
- assert parse('*;q=1000.0') == ('*', '*', {'q': '1'})
- assert parse('*;q=0') == ('*', '*', {'q': '0'})
- assert parse('*;q=0.0000') == ('*', '*', {'q': '0.0000'})
- assert parse('*;q=1.0001') == ('*', '*', {'q': '1'})
- assert parse('*;q=2') == ('*', '*', {'q': '1'})
- assert parse('*;q=1e3') == ('*', '*', {'q': '1'})
- assert parse('image/gif; level=2') == (
- 'image', 'gif', {'level': '2', 'q': '1'})
- assert parse('image/gif; level=2; q=0.5') == (
- 'image', 'gif', {'level': '2', 'q': '0.5'})
- assert parse('image/gif; level=2; q=2') == (
- 'image', 'gif', {'level': '2', 'q': '1'})
- assert parse('application/xml') == ('application', 'xml', {'q': '1'})
- assert parse('application/xml;q=1') == ('application', 'xml', {'q': '1'})
- assert parse('application/xml;q=') == ('application', 'xml', {'q': '1'})
- assert parse('application/xml ;q=') == ('application', 'xml', {'q': '1'})
- assert parse('application/xml ; q=1;b=other') == (
- 'application', 'xml', {'q': '1', 'b': 'other'})
- assert parse('application/xml ; q=2;b=other') == (
- 'application', 'xml', {'q': '1', 'b': 'other'})
- assert parse(' *; q =;') == ('*', '*', {'q': '1'})
- assert parse(' *; q=.2') == ('*', '*', {'q': '.2'})
-
-def test_fitness_and_quality_parsed():
- faq = fitness_and_quality_parsed
- assert faq('*/*;q=0.7', [
- ('foo', 'bar', {'q': '0.5'})]) == (0, 0.5)
- assert faq('foo/*;q=0.7', [
- ('foo', 'bar', {'q': '0.5'})]) == (100, 0.5)
- assert faq('*/bar;q=0.7', [
- ('foo', 'bar', {'q': '0.5'})]) == (10, 0.5)
- assert faq('foo/bar;q=0.7', [
- ('foo', 'bar', {'q': '0.5'})]) == (110, 0.5)
- assert faq('text/html;q=0.7', [
- ('foo', 'bar', {'q': '0.5'})]) == (-1, 0)
- assert faq('text/html;q=0.7', [
- ('text', 'bar', {'q': '0.5'})]) == (-1, 0)
- assert faq('text/html;q=0.7', [
- ('foo', 'html', {'q': '0.5'})]) == (-1, 0)
- assert faq('text/html;q=0.7', [
- ('text', '*', {'q': '0.5'})]) == (100, 0.5)
- assert faq('text/html;q=0.7', [
- ('*', 'html', {'q': '0.5'})]) == (10, 0.5)
- assert faq('text/html;q=0.7', [
- ('*', '*', {'q': '0'}), ('text', 'html', {'q': '0.5'})]) == (110, 0.5)
- assert faq('text/html;q=0.7', [
- ('*', '*', {'q': '0.5'}), ('audio', '*', {'q': '0'})]) == (0, 0.5)
- assert faq('audio/mp3;q=0.7', [
- ('*', '*', {'q': '0'}), ('audio', '*', {'q': '0.5'})]) == (100, 0.5)
- assert faq('*/mp3;q=0.7', [
- ('foo', 'mp3', {'q': '0.5'}), ('audio', '*', {'q': '0'})]) == (10, 0.5)
- assert faq('audio/mp3;q=0.7', [
- ('audio', 'ogg', {'q': '0'}), ('*', 'mp3', {'q': '0.5'})]) == (10, 0.5)
- assert faq('audio/mp3;q=0.7', [
- ('*', 'ogg', {'q': '0'}), ('*', 'mp3', {'q': '0.5'})]) == (10, 0.5)
- assert faq('text/html;q=0.7', [
- ('text', 'plain', {'q': '0'}),
- ('plain', 'html', {'q': '0'}),
- ('text', 'html', {'q': '0.5'}),
- ('html', 'text', {'q': '0'})]) == (110, 0.5)
- assert faq('text/html;q=0.7;level=2', [
- ('plain', 'html', {'q': '0', 'level': '2'}),
- ('text', '*', {'q': '0.5', 'level': '3'}),
- ('*', 'html', {'q': '0.5', 'level': '2'}),
- ('image', 'gif', {'q': '0.5', 'level': '2'})]) == (100, 0.5)
- assert faq('text/html;q=0.7;level=2', [
- ('text', 'plain', {'q': '0'}), ('text', 'html', {'q': '0'}),
- ('text', 'plain', {'q': '0', 'level': '2'}),
- ('text', 'html', {'q': '0.5', 'level': '2'}),
- ('*', '*', {'q': '0', 'level': '2'}),
- ('text', 'html', {'q': '0', 'level': '3'})]) == (111, 0.5)
- assert faq('text/html;q=0.7;level=2;opt=3', [
- ('text', 'html', {'q': '0'}),
- ('text', 'html', {'q': '0', 'level': '2'}),
- ('text', 'html', {'q': '0', 'opt': '3'}),
- ('*', '*', {'q': '0', 'level': '2', 'opt': '3'}),
- ('text', 'html', {'q': '0', 'level': '3', 'opt': '3'}),
- ('text', 'html', {'q': '0.5', 'level': '2', 'opt': '3'}),
- ('*', '*', {'q': '0', 'level': '3', 'opt': '3'})]) == (112, 0.5)
-
-def test_quality_parsed():
- qp = quality_parsed
- assert qp('image/gif;q=0.7', [('image', 'jpg', {'q': '0.5'})]) == 0
- assert qp('image/gif;q=0.7', [('image', '*', {'q': '0.5'})]) == 0.5
- assert qp('audio/mp3;q=0.7;quality=100', [
- ('*', '*', {'q': '0', 'quality': '100'}),
- ('audio', '*', {'q': '0', 'quality': '100'}),
- ('*', 'mp3', {'q': '0', 'quality': '100'}),
- ('audio', 'mp3', {'q': '0', 'quality': '50'}),
- ('audio', 'mp3', {'q': '0.5', 'quality': '100'}),
- ('audio', 'mp3', {'q': '0.5'})]) == 0.5
-
-def test_quality():
- assert quality('text/html',
- 'text/*;q=0.3, text/html;q=0.75, text/html;level=1,'
- ' text/html;level=2;q=0.4, */*;q=0.5') == 0.75
- assert quality('text/html;level=2',
- 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
- ' text/html;level=2;q=0.4, */*;q=0.5') == 0.4
- assert quality('text/plain',
- 'text/*;q=0.25, text/html;q=0.7, text/html;level=1,'
- ' text/html;level=2;q=0.4, */*;q=0.5') == 0.25
- assert quality('plain/text',
- 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
- ' text/html;level=2;q=0.4, */*;q=0.5') == 0.5
- assert quality('text/html;level=1',
- 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
- ' text/html;level=2;q=0.4, */*;q=0.5') == 1
- assert quality('image/jpeg',
- 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
- ' text/html;level=2;q=0.4, */*;q=0.5') == 0.5
- assert quality('text/html;level=2',
- 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
- ' text/html;level=2;q=0.375, */*;q=0.5') == 0.375
- assert quality('text/html;level=3',
- 'text/*;q=0.3, text/html;q=0.75, text/html;level=1,'
- ' text/html;level=2;q=0.4, */*;q=0.5') == 0.75
-
-def test_best_match():
- bm = best_match
- assert bm([], '*/*') == ''
- assert bm(['application/xbel+xml', 'text/xml'],
- 'text/*;q=0.5,*/*; q=0.1') == 'text/xml'
- assert bm(['application/xbel+xml', 'audio/mp3'],
- 'text/*;q=0.5,*/*; q=0.1') == 'application/xbel+xml'
- assert bm(['application/xbel+xml', 'audio/mp3'],
- 'text/*;q=0.5,*/mp3; q=0.1') == 'audio/mp3'
- assert bm(['application/xbel+xml', 'text/plain', 'text/html'],
- 'text/*;q=0.5,*/plain; q=0.1') == 'text/plain'
- assert bm(['application/xbel+xml', 'text/html', 'text/xhtml'],
- 'text/*;q=0.1,*/xhtml; q=0.5') == 'text/html'
- assert bm(['application/xbel+xml', 'text/html', 'text/xhtml'],
- '*/html;q=0.1,*/xhtml; q=0.5') == 'text/xhtml'
- assert bm(['application/xbel+xml', 'application/xml'],
- 'application/xbel+xml') == 'application/xbel+xml'
- assert bm(['application/xbel+xml', 'application/xml'],
- 'application/xbel+xml; q=1') == 'application/xbel+xml'
- assert bm(['application/xbel+xml', 'application/xml'],
- 'application/xml; q=1') == 'application/xml'
- assert bm(['application/xbel+xml', 'application/xml'],
- 'application/*; q=1') == 'application/xbel+xml'
- assert bm(['application/xbel+xml', 'application/xml'],
- '*/*, application/xml') == 'application/xml'
- assert bm(['application/xbel+xml', 'text/xml'],
- 'text/*;q=0.5,*/*; q=0.1') == 'text/xml'
- assert bm(['application/xbel+xml', 'text/xml'],
- 'text/html,application/atom+xml; q=0.9') == ''
- assert bm(['application/json', 'text/html'],
- 'application/json, text/javascript, */*') == 'application/json'
- assert bm(['application/json', 'text/html'],
- 'application/json, text/html;q=0.9') == 'application/json'
- assert bm(['image/*', 'application/xml'], 'image/png') == 'image/*'
- assert bm(['image/*', 'application/xml'], 'image/*') == 'image/*'
-
-def test_illformed_best_match():
- bm = best_match
- assert bm(['image/png', 'image/jpeg', 'image/gif', 'text/html'],
- 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2') == 'image/jpeg'
- assert bm(['image/png', 'image/jpg', 'image/tif', 'text/html'],
- 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2') == 'text/html'
- assert bm(['image/png', 'image/jpg', 'image/tif', 'audio/mp3'],
- 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2') == 'image/png'
-
-def test_sorted_match():
- dm = desired_matches
- assert dm(['text/html', 'application/xml'],
- 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,'
- 'text/plain;q=0.8,image/png') == ['text/html', 'application/xml']
- assert dm(['text/html', 'application/xml'],
- 'application/xml,application/json') == ['application/xml']
- assert dm(['text/xhtml', 'text/plain', 'application/xhtml'],
- 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,'
- 'text/plain;q=0.8,image/png') == ['text/plain']
+# (c) 2010 Ch. Zwerschke and contributors
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+from paste.util.mimeparse import *
+
+def test_parse_mime_type():
+ parse = parse_mime_type
+ assert parse('*/*') == ('*', '*', {})
+ assert parse('text/html') == ('text', 'html', {})
+ assert parse('audio/*; q=0.2') == ('audio', '*', {'q': '0.2'})
+ assert parse('text/x-dvi;level=1') == ('text', 'x-dvi', {'level': '1'})
+ assert parse('image/gif; level=2; q=0.4') == (
+ 'image', 'gif', {'level': '2', 'q': '0.4'})
+ assert parse('application/xhtml;level=3;q=0.5') == (
+ 'application', 'xhtml', {'level': '3', 'q': '0.5'})
+ assert parse('application/xml') == ('application', 'xml', {})
+ assert parse('application/xml;q=1') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml ; q=1;b=other') == (
+ 'application', 'xml', {'q': '1', 'b': 'other'})
+ assert parse('application/xml ; q=2;b=other') == (
+ 'application', 'xml', {'q': '2', 'b': 'other'})
+ assert parse('application/xhtml;q=0.5') == (
+ 'application', 'xhtml', {'q': '0.5'})
+ assert parse('application/xhtml;q=0.5;ver=1.2') == (
+ 'application', 'xhtml', {'q': '0.5', 'ver': '1.2'})
+
+def test_parse_illformed_mime_type():
+ parse = parse_mime_type
+ assert parse('*') == ('*', '*', {})
+ assert parse('text') == ('text', '*', {})
+ assert parse('text/') == ('text', '*', {})
+ assert parse('/plain') == ('*', 'plain', {})
+ assert parse('/') == ('*', '*', {})
+ assert parse('text/plain;') == ('text', 'plain', {})
+ assert parse(';q=0.5') == ('*', '*', {'q': '0.5'})
+ assert parse('*; q=.2') == ('*', '*', {'q': '.2'})
+ assert parse('image; q=.7; level=3') == (
+ 'image', '*', {'q': '.7', 'level': '3'})
+ assert parse('*;q=1') == ('*', '*', {'q': '1'})
+ assert parse('*;q=') == ('*', '*', {})
+ assert parse('*;=0.5') == ('*', '*', {})
+ assert parse('*;q=foobar') == ('*', '*', {'q': 'foobar'})
+ assert parse('image/gif; level=2; q=2') == (
+ 'image', 'gif', {'level': '2', 'q': '2'})
+ assert parse('application/xml;q=') == ('application', 'xml', {})
+ assert parse('application/xml ;q=') == ('application', 'xml', {})
+ assert parse(' *; q =;') == ('*', '*', {})
+ assert parse(' *; q=.2') == ('*', '*', {'q': '.2'})
+
+def test_parse_media_range():
+ parse = parse_media_range
+ assert parse('application/*;q=0.5') == ('application', '*', {'q': '0.5'})
+ assert parse('text/plain') == ('text', 'plain', {'q': '1'})
+ assert parse('*') == ('*', '*', {'q': '1'})
+ assert parse(';q=0.5') == ('*', '*', {'q': '0.5'})
+ assert parse('*;q=0.5') == ('*', '*', {'q': '0.5'})
+ assert parse('*;q=1') == ('*', '*', {'q': '1'})
+ assert parse('*;q=') == ('*', '*', {'q': '1'})
+ assert parse('*;q=-1') == ('*', '*', {'q': '1'})
+ assert parse('*;q=foobar') == ('*', '*', {'q': '1'})
+ assert parse('*;q=0.0001') == ('*', '*', {'q': '0.0001'})
+ assert parse('*;q=1000.0') == ('*', '*', {'q': '1'})
+ assert parse('*;q=0') == ('*', '*', {'q': '0'})
+ assert parse('*;q=0.0000') == ('*', '*', {'q': '0.0000'})
+ assert parse('*;q=1.0001') == ('*', '*', {'q': '1'})
+ assert parse('*;q=2') == ('*', '*', {'q': '1'})
+ assert parse('*;q=1e3') == ('*', '*', {'q': '1'})
+ assert parse('image/gif; level=2') == (
+ 'image', 'gif', {'level': '2', 'q': '1'})
+ assert parse('image/gif; level=2; q=0.5') == (
+ 'image', 'gif', {'level': '2', 'q': '0.5'})
+ assert parse('image/gif; level=2; q=2') == (
+ 'image', 'gif', {'level': '2', 'q': '1'})
+ assert parse('application/xml') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml;q=1') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml;q=') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml ;q=') == ('application', 'xml', {'q': '1'})
+ assert parse('application/xml ; q=1;b=other') == (
+ 'application', 'xml', {'q': '1', 'b': 'other'})
+ assert parse('application/xml ; q=2;b=other') == (
+ 'application', 'xml', {'q': '1', 'b': 'other'})
+ assert parse(' *; q =;') == ('*', '*', {'q': '1'})
+ assert parse(' *; q=.2') == ('*', '*', {'q': '.2'})
+
+def test_fitness_and_quality_parsed():
+ faq = fitness_and_quality_parsed
+ assert faq('*/*;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (0, 0.5)
+ assert faq('foo/*;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (100, 0.5)
+ assert faq('*/bar;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (10, 0.5)
+ assert faq('foo/bar;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (110, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('foo', 'bar', {'q': '0.5'})]) == (-1, 0)
+ assert faq('text/html;q=0.7', [
+ ('text', 'bar', {'q': '0.5'})]) == (-1, 0)
+ assert faq('text/html;q=0.7', [
+ ('foo', 'html', {'q': '0.5'})]) == (-1, 0)
+ assert faq('text/html;q=0.7', [
+ ('text', '*', {'q': '0.5'})]) == (100, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('*', 'html', {'q': '0.5'})]) == (10, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('*', '*', {'q': '0'}), ('text', 'html', {'q': '0.5'})]) == (110, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('*', '*', {'q': '0.5'}), ('audio', '*', {'q': '0'})]) == (0, 0.5)
+ assert faq('audio/mp3;q=0.7', [
+ ('*', '*', {'q': '0'}), ('audio', '*', {'q': '0.5'})]) == (100, 0.5)
+ assert faq('*/mp3;q=0.7', [
+ ('foo', 'mp3', {'q': '0.5'}), ('audio', '*', {'q': '0'})]) == (10, 0.5)
+ assert faq('audio/mp3;q=0.7', [
+ ('audio', 'ogg', {'q': '0'}), ('*', 'mp3', {'q': '0.5'})]) == (10, 0.5)
+ assert faq('audio/mp3;q=0.7', [
+ ('*', 'ogg', {'q': '0'}), ('*', 'mp3', {'q': '0.5'})]) == (10, 0.5)
+ assert faq('text/html;q=0.7', [
+ ('text', 'plain', {'q': '0'}),
+ ('plain', 'html', {'q': '0'}),
+ ('text', 'html', {'q': '0.5'}),
+ ('html', 'text', {'q': '0'})]) == (110, 0.5)
+ assert faq('text/html;q=0.7;level=2', [
+ ('plain', 'html', {'q': '0', 'level': '2'}),
+ ('text', '*', {'q': '0.5', 'level': '3'}),
+ ('*', 'html', {'q': '0.5', 'level': '2'}),
+ ('image', 'gif', {'q': '0.5', 'level': '2'})]) == (100, 0.5)
+ assert faq('text/html;q=0.7;level=2', [
+ ('text', 'plain', {'q': '0'}), ('text', 'html', {'q': '0'}),
+ ('text', 'plain', {'q': '0', 'level': '2'}),
+ ('text', 'html', {'q': '0.5', 'level': '2'}),
+ ('*', '*', {'q': '0', 'level': '2'}),
+ ('text', 'html', {'q': '0', 'level': '3'})]) == (111, 0.5)
+ assert faq('text/html;q=0.7;level=2;opt=3', [
+ ('text', 'html', {'q': '0'}),
+ ('text', 'html', {'q': '0', 'level': '2'}),
+ ('text', 'html', {'q': '0', 'opt': '3'}),
+ ('*', '*', {'q': '0', 'level': '2', 'opt': '3'}),
+ ('text', 'html', {'q': '0', 'level': '3', 'opt': '3'}),
+ ('text', 'html', {'q': '0.5', 'level': '2', 'opt': '3'}),
+ ('*', '*', {'q': '0', 'level': '3', 'opt': '3'})]) == (112, 0.5)
+
+def test_quality_parsed():
+ qp = quality_parsed
+ assert qp('image/gif;q=0.7', [('image', 'jpg', {'q': '0.5'})]) == 0
+ assert qp('image/gif;q=0.7', [('image', '*', {'q': '0.5'})]) == 0.5
+ assert qp('audio/mp3;q=0.7;quality=100', [
+ ('*', '*', {'q': '0', 'quality': '100'}),
+ ('audio', '*', {'q': '0', 'quality': '100'}),
+ ('*', 'mp3', {'q': '0', 'quality': '100'}),
+ ('audio', 'mp3', {'q': '0', 'quality': '50'}),
+ ('audio', 'mp3', {'q': '0.5', 'quality': '100'}),
+ ('audio', 'mp3', {'q': '0.5'})]) == 0.5
+
+def test_quality():
+ assert quality('text/html',
+ 'text/*;q=0.3, text/html;q=0.75, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.75
+ assert quality('text/html;level=2',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.4
+ assert quality('text/plain',
+ 'text/*;q=0.25, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.25
+ assert quality('plain/text',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.5
+ assert quality('text/html;level=1',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 1
+ assert quality('image/jpeg',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.5
+ assert quality('text/html;level=2',
+ 'text/*;q=0.3, text/html;q=0.7, text/html;level=1,'
+ ' text/html;level=2;q=0.375, */*;q=0.5') == 0.375
+ assert quality('text/html;level=3',
+ 'text/*;q=0.3, text/html;q=0.75, text/html;level=1,'
+ ' text/html;level=2;q=0.4, */*;q=0.5') == 0.75
+
+def test_best_match():
+ bm = best_match
+ assert bm([], '*/*') == ''
+ assert bm(['application/xbel+xml', 'text/xml'],
+ 'text/*;q=0.5,*/*; q=0.1') == 'text/xml'
+ assert bm(['application/xbel+xml', 'audio/mp3'],
+ 'text/*;q=0.5,*/*; q=0.1') == 'application/xbel+xml'
+ assert bm(['application/xbel+xml', 'audio/mp3'],
+ 'text/*;q=0.5,*/mp3; q=0.1') == 'audio/mp3'
+ assert bm(['application/xbel+xml', 'text/plain', 'text/html'],
+ 'text/*;q=0.5,*/plain; q=0.1') == 'text/plain'
+ assert bm(['application/xbel+xml', 'text/html', 'text/xhtml'],
+ 'text/*;q=0.1,*/xhtml; q=0.5') == 'text/html'
+ assert bm(['application/xbel+xml', 'text/html', 'text/xhtml'],
+ '*/html;q=0.1,*/xhtml; q=0.5') == 'text/xhtml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ 'application/xbel+xml') == 'application/xbel+xml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ 'application/xbel+xml; q=1') == 'application/xbel+xml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ 'application/xml; q=1') == 'application/xml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ 'application/*; q=1') == 'application/xbel+xml'
+ assert bm(['application/xbel+xml', 'application/xml'],
+ '*/*, application/xml') == 'application/xml'
+ assert bm(['application/xbel+xml', 'text/xml'],
+ 'text/*;q=0.5,*/*; q=0.1') == 'text/xml'
+ assert bm(['application/xbel+xml', 'text/xml'],
+ 'text/html,application/atom+xml; q=0.9') == ''
+ assert bm(['application/json', 'text/html'],
+ 'application/json, text/javascript, */*') == 'application/json'
+ assert bm(['application/json', 'text/html'],
+ 'application/json, text/html;q=0.9') == 'application/json'
+ assert bm(['image/*', 'application/xml'], 'image/png') == 'image/*'
+ assert bm(['image/*', 'application/xml'], 'image/*') == 'image/*'
+
+def test_illformed_best_match():
+ bm = best_match
+ assert bm(['image/png', 'image/jpeg', 'image/gif', 'text/html'],
+ 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2') == 'image/jpeg'
+ assert bm(['image/png', 'image/jpg', 'image/tif', 'text/html'],
+ 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2') == 'text/html'
+ assert bm(['image/png', 'image/jpg', 'image/tif', 'audio/mp3'],
+ 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2') == 'image/png'
+
+def test_sorted_match():
+ dm = desired_matches
+ assert dm(['text/html', 'application/xml'],
+ 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,'
+ 'text/plain;q=0.8,image/png') == ['text/html', 'application/xml']
+ assert dm(['text/html', 'application/xml'],
+ 'application/xml,application/json') == ['application/xml']
+ assert dm(['text/xhtml', 'text/plain', 'application/xhtml'],
+ 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,'
+ 'text/plain;q=0.8,image/png') == ['text/plain']
diff --git a/tests/test_wsgiwrappers.py b/tests/test_wsgiwrappers.py
index b552f86..833b4f2 100644
--- a/tests/test_wsgiwrappers.py
+++ b/tests/test_wsgiwrappers.py
@@ -4,6 +4,7 @@
import cgi
from paste.fixture import TestApp
from paste.wsgiwrappers import WSGIRequest, WSGIResponse
+import six
class AssertApp(object):
def __init__(self, assertfunc):
@@ -42,7 +43,7 @@ def test_wsgirequest_charset():
# Tanaka, '田中'
app = TestApp(AssertApp(assertfunc=valid_name(u'田中', encoding='UTF-8')))
res = app.get('/?name=%E7%94%B0%E4%B8%AD')
-
+
# Nippon (Japan), '日本'
app = TestApp(AssertApp(assertfunc=valid_name(u'日本', encoding='UTF-8',
post=True)))
@@ -79,23 +80,23 @@ def test_wsgirequest_charset_fileupload():
assert isinstance(fs, cgi.FieldStorage)
assert isinstance(fs.filename, str)
assert fs.filename == '寿司.txt'
- assert fs.value == 'Sushi'
+ assert fs.value == b'Sushi'
request.charset = 'UTF-8'
assert len(request.POST) == 1
assert isinstance(request.POST.keys()[0], str)
fs = request.POST['thefile']
assert isinstance(fs, cgi.FieldStorage)
- assert isinstance(fs.filename, unicode)
+ assert isinstance(fs.filename, six.text_type)
assert fs.filename == u'寿司.txt'
- assert fs.value == 'Sushi'
+ assert fs.value == b'Sushi'
request.charset = None
- assert fs.value == 'Sushi'
+ assert fs.value == b'Sushi'
return []
app = TestApp(handle_fileupload)
- res = app.post('/', upload_files=[('thefile', '寿司.txt', 'Sushi')])
+ res = app.post('/', upload_files=[('thefile', '寿司.txt', b'Sushi')])
def test_wsgiresponse_charset():
response = WSGIResponse(mimetype='text/html; charset=UTF-8')
@@ -106,7 +107,7 @@ def test_wsgiresponse_charset():
response.write('test3')
status, headers, content = response.wsgi_response()
for data in content:
- assert isinstance(data, str)
+ assert isinstance(data, six.binary_type)
WSGIResponse.defaults._push_object(dict(content_type='text/html',
charset='iso-8859-1'))
@@ -117,7 +118,7 @@ def test_wsgiresponse_charset():
response.write('test3')
status, headers, content = response.wsgi_response()
for data in content:
- assert isinstance(data, str)
+ assert isinstance(data, six.binary_type)
finally:
WSGIResponse.defaults._pop_object()
@@ -130,7 +131,7 @@ def test_wsgiresponse_charset():
response.write(u'test1')
status, headers, content = response.wsgi_response()
for data in content:
- assert isinstance(data, unicode)
+ assert isinstance(data, six.text_type)
finally:
WSGIResponse.defaults._pop_object()
@@ -141,6 +142,6 @@ def test_wsgiresponse_charset():
response.write(u'test1')
status, headers, content = response.wsgi_response()
for data in content:
- assert isinstance(data, unicode)
+ assert isinstance(data, six.text_type)
finally:
WSGIResponse.defaults._pop_object()
diff --git a/tests/urlparser_data/hook/app.py b/tests/urlparser_data/hook/app.py
index d2714e5..1a98013 100644
--- a/tests/urlparser_data/hook/app.py
+++ b/tests/urlparser_data/hook/app.py
@@ -1,5 +1,9 @@
+import six
+
def application(environ, start_response):
start_response('200 OK', [('Content-type', 'text/html')])
- return ['user: %s' % environ['app.user']]
+ body = 'user: %s' % environ['app.user']
+ if six.PY3:
+ body = body.encode('ascii')
+ return [body]
-
diff --git a/tests/urlparser_data/hook/index.py b/tests/urlparser_data/hook/index.py
index 49e89f0..92f3d66 100644
--- a/tests/urlparser_data/hook/index.py
+++ b/tests/urlparser_data/hook/index.py
@@ -1,4 +1,9 @@
+import six
+
def application(environ, start_response):
start_response('200 OK', [('Content-type', 'text/html')])
- return ['index: %s' % environ['app.user']]
+ body = 'index: %s' % environ['app.user']
+ if six.PY3:
+ body = body.encode('ascii')
+ return [body]
diff --git a/tests/urlparser_data/not_found/simple/__init__.py b/tests/urlparser_data/not_found/simple/__init__.py
index f1e7faa..7186daa 100644
--- a/tests/urlparser_data/not_found/simple/__init__.py
+++ b/tests/urlparser_data/not_found/simple/__init__.py
@@ -1,3 +1,3 @@
def not_found_hook(environ, start_response):
start_response('200 OK', [('Content-type', 'text/plain')])
- return ['not found']
+ return [b'not found']
diff --git a/tests/urlparser_data/not_found/user/list.py b/tests/urlparser_data/not_found/user/list.py
index f6228f0..fd7482f 100644
--- a/tests/urlparser_data/not_found/user/list.py
+++ b/tests/urlparser_data/not_found/user/list.py
@@ -1,3 +1,8 @@
+import six
+
def application(environ, start_response):
start_response('200 OK', [('Content-type', 'text/plain')])
- return ['user: %s' % environ.get('app.user')]
+ body = 'user: %s' % environ.get('app.user')
+ if six.PY3:
+ body = body.encode('ascii')
+ return [body]
diff --git a/tests/urlparser_data/python/simpleapp.py b/tests/urlparser_data/python/simpleapp.py
index cbef9f1..7a36ce9 100644
--- a/tests/urlparser_data/python/simpleapp.py
+++ b/tests/urlparser_data/python/simpleapp.py
@@ -1,6 +1,5 @@
def application(environ, start_response):
start_response('200 OK', [('Content-type', 'text/html'),
('test-header', 'TEST!')])
- return ['test1']
+ return [b'test1']
-
diff --git a/tests/urlparser_data/python/stream.py b/tests/urlparser_data/python/stream.py
index 121b4d1..e81fd1c 100644
--- a/tests/urlparser_data/python/stream.py
+++ b/tests/urlparser_data/python/stream.py
@@ -1,7 +1,7 @@
def stream():
def app(environ, start_response):
writer = start_response('200 OK', [('Content-type', 'text/html')])
- writer('te')
- writer('st')
- return ['2']
+ writer(b'te')
+ writer(b'st')
+ return [b'2']
return app
diff --git a/tests/urlparser_data/python/sub/simpleapp.py b/tests/urlparser_data/python/sub/simpleapp.py
index fd90966..88bd975 100644
--- a/tests/urlparser_data/python/sub/simpleapp.py
+++ b/tests/urlparser_data/python/sub/simpleapp.py
@@ -1,6 +1,4 @@
def application(environ, start_response):
start_response('200 OK', [('Content-type', 'text/html'),
('test-header', 'TEST!')])
- return ['subsimple']
-
-
+ return [b'subsimple']
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..27fbcb8
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,10 @@
+[tox]
+envlist = py26,py27,py34
+
+[testenv]
+deps=
+ nose
+ six
+commands=
+ nosetests
+